UnifiedFontsImpl.mesa
Written November 1, 1982 4:53 pm
Last edited by Michael Plass, February 17, 1984 1:07:01 pm PST
Last edited by Doug Wyatt, November 22, 1983 4:46 pm
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;
UnifiedFontsImpl: CEDAR MONITOR
IMPORTS Atom, FS, Imager, ImagerTransform, IO, Real, RealFns, RefText, Rope, Scaled, UFFileManager, UFFontDirReader
EXPORTS UFont
= BEGIN OPEN UFont;
BadFontFileFormat: PUBLIC ERROR = CODE;
rotationFuzz: REAL = 1/3600.0;
fontDirFileName: ROPE ← "Cedar.FontDir";
fontHead: FONTNIL; -- all FONTs are linked together, starting here.
defaultFont: FONT;
defaultFontGenerated: BOOLEANFALSE;
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: ROPEIF 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 [FONTNIL] = {
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.STREAMFS.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.