ImagerGFTypefaceImpl.mesa
Copyright Ó 1985, 1986, 1987, 1988, 1991 by Xerox Corporation. All rights reserved.
Michael Plass, October 10, 1991 1:39 pm PDT
Pavel, February 24, 1986 3:55:13 pm PST
DIRECTORY
Char,
Imager USING [Context, MaskBitmap, ScaleT],
ImagerFont USING [CorrectionType, Extents, nullXChar, XChar],
ImagerSample USING [Clear, Fill, ObtainScratchMap, ReleaseScratchMap, SampleMap],
ImagerTypeface USING [CreatorFromFileExtension, RegisterCreator, Typeface, TypefaceClass, TypefaceClassRep, TypefaceRep],
IO USING [EndOf, EndOfStream, Error, GetBlock, GetChar, GetID, GetIndex, GetLength, int, SetIndex, STREAM, TIS, Value],
RefText USING [New],
Rope USING [ROPE],
Vector2 USING [VEC];
ImagerGFTypefaceImpl: CEDAR MONITOR
LOCKS NARROW[self.data, Data] USING self: ImagerTypeface.Typeface
IMPORTS Char, Imager, ImagerSample, ImagerTypeface, IO, RefText
~ BEGIN
FormatError: ERROR [explanation: Rope.ROPE, v1, v2: IO.Value ¬ [null[]]] = CODE;
XChar: TYPE ~ ImagerFont.XChar;
CorrectionType: TYPE ~ ImagerFont.CorrectionType;
Extents: TYPE ~ ImagerFont.Extents;
SampleMap: TYPE ~ ImagerSample.SampleMap;
Typeface: TYPE ~ ImagerTypeface.Typeface;
TypefaceClass: TYPE ~ ImagerTypeface.TypefaceClass;
TypefaceClassRep: TYPE ~ ImagerTypeface.TypefaceClassRep;
TypefaceRep: TYPE ~ ImagerTypeface.TypefaceRep;
VEC: TYPE ~ Vector2.VEC;
nullXChar: XChar ~ ImagerFont.nullXChar;
Data: TYPE ~ REF DataRep; -- Private data for instances of this Typeface class
DataRep: TYPE ~ MONITORED RECORD [
stream: IO.STREAM, -- SHARED open stream on the GF file. Users of this must be ENTRYs
charPtrs: ARRAY BYTE OF INT ¬ ALL[-1], -- Character positions in GF file, -1 if not present
widths: ARRAY BYTE OF REAL ¬ ALL[0.0], -- Character widths in design-size units
fontBB: Extents,  -- Bounding box of the whole font (not precise), in design-size units
bitsPerEm: REAL  -- Number of pixels in a single design-size unit
];
Ord: PROC [ch: CHAR] RETURNS [BYTE] = INLINE { RETURN[ch - 0C]; };
GetByte: PROC [gfStream: IO.STREAM] RETURNS [BYTE] = INLINE {
IF gfStream.EndOf[] THEN
RETURN[0]
ELSE
RETURN[Ord[gfStream.GetChar[]]];
};
GetTwoBytes: PROC [gfStream: IO.STREAM] RETURNS [WORD] = {
a: BYTE ¬ GetByte[gfStream];
RETURN[a * 256 + GetByte[gfStream]];
};
GetThreeBytes: PROC [gfStream: IO.STREAM] RETURNS [INT] = {
a: INT ¬ GetByte[gfStream];
b: BYTE ¬ GetByte[gfStream];
RETURN[(a * 256 + b) * 256 + GetByte[gfStream]];
};
SignedQuad: PROC [gfStream: IO.STREAM] RETURNS [INT] = {
a: INT ¬ GetByte[gfStream];
b: BYTE ¬ GetByte[gfStream];
c: BYTE ¬ GetByte[gfStream];
IF a < 128 THEN
RETURN[((a * 256 + b) * 256 + c) * 256 + GetByte[gfStream]]
ELSE
RETURN[(((a - 256) * 256 + b) * 256 + c) * 256 + GetByte[gfStream]];
};
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
fillerByte: BYTE = 223; -- Padding bytes at very end of file
StartOp: PROC [gfStream: IO.STREAM] RETURNS [op: BYTE -- Current opcode --, par: INT -- First parameter to current opcode --] = {
op ¬ GetByte[gfStream];
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[gfStream];
paint1 + 1, skip1 + 1, xxx1 + 1  => par ¬ GetTwoBytes[gfStream];
paint1 + 2, skip1 + 2, xxx1 + 2  => par ¬ GetThreeBytes[gfStream];
boc, xxx1 + 3, yyy   => par ¬ SignedQuad[gfStream];
ENDCASE     => par ¬ 0;
};
LoadGF: PROC [gfStream: IO.STREAM] RETURNS [font: Data] = {
ReadPreamble: PROC = {
byte: BYTE;
IF (byte ¬ GetByte[gfStream]) # pre THEN
ERROR FormatError["First byte is %g, not start of preamble.", IO.int[byte]];
IF (byte ¬ GetByte[gfStream]) # IDByte THEN
ERROR FormatError["Identification byte is %g, should be %g.",
IO.int[byte], IO.int[IDByte]];
};
FindPostamble: PROC ~ {
byte: BYTE;
FOR index: INT ¬ gfStream.GetLength[] - 1, index - 1 DO
gfStream.SetIndex[index];
SELECT (byte ¬ GetByte[gfStream]) FROM
fillerByte => NULL;
IDByte => {
gfStream.SetIndex[index - 4];
EXIT;
};
ENDCASE => ERROR FormatError["Strange byte (%g) at end of file.", IO.int[byte]];
ENDLOOP;
gfStream.SetIndex[SignedQuad[gfStream]];
IF (byte ¬ GetByte[gfStream]) # post THEN
ERROR FormatError["Postamble byte isn't that, it's %g.", IO.int[byte]];
};
ReadPostamble: PROC = {
op: BYTE;  -- Current opcode
par: INT;  -- First parameter to that opcode
designSize: INT; -- Design size in points * 2­20
hppp: INT;  -- Horizontal pixels per point * 2­16.
[] ¬ SignedQuad[gfStream]; -- Skip back pointer
designSize ¬ SignedQuad[gfStream];
[] ¬ SignedQuad[gfStream]; -- Skip checksum
hppp ¬ SignedQuad[gfStream];
[] ¬ SignedQuad[gfStream]; -- Skip vppp
font.bitsPerEm ¬ (designSize / (65536.0 * 16.0)) * (hppp / 65536.0);
font.fontBB.leftExtent ¬ - SignedQuad[gfStream] / font.bitsPerEm; -- min m
font.fontBB.rightExtent ¬ SignedQuad[gfStream] / font.bitsPerEm; -- max m
font.fontBB.descent ¬ - SignedQuad[gfStream] / font.bitsPerEm; -- min n
font.fontBB.ascent ¬ SignedQuad[gfStream] / font.bitsPerEm; -- max n
DO-- Read the charLoc commands for the character widths and positions
[op: op, par: par] ¬ StartOp[gfStream];
SELECT op FROM
charLoc => { -- Character locator
[] ¬ SignedQuad[gfStream]; -- Skip escapement ``dx''
[] ¬ SignedQuad[gfStream]; -- Skip escapement ``dy''
font.widths[par] ¬ SignedQuad[gfStream] / (65536.0 * 16.0); -- TFM width
font.charPtrs[par] ¬ SignedQuad[gfStream]; -- File pointer
};
charLoc + 1 => { -- Abbreviated charLoc
[] ¬ GetByte[gfStream];  -- Skip escapement abbreviation ``dm''
font.widths[par] ¬ SignedQuad[gfStream] / (65536.0 * 16.0); -- TFM width
font.charPtrs[par] ¬ SignedQuad[gfStream]; -- File pointer
};
nop => NULL;
postpost => EXIT;
ENDCASE =>
ERROR FormatError["Funny opcode (%g) in postamble at pos. %g.",
IO.int[op], IO.int[gfStream.GetIndex[]]];
ENDLOOP;
};
Beginning of LoadGF
font ¬ NEW[DataRep];
font.stream ¬ gfStream;
ReadPreamble[];
FindPostamble[];
ReadPostamble[];
};
GetBOC: INTERNAL PROC [gfStream: IO.STREAM] RETURNS [code: BYTE, mMin, mMax, nMin, nMax: INT] = {
op: BYTE;
par: INT;
DO -- skip the nop's and specials in front of the character
[op: op, par: par] ¬ StartOp[gfStream];
SELECT op FROM
boc => {
code ¬ par MOD 256;
[] ¬ SignedQuad[gfStream];  -- Skip back pointer
mMin ¬ SignedQuad[gfStream];
mMax ¬ SignedQuad[gfStream];
nMin ¬ SignedQuad[gfStream];
nMax ¬ SignedQuad[gfStream];
EXIT;
};
boc1 => {
mDel, nDel : INT; -- Deltas for abbreviating [mn]Min
code ¬ par;
mDel ¬ GetByte[gfStream];
mMax ¬ GetByte[gfStream];
nDel ¬ GetByte[gfStream];
nMax ¬ GetByte[gfStream];
mMin ¬ mMax - mDel;
nMin ¬ nMax - nDel;
EXIT;
};
nop, yyy => NULL;
IN [xxx1..xxx1+3] =>
ProcessSpecial[gfStream, par];
ENDCASE => {
ERROR FormatError["Byte %g is not BOC (%g)",
IO.int[gfStream.GetIndex[] - 1], IO.int[op]];
};
ENDLOOP;
};
ProcessSpecial: INTERNAL PROC [gfStream: IO.STREAM, len: NAT] = {
An XXXn opcode has just been read. The next len characters of input are the text of the special command. This code is a boilerplate for future use; we currently ignore all specials.
text: REF TEXT ¬ RefText.New[nChars: len];
specStream: IO.STREAM;
operation: Rope.ROPE;
IF gfStream.GetBlock[block: text, count: len] # len THEN
ERROR FormatError["File not long enough to hold stated length of a SPECIAL text; length=%g", IO.int[len]];
specStream ¬ IO.TIS[text];
operation ¬ specStream.GetID[
! IO.EndOfStream, IO.Error =>
GOTO GiveUp
];
SELECT TRUE FROM
Rope.Equal[operation, "foo"] => {};
ENDCASE => NULL;
EXITS
GiveUp => NULL;
};
GetRaster: INTERNAL PROC [data: Data, code: BYTE] RETURNS [pm: SampleMap] ~ {
mMin, mMax, nMin, nMax : INT; -- Bounding box
m, n: INT;   -- GF abstract machine registers
paintSwitch: BOOLEAN;  -- TRUE means black
op: BYTE;
par: INT;
data.stream.SetIndex[data.charPtrs[code]];
[mMin: mMin, mMax: mMax, nMin: nMin, nMax: nMax] ¬ GetBOC[data.stream];
pm ¬ ImagerSample.ObtainScratchMap[box: [[-nMax, mMin], [1-nMin, mMax+1]]];
ImagerSample.Clear[pm];
m ¬ mMin; -- Initialise GF abstract machine state
n ¬ nMax;
paintSwitch ¬ FALSE;
DO
[op: op, par: par] ¬ StartOp[data.stream];
SELECT op FROM
IN [paint0..paint1 + 2] => { -- Paint par pixels
IF paintSwitch AND par > 0 THEN
ImagerSample.Fill[map: pm, box: [[-n, m], [1-n, par+m]], value: 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 => NULL;  -- Do nothing
pre =>   -- Bad preamble command
ERROR FormatError["Preamble command within a character at pos. %g.", IO.int[data.stream.GetIndex[] - 1]];
post, postpost =>  -- Bad postamble command
ERROR FormatError["Postamble command within a character at pos. %g.", IO.int[data.stream.GetIndex[] - 1]];
charLoc, charLoc + 1 =>  -- Bad character locator
ERROR FormatError["charLoc command within a character at pos. %g.", IO.int[data.stream.GetIndex[] - 1]];
boc =>   -- Bad beginning of character
ERROR FormatError["BOC command within a character at pos. %g.", IO.int[data.stream.GetIndex[] - 1]];
eoc =>   -- End of character
EXIT;
IN [xxx1..xxx1 + 3] =>  -- special string (process it)
ProcessSpecial[data.stream, par];
yyy =>   -- numspecial number (ignore it)
NULL;
ENDCASE =>   -- Unknown opcode
ERROR FormatError["Undefined Opcode %g at pos. %g.",
IO.int[op], IO.int[data.stream.GetIndex[] - 1]];
ENDLOOP;
};
GFCreate: PROC [stream: IO.STREAM] RETURNS [Typeface] ~ {
data: Data ~ LoadGF[stream];
RETURN[NEW[TypefaceRep ¬ [class: gfClass, data: data]]];
};
GFContains: PROC [self: Typeface, char: XChar] RETURNS [BOOL] ~ {
data: Data ~ NARROW[self.data];
RETURN [Char.Set[char] = 0 AND data.charPtrs[Char.Code[char]] # -1];
};
GFNextChar: PROC [self: Typeface, char: XChar] RETURNS [next: XChar] ~ {
data: Data ~ NARROW[self.data];
start: BYTE;
IF char=nullXChar
THEN start ¬ 0
ELSE IF Char.Set[char] = 0 AND Char.Code[char] < LAST[BYTE] THEN start ¬ Char.Code[char] + 1
ELSE RETURN[nullXChar];
FOR code: BYTE IN [start..LAST[BYTE]] DO
IF data.charPtrs[code] # -1 THEN RETURN[Char.Make[set: 0, code: code]];
ENDLOOP;
RETURN[nullXChar];
};
GFEscapement: PROC [self: Typeface, char: XChar] RETURNS [VEC] ~ {
data: Data ~ NARROW[self.data];
IF Char.Set[char] = 0
THEN RETURN [[data.widths[Char.Code[char]], 0]]
ELSE RETURN [[0, 0]]
};
GFAmplified: PROC [self: Typeface, char: XChar] RETURNS [BOOL] ~ {
RETURN [FALSE];
};
GFCorrection: PROC [self: Typeface, char: XChar] RETURNS [CorrectionType] ~ {
RETURN [mask];
};
GFBoundingBox: ENTRY PROC [self: Typeface, char: XChar] RETURNS [Extents] ~ {
data: Data ~ NARROW[self.data];
IF Char.Set[char] = 0 AND data.charPtrs[Char.Code[char]] # -1
THEN {
mMin, mMax, nMin, nMax: INT;
data.stream.SetIndex[data.charPtrs[Char.Code[char]]];
[mMin: mMin, mMax: mMax, nMin: nMin, nMax: nMax] ¬ GetBOC[data.stream];
RETURN [[
leftExtent: - mMin / data.bitsPerEm,
rightExtent: mMax / data.bitsPerEm,
descent: - nMin / data.bitsPerEm,
ascent: nMax / data.bitsPerEm
]];
}
ELSE RETURN [[0, 0, 0, 0]];
};
GFFontBoundingBox: PROC [self: Typeface] RETURNS [Extents] ~ {
data: Data ~ NARROW[self.data];
RETURN [data.fontBB];
};
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: ENTRY PROC [self: Typeface, char: XChar, context: Imager.Context] ~ {
data: Data ~ NARROW[self.data];
IF Char.Set[char] = 0 AND data.charPtrs[Char.Code[char]] # -1 THEN {
pm: SampleMap ¬ GetRaster[data, Char.Code[char]];
Imager.ScaleT[context, 1.0/data.bitsPerEm];
Imager.MaskBitmap[context: context, bitmap: pm];
ImagerSample.ReleaseScratchMap[pm]; pm ¬ NIL;
};
};
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.RegisterCreator[ImagerTypeface.CreatorFromFileExtension["GF", GFCreate]];
END.