DIRECTORY
Atom USING [GetPName, GetProp, PutProp],
UFont,
FS USING [StreamOpen],
Imager USING [Error],
ImagerTransform,
IO USING [Close, PutF, real, rope, STREAM, time],
Real USING [MinusZero, SqRt],
RealFns USING [ArcTanDeg],
RefText USING [TrustTextAsRope],
Rope USING [Equal, Fetch, FromProc, Length, Map],
Scaled USING [Float, FromReal, Max, PLUS, Round, Value, zero],
UFFileManager,
UFFontDirReader;
BadFontFileFormat: PUBLIC ERROR = CODE;
rotationFuzz: REAL = 1/3600.0;
fontDirFileName: ROPE ← "Cedar.FontDir";
fontHead: FONT ← NIL; -- all FONTs are linked together, starting here.
defaultFont: FONT;
defaultFontGenerated: BOOLEAN ← FALSE;
DefaultFont:
PROCEDURE [fontName: RopeOrRefText, transformation: Transformation, deviceType:
ATOM]
RETURNS [font:
FONT] = {
RememberFont: ENTRY PROC = INLINE {font.link ← fontHead; fontHead ← font};
IF defaultFontGenerated AND defaultFont = NIL THEN ERROR;
IF defaultFont = NIL THEN {defaultFontGenerated ← TRUE; defaultFont ← Create["Xerox/PressFonts/Tioga/MRR", ImagerTransform.Scale[10], $Screen]};
font ← NEW[FontRec];
font^ ← defaultFont^;
TRUSTED {font.name ← CopyRope[LOOPHOLE[fontName]]};
font.requestedTransformation ← transformation;
font.deviceType ← deviceType;
RememberFont[];
};
initProcRec:
TYPE =
RECORD [
init: PROC [font: FONT]
];
RegisterFontFormattingClass:
PUBLIC
PROC [flavor:
ATOM, init:
PROC [font:
FONT]] = {
Atom.PutProp[atom: flavor, prop: $FontFormattingClass, val: NEW[initProcRec ← [init: init]]];
};
RegisterFontGraphicsClass:
PUBLIC
PROCEDURE [flavor:
ATOM, init:
PROC [font:
FONT]] = {
Atom.PutProp[atom: flavor, prop: $FontGraphicsClass, val: NEW[initProcRec ← [init: init]]];
};
areaLimit: REAL ← 80*80;
Area:
PROC [b: Box]
RETURNS [area:
REAL] = {
OPEN b;
area ← (ymax-ymin)*(xmax-xmin);
IF area<0 THEN ERROR;
};
CopyRope:
PROCEDURE [rope:
ROPE]
RETURNS [
ROPE] = {
i: INT ← -1;
CharProc: PROCEDURE RETURNS [CHAR] = {RETURN[rope.Fetch[i←i+1]]};
RETURN[Rope.FromProc[len: rope.Length[], p: CharProc]];
};
TrustAsRope:
PROCEDURE [text:
REF]
RETURNS [
ROPE] =
TRUSTED {
RETURN[LOOPHOLE[text]];
};
VecMag: PROC [x, y: REAL] RETURNS [REAL] = INLINE {RETURN[Real.SqRt[x*x + y*y]]};
Create:
PUBLIC
PROCEDURE [fontName: RopeOrRefText, transformation: Transformation, deviceType:
ATOM]
RETURNS [font:
FONT] = {
m: ImagerTransform.TransformationRec ~ transformation.Contents;
name: ROPE ← IF ISTYPE[fontName, REF TEXT] THEN TrustAsRope[fontName] ELSE NARROW[fontName];
scale: REAL ← VecMag[m.a, m.d];
rotation: REAL ← RealFns.ArcTanDeg[m.d, m.a];
TestForMatch: UFFontDirReader.ExamineProc = {
IF deviceType # candidateDeviceType THEN RETURN [FALSE];
IF scale < candidateMinSize OR scale > candidateMaxSize THEN RETURN [FALSE];
IF NOT name.Equal[RefText.TrustTextAsRope[candidateFontName], FALSE] THEN RETURN [FALSE];
IF candidateRotation >= 0 AND ABS[candidateRotation - rotation] >= rotationFuzz THEN RETURN [FALSE];
RETURN [TRUE];
};
RememberFont: ENTRY PROC = {font.link ← fontHead; fontHead ← font};
SaveResult: UFFontDirReader.ResultProc = {
formattingClassInit: REF initProcRec ← NARROW[Atom.GetProp[atom: metricsType, prop: $FontFormattingClass]];
graphicsClassInit: REF initProcRec ← NARROW[Atom.GetProp[atom: graphicsType, prop: $FontGraphicsClass]];
font ← NEW[FontRec];
font.name ← CopyRope[name]; -- So it is OK to trust a REF TEXT as a ROPE
font.requestedTransformation ← font.actualTransformation ← transformation;
PutProp[font, $candidateSize,
NEW[
REAL ← candidateSize]];
the candidateSize is needed for strike fonts
IF candidateSize # 0.0 THEN {
font.actualTransformation ← ImagerTransform.Scale[candidateSize];
IF candidateRotation >= 0 THEN font.actualTransformation ← ImagerTransform.PreRotate[candidateRotation, font.actualTransformation];
};
font.deviceType ← candidateDeviceType;
font.nsCodeTable ← codeScheme;
font.formattingKey ← metricsName;
font.graphicsKey ← graphicsName;
formattingClassInit.init[font];
graphicsClassInit.init[font];
InitFontBoundingBox[font];
RememberFont[];
quit ← TRUE;
};
LookAmongExistingFonts:
ENTRY
PROC
RETURNS [
FONT ←
NIL] = {
FOR f:
FONT ← fontHead, f.link
UNTIL f=
NIL
DO
IF deviceType=f.deviceType AND transformation.Contents = f.requestedTransformation.Contents AND name.Equal[f.name, FALSE] THEN RETURN[f];
ENDLOOP;
};
font ← LookAmongExistingFonts[];
IF font=NIL THEN UFFontDirReader.EnumerateFontDirectory[fontDirFileName, TestForMatch, SaveResult];
IF font=
NIL THEN {
Always want to return something.
stream: IO.STREAM ← FS.StreamOpen["UnifiedFonts.errorLog", $append];
stream.PutF[
"%g Default substituted for %g font %g at %gbp, %g\n",
IO.time[],
IO.rope[Atom.GetPName[deviceType]],
IO.rope[CopyRope[name]],
IO.real[transformation.Contents.a -- *********************** --],
IO.real[0 -- *********************** --]
];
stream.Close;
Imager.Error[$NonexistentFont];
font ← DefaultFont[fontName, transformation, deviceType];
};
};
CreateScaled:
PUBLIC
PROCEDURE [fontName: RopeOrRefText, scale:
REAL, deviceType:
ATOM]
RETURNS [font:
FONT] = {
RETURN[Create[fontName, ImagerTransform.Scale[scale], deviceType]]
};
InitFontBoundingBox:
PROCEDURE [font:
FONT] = {
bb: Box ← [0,0,0,0];
FOR c:
CHAR
IN [font.bc..font.ec]
DO
cbb: Box ← BoundingBox[font, c];
bb.xmin ← MIN[bb.xmin, cbb.xmin];
bb.ymin ← MIN[bb.ymin, cbb.ymin];
bb.xmax ← MAX[bb.xmax, cbb.xmax];
bb.ymax ← MAX[bb.ymax, cbb.ymax];
ENDLOOP;
font.fontBoundingBox ← bb;
};
GetPressFontSpecification:
PUBLIC PROCEDURE [font:
FONT]
RETURNS[pfs: PressFontSpecification] = {
};
GetLigatureOrKern:
PUBLIC
PROCEDURE [font:
FONT, char1, char2:
CHAR]
RETURNS [ligatureOrKern: LigatureOrKern] = {
RETURN[font.fontFormattingClass.ligKernProc[font, char1, char2]];
};
NSCode:
PUBLIC
PROCEDURE [font:
FONT, char:
CHAR]
RETURNS [nsCode:
INT] = {
IF NOT font.fontGraphicsClass.containsProc[font, char] THEN RETURN[nonexistentNSCode]
ELSE
TRUSTED {
fontFile: UFFileManager.FontFile ← UFFileManager.Open[font.nsCodeTable];
p: LONG POINTER TO CARDINAL ← fontFile.Pointer[] + LOOPHOLE[char, NAT];
IF NOT fontFile.InBounds[p] THEN RETURN[nonexistentNSCode];
RETURN[p^];
};
};
FindCharCode:
PUBLIC
PROCEDURE [font:
FONT, nsCode:
INT]
RETURNS [char:
CHAR] =
TRUSTED {
fontFile: UFFileManager.FontFile ← UFFileManager.Open[font.nsCodeTable];
code: CARDINAL ← nsCode;
p: LONG POINTER TO CARDINAL ← fontFile.Pointer[];
IF NOT fontFile.InBounds[p, font.ec - '\000] THEN RETURN['\000];
IF (p+code)^ = code THEN RETURN['\000 + code];
FOR i:
NAT
IN [font.bc - '\000 .. font.ec - '\000]
DO
IF (p+i)^ = code THEN RETURN['\000 + i];
ENDLOOP;
RETURN['\000];
};
TextWidth:
PUBLIC
PROCEDURE [font:
FONT, text: RopeOrRefText]
RETURNS [
REAL] ~ {
w: Scaled.Value ← Scaled.zero;
width: REF READONLY ARRAY CHAR OF FixedUnit ← GetWidthArray[font, Scaled.zero, Scaled.zero];
WITH text
SELECT
FROM
t: REF TEXT => FOR i: NAT IN [0..t.length) DO w ← w.PLUS[width[t[i]]] ENDLOOP;
r:
ROPE => {
action:
PROC [c:
CHAR]
RETURNS [
BOOLEAN] ~ {
w ← w.PLUS[width[c]];
RETURN [FALSE]
};
[] ← r.Map[action: action];
};
ENDCASE => w ← Scaled.zero;
RETURN [Scaled.Float[w]]
};
RoundedTextWidth:
PUBLIC
PROCEDURE [font:
FONT, text: RopeOrRefText]
RETURNS [
INTEGER] ~ {
w: Scaled.Value ← Scaled.zero;
width: REF READONLY ARRAY CHAR OF FixedUnit ← GetWidthArray[font, Scaled.zero, Scaled.zero];
WITH text
SELECT
FROM
t: REF TEXT => FOR i: NAT IN [0..t.length) DO w ← w.PLUS[width[t[i]]] ENDLOOP;
r:
ROPE => {
action:
PROC [c:
CHAR]
RETURNS [
BOOLEAN] ~ {
w ← w.PLUS[width[c]];
RETURN [FALSE]
};
[] ← r.Map[action: action];
};
ENDCASE => w ← Scaled.zero;
RETURN [Scaled.Round[w]]
};
GetWidthArray:
PUBLIC
PROCEDURE [font:
FONT, minimum, undefined: FixedUnit]
RETURNS [
REF
READONLY
ARRAY
CHAR
OF FixedUnit] = {
GetCache: ENTRY PROCEDURE = INLINE {IF font.cachedWidthsMinimum = minimum AND font.cachedWidthsUndefined = undefined THEN cachedWidths ← font.cachedWidths};
SetCache: ENTRY PROCEDURE = INLINE {font.cachedWidths ← cachedWidths; font.cachedWidthsMinimum ← minimum; font.cachedWidthsUndefined ← undefined};
cachedWidths: REF ARRAY CHAR OF FixedUnit ← NIL;
GetCache[];
IF cachedWidths =
NIL
THEN {
cachedWidths ← NEW[ARRAY CHAR OF FixedUnit];
FOR c: CHAR IN [FIRST[CHAR]..font.bc) DO cachedWidths[c] ← undefined ENDLOOP;
FOR c:
CHAR
IN [font.bc..font.ec]
DO
cachedWidths[c] ← minimum.Max[Scaled.FromReal[Width[font, c]]]
ENDLOOP;
FOR c: CHAR IN (font.ec..LAST[CHAR]] DO cachedWidths[c] ← undefined ENDLOOP;
SetCache[];
};
RETURN[cachedWidths];
};
AtomValuePair: TYPE = RECORD [atom: ATOM, value: REF ANY];
PutProp:
PUBLIC
ENTRY
PROCEDURE [font:
FONT, name:
ATOM, value:
REF
ANY] = {
list: LIST OF AtomValuePair ← NARROW[font.propertyList];
FOR a:
LIST
OF AtomValuePair ← list, a.rest
UNTIL a=
NIL
DO
IF a.first.atom = name THEN {a.first.value ← value; RETURN};
ENDLOOP;
font.propertyList ← list ← CONS[[name, value], list];
};
GetProp:
PUBLIC
ENTRY
PROCEDURE [font:
FONT, name:
ATOM]
RETURNS [value:
REF
ANY] = {
FOR a:
LIST
OF AtomValuePair ←
NARROW[font.propertyList], a.rest
UNTIL a=
NIL
DO
IF a.first.atom = name THEN {value ← a.first.value; RETURN};
ENDLOOP;
value ← NIL;
};
Name: PUBLIC PROCEDURE [font: FONT] RETURNS [fontName: ROPE] = {RETURN[font.name]};
NextCharacterDirection: PUBLIC PROCEDURE [font: FONT] RETURNS [Direction] = {RETURN[right]};
NextLineDirection: PUBLIC PROCEDURE [font: FONT] RETURNS [Direction] = {RETURN[down]};
FormattingBox: PUBLIC PROCEDURE [font: FONT, char: CHAR] RETURNS [Box] = {RETURN[font.fontFormattingClass.formattingBoxProc[font, char]]};
FormattingMetric: PUBLIC PROCEDURE [font: FONT, metric: ATOM, char: CHAR ← '\000] RETURNS [REAL] = {RETURN[font.fontFormattingClass.formattingMetricProc[font, metric, char]]};
BoundingBox: PUBLIC PROCEDURE [font: FONT, char: CHAR] RETURNS [Box] = {RETURN[IF char IN [font.bc..font.ec] THEN font.fontGraphicsClass.boundingBoxProc[font, char] ELSE [Real.MinusZero, Real.MinusZero, Real.MinusZero, Real.MinusZero]]};
WidthVector: PUBLIC PROCEDURE [font: FONT, char: CHAR] RETURNS [Pair] = {RETURN[IF char IN [font.bc..font.ec] THEN font.fontGraphicsClass.widthVectorProc[font, char] ELSE [Real.MinusZero, Real.MinusZero]]};
ActualTransformation: PUBLIC PROCEDURE [font: FONT] RETURNS [Transformation] = {RETURN[font.actualTransformation]};
RequestedTransformation: PUBLIC PROCEDURE [font: FONT] RETURNS [Transformation] = {RETURN[font.requestedTransformation]};
Contains: PUBLIC PROCEDURE [font: FONT, char: CHAR] RETURNS [BOOLEAN] = {RETURN[font.fontGraphicsClass.containsProc[font, char]]};
CharCode: PUBLIC PROCEDURE [font: FONT, nsCode: INT] RETURNS [char: CHAR] = {RETURN[IF nsCode < 255 AND NSCode[font, '\000 + nsCode] = nsCode THEN '\000 + nsCode ELSE FindCharCode[font, nsCode]]};
Width: PUBLIC PROCEDURE [font: FONT, char: CHAR] RETURNS [REAL] = {box: Box ← FormattingBox[font, char]; RETURN[box.xmax-box.xmin]};
END.