GFtoAC.mesa
Reads METAFONT84-produced GF (``generic font'') files and writes equivalent AC files.
Last changed by Pavel on February 5, 1986 4:02:34 pm PST
Rick Beach, January 17, 1986 9:38:02 am PST
DIRECTORY
Ascii USING [Upper],
Commander USING [CommandProc, Register],
CommandTool USING [ArgumentVector, Parse, StarExpansion],
Convert USING [Error, IntFromRope],
FS USING [ComponentPositions, ComponentRopes, ConstructFName, Error, ExpandName, StreamOpen],
ImagerPixelMap USING [Clear, Create, DeviceRectangle, Fill, PixelMap],
IO USING [Close, EndOf, EndOfStream, Error, GetBlock, GetChar, GetID, GetIndex, GetInt, int, PutF, PutFR, RIS, rope, SetIndex, STREAM, TIS, Value],
RasterFontIO USING [Create, InternalCharRep, InternalFont, WriteAC],
RefText USING [New],
Rope USING [Cat, Equal, Fetch, IsEmpty, ROPE, Substr, Translate, TranslatorType];
GFtoAC: CEDAR PROGRAM
IMPORTS Ascii, Commander, CommandTool, Convert, FS, ImagerPixelMap, IO, RasterFontIO, RefText, Rope =
BEGIN
FormatError: ERROR [explanation: Rope.ROPE, v1, v2: IO.Value ← [null[]]] = CODE;
BYTE: TYPE = [0..255];
WORD: TYPE = [0..65535];
Ord: PROC [ch: CHAR] RETURNS [BYTE] = INLINE { RETURN[ch - 0C] };
Chr: PROC [i: BYTE] RETURNS [CHAR] = INLINE { RETURN[i + 0C] };
LoadGF: PROC [gfStream: IO.STREAM, deviceResolution: INTEGER]
RETURNS [font: RasterFontIO.InternalFont] =
-- This tries to read GF files in the same way that GFType does
BEGIN
GetByte: PROC RETURNS [BYTE] = INLINE
BEGIN
IF gfStream.EndOf[] THEN
RETURN[0]
ELSE
RETURN[Ord[gfStream.GetChar[]]];
END;
GetTwoBytes: PROC RETURNS [WORD] =
BEGIN
a: BYTE ← GetByte[];
RETURN[a * 256 + GetByte[]];
END;
GetThreeBytes: PROC RETURNS [INT] =
BEGIN
a: INT ← GetByte[];
b: BYTE ← GetByte[];
RETURN[(a * 256 + b) * 256 + GetByte[]];
END;
SignedQuad: PROC RETURNS [INT] =
BEGIN
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[]];
END;
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 é 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 =
BEGIN
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;
END;
ReadPreamble: PROC =
BEGIN
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
END;
LoadCharacters: PROC =
BEGIN
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.
BEGIN
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;
};
Rope.Equal[operation, "deviceresolution"] => {
StartOp[];
IF op # yyy THEN
ERROR FormatError["SPECIAL \"deviceresolution\" not followed by a NUMSPECIAL, at byte %g", IO.int[gfStream.GetIndex[] - 1]];
font.bitsPerInch ← par / 65536;
};
ENDCASE => NULL;
EXITS
GiveUp => NULL;
END;
GetBOC: PROC =
BEGIN
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]];
{ OPEN char: font.charRep[ch];
char.fWidth will be set when reading the postamble.
char.sWidth ← 0;
char.pixels ← ImagerPixelMap.Create[0,
[-nMax, mMin, nMax - nMin + 1, mMax - mMin + 1]];
char.pixels.Clear[];
};
m ← mMin;
n ← nMax;
paintSwitch ← FALSE;
END;
DoChar: PROC =
BEGIN
OPEN char: font.charRep[ch];
DO -- for each operation
StartOp[];
SELECT op FROM
IN [paint0..paint1 + 2] => { -- Paint par pixels
IF paintSwitch AND par > 0 THEN
char.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;
END;
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-- oops! not a character after all
EXIT;
GetBOC[];
DoChar[];
ENDLOOP;
END;
ReadPostamble: PROC =
BEGIN
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;
IF font.bitsPerInch = 0 THEN
ERROR FormatError["Device resolution unspecified in GF file."]
ELSE
font.bitsPerEmQuad ← (designSize / (65536 * 16.0)) * (hppp / 65536.0);
DO-- Read the charLoc commands for the character widths
StartOp[];
SELECT op FROM
charLoc => { -- Character locator
font.charRep[Chr[par]].fWidth ← 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]].fWidth ← 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;
END;
Start of LoadGF
font ← RasterFontIO.Create[[0,0,0,0], 0];
font.bitsPerInch ← deviceResolution;
  -- This can be reset by the 'deviceresolution' special
ReadPreamble[];
LoadCharacters[];
ReadPostamble[];
END;
GFtoACCommand: Commander.CommandProc
-- PROC [cmd: Handle] RETURNS [result: REF ← NIL, msg: Rope.ROPE ← NIL] -- =
BEGIN
argv: CommandTool.ArgumentVector;
gfStream: IO.STREAM;
font: RasterFontIO.InternalFont;
gfName, acName: Rope.ROPE;
fontName, fullName: Rope.ROPENIL;
cp: FS.ComponentPositions;
cr: FS.ComponentRopes;
interpressMode: BOOLEANFALSE;
deviceResolution: INTEGER ← 0;
i: INTEGER;
CommandTool.StarExpansion[cmd];
argv ← CommandTool.Parse[cmd];
IF argv.argc <= 1 THEN GO TO Usage;
i ← 1;
WHILE i < argv.argc DO
IF Rope.Fetch[argv.s[i], 0] = '- THEN
SELECT Rope.Fetch[argv.s[i], 1] FROM
'i => interpressMode ← TRUE;
'r => {
i ← i + 1;
deviceResolution ← Convert.IntFromRope[argv.s[i] !
Convert.Error => GOTO Usage
]
};
ENDCASE => GOTO Usage
ELSE
IF fontName = NIL THEN
fontName ← argv.s[i]
ELSE
GOTO Usage;
i ← i + 1;
ENDLOOP;
IF fontName.IsEmpty THEN GO TO Usage;
[fullName, cp] ← FS.ExpandName[fontName];
cr.server ← fullName.Substr[cp.server.start, cp.server.length];
cr.dir ← fullName.Substr[cp.dir.start, cp.dir.length];
cr.subDirs ← fullName.Substr[cp.subDirs.start, cp.subDirs.length];
cr.base ← fullName.Substr[cp.base.start, cp.base.length];
cr.ext ← fullName.Substr[cp.ext.start, cp.ext.length];
fontName ← cr.base;
IF cp.ext.length = 0 THEN {
cr.ext ← "gf";
gfName ← FS.ConstructFName[cr];
}
ELSE
gfName ← fullName;
{
res: INT ← 0;
res ← IO.GetInt[IO.RIS[cr.ext] ! IO.Error => CONTINUE];
IF res # 0 THEN cr.base ← IO.PutFR["%g-%g", IO.rope[cr.base], IO.int[res]];
};
acName ← FS.ExpandName[Rope.Cat[cr.base, ".ac"]].fullFName;
gfStream ← FS.StreamOpen[gfName
! FS.Error =>
IF error.group = user THEN {
cmd.err.PutF["Cannot open %g: %g\n",
IO.rope[gfName], IO.rope[error.explanation]];
GOTO Abort;
};
];
font ← LoadGF[gfStream, deviceResolution
! FormatError => {
cmd.err.PutF[Rope.Cat["GF file has bad format: ", explanation, "\n"], v1, v2];
GOTO Abort;
};
];
gfStream.Close[];
IF interpressMode THEN {
MyUpper: Rope.TranslatorType = { new ← Ascii.Upper[old] };
font.family ← Rope.Translate[base: fontName, translator: MyUpper];
font.face ← 0;
};
RasterFontIO.WriteAC[font, acName];
cmd.out.PutF["AC Font written on %g\n", IO.rope[acName]];
EXITS
Usage => RETURN[$Failure, commandDoc];
Abort => RETURN[$Failure];
END;
commandDoc: Rope.ROPE ~ "
`GFtoAC [-i] [-r RES] fontname'
 translates fonts from GF format to AC format.

If fontname doesn't have an extension, '.gf' is added. If fontname is 'foo.gf', the resulting file will be 'foo.ac'. If fontname is 'foo.NNNgf', the resulting file will be 'foo-NNN.ac' for any number NNN. GFtoAC always creates its AC files in its current working directory.

-i means to make AC fonts for Xerox product Interpress printers.
-r RES means to claim the device resolution is RES bits per inch.";
Commander.Register["GFtoAC", GFtoACCommand, commandDoc];
END.