<> <> <> <<>> DIRECTORY Imager, ImagerFont, ImagerTypeface, Vector2, ImagerPixelMap, IO, RefText, Rope, FS; ImagerGFTypefaceImpl: CEDAR PROGRAM IMPORTS Imager, ImagerTypeface, ImagerPixelMap, IO, RefText, Rope, FS ~ BEGIN OPEN ImagerTypeface, ImagerFont; FormatError: ERROR [explanation: Rope.ROPE, v1, v2: IO.Value _ [null[]]] = CODE; BYTE: TYPE ~ [0..377B]; XChar: TYPE ~ ImagerFont.XChar; CorrectionType: TYPE ~ ImagerFont.CorrectionType; Extents: TYPE ~ ImagerFont.Extents; PixelMap: TYPE ~ ImagerPixelMap.PixelMap; DeviceRectangle: TYPE ~ ImagerPixelMap.DeviceRectangle; Ord: PROC [ch: CHAR] RETURNS [BYTE] = INLINE { RETURN[ch - 0C] }; Chr: PROC [i: BYTE] RETURNS [CHAR] = INLINE { RETURN[i + 0C] }; VEC: TYPE ~ Vector2.VEC; Data: TYPE ~ REF DataRep; DataRep: TYPE ~ RECORD[ family: Rope.ROPE _ NIL, face: [0..255] _ 0, bitsPerEmQuad: REAL _ 0, bitsPerInch: REAL _ 72, defaultChar: InternalCharRep, charRep: ARRAY CHAR OF InternalCharRep ]; InternalCharRep: TYPE ~ RECORD [ width: REAL, pixels: PixelMap ]; LoadGF: PROC [gfStream: IO.STREAM] RETURNS [font: Data] = { <> GetByte: PROC RETURNS [BYTE] = INLINE { IF gfStream.EndOf[] THEN RETURN[0] ELSE RETURN[Ord[gfStream.GetChar[]]]; }; GetTwoBytes: PROC RETURNS [WORD] = { a: BYTE _ GetByte[]; RETURN[a * 256 + GetByte[]]; }; GetThreeBytes: PROC RETURNS [INT] = { a: INT _ GetByte[]; b: BYTE _ GetByte[]; RETURN[(a * 256 + b) * 256 + GetByte[]]; }; SignedQuad: PROC RETURNS [INT] = { 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[]]; }; <> paint0: BYTE = 0; -- Beginning of the paint commands paint1: BYTE = 64; -- Move right a given # of columns, then black f white 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 = { 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; }; ReadPreamble: PROC = { 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 }; LoadCharacters: PROC = { 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 = { <> 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; }; ENDCASE => NULL; EXITS GiveUp => NULL; }; GetBOC: PROC = { 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]]; }; font.charRep[ch].pixels _ ImagerPixelMap.Create[0, [-nMax, mMin, nMax - nMin + 1, mMax - mMin + 1]]; font.charRep[ch].pixels.Clear[]; m _ mMin; n _ nMax; paintSwitch _ FALSE; }; DoChar: PROC = { DO -- for each operation StartOp[]; SELECT op FROM IN [paint0..paint1 + 2] => { -- Paint par pixels IF paintSwitch AND par > 0 THEN font.charRep[ch].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; }; <> 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 EXIT; -- oops! not a character after all GetBOC[]; DoChar[]; ENDLOOP; }; ReadPostamble: PROC = { 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; font.bitsPerEmQuad _ (designSize / (65536 * 16.0)) * (hppp / 65536.0); font.bitsPerInch _ hppp * ppi / 65536; DO -- Read the charLoc commands for the character widths StartOp[]; SELECT op FROM charLoc => { -- Character locator font.charRep[Chr[par]].width _ 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]].width _ 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; }; <> font _ CreateInternal[[0,0,0,0], 0]; ReadPreamble[]; LoadCharacters[]; ReadPostamble[]; }; CreateInternal: PROC [defaultBoxBounds: ImagerPixelMap.DeviceRectangle, defaultWidth: INTEGER] RETURNS [internalFont: Data] ~ { defaultPixels: ImagerPixelMap.PixelMap _ ImagerPixelMap.Create[0, defaultBoxBounds]; defaultPixels.Clear; defaultPixels.Fill[defaultBoxBounds, 1]; internalFont _ NEW [DataRep]; internalFont.defaultChar _ [width: defaultWidth, pixels: defaultPixels]; FOR char: CHAR IN CHAR DO internalFont.charRep[char] _ internalFont.defaultChar; ENDLOOP; }; GFCreate: PROC [file: FS.OpenFile] RETURNS [Typeface] ~ { stream: IO.STREAM ~ FS.StreamFromOpenFile[file]; data: Data ~ LoadGF[stream]; IO.Close[stream]; RETURN[NEW[TypefaceRep _ [class: gfClass, data: data]]]; }; GFContains: PROC [self: Typeface, char: XChar] RETURNS [BOOL] ~ { data: Data ~ NARROW[self.data]; RETURN [char.set=0 AND data.charRep[VAL[char.code]].pixels.refRep # data.defaultChar.pixels.refRep]; }; GFNextChar: PROC [self: Typeface, char: XChar] RETURNS [next: XChar] ~ { data: Data ~ NARROW[self.data]; start: CHAR _ '\000; IF char=nullXChar THEN NULL ELSE IF char.set=0 AND char.code 0 THEN Imager.DoSave[context, action]; }; gfClass: TypefaceClass ~ NEW[TypefaceClassRep _ [ type: $GF, Contains: GFContains, NextChar: GFNextChar, Width: GFWidth, Amplified: GFAmplified, Correction: GFCorrection, BoundingBox: GFBoundingBox, FontBoundingBox: GFFontBoundingBox, Ligature: GFLigature, NextLigature: GFNextLigature, Kern: GFKern, NextKern: GFNextKern, Mask: GFMask ]]; ImagerTypeface.Register["GF", GFCreate]; END.