<> <> <> <> DIRECTORY Ascii USING [Upper], Commander USING [CommandProc, Register], CommandTool USING [ArgumentVector, Parse, StarExpansion], Convert USING [Error, IntFromRope], FS USING [ComponentPositions, ComponentRopes, ConstructFName, Error, ExpandName, StreamOpen], ImagerPixelMap USING [Clear, Create, DeviceRectangle, Fill, PixelMap], IO USING [Close, EndOf, EndOfStream, Error, GetBlock, GetChar, GetID, GetIndex, GetInt, int, PutF, PutFR, RIS, rope, SetIndex, STREAM, TIS, Value], RasterFontIO USING [Create, InternalCharRep, InternalFont, WriteAC], RefText USING [New], Rope USING [Cat, Equal, Fetch, IsEmpty, ROPE, Substr, Translate, TranslatorType]; GFtoAC: CEDAR PROGRAM IMPORTS Ascii, Commander, CommandTool, Convert, FS, ImagerPixelMap, IO, RasterFontIO, RefText, Rope = BEGIN FormatError: ERROR [explanation: Rope.ROPE, v1, v2: IO.Value _ [null[]]] = CODE; BYTE: TYPE = [0..255]; WORD: TYPE = [0..65535]; Ord: PROC [ch: CHAR] RETURNS [BYTE] = INLINE { RETURN[ch - 0C] }; Chr: PROC [i: BYTE] RETURNS [CHAR] = INLINE { RETURN[i + 0C] }; LoadGF: PROC [gfStream: IO.STREAM, deviceResolution: INTEGER] RETURNS [font: RasterFontIO.InternalFont] = -- This tries to read GF files in the same way that GFType does BEGIN GetByte: PROC RETURNS [BYTE] = INLINE BEGIN IF gfStream.EndOf[] THEN RETURN[0] ELSE RETURN[Ord[gfStream.GetChar[]]]; END; GetTwoBytes: PROC RETURNS [WORD] = BEGIN a: BYTE _ GetByte[]; RETURN[a * 256 + GetByte[]]; END; GetThreeBytes: PROC RETURNS [INT] = BEGIN a: INT _ GetByte[]; b: BYTE _ GetByte[]; RETURN[(a * 256 + b) * 256 + GetByte[]]; END; SignedQuad: PROC RETURNS [INT] = BEGIN a: INT _ GetByte[]; b: BYTE _ GetByte[]; c: BYTE _ GetByte[]; IF a < 128 THEN RETURN[((a * 256 + b) * 256 + c) * 256 + GetByte[]] ELSE RETURN[(((a - 256) * 256 + b) * 256 + c) * 256 + GetByte[]]; END; <<>> <> paint0: BYTE = 0; -- Beginning of the paint commands paint1: BYTE = 64; -- Move right a given # of columns, then black boc: BYTE = 67; -- Beginning of a character boc1: BYTE = 68; -- Abbreviated boc eoc: BYTE = 69; -- End of a character skip0: BYTE = 70; -- Skip no blank rows skip1: BYTE = 71; -- Skip over blank rows newRow0: BYTE = 74; -- Move down one row and then right xxx1: BYTE = 239; -- For special strings yyy: BYTE = 243; -- For numspecial numbers nop: BYTE = 244; -- No operation charLoc: BYTE = 245; -- Character locators in the postamble pre: BYTE = 247; -- Preamble post: BYTE = 248; -- Postamble beginning postpost: BYTE = 249; -- Postamble ending IDByte: BYTE = 131; -- Identifies the kind of GF files described here op: BYTE; -- Current opcode par: INT; -- First parameter to current opcode StartOp: PROC = BEGIN op _ GetByte[]; SELECT op FROM IN [paint0..paint1) => par _ op - paint0; IN [newRow0..newRow0+164] => par _ op - newRow0; boc1, paint1, skip1, charLoc, charLoc + 1, xxx1 => par _ GetByte[]; paint1 + 1, skip1 + 1, xxx1 + 1 => par _ GetTwoBytes[]; paint1 + 2, skip1 + 2, xxx1 + 2 => par _ GetThreeBytes[]; boc, xxx1 + 3, yyy => par _ SignedQuad[]; ENDCASE => par _ 0; END; ReadPreamble: PROC = BEGIN byte: BYTE; IF (byte _ GetByte[]) # pre THEN ERROR FormatError["First byte is %g, not start of preamble.", IO.int[byte]]; IF (byte _ GetByte[]) # IDByte THEN ERROR FormatError["Identification byte is %g, should be %g.", IO.int[byte], IO.int[IDByte]]; byte _ GetByte[]; gfStream.SetIndex[gfStream.GetIndex[] + byte]; -- Skip title string END; LoadCharacters: PROC = BEGIN mMin, mMax, nMin, nMax : INT; -- Bounding box ch: CHAR; -- Current character m, n: INT; -- GF abstract machine registers paintSwitch: BOOLEAN; -- TRUE means black ProcessSpecial: PROC = <> BEGIN text: REF TEXT _ RefText.New[nChars: par]; specStream: IO.STREAM; operation: Rope.ROPE; IF gfStream.GetBlock[block: text, count: par] # par THEN ERROR FormatError["File not long enough to hold stated length of a SPECIAL text; length=%g", IO.int[par]]; specStream _ IO.TIS[text]; operation _ specStream.GetID[ ! IO.EndOfStream, IO.Error => GOTO GiveUp ]; SELECT TRUE FROM Rope.Equal[operation, "identifier"] => { font.family _ specStream.GetID[ ! IO.EndOfStream, IO.Error => GOTO GiveUp ]; }; Rope.Equal[operation, "fontfacebyte"] => { StartOp[]; IF op # yyy THEN ERROR FormatError["SPECIAL \"fontfacebyte\" not followed by a NUMSPECIAL, at byte %g", IO.int[gfStream.GetIndex[] - 1]]; par _ par / 65536; IF NOT (par IN [0..255]) THEN ERROR FormatError["NUMSPECIAL \"fontfacebyte\" is too large, at byte %g", IO.int[gfStream.GetIndex[] - 1]]; font.face _ par; }; Rope.Equal[operation, "deviceresolution"] => { StartOp[]; IF op # yyy THEN ERROR FormatError["SPECIAL \"deviceresolution\" not followed by a NUMSPECIAL, at byte %g", IO.int[gfStream.GetIndex[] - 1]]; font.bitsPerInch _ par / 65536; }; ENDCASE => NULL; EXITS GiveUp => NULL; END; GetBOC: PROC = BEGIN junk : INT; -- Byte to be ignored SELECT op FROM boc => { ch _ Chr[par MOD 256]; junk _ SignedQuad[]; -- Skip back pointer mMin _ SignedQuad[]; mMax _ SignedQuad[]; nMin _ SignedQuad[]; nMax _ SignedQuad[]; }; boc1 => { mDel, nDel : INT; -- Deltas for abbreviating [mn]Min ch _ Chr[par]; mDel _ GetByte[]; mMax _ GetByte[]; nDel _ GetByte[]; nMax _ GetByte[]; mMin _ mMax - mDel; nMin _ nMax - nDel; }; ENDCASE => ERROR FormatError["Byte %g is not BOC (%g)", IO.int[gfStream.GetIndex[] - 1], IO.int[op]]; { OPEN char: font.charRep[ch]; <> char.sWidth _ 0; char.pixels _ ImagerPixelMap.Create[0, [-nMax, mMin, nMax - nMin + 1, mMax - mMin + 1]]; char.pixels.Clear[]; }; m _ mMin; n _ nMax; paintSwitch _ FALSE; END; DoChar: PROC = BEGIN OPEN char: font.charRep[ch]; DO -- for each operation StartOp[]; SELECT op FROM IN [paint0..paint1 + 2] => { -- Paint par pixels IF paintSwitch AND par > 0 THEN char.pixels.Fill[[-n, m, 1, par], 1]; m _ m + par; paintSwitch _ NOT paintSwitch; }; IN [newRow0..newRow0+164] => { -- Move on to the next row m _ mMin + par; n _ n - 1; paintSwitch _ TRUE; }; IN [skip0..skip1 + 2] => { -- Skip some empty rows n _ n - (par + 1); m _ mMin; paintSwitch _ FALSE; }; nop => -- Do nothing NULL; pre => -- Bad preamble command ERROR FormatError["Preamble command within a character at pos. %g.", IO.int[gfStream.GetIndex[] - 1]]; post, postpost => -- Bad postamble command ERROR FormatError["Postamble command within a character at pos. %g.", IO.int[gfStream.GetIndex[] - 1]]; charLoc, charLoc + 1 => -- Bad character locator ERROR FormatError["charLoc command within a character at pos. %g.", IO.int[gfStream.GetIndex[] - 1]]; boc => -- Bad beginning of character ERROR FormatError["BOC command within a character at pos. %g.", IO.int[gfStream.GetIndex[] - 1]]; eoc => -- End of character EXIT; IN [xxx1..xxx1 + 3] => -- special string (ignore it) ProcessSpecial[]; yyy => -- numspecial number (ignore it) NULL; ENDCASE => -- Unknown opcode ERROR FormatError["Undefined Opcode %g at pos. %g.", IO.int[op], IO.int[gfStream.GetIndex[] - 1]]; ENDLOOP; END; <> DO -- for each character DO -- skip the nop's and specials in front of the character StartOp[]; SELECT op FROM nop, yyy => NULL; IN [xxx1..xxx1+3] => ProcessSpecial[]; ENDCASE => EXIT; ENDLOOP; IF op = post THEN -- oops! not a character after all EXIT; GetBOC[]; DoChar[]; ENDLOOP; END; ReadPostamble: PROC = BEGIN junk: INT; -- Words to be discarded designSize: INT; -- Design size in points * 2^20 hppp: INT; -- Horizontal pixels per point * 2^16. ppi: REAL = 72.27; -- Points per inch junk _ SignedQuad[]; -- Skip back pointer designSize _ SignedQuad[]; junk _ SignedQuad[]; -- Skip checksum hppp _ SignedQuad[]; FOR i:INTEGER IN [1..5] DO -- Skip vppp and {m,n}{Max,Min} junk _ SignedQuad[]; ENDLOOP; IF font.bitsPerInch = 0 THEN ERROR FormatError["Device resolution unspecified in GF file."] ELSE font.bitsPerEmQuad _ (designSize / (65536 * 16.0)) * (hppp / 65536.0); DO -- Read the charLoc commands for the character widths StartOp[]; SELECT op FROM charLoc => { -- Character locator font.charRep[Chr[par]].fWidth _ GetTwoBytes[]; junk _ GetTwoBytes[]; -- Skip extra precision of escapement ``dx'' junk _ SignedQuad[]; -- Skip escapement ``dy'' junk _ SignedQuad[]; -- Skip TFM width and back pointer junk _ SignedQuad[]; }; charLoc + 1 => { -- Abbreviated charLoc font.charRep[Chr[par]].fWidth _ GetByte[]; junk _ SignedQuad[]; -- Skip TFM width and back pointer junk _ SignedQuad[]; }; postpost => EXIT; ENDCASE => ERROR FormatError["Funny opcode (%g) in postamble at pos. %g.", IO.int[op], IO.int[gfStream.GetIndex[]]]; ENDLOOP; END; <> <<>> font _ RasterFontIO.Create[[0,0,0,0], 0]; font.bitsPerInch _ deviceResolution; -- This can be reset by the 'deviceresolution' special ReadPreamble[]; LoadCharacters[]; ReadPostamble[]; END; GFtoACCommand: Commander.CommandProc -- PROC [cmd: Handle] RETURNS [result: REF _ NIL, msg: Rope.ROPE _ NIL] -- = BEGIN argv: CommandTool.ArgumentVector; gfStream: IO.STREAM; font: RasterFontIO.InternalFont; gfName, acName: Rope.ROPE; fontName, fullName: Rope.ROPE _ NIL; cp: FS.ComponentPositions; cr: FS.ComponentRopes; interpressMode: BOOLEAN _ FALSE; deviceResolution: INTEGER _ 0; i: INTEGER; CommandTool.StarExpansion[cmd]; argv _ CommandTool.Parse[cmd]; IF argv.argc <= 1 THEN GO TO Usage; i _ 1; WHILE i < argv.argc DO IF Rope.Fetch[argv.s[i], 0] = '- THEN SELECT Rope.Fetch[argv.s[i], 1] FROM 'i => interpressMode _ TRUE; 'r => { i _ i + 1; deviceResolution _ Convert.IntFromRope[argv.s[i] ! Convert.Error => GOTO Usage ] }; ENDCASE => GOTO Usage ELSE IF fontName = NIL THEN fontName _ argv.s[i] ELSE GOTO Usage; i _ i + 1; ENDLOOP; IF fontName.IsEmpty THEN GO TO Usage; [fullName, cp] _ FS.ExpandName[fontName]; cr.server _ fullName.Substr[cp.server.start, cp.server.length]; cr.dir _ fullName.Substr[cp.dir.start, cp.dir.length]; cr.subDirs _ fullName.Substr[cp.subDirs.start, cp.subDirs.length]; cr.base _ fullName.Substr[cp.base.start, cp.base.length]; cr.ext _ fullName.Substr[cp.ext.start, cp.ext.length]; fontName _ cr.base; IF cp.ext.length = 0 THEN { cr.ext _ "gf"; gfName _ FS.ConstructFName[cr]; } ELSE gfName _ fullName; { res: INT _ 0; res _ IO.GetInt[IO.RIS[cr.ext] ! IO.Error => CONTINUE]; IF res # 0 THEN cr.base _ IO.PutFR["%g-%g", IO.rope[cr.base], IO.int[res]]; }; acName _ FS.ExpandName[Rope.Cat[cr.base, ".ac"]].fullFName; gfStream _ FS.StreamOpen[gfName ! FS.Error => IF error.group = user THEN { cmd.err.PutF["Cannot open %g: %g\n", IO.rope[gfName], IO.rope[error.explanation]]; GOTO Abort; }; ]; font _ LoadGF[gfStream, deviceResolution ! FormatError => { cmd.err.PutF[Rope.Cat["GF file has bad format: ", explanation, "\n"], v1, v2]; GOTO Abort; }; ]; gfStream.Close[]; IF interpressMode THEN { MyUpper: Rope.TranslatorType = { new _ Ascii.Upper[old] }; font.family _ Rope.Translate[base: fontName, translator: MyUpper]; font.face _ 0; }; RasterFontIO.WriteAC[font, acName]; cmd.out.PutF["AC Font written on %g\n", IO.rope[acName]]; EXITS Usage => RETURN[$Failure, commandDoc]; Abort => RETURN[$Failure]; END; commandDoc: Rope.ROPE ~ " `GFtoAC [-i] [-r RES] fontname' translates fonts from GF format to AC format. If fontname doesn't have an extension, '.gf' is added. If fontname is 'foo.gf', the resulting file will be 'foo.ac'. If fontname is 'foo.NNNgf', the resulting file will be 'foo-NNN.ac' for any number NNN. GFtoAC always creates its AC files in its current working directory. -i means to make AC fonts for Xerox product Interpress printers. -r RES means to claim the device resolution is RES bits per inch."; Commander.Register["GFtoAC", GFtoACCommand, commandDoc]; END.