ImagerGFTypefaceImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Michael Plass, November 6, 1985 5:08:42 pm PST
Doug Wyatt, November 11, 1985 5:49:12 pm PST
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.ROPENIL,
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] = {
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 ← 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;
};
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 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;
};
Start of LoadGF
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<LAST[BYTE] THEN start ← VAL[char.code+1]
ELSE RETURN[nullXChar];
FOR c: CHAR IN [start..LAST[CHAR]] DO
IF data.charRep[c].pixels.refRep # data.defaultChar.pixels.refRep THEN RETURN[[set: 0, code: ORD[c]]];
ENDLOOP;
RETURN[nullXChar];
};
GFEscapement: PROC [self: Typeface, char: XChar] RETURNS [VEC] ~ {
data: Data ~ NARROW[self.data];
bitWidth: REAL ~ IF char.set=0 THEN data.charRep[VAL[char.code]].width ELSE data.defaultChar.width;
RETURN[[bitWidth/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];
pm: PixelMap ~ IF char.set=0 THEN data.charRep[VAL[char.code]].pixels ELSE data.defaultChar.pixels;
w: DeviceRectangle ~ ImagerPixelMap.Window[pm];
s: REAL ~ 1.0/data.bitsPerEmQuad;
RETURN[[leftExtent: -w.fMin*s, rightExtent: (w.fMin+w.fSize)*s, descent: (w.sMin+w.sSize)*s, ascent: -w.sMin*s]];
};
GFFontBoundingBox: PROC [self: Typeface] RETURNS [Extents] ~ {
data: Data ~ NARROW[self.data];
left, right, top, bottom: INTEGER ← 0;
Do: PROC [pm: PixelMap] ~ {
w: DeviceRectangle ~ ImagerPixelMap.Window[pm];
left ← MAX[left, -w.fMin];
right ← MAX[right, (w.fMin+w.fSize)];
top ← MAX[top, -w.sMin];
bottom ← MAX[bottom, (w.sMin+w.sSize)];
};
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: Imager.Context] ~ {
data: Data ~ NARROW[self.data];
pm: PixelMap ~ IF char.set=0 THEN data.charRep[VAL[char.code]].pixels ELSE data.defaultChar.pixels;
action: PROC ~ {
Imager.ScaleT[context, 1.0/data.bitsPerEmQuad];
Imager.MaskBits[context: context, base: pm.refRep.pointer, wordsPerLine: pm.refRep.rast,
sMin: pm.sMin, fMin: pm.fMin, sSize: pm.sSize, fSize: pm.fSize, tx: pm.fOrigin-pm.fMin, ty: -(pm.sOrigin-pm.sMin)];
};
IF pm.sSize > 0 THEN Imager.DoSave[context, action];
};
gfClass: TypefaceClass ~ NEW[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
]];
ImagerTypeface.Register["GF", GFCreate];
END.