DIRECTORY
AIS USING [CloseFile, CloseWindow, CreateFile, FRef, OpenWindow, Raster, RasterPart, WRef, WriteSample],
Basics USING [BITOR, BytePair],
RefText USING [New],
BasicTime USING [OutOfRange, GMT, nullGMT, Pack, Unpacked, Now, Unpack],
CountedVM USING [Handle, SimpleAllocate],
PrincOpsUtils USING [LongCopy],
Real USING [Round],
CedarProcess USING [CheckAbort],
Rope USING [Cat, Fetch, FromProc, Length, ROPE, Substr],
TapesCommon,
CrosfieldTape,
UnixTapeOps;
ROPE: TYPE ~ Rope.ROPE;
Identity: TYPE ~ REF IdentityRep;
IdentityRep: TYPE ~ RECORD [
tapeNumber: [1..999],
date: BasicTime.GMT,
name: ROPE
];
IdentityOnTape:
TYPE ~
MACHINE
DEPENDENT
RECORD [
All quantities marked WORD are only accurate once byteswapped
checkWord1(0): WORD ← 0,
tapeID(1): WORD ← 0,
dateYMD(2): WORD ← 0,
dateHM(3): DateHM ← [0,0],
name(4:0..159): PACKED ARRAY [0..20) OF CHAR ← ALL [' ],
ws2(14): WORD ← 0,
checkWord2(15): WORD ← 0
];
nameLen: NAT ~ (159+1)/16*2;
Read Tape
Directory: TYPE ~ REF DirectoryRep;
DirectoryRep: TYPE ~ RECORD [
fileNumber: [1..70],
contone: BOOL,
color: Color,
hRes, vRes: PixelsPerInch,
pixelsPerLine: CARDINAL,
lines: CARDINAL,
createDate: BasicTime.GMT,
note: ROPE,
tapeNumber: [0..999],
linesPerCylinder, sectorsPerLine, cylinders, blocks: CARDINAL
];
DirectoryOnTape:
TYPE ~
MACHINE
DEPENDENT
RECORD [
All quantities marked WORD are only accurate once byteswapped
idn(0): WORD ← 0, -- File number on tape (range 1- 70)
ws1(1): WORD ← 0,
ws3(2:0..10): [0..1024] ← 2,
att(2:11..11): BOOL ← FALSE, -- TRUE if contone, FALSE if screen
ws2(2:12..15): [0..15] ← 0,
color(3): Color ← [],
ws4(4:0..31): CARD ← 0,
hRes(6): WORD ← 0,
vRes(7): WORD ← 0,
pixelsPerLine(8): WORD ← 0,
lines(9): WORD ← 0,
linesPerCylinder(10): WORD ← 0,
sectorsPerLine(11): WORD ← 0,
createTimeYMD(12): WORD ← 0,
createTimeHM(13): DateHM ← [0, 0],
ws5(14:0..31): CARD ← 0,
note(16:0..159): PACKED ARRAY [0..20) OF CHAR ← ALL[' ],
ws6(26:0..111): ARRAY [0..7) OF WORD ← ALL[0],
cylinders(33): WORD ← 0,
blocks(34): WORD ← 0,
tapeID(35): WORD ← 0,
ws7(36:0..31): CARD ← 0,
ws8(38:0..31): CARD ← 0
];
noteLen: NAT ~ (159+1)/16*2;
Color: TYPE ~ MACHINE DEPENDENT RECORD [
cyan(0:7..7): BOOL ← TRUE,
magenta(0:6..6): BOOL ← TRUE,
yellow(0:5..5): BOOL ← TRUE,
black(0:4..4): BOOL ← TRUE,
red(0:3..3): BOOL ← FALSE,
green(0:2..2): BOOL ← FALSE,
blue(0:1..1): BOOL ← FALSE,
ws1(0:0..0): BOOL ← FALSE, -- filler
negativeImage(0:15..15): BOOL ← FALSE, -- low values represent dark print/film
ws2(0:14..14): BOOL ← FALSE, -- filler
bytesPerPixel(0:11..13): [0 .. 7) ← 4,
ws3(0:8..10): [0 .. 7) ← 0
];
Two separate records because we need to ByteSwap the words in DateYMD
DateYMD:
TYPE ~
MACHINE
DEPENDENT
RECORD [
Apply ByteSwap before using
year(0:9..15): [0..127], -- Year-1900
month(0:5..8): [0..15],
day(0:0..4): [0..31]
];
DateHM:
TYPE ~
MACHINE
DEPENDENT
RECORD [
minute(0:0..7): [0..255],
hour(0:8..15): [0..255]
];
State: TYPE ~ REF StateRep;
StateRep:
TYPE ~
RECORD [
newState: BOOLEAN ← TRUE,
tapeHandle: TapeHandle ← NIL,
dir: Directory,
text: REF TEXT, --one record (32 sectors) worth
atByte: INT ← -1, -- the current byte offset in text
sector: [0..32) ← 31, --sector in the current record
recordsLeftInCylinder: [0..19] ← 0,
linesLeftInCylinder: NAT ← 0, --dir.linesPerCylinder
bytesLeftInSector: [0..512] ← 0,
linesLeftInFile: NAT ← 0,
mapPixel: PixelProc,
dirAsText: REF TEXT ← NIL
];
Pixel:
TYPE ~
MACHINE
DEPENDENT
RECORD [
SELECT
OVERLAID *
FROM
cmky => [cyan, magenta, yellow, black: BYTE],
rgb => [red, green, blue, blank: BYTE],
ENDCASE
];
Colors: TYPE ~ {cyan, magenta, yellow, black};
AISInfo: TYPE ~ RECORD [fileName: ROPE, raster: AIS.Raster, fRef: AIS.FRef, wRef: AIS.WRef];
suffix: ARRAY Colors OF ROPE = ["cyan", "magenta", "yellow", "black"];
Write Tape
WriteTape:
PUBLIC PROC [tapeHandle: TapeHandle, files: FileSpecList, tapeNumber: [1..999] ← 102, name:
ROPE]
RETURNS [FileHandleList] = {
Initializes the tape for writing. Writes the master directory. Returns a list of FileHandle.
Client rasterizes into each FileHandle in turn. newLine.eof is TRUE when tape has recorded nLines worth of rasters. Client can check newLine.eof to verify that the client and the tape are still in sync.
fileHandleList: FileHandleList ← NIL;
fileNumber: NAT ← 1;
reverse:
PROC[files: FileHandleList]
RETURNS[r: FileHandleList] = {
r ← NIL;
FOR f: FileHandleList ← files, f.rest
UNTIL f=
NIL
DO
r ← CONS[f.first, r];
ENDLOOP;
};
InitializeTape[tapeHandle, files, tapeNumber, name];
Build the fileHandleList
FOR f: FileSpecList ← files, f.rest
UNTIL f=
NIL
DO
fileSpec: FileSpec ← f.first;
fileHandleList ← CONS[FileHandleFromFileSpec[fileSpec, tapeHandle, fileNumber, tapeNumber], fileHandleList];
fileNumber ← fileNumber+1;
ENDLOOP;
Reverse the fileHandleList
fileHandleList ← reverse[fileHandleList];
RETURN[fileHandleList];
};
InitializeTape:
PUBLIC PROC [tapeHandle: TapeHandle, files: FileSpecList, tapeNumber: [1..999] ← 102, name:
ROPE] = {
Must include correct FileSpecs for every file that may appear on the tape.
identity: Identity ← NEW[IdentityRep ← [tapeNumber: tapeNumber, date: BasicTime.Now[], name: name]];
idAsText: REF TEXT;
masterDir: REF TEXT;
fileNumber: NAT ← 1;
Write the Identity block
idAsText ← TextFromIdentity[identity];
UnixTapeOps.Rewind[tapeHandle];
UnixTapeOps.WriteRecord[tapeHandle, idAsText];
Allocate and initialize the master directory
masterDir ← AllocateBuffer[sizeOfMasterDirectory];
FOR i:
NAT
IN [0..sizeOfMasterDirectory)
DO
masterDir[i] ← VAL[377B]; --end of directories mark
ENDLOOP;
FOR f: FileSpecList ← files, f.rest
UNTIL f=
NIL
DO
fileSpec: FileSpec ← f.first;
dirAsText: REF TEXT ← TextFromDirectory[DirectoryFromFileSpec[fileSpec, fileNumber, tapeNumber]];
offset: NAT ← (fileNumber-1)*bytesPerDirectory;
FOR i:
NAT
IN [0..bytesPerDirectory)
DO
--put directory into the master directory
masterDir[offset+i] ← dirAsText[i];
ENDLOOP;
fileNumber ← fileNumber+1;
ENDLOOP;
Write the master directory
UnixTapeOps.WriteRecord[tapeHandle, masterDir];
UnixTapeOps.WriteFileMark[tapeHandle];
};
AddFileToTape:
PUBLIC PROC [tapeHandle: TapeHandle, file: FileSpec, fileNumber, tapeNumber:
NAT, inPosition:
BOOLEAN ←
FALSE]
RETURNS [TapesCommon.FileHandle] = {
File numbers start at 1. Files must be added in the order specified in the master directory
IF rewind, will use the file number to position the tape.
Otherwise, will start writing at the current tape position.
IF
NOT inPosition
THEN {
UnixTapeOps.Rewind[tapeHandle];
UnixTapeOps.ForwardSpaceFile[tapeHandle]; --skip the master directory
FOR i:
NAT
IN [1..fileNumber)
DO
UnixTapeOps.ForwardSpaceFile[tapeHandle];
ENDLOOP;
};
RETURN[FileHandleFromFileSpec[file,tapeHandle, fileNumber, tapeNumber]];
};
FileHandleFromFileSpec:
PROC [fileSpec: FileSpec, tapeHandle: TapeHandle, fileNumber, tapeNumber:
NAT]
RETURNS [FileHandle] ~ {
dir: Directory ← DirectoryFromFileSpec[fileSpec, fileNumber, tapeNumber];
state: State;
linesPerCylinder, nCylinders, sectorsPerLine, nRecords: NAT;
[linesPerCylinder, nCylinders, sectorsPerLine, nRecords] ← FileStructures[fileSpec];
state ←
NEW[StateRep ← [
newState: TRUE,
tapeHandle: tapeHandle,
dir: dir,
text: AllocateBuffer[bytesPerRecord],
atByte: 0,
sector: 0,
recordsLeftInCylinder: recordsPerCylinder,
linesLeftInCylinder: linesPerCylinder,
bytesLeftInSector: bytesPerSector,
linesLeftInFile: fileSpec.nLines,
mapPixel: fileSpec.mapPixel,
dirAsText: TextFromDirectory[dir]
]];
RETURN[NEW[FileHandleRep ← [newLine: WriteNewLine, data: state]]];
};
DirectoryFromFileSpec:
PROC [fileSpec: FileSpec, fileNumber, tapeNumber:
NAT]
RETURNS [dir: Directory] ~ {
linesPerCylinder, nCylinders, sectorsPerLine, nRecords: NAT;
[linesPerCylinder, nCylinders, sectorsPerLine, nRecords] ← FileStructures[fileSpec];
dir ←
NEW[DirectoryRep ← [
fileNumber: fileNumber,
contone: FALSE,
color: color, --it defaults correctly
hRes: fileSpec.pixelsPerInch,
vRes: fileSpec.pixelsPerInch,
pixelsPerLine: fileSpec.nPixels,
lines: fileSpec.nLines,
createDate: BasicTime.Now[],
note: fileSpec.note,
tapeNumber: tapeNumber,
linesPerCylinder: linesPerCylinder,
sectorsPerLine: sectorsPerLine,
cylinders: nCylinders,
blocks: nRecords
]];
};
Read Tape
ReadTapeIdentity:
PUBLIC PROC [tapeHandle: TapeHandle]
RETURNS [Identity] = {
leaves tape ready to read the master directory
tapeMark: BOOL;
text: REF TEXT;
UnixTapeOps.Rewind[tapeHandle];
The tape identity
[tapeMark, text] ← UnixTapeOps.ReadRecord[tapeHandle: tapeHandle, buffer: text];
IF tapeMark THEN ERROR; -- something wrong here
RETURN[IdentityFromText[text]];
};
ReadMasterDirectory:
PUBLIC PROC[tapeHandle: TapeHandle]
RETURNS [nFiles:
NAT, masterDirectory:
LIST
OF Directory] = {
id: Identity ← ReadTapeIdentity[tapeHandle];
mDirAsText: REF TEXT ← NIL;
tapeMark: BOOL;
[tapeMark, mDirAsText] ← UnixTapeOps.ReadRecord[tapeHandle: tapeHandle, buffer: NIL];
IF tapeMark THEN ERROR; -- something wrong here
Read the tape mark
[tapeMark, ] ← UnixTapeOps.ReadRecord[tapeHandle: tapeHandle, buffer: NIL];
IF NOT tapeMark THEN ERROR; -- something wrong here
[nFiles, masterDirectory] ← ParseMasterDirectory[mDirAsText];
};
ParseMasterDirectory:
PROC [dir:
REF
TEXT]
RETURNS [nFiles:
NAT, masterDirectory:
LIST
OF Directory] = {
directory: Directory;
list: LIST OF Directory;
start, nRecords: NAT ← 0;
reverse:
PROC[l:
LIST
OF Directory]
RETURNS[r:
LIST
OF Directory] = {
FOR d:
LIST
OF Directory ← l, d.rest
UNTIL d=
NIL
DO
r ← CONS[d.first, r];
ENDLOOP;
};
nFiles ← 0;
DO
directory ← DirectoryFromText[text: dir, start: start];
IF directory=NIL THEN EXIT;
nFiles ← nFiles+1;
nRecords ← nRecords+directory.blocks;
start ← start+bytesPerDirectory;
list ← CONS[directory, list];
ENDLOOP;
list ← reverse[list];
RETURN[nFiles, list];
};
ReadFile:
PUBLIC
PROC [tapeHandle: TapeHandle, fileNumber: [1..70] ← 1]
RETURNS[Directory] ~ {
stateProc: PROC[line: NAT, state: State] = {};
RETURN[ReadAndProcessFile[tapeHandle, fileNumber, stateProc]];
};
ReadAndProcessFile:
PROC [tape: TapeHandle, fileNumber: [1..70] ← 1, stateProc:
PROC[line:
NAT, state: State]]
RETURNS[Directory] ~ {
Will rewind the tape, read the identity and master directory, then find the file
state: State ← NEW[StateRep ← [tapeHandle: tape]];
nFiles: NAT;
tapeMark: BOOL;
[nFiles, ] ← ReadMasterDirectory[tape];
IF fileNumber > nFiles THEN ERROR;
FOR i:
NAT
IN [1..fileNumber)
DO
UnixTapeOps.ForwardSpaceFile[tape]; --skip over the unwanted files
ENDLOOP;
Read the directory in front of file
[tapeMark, state.dirAsText] ← UnixTapeOps.ReadRecord[tapeHandle: tape, buffer: NIL];
IF tapeMark THEN ERROR; -- something wrong here
state.dir ← DirectoryFromText[text: state.dirAsText, start: 0];
state.linesLeftInFile ← state.dir.lines;
FOR line:
NAT
IN [0 .. state.dir.lines)
DO
ReadNewLine[state];
stateProc[line, state];
CedarProcess.CheckAbort[];
ENDLOOP;
FOR i:
NAT
IN [0..state.recordsLeftInCylinder)
DO
ReadNewRecord[state];
ENDLOOP;
[tapeMark, state.text] ← UnixTapeOps.ReadRecord[tapeHandle: tape, buffer: state.text];
IF NOT tapeMark THEN ERROR; -- something wrong here
RETURN[state.dir];
};
FileTo4AIS:
PUBLIC
PROC [tapeHandle: TapeHandle, fileNumber: [1..70] ← 1, aisRootName:
ROPE, mapPixel: PixelProc, reduce:
NAT ← 4]
RETURNS [Directory] ~ {
AIS files are 1/reduce2 the size of the original (ie for reduce = 4, 300 spi => 75 spi)
ais: ARRAY Colors OF AISInfo;
dir: Directory;
Done:
PROC [c: Colors] = {
AIS.CloseWindow[w: ais[c].wRef];
AIS.CloseFile[f: ais[c].fRef];
};
stateProc:
PROC[line:
NAT, state: State] = {
SetUp:
PROC [c: Colors] = {
ais[c].raster ← NEW[AIS.RasterPart ← [scanCount: 1+state.dir.lines/reduce,
scanLength: 1+state.dir.pixelsPerLine/reduce, scanMode: rd, bitsPerPixel: 8, linesPerBlock: -1, paddingPerBlock: 0]];
ais[c].fileName ← Rope.Cat[r1: aisRootName, r2: "-", r3: suffix[c], r4: ".ais"];
ais[c].fRef ← AIS.CreateFile[name: ais[c].fileName, raster: ais[c].raster];
ais[c].wRef ← AIS.OpenWindow[f: ais[c].fRef];
MakeScreenToByte[mapPixel];
};
IF state.newState
THEN {
SetUp[cyan]; SetUp[magenta]; SetUp[yellow]; SetUp[black];
state.newState ← FALSE;
};
FOR pixel:
NAT
IN [0 .. state.dir.pixelsPerLine)
DO
p: Pixel ← ReadPixel[state];
store:
PROC [c: Colors, data:
BYTE] = {
lastPixel: NAT ← state.dir.pixelsPerLine-1;
data ← ScreenToByte[data];
AIS.WriteSample[w: ais[c].wRef, value: data, line: line/reduce, pixel: (lastPixel-pixel)/reduce];
};
IF line MOD reduce #0 OR pixel MOD reduce #0 THEN LOOP;
store[cyan, p.cyan];
store[magenta, p.magenta];
store[yellow, p.yellow];
store[black, p.black];
ENDLOOP;
};
dir ← ReadAndProcessFile[tapeHandle, fileNumber, stateProc];
Done[cyan]; Done[magenta]; Done[yellow]; Done[black];
RETURN[dir];
};
Raster handlers
bytesPerSector: NAT ~ 512;
bytesPerRecord: NAT ~ 16384;
sizeOfMasterDirectory: NAT ~5602;
bytesPerDirectory: NAT ~80;
recordsPerCylinder: NAT ~ 19;
sectorsPerRecord: NAT ~ 32;
lastSector: NAT ← sectorsPerRecord-1;
For tape file writing
WriteNewLine:
PROC[self: FileHandle, line: PixelBuffer]
RETURNS[eof:
BOOLEAN]= {
state: State ← NARROW[self.data];
IF line.samplesPerPixel # 4 THEN ERROR;
IF state.newState
THEN {
--first time with this state
MakeByteToScreen[state.mapPixel]; --make the byte translation table
UnixTapeOps.WriteRecord[state.tapeHandle, state.dirAsText]; --write the directory
state.newState ← FALSE;
};
SELECT
TRUE
FROM
state.linesLeftInCylinder=0 => WriteNewCylinder[state];
state.atByte#0 AND state.linesLeftInCylinder#0 => WriteNewSector[state];
ENDCASE; --state.atByte=0 AND linesLeftInCylinder#0
FOR i:
NAT DECREASING
IN [0..line.length)
DO
p: Pixel;
p.cyan ← ByteToScreen[line[0][i]];
p.magenta ← ByteToScreen[line[1][i]];
p.yellow ← ByteToScreen[line[2][i]];
p.black ← ByteToScreen[line[3][i]];
WritePixel[state,p];
ENDLOOP;
state.linesLeftInFile ← state.linesLeftInFile-1;
state.linesLeftInCylinder ← state.linesLeftInCylinder-1;
IF state.linesLeftInFile = 0
THEN {
--write the rest of the records and the tape mark
UNTIL state.recordsLeftInCylinder=0
DO
WriteNewRecord[state];
ENDLOOP;
UnixTapeOps.WriteFileMark[state.tapeHandle];
eof ← TRUE;
}
ELSE eof ← FALSE;
};
WritePixel:
PROC [state: State, p: Pixel] ~ {
IF state.bytesLeftInSector=0 THEN WriteNewSector[state];
state.text[state.atByte] ← VAL[p.cyan];
state.text[state.atByte+1] ← VAL[p.magenta];
state.text[state.atByte+2] ← VAL[p.yellow];
state.text[state.atByte+3] ← VAL[ p.black];
state.atByte←state.atByte+4;
state.bytesLeftInSector← state.bytesLeftInSector-4;
};
WriteNewSector:
PROC [state: State] ~ {
IF state.sector>=lastSector THEN WriteNewRecord[state]
ELSE {state.sector ← state.sector+1; state.atByte←state.sector*bytesPerSector};
state.bytesLeftInSector ← bytesPerSector;
};
WriteNewCylinder:
PROC [state: State] ~ {
UNTIL state.recordsLeftInCylinder=0 DO WriteNewRecord[state]; ENDLOOP;
state.recordsLeftInCylinder ← recordsPerCylinder;
state.atByte← 0;
state.sector ← 0;
state.bytesLeftInSector ← bytesPerSector;
state.linesLeftInCylinder ← state.dir.linesPerCylinder;
};
WriteNewRecord:
PROC [state: State] ~ {
UnixTapeOps.WriteRecord[state.tapeHandle, state.text];
state.atByte← 0;
state.sector ← 0;
state.recordsLeftInCylinder ← state.recordsLeftInCylinder-1;
};
For tape file reading
ReadNewLine:
PROC [state: State] ~ {
--aligns state.atByte to start of next line
IF state.linesLeftInCylinder=0 THEN ReadNewCylinder[state] ELSE ReadNewSector[state];
state.linesLeftInCylinder ← state.linesLeftInCylinder-1;
state.linesLeftInFile ← state.linesLeftInFile-1;
};
ReadNewCylinder:
PROC [state: State] ~ {
UNTIL state.recordsLeftInCylinder=0 DO ReadNewRecord[state]; ENDLOOP;
state.recordsLeftInCylinder ← recordsPerCylinder;
ReadNewRecord[state]; --sets atByte and sector
state.bytesLeftInSector ← bytesPerSector;
state.linesLeftInCylinder ← state.dir.linesPerCylinder;
};
ReadNewSector:
PROC [state: State] ~ {
--aligns state.atByte to start of next Sector
IF state.sector>=lastSector THEN ReadNewRecord[state]
ELSE {state.sector ← state.sector+1; state.atByte←state.sector*512};
state.bytesLeftInSector ← bytesPerSector;
};
ReadNewRecord:
PROC [state: State] ~ {
--reads a tape record
tapeMark: BOOL;
[tapeMark, state.text] ← UnixTapeOps.ReadRecord[state.tapeHandle, state.text];
IF tapeMark THEN ERROR; -- out of sync
state.atByte← 0;
state.sector ← 0;
state.recordsLeftInCylinder ← state.recordsLeftInCylinder-1;
};
ReadPixel:
PROC [state: State]
RETURNS [p: Pixel] ~ {
--gets pixels out of the line
IF state.bytesLeftInSector=0 THEN ReadNewSector[state];
p.cyan ← ORD[state.text[state.atByte]];
p.magenta ← ORD[state.text[state.atByte+1]];
p.yellow ← ORD[state.text[state.atByte+2]];
p.black ← ORD[state.text[state.atByte+3]];
state.atByte←state.atByte+4;
state.bytesLeftInSector← state.bytesLeftInSector-4;
};
Convert from Tape to Cedar formats
Error: PUBLIC ERROR [r: ROPE] = CODE;
TextFromIdentity:
PROC [identity: Identity]
RETURNS [text:
REF
TEXT] ~ {
onTape: IdentityOnTape ← [];
size: NAT ← SIZE[IdentityOnTape];
text ← AllocateBuffer[size*2];
onTape.checkWord1 ← ByteSwap[648Ch];
onTape.tapeID ← ByteSwap[identity.tapeNumber];
[onTape.dateYMD, onTape.dateHM] ← ToDate[identity.date];
onTape.name ← RopeToArray[identity.name];
onTape.checkWord2 ← ByteSwap[7D47h];
TRUSTED {
placeToPut: LONG POINTER ← LOOPHOLE[text, LONG POINTER] + SIZE[TEXT[0]];
placeToStart: LONG POINTER ← @onTape;
PrincOpsUtils.LongCopy[from: placeToStart, nwords: size, to: placeToPut];
};
};
IdentityFromText:
PROC [text:
REF
TEXT]
RETURNS [identity: Identity] ~ {
i: NAT ← 0;
P: PROC RETURNS [c: CHAR] = {c ← onTape.name[i]; i ← i + 1};
placeToStart: LONG POINTER ← LOOPHOLE[text, LONG POINTER] + SIZE[TEXT[0]];
onTape: IdentityOnTape;
TRUSTED {
placeToPut: LONG POINTER ← @onTape;
PrincOpsUtils.LongCopy[from: placeToStart, nwords: SIZE[IdentityOnTape], to: placeToPut];
};
IF ByteSwap[onTape.checkWord1] # 648Ch THEN ERROR Error["bad Identity checkword 1"];
IF ByteSwap[onTape.checkWord2] # 7D47h THEN ERROR Error["bad Identity checkword 2"];
identity ← NEW[IdentityRep];
identity.tapeNumber ← ByteSwap[onTape.tapeID];
identity.date ← FromDate[onTape.dateYMD, onTape.dateHM];
identity.name ← Rope.FromProc[nameLen, P];
identity.name ← StripRope[identity.name];
};
ValHRes: ARRAY PixelsPerInch OF WORD ← [ppi300: 006000B, ppi450: 010000B, ppi600: 014000B];
ValVRes: ARRAY PixelsPerInch OF WORD ← [ppi300: 005717B, ppi450: 010667B, ppi600: 013637B];
HResFromVal:
PROC [val:
WORD]
RETURNS [ppi: PixelsPerInch] ~ {
ppi ←
SELECT val
FROM
006000B => ppi300, 010000B => ppi450, 014000B => ppi600, ENDCASE => ERROR;
};
VResFromVal:
PROC [val:
WORD]
RETURNS [ppi: PixelsPerInch] ~ {
ppi ←
SELECT val
FROM
005717B => ppi300, 010667B => ppi450, 013637B => ppi600, ENDCASE => ERROR;
};
TextFromDirectory:
PROC [directory: Directory]
RETURNS [text:
REF
TEXT] ~ {
size: NAT ← SIZE[DirectoryOnTape];
onTape: DirectoryOnTape ← [];
text ← AllocateBuffer[size*2];
onTape.idn ← ByteSwap[directory.fileNumber];
onTape.att ← directory.contone;
onTape.color ← directory.color;
onTape.hRes ← ByteSwap[ValHRes[directory.hRes]];
onTape.vRes ← ByteSwap[ValVRes[directory.vRes]];
onTape.pixelsPerLine ← ByteSwap[directory.pixelsPerLine];
onTape.lines ← ByteSwap[directory.lines];
[onTape.createTimeYMD, onTape.createTimeHM] ← ToDate[directory.createDate];
onTape.note ← RopeToArray[directory.note];
onTape.tapeID ← ByteSwap[directory.tapeNumber];
onTape.linesPerCylinder ← ByteSwap[directory.linesPerCylinder];
onTape.sectorsPerLine ← ByteSwap[directory.sectorsPerLine];
onTape.cylinders ← ByteSwap[directory.cylinders];
onTape.blocks ← ByteSwap[directory.blocks];
TRUSTED {
placeToPut: LONG POINTER ← LOOPHOLE[text, LONG POINTER] + SIZE[TEXT[0]];
placeToStart: LONG POINTER ← @onTape;
PrincOpsUtils.LongCopy[from: placeToStart, nwords: size, to: placeToPut];
};
};
DirectoryFromText:
PROC [text:
REF
TEXT, start:
CARD]
RETURNS [directory: Directory] ~ {
placeToStart: LONG POINTER ← LOOPHOLE[text, LONG POINTER] + SIZE[TEXT[0]] + start/2;
i: NAT ← 0;
P: PROC RETURNS [c: CHAR] = {c ← onTape.note[i]; i ← i + 1};
onTape: DirectoryOnTape ← [];
TRUSTED {
placeToPut: LONG POINTER ← @onTape;
PrincOpsUtils.LongCopy[placeToStart, SIZE[DirectoryOnTape], placeToPut];
};
IF onTape.idn = 0FFFFh THEN RETURN [NIL]; -- The last one
directory ← NEW[DirectoryRep];
directory.fileNumber ← ByteSwap[onTape.idn];
directory.contone ← onTape.att;
directory.color ← onTape.color;
directory.hRes ← HResFromVal[ByteSwap[onTape.hRes]];
directory.vRes ← VResFromVal[ByteSwap[onTape.vRes]];
directory.pixelsPerLine ← ByteSwap[onTape.pixelsPerLine];
directory.lines ← ByteSwap[onTape.lines];
directory.createDate ← FromDate[onTape.createTimeYMD, onTape.createTimeHM];
directory.note ← Rope.FromProc[noteLen, P];
directory.note ← StripRope[directory.note];
directory.tapeNumber ← ByteSwap[onTape.tapeID];
directory.linesPerCylinder ← ByteSwap[onTape.linesPerCylinder];
directory.sectorsPerLine ← ByteSwap[onTape.sectorsPerLine];
directory.cylinders ← ByteSwap[onTape.cylinders];
directory.blocks ← ByteSwap[onTape.blocks];
};
ByteSwap:
PROC [in:
WORD]
RETURNS [
WORD] =
INLINE {
RETURN[Basics.BITOR[LOOPHOLE[in, Basics.BytePair].low*256,
LOOPHOLE[in, Basics.BytePair].high]];
};
ByteSwapProc:
PROC [in:
WORD]
RETURNS [out:
WORD] = {
out ← Basics.BITOR[LOOPHOLE[in, Basics.BytePair].low*256,
LOOPHOLE[in, Basics.BytePair].high];
};
StripRope:
PROC [rope:
ROPE]
RETURNS [
ROPE] ~ {
last: INT ← Rope.Length[rope];
FOR i:
INT
DECREASING
IN [0 .. Rope.Length[rope])
DO
IF Rope.Fetch[base: rope, index: i] = '\000 THEN last ← last - 1 ELSE EXIT;
ENDLOOP;
RETURN[Rope.Substr[base: rope, start: 0, len: last]];
};
RopeToArray:
PROC [rope:
ROPE]
RETURNS [array:
PACKED
ARRAY [0..20)
OF
CHAR] ~ {
length: NAT ← Rope.Length[rope];
FOR i:
NAT
IN [0..length)
DO
array[i] ← Rope.Fetch[base: rope, index: i];
ENDLOOP;
FOR i:
NAT
IN [length..20)
DO
array[i] ← '\000;
ENDLOOP;
};
ToDate:
PROC [t: BasicTime.
GMT]
RETURNS [rawYMD:
WORD, dateHM: DateHM] ~ {
unpacked: BasicTime.Unpacked ← BasicTime.Unpack[t];
dateYMD: DateYMD;
dateYMD.year ← unpacked.year - 1900;
dateYMD.month ←
SELECT unpacked.month
FROM
January => 1, February=> 2, March=> 3, April=> 4, May => 5, June => 6, July=> 7,
August => 8, September => 9, October => 10, November => 11, December => 12,
ENDCASE => ERROR;
dateYMD.day ← unpacked.day;
dateHM.hour ← unpacked.hour;
dateHM.minute ← unpacked.minute;
rawYMD ← ByteSwapProc[LOOPHOLE[dateYMD]];
};
FromDate:
PROC [rawYMD:
WORD, dateHM: DateHM]
RETURNS [t: BasicTime.
GMT] ~ {
unpacked: BasicTime.Unpacked;
dateYMD: DateYMD ← LOOPHOLE[ByteSwapProc[rawYMD]];
unpacked.year ← dateYMD.year+1900;
unpacked.month ←
SELECT dateYMD.month
FROM
1 => January, 2=> February, 3=> March, 4=> April, 5 => May, 6 => June, 7=> July,
8 => August, 9 => September, 10 => October, 11 => November, 12 => December,
ENDCASE => unspecified;
IF dateYMD.day NOT IN [1..31] THEN dateYMD.day ← 1;
IF dateHM.hour NOT IN [0..12] THEN dateHM.hour ← 0;
IF dateHM.minute NOT IN [0..60] THEN dateHM.minute ← 0;
unpacked.day ← dateYMD.day;
unpacked.hour ← dateHM.hour;
unpacked.minute ← dateHM.minute;
t ← BasicTime.nullGMT;
t ← BasicTime.Pack[unpacked ! BasicTime.OutOfRange => CONTINUE];
RETURN[t];
};
ByteMapping, Pixel Procs, etc
ScreenToByte: ARRAY[0..255] OF BYTE ← ALL[0];
ByteToScreen: ARRAY[0..255] OF BYTE ← ALL[0];
MakeScreenToByte:
PROC[pixelProc: PixelProc] = {
FOR i:
NAT
IN[0..255]
DO
ScreenToByte[i] ← pixelProc[i];
ENDLOOP;
};
MakeByteToScreen: PROC[pixelProc: PixelProc] = {
FOR i:
NAT
IN[0..255]
DO
ByteToScreen[i] ← pixelProc[i];
ENDLOOP;
};
CrosfieldToByte:
PUBLIC PixelProc = {
In the Crosfield file, pixel = (DotPercent*2)+25
Except that 21=0 and 229=100 on some page makeup systems
SELECT
TRUE
FROM
pixel =21 => RETURN[0];
pixel =229 => RETURN[255];
pixel IN [25..225] => RETURN[ Real.Round[255*0.01* (pixel -25)/2.0]];
pixel < 25 => RETURN[0];
pixel > 225 => RETURN[255];
ENDCASE => ERROR;
};
KOToByte:
PUBLIC PixelProc = {
data on tape runs from 6 to 245
dotPercent: REAL ← (pixel -6)*(100.0/239.0);
dotPercent ← MIN[MAX[dotPercent, 0], 255];
RETURN[Real.Round[dotPercent*2.55]];
};
ByteToCrosfield:
PUBLIC PixelProc = {
SELECT
TRUE
FROM
pixel =0 => RETURN[21];
pixel =255 => RETURN[229];
pixel IN [1..254] => RETURN[ Real.Round[(100.0*pixel /255.0)*2+25]];
ENDCASE => ERROR;
};
IdentityByte: PUBLIC PixelProc = {RETURN[pixel]}; --use for read for KedieOrent tapes
AllocateBuffer:
PROC[sizeInBytes:
INT]
RETURNS[
t: REF
TEXT] = {
t ← RefText.New[sizeInBytes];
t.length ← sizeInBytes;
};
FileStructures:
PROC [fileSpec: FileSpec]
RETURNS [linesPerCylinder, nCylinders, sectorsPerLine, nRecords:
NAT] = {
sectorsPerLine ← Real.Round[0.5+(fileSpec.nPixels*4.0)/bytesPerSector];
linesPerCylinder ← (recordsPerCylinder*sectorsPerRecord)/sectorsPerLine;
nCylinders ← Real.Round[0.5+REAL[fileSpec.nLines]/linesPerCylinder];
nRecords ← nCylinders*recordsPerCylinder;
};
sizeRecordGap: REAL ← 0.8; --the inter-record gap, in inches
sizeFileGap: REAL ← 2; --the inter-file gap, in feet
SizeOfHeader:
PUBLIC PROC [density: UnixTapeOps.Density ← GCR6250]
RETURNS [sizeInFeet:
REAL] = {
d: INTEGER ← SELECT density FROM GCR6250 => 6250, PE1600 => 1600, ENDCASE => ERROR;
id: REAL ← sizeRecordGap+ SIZE[IdentityOnTape]/d;
masterDir: REAL ← sizeRecordGap+ sizeOfMasterDirectory/d;
RETURN[sizeFileGap+(id+masterDir)/12.0];
};
SizeOfFile:
PUBLIC PROC [fileSpec: FileSpec, density: UnixTapeOps.Density ← GCR6250]
RETURNS [sizeInFeet:
REAL] = {
d: INTEGER ← SELECT density FROM GCR6250 => 6250, PE1600 => 1600, ENDCASE => ERROR;
dir: REAL ← sizeRecordGap+ SIZE[DirectoryOnTape]/d;
records: REAL;
nCylinders: NAT;
[,nCylinders] ← FileStructures[fileSpec];
records ← recordsPerCylinder*nCylinders*(sizeRecordGap+REAL[bytesPerRecord]/d);
RETURN[sizeFileGap+(dir+records)/12.0];
};
SizeOfList:
PUBLIC PROC [files: FileSpecList, density: UnixTapeOps.Density ← GCR6250]
RETURNS [sizeInFeet:
REAL] = {
Assumes all files are on one tape
sizeInFeet ← SizeOfHeader[density];
FOR f: FileSpecList ← files, f.rest
UNTIL f=
NIL
DO
sizeInFeet ← SizeOfFile[f.first, density]+sizeInFeet;
ENDLOOP;
};