ImagerGFTypefaceImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Michael Plass, November 21, 1986 1:33:39 pm PST
DIRECTORY
II, IIFont, IITypeface, Vector2, IISample, IO, RefText, Rope, FS, SF;
IIGFTypefaceImpl: CEDAR PROGRAM
IMPORTS II, IITypeface, IISample, IO, RefText, Rope, FS
~ BEGIN
FormatError: ERROR [explanation: Rope.ROPE, v1, v2: IO.Value ← [null[]]] = CODE;
BYTE: TYPE ~ [0..377B];
XChar: TYPE ~ IIFont.XChar;
CorrectionType: TYPE ~ IIFont.CorrectionType;
Extents: TYPE ~ IIFont.Extents;
Typeface: TYPE ~ IITypeface.Typeface;
nullXChar: XChar ~ IIFont.nullXChar;
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.ROPENIL,
face: [0..255] ← 0,
bitsPerEmQuad: REAL ← 0,
bitsPerInch: REAL ← 72,
defaultChar: InternalCharRep,
charRep: ARRAY CHAR OF InternalCharRep
];
InternalCharRep: TYPE ~ RECORD [
escapement: REAL,
pixels: IISample.SampleMap
];
LoadGF: PROC [gfStream: IO.STREAM] RETURNS [font: Data] = {
This tries to read GF files in the same way that GFType does
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[]];
};
Symbolic names for some GF opcodes and quantities:
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 = {
An XXXn opcode has just been read. The next par characters of input are the text of the special command.
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 ← IISample.NewSampleMap[
box: [min: [s: -nMax, f: mMin], max: [s: nMax+1, f: mMax+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], [-n+1, par+m]], 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;
};
Start of LoadCharacters
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 escapements
StartOp[];
SELECT op FROM
charLoc => { -- Character locator
font.charRep[Chr[par]].escapement ← 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]].escapement ← 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;
};
Start of LoadGF
font ← CreateInternal[[[1,1], [5,5]], 6];
ReadPreamble[];
LoadCharacters[];
ReadPostamble[];
};
CreateInternal: PROC [defaultBoxBounds: SF.Box, defaultEscapement: INTEGER] RETURNS [internalFont: Data] ~ {
defaultPixels: IISample.SampleMap ← IISample.NewSampleMap[defaultBoxBounds];
defaultPixels.Clear[];
defaultPixels.Fill[defaultBoxBounds, 1];
internalFont ← NEW [DataRep];
internalFont.defaultChar ← [escapement: defaultEscapement, 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[IITypeface.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 # data.defaultChar.pixels];
};
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<LAST[BYTE] THEN start ← VAL[char.code+1]
ELSE RETURN[nullXChar];
FOR c: CHAR IN [start..LAST[CHAR]] DO
IF data.charRep[c].pixels # data.defaultChar.pixels THEN RETURN[[set: 0, code: ORD[c]]];
ENDLOOP;
RETURN[nullXChar];
};
GFEscapement: PROC [self: Typeface, char: XChar] RETURNS [VEC] ~ {
data: Data ~ NARROW[self.data];
bitEscapement: REAL ~ IF char.set=0 THEN data.charRep[VAL[char.code]].escapement ELSE data.defaultChar.escapement;
RETURN[[bitEscapement/data.bitsPerEmQuad, 0]];
};
GFAmplified: PROC [self: Typeface, char: XChar] RETURNS [BOOL] ~ {
RETURN[FALSE];
};
GFCorrection: PROC [self: Typeface, char: XChar] RETURNS [CorrectionType] ~ {
RETURN[mask];
};
GFBoundingBox: PROC [self: Typeface, char: XChar] RETURNS [Extents] ~ {
data: Data ~ NARROW[self.data];
bits: IISample.SampleMap ~ IF char.set=0 THEN data.charRep[VAL[char.code]].pixels ELSE data.defaultChar.pixels;
w: SF.Box ~ IISample.GetBox[bits];
s: REAL ~ 1.0/data.bitsPerEmQuad;
RETURN[[leftExtent: -w.min.f*s, rightExtent: w.max.f*s, descent: w.max.s*s, ascent: -w.min.s*s]];
};
GFFontBoundingBox: PROC [self: Typeface] RETURNS [Extents] ~ {
data: Data ~ NARROW[self.data];
left, right, top, bottom: INTEGER ← 0;
Do: PROC [bits: IISample.SampleMap] ~ {
w: SF.Box ~ IISample.GetBox[bits];
left ← MAX[left, -w.min.f];
right ← MAX[right, w.max.f];
top ← MAX[top, -w.min.s];
bottom ← MAX[bottom, w.max.f];
};
s: REAL ~ 1.0/data.bitsPerEmQuad;
Do[data.defaultChar.pixels];
FOR c: CHAR IN CHAR DO Do[data.charRep[c].pixels] ENDLOOP;
RETURN[[leftExtent: left*s, rightExtent: right*s, descent: bottom*s, ascent: top*s]];
};
GFKern: PROC [self: Typeface, char, successor: XChar] RETURNS [VEC] ~ {
RETURN[[0, 0]];
};
GFNextKern: PROC [self: Typeface, char, successor: XChar] RETURNS [XChar] ~ {
RETURN[nullXChar];
};
GFLigature: PROC [self: Typeface, char, successor: XChar] RETURNS [XChar] ~ {
RETURN[nullXChar];
};
GFNextLigature: PROC [self: Typeface, char, successor: XChar] RETURNS [XChar] ~ {
RETURN[nullXChar];
};
GFMask: PROC [self: Typeface, char: XChar, context: II.Context] ~ {
data: Data ~ NARROW[self.data];
bits: IISample.SampleMap ~ IF char.set=0 THEN data.charRep[VAL[char.code]].pixels ELSE data.defaultChar.pixels;
action: PROC ~ {
II.ScaleT[context, 1.0/data.bitsPerEmQuad];
II.MaskBitmap[context: context, bitmap: bits];
};
IF IISample.GetSize[bits].s > 0 THEN II.DoSave[context, action];
};
gfClass: IITypeface.TypefaceClass ~ NEW[IITypeface.TypefaceClassRep ← [
type: $GF,
Contains: GFContains,
NextChar: GFNextChar,
Escapement: GFEscapement,
Amplified: GFAmplified,
Correction: GFCorrection,
BoundingBox: GFBoundingBox,
FontBoundingBox: GFFontBoundingBox,
Ligature: GFLigature,
NextLigature: GFNextLigature,
Kern: GFKern,
NextKern: GFNextKern,
Mask: GFMask
]];
IITypeface.Register["GF", GFCreate];
END.