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],
~
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 * 220
hppp: INT; -- Horizontal pixels per point * 216.
[] ¬ 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]];