PrintFontsPrivateImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Written by: Maureen Stone April 6, 1985 3:48:30 pm PST
Last Changed: Maureen Stone May 28, 1985 9:28:30 am PDT
DIRECTORY
PrintFontsPrivate,
Cubic USING [Bezier, Coeffs, CoeffsToBezier],
Quadratic USING [Roots, RealRoots],
Basics USING [LowHalf],
Rope USING [ROPE, Fetch, Equal],
IO USING [STREAM, Backup, GetTokenRope, GetInt, GetReal, GetTime, rope,char, Close, PutFR, EndOfStream],
FS USING [StreamOpen],
Font USING [Key],
BasicTime USING [GMT],
UFFileManager USING[KeyOf],
UFPressFontReader USING[Size, Range, Family, Face, Resolution, GetCharInfo, GetCharRaster, CharInfo, NumberOfFontsInFile, GetCharPath],
ImagerBasic USING [PixelArray, PathMapType, Pair];
PrintFontsPrivateImpl:
CEDAR PROGRAM
IMPORTS UFFileManager, UFPressFontReader, IO, Rope, FS, Cubic, Basics, Quadratic
EXPORTS PrintFontsPrivate = BEGIN OPEN PrintFontsPrivate;
FontNotFound: PUBLIC SIGNAL=CODE;
STREAM: TYPE = IO.STREAM;
ROPE: TYPE = Rope.ROPE;
Pair: TYPE = ImagerBasic.Pair;
LoadAC:
PUBLIC PROC [fileName: Rope.
ROPE]
RETURNS [font: PrivateFont] ~ {
fileKey: Font.Key ← UFFileManager.KeyOf[fileName];
sizeInMeters, bitsPerEmQuad: REAL;
IF UFPressFontReader.NumberOfFontsInFile[fileKey] = 0 THEN SIGNAL FontNotFound;
sizeInMeters ← UFPressFontReader.Size[[fileKey, 0]];
font ← NEW [PrivateFontRep];
font.type ← raster;
[font.bc, font.ec] ← UFPressFontReader.Range[[fileKey, 0]];
font.family ← UFPressFontReader.Family[[fileKey, 0]];
font.face ← UFPressFontReader.Face[[fileKey, 0]];
bitsPerEmQuad ← sizeInMeters*UFPressFontReader.Resolution[[fileKey, 0]].xRes/0.0254;
FOR char:
CHAR
IN [font.bc..font.ec]
DO
info: UFPressFontReader.CharInfo ← UFPressFontReader.GetCharInfo[[fileKey, 0], char];
pixelArray: ImagerBasic.PixelArray ← UFPressFontReader.GetCharRaster[[fileKey, 0], char];
font.charRep[char] ← [
width: (info.maxX-info.minX)*bitsPerEmQuad,
pixels: pixelArray
];
IF font.charRep[char].width > font.maxW THEN font.maxW ← font.charRep[char].width;
IF info.maxY > font.ascent THEN font.ascent ← info.maxY;
IF ABS[info.minY] > font.descent THEN font.descent ← ABS[info.minY];
ENDLOOP;
font.ascent ← font.ascent*bitsPerEmQuad;
font.descent ← font.descent*bitsPerEmQuad;
};
Compute sizes relative to pointSize. PointSize is in device pixels
LoadSD:
PUBLIC PROC [fileName: Rope.
ROPE, pointSize:
REAL]
RETURNS [font: PrivateFont] ~ {
fileKey: Font.Key ← UFFileManager.KeyOf[fileName];
IF UFPressFontReader.NumberOfFontsInFile[fileKey] = 0 THEN SIGNAL FontNotFound;
font ← NEW [PrivateFontRep];
[font.bc, font.ec] ← UFPressFontReader.Range[[fileKey, 0]];
font.type ← outline;
font.pointSize ← pointSize;
font.family ← UFPressFontReader.Family[[fileKey, 0]];
font.face ← UFPressFontReader.Face[[fileKey, 0]];
FOR char:
CHAR
IN [font.bc..font.ec]
DO
info: UFPressFontReader.CharInfo ← UFPressFontReader.GetCharInfo[[fileKey, 0], char];
pathMap: ImagerBasic.PathMapType;
pathData: REF;
[pathMap, pathData]← UFPressFontReader.GetCharPath[[fileKey, 0], char];
font.charRep[char] ← [
width: (info.maxX-info.minX)*pointSize,
outline: NEW[PathTypeRec ← [pathMap, pathData]]
];
IF font.charRep[char].width > font.maxW THEN font.maxW ← font.charRep[char].width;
IF info.maxY > font.ascent THEN font.ascent ← info.maxY;
IF ABS[info.minY] > font.descent THEN font.descent ← ABS[info.minY];
ENDLOOP;
font.ascent ← font.ascent*pointSize;
font.descent ← font.descent*pointSize;
};
LoadSF:
PUBLIC PROC [fileName: Rope.
ROPE, pointSize:
REAL]
RETURNS [font: PrivateFont] ~ {
stream: IO.STREAM ← FS.StreamOpen[fileName];
first: BOOL ← TRUE;
font ← NEW[PrivateFontRep];
font.type ← outline;
font.pointSize ← pointSize;
font.bc ← VAL[255]; --set with max/min in GetSFChar
font.ec ← VAL[0];
font.descent ← font.ascent ← 0; --set with max/min in GetSFChar
DO
GetSFChar[font, stream, first, pointSize! Stop => EXIT];
first ← FALSE;
ENDLOOP;
IO.Close[stream];
};
CloseP: SIGNAL = CODE;
Stop: SIGNAL = CODE;
GetOpenP:
PROC[stream:
IO.
STREAM] = {
r: ROPE ← GetToken[stream ! IO.EndOfStream => SIGNAL Stop];
char: CHAR ← Rope.Fetch[r,0];
IF char='( THEN RETURN;
IF char= ') THEN {IO.Backup[stream, char]; SIGNAL CloseP}
ELSE IF Rope.Equal["STOP", r] THEN SIGNAL Stop
ELSE ERROR;
};
GetCloseP:
PROC[stream:
IO.
STREAM] = {
char: CHAR ← Rope.Fetch[GetToken[stream],0];
IF ') # char THEN ERROR;
};
GetToken:
PROC[stream:
IO.
STREAM]
RETURNS[
ROPE] = {
r: ROPE; [r,] ← IO.GetTokenRope[stream]; RETURN[r]};
GetSFChar:
PROC[font: PrivateFont, stream:
STREAM, first:
BOOL, pointSize:
REAL] = {
wx, wy: REAL;
fx, fy: REAL;
minX, minY, maxX, maxY: REAL;
version: INT;
char: CHAR;
time: BasicTime.GMT;
face0, face1, face2, token, family: ROPE;
pathData: REF;
GetOpenP[stream];
DO
--get character pieces
GetOpenP[stream ! CloseP => {GetCloseP[stream]; EXIT}]; --CloseP does a PutBack
token ← GetToken[stream]; --now looking for a key word item
SELECT
TRUE
FROM
Rope.Equal[token, "FAMILY", FALSE] => family ← GetToken[stream];
Rope.Equal[token, "CHARACTER",
FALSE] =>
char ← VAL[Basics.LowHalf[IO.GetInt[stream]]];
Rope.Equal[token, "FACE",
FALSE] => {
face0 ← GetToken[stream];
face1 ← GetToken[stream];
face2 ← GetToken[stream];
};
Rope.Equal[token, "WIDTH",
FALSE] => {
wx ← IO.GetReal[stream];
wy ← IO.GetReal[stream];
};
Rope.Equal[token, "FIDUCIAL",
FALSE] => {
fx ← IO.GetReal[stream];
fy ← IO.GetReal[stream];
};
Rope.Equal[token, "VERSION",
FALSE] => {
version ← IO.GetInt[stream];
time ← IO.GetTime[stream]
};
Rope.Equal[token, "MADE-FROM", FALSE] => NULL;
Rope.Equal[token, "SPLINES",
FALSE] =>
--get spline path and a bounding box
[pathData, minX, maxX, minY, maxY] ← GetSFSplines[stream];
ENDCASE;
UNTIL Rope.Fetch[GetToken[stream],0] =') DO ENDLOOP; --end the item
ENDLOOP;
Fill-in charRep from variables. max, min and splines are relative to the fiducial
ScalePathData[pathData, 1/fx, 1/fy];
minX ← pointSize*minX/fx; minY ← pointSize*minY/fy;
maxX ← pointSize*maxX/fx; maxY ← pointSize*maxY/fy;
font.charRep[char] ← [
width: (maxX-minX),
outline: NEW[PathTypeRec ← [PathMap, pathData]]
];
IF first
THEN font.family ←
IO.PutFR["%g-%g%g%g",
IO.rope[family],
IO.char[Rope.Fetch[face0,0]],IO.char[Rope.Fetch[face1,0]],IO.char[Rope.Fetch[face2,0]]];
IF font.charRep[char].width > font.maxW THEN font.maxW ← font.charRep[char].width;
IF char<font.bc THEN font.bc ← char;
IF char>font.ec THEN font.ec ← char;
IF maxY > font.ascent THEN font.ascent ← maxY;
IF ABS[minY] > font.descent THEN font.descent ← ABS[minY];
};
PairSeq: TYPE = REF PairSeqRec;
PairSeqRec: TYPE = RECORD[element: SEQUENCE length:NAT OF Pair];
BezierSeq: TYPE = REF BezierSeqRec;
BezierSeqRec:
TYPE =
RECORD[element:
SEQUENCE length:
NAT
OF Cubic.Bezier];
Get PathData and geometric bounding box
GetSFSplines:
PROC[stream:
IO.
STREAM]
RETURNS[pathData:
LIST
OF BezierSeq, minX, maxX, minY, maxY:
REAL] = {
minX ← minY ← 10E6;
maxX ← maxY ← -10E6;
DO
bezierList: LIST OF BezierSeq;
closedContour: BezierSeq;
GetOpenP[stream! CloseP => EXIT]; --open closed curve
DO
--get splines
knots, derivatives: PairSeq;
bezier: BezierSeq;
minBX, maxBX, minBY, maxBY: REAL;
nKnots: INT;
token: ROPE;
GetOpenP[stream ! CloseP => EXIT]; --open spline
nKnots ← IO.GetInt[stream];
knots ← NEW[PairSeqRec[nKnots]];
GetOpenP[stream]; --open knot list
FOR i:
INT
IN [0..nKnots)
DO
--knot sequence
GetOpenP[stream];
knots[i].x ← IO.GetReal[stream];
knots[i].y ← IO.GetReal[stream];
GetCloseP[stream];
ENDLOOP;
GetCloseP[stream]; --close knot list
token ← GetToken[stream]; --weight list. I assume this is always NIL
IF NOT Rope.Equal[token, "NIL", FALSE] THEN ERROR;
derivatives ← NEW[PairSeqRec[3*(nKnots-1)]];
GetOpenP[stream]; -- open derivative list
FOR i:
INT
IN [0..nKnots-1)
DO
--
derivatives
sequence
GetOpenP[stream];
derivatives[3*i].x ← IO.GetReal[stream]; --first
derivatives[3*i].y ← IO.GetReal[stream];
derivatives[3*i+1].x ← IO.GetReal[stream]; --second
derivatives[3*i+1].y ← IO.GetReal[stream];
derivatives[3*i+2].x ← IO.GetReal[stream]; --third
derivatives[3*i+2].y ← IO.GetReal[stream];
GetCloseP[stream];
ENDLOOP;
GetCloseP[stream]; --close derivative list
token ← GetToken[stream]; --solution method, but we don't need it
GetCloseP[stream]; --close spline
Convert the knots and derivatives to a BezierSequence.
Also computes bounding box for this sequence of cubics
[bezier, minBX, maxBX, minBY, maxBY] ← KnotsToBezier[knots, derivatives];
Add cubics to list. Compute global max and min
bezierList ← CONS[bezier, bezierList];
minX ← MIN[minX, minBX];
minY ← MIN[minY, minBY];
maxX ← MAX[maxX, maxBX];
maxY ← MAX[maxY, maxBY];
ENDLOOP;
now we have a LIST of Bezier sequences. Convert this to one sequence
IF bezierList.rest=NIL THEN closedContour ← bezierList.first
ELSE {
--copy the elements into one long sequence
nCubics: NAT ← 0;
index: INT ← 0;
rList: LIST OF BezierSeq;
FOR l:
LIST
OF BezierSeq ← bezierList, l.rest
UNTIL l=
NIL
DO
rList ← CONS[l.first, rList]; --reverse the order
nCubics ← nCubics+l.first.length;
ENDLOOP;
closedContour ← NEW[BezierSeqRec[nCubics]];
FOR l:
LIST
OF BezierSeq ← rList, l.rest
UNTIL l=
NIL
DO
FOR i:
INT
IN [0..l.first.length)
DO
closedContour[index]← l.first[i];
index ← index+1;
ENDLOOP;
ENDLOOP;
};
pathData ← CONS[closedContour, pathData];
GetCloseP[stream]; --close closed curve
ENDLOOP;
IF pathData=NIL THEN minX ← maxX ← minY ← maxY ← 0;
RETURN[pathData, minX, maxX, minY, maxY];
};
KnotsToBezier:
PROC [knots, derivatives: PairSeq]
RETURNS [bezier: BezierSeq, minX, maxX, minY, maxY:
REAL] = {
getMinMax:
PROC [a, b, c, d:
REAL]
RETURNS [min, max:
REAL] = {
min ← MIN[d, (a+b+c+d)];
max ← MAX[d, (a+b+c+d)];
IF c*(3*a+2*b+c) < 0
THEN {
--sign of second derivative changes in [0..1]
There's a max/min inside this piece. Find the root of the derivative
roots: Quadratic.Roots ← Quadratic.RealRoots[3*a, 2*b, c];
IF roots.nRoots=0 THEN ERROR;
IF roots.r1
IN [0..1]
THEN {
x: REAL ← (((a*roots.r1+b)*roots.r1+c)*roots.r1+d);
min ← MIN[min, x];
max ← MAX[max, x];
};
IF roots.nRoots=2 AND roots.r2
IN [0..1]
THEN {
x: REAL ← (((a*roots.r2+b)*roots.r2+c)*roots.r2+d);
min ← MIN[min, x];
max ← MAX[max, x];
};
};
};
nCubics: INT ← knots.length-1;
minX ← minY ← 10E+10;
maxX ← maxY ← -10E+10;
Make knots and derivatives into a sequence of Bezier curves
bezier ← NEW[BezierSeqRec[nCubics]];
FOR i:
INT
IN [0..nCubics)
DO
--knot sequence
cubic: Cubic.Coeffs;
min, max: REAL;
cubic.c0.x ← knots[i].x;
cubic.c0.y ← knots[i].y;
cubic.c1.x ← derivatives[3*i].x;
cubic.c1.y ← derivatives[3*i].y;
cubic.c2.x ← derivatives[3*i+1].x/2;
cubic.c2.y ← derivatives[3*i+1].y/2;
cubic.c3.x ← derivatives[3*i+2].x/6;
cubic.c3.y ← derivatives[3*i+2].y/6;
bezier[i] ← Cubic.CoeffsToBezier[cubic];
[min, max] ← getMinMax[cubic.c3.x, cubic.c2.x, cubic.c1.x, cubic.c0.x];
minX ← MIN[min, minX]; maxX ← MAX[max, maxX];
[min, max] ← getMinMax[cubic.c3.y, cubic.c2.y, cubic.c1.y, cubic.c0.y];
minY ← MIN[min, minY]; maxY ← MAX[max, maxY];
IF debug
THEN {
IF minX/15840 NOT IN [-range..range] OR minY/15840 NOT IN [-range..range]
OR maxX/15840
NOT
IN [-range..range]
OR maxY/15840
NOT
IN [-range..range]
THEN
SIGNAL Debug[minX/15840, maxX/15840, minY/15840, maxY/15840]};
ENDLOOP;
};
Debug: SIGNAL[minX, maxX, minY, maxY: REAL] = CODE;
range: REAL ← 200;
debug: BOOLEAN ← FALSE;
ScalePathData:
PUBLIC PROC[data:
REF, sx, sy:
REAL] = {
bezierList: LIST OF BezierSeq ← NARROW[data];
FOR l:
LIST
OF BezierSeq ← bezierList, l.rest
UNTIL l=
NIL
DO
FOR i:
INT
IN [0..l.first.length)
DO
l.first[i].b0.x ← l.first[i].b0.x*sx; l.first[i].b0.y ← l.first[i].b0.y*sy;
l.first[i].b1.x ← l.first[i].b1.x*sx; l.first[i].b1.y ← l.first[i].b1.y*sy;
l.first[i].b2.x ← l.first[i].b2.x*sx; l.first[i].b2.y ← l.first[i].b2.y*sy;
l.first[i].b3.x ← l.first[i].b3.x*sx; l.first[i].b3.y ← l.first[i].b3.y*sy;
ENDLOOP;
ENDLOOP;
};
PathMap: ImagerBasic.PathMapType = {
data: REF,
move: PROC[p: Pair],
line: PROC[p: Pair],
curve: PROC[p1, p2, p3: Pair],
conic: PROC[p1, p2: Pair, r: REAL]
bezierList: LIST OF BezierSeq ← NARROW[data];
FOR l:
LIST
OF BezierSeq ← bezierList, l.rest
UNTIL l=
NIL
DO
move[[l.first[0].b0.x, l.first[0].b0.y]];
FOR i:
INT
IN [0..l.first.length)
DO
curve[[l.first[i].b1.x, l.first[i].b1.y],
[l.first[i].b2.x, l.first[i].b2.y], [l.first[i].b3.x, l.first[i].b3.y]];
ENDLOOP;
ENDLOOP;
};
END.