UnifiedFontsImpl:
CEDAR
MONITOR
IMPORTS Atom, RefText, Rope, SafeStorage, Scaled, UFFontDirReader, UFFileManager, MessageWindow,
IO
EXPORTS UnifiedFonts =
BEGIN OPEN UnifiedFonts;
BadFontFileFormat: PUBLIC ERROR = CODE;
pZone: PUBLIC ZONE ← SafeStorage.NewZone[initialSize: 0];
rotationFuzz: REAL = 1/3600.0;
fontDirFileName: ROPE ← "Default.FontDir";
fontHead: FONT ← NIL; -- all FONTs are linked together, starting here.
defaultFont: FONT;
DefaultFont:
PROCEDURE
RETURNS [font:
FONT] = {
formattingClassInit: REF initProcRec ← NARROW[Atom.GetProp[atom: $Strike, prop: $FontFormattingClass]];
graphicsClassInit: REF initProcRec ← NARROW[Atom.GetProp[atom: $Strike, prop: $FontGraphicsClass]];
IF defaultFont#NIL THEN RETURN [defaultFont];
font ← pZone.NEW[FontRec];
font.name ← "Default";
font.requestedTransformation ← font.actualTransformation ← [10, 0];
font.deviceType ← $Screen;
font.nsCodeTable ← UFFileManager.KeyOf["Ascii.codeScheme"];
font.formattingKey ← font.graphicsKey ← UFFileManager.KeyOf["[Indigo]<Tioga>Strikefonts>Tioga10.strike"];
formattingClassInit.init[font];
graphicsClassInit.init[font];
InitFontBoundingBox[font];
font.bitmapCacheable ← Area[font.fontBoundingBox]<areaLimit;
defaultFont ← font;
};
initProcRec:
TYPE =
RECORD [
init: PROC [font: FONT]
];
RegisterFontFormattingClass:
PUBLIC
PROC [flavor:
ATOM, init:
PROC [font:
FONT]] = {
Atom.PutProp[atom: flavor, prop: $FontFormattingClass, val: pZone.NEW[initProcRec ← [init: init]]];
};
RegisterFontGraphicsClass:
PUBLIC
PROCEDURE [flavor:
ATOM, init:
PROC [font:
FONT]] = {
Atom.PutProp[atom: flavor, prop: $FontGraphicsClass, val: pZone.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]];
};
Create:
PUBLIC
PROCEDURE [fontName: RopeOrRefText, transformation: Transformation, deviceType: DeviceType]
RETURNS [font:
FONT] = {
name: ROPE ← IF ISTYPE[fontName, REF TEXT] THEN TrustAsRope[fontName] ELSE NARROW[fontName];
TestForMatch: UFFontDirReader.ExamineProc = {
IF deviceType # candidateDeviceType THEN RETURN [FALSE];
IF transformation.scale < candidateMinSize OR transformation.scale > candidateMaxSize THEN RETURN [FALSE];
IF NOT name.Equal[RefText.TrustTextAsRope[candidateFontName], FALSE] THEN RETURN [FALSE];
IF candidateRotation >= 0 AND ABS[candidateRotation - transformation.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 ← pZone.NEW[FontRec];
font.name ← CopyRope[name]; -- So it is OK to trust a REF TEXT as a ROPE
font.requestedTransformation ← font.actualTransformation ← transformation;
IF candidateSize # 0.0 THEN font.actualTransformation.scale ← candidateSize;
IF candidateRotation >= 0 THEN font.actualTransformation.rotation ← candidateRotation;
font.deviceType ← candidateDeviceType;
font.nsCodeTable ← codeScheme;
font.formattingKey ← metricsName;
font.graphicsKey ← graphicsName;
formattingClassInit.init[font];
graphicsClassInit.init[font];
InitFontBoundingBox[font];
font.bitmapCacheable ← Area[font.fontBoundingBox]<areaLimit;
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 = f.requestedTransformation 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.
msg:
ROPE ←
IO.PutFToRope[
"Default substituted for %g font %g at %gbp, %g",
IO.rope[Atom.GetPName[deviceType]],
IO.rope[CopyRope[name]],
IO.real[transformation.scale],
IO.real[transformation.rotation]
];
MessageWindow.Append[msg, TRUE];
font ← DefaultFont[];
};
};
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;
};
EasyTransformations:
PUBLIC PROCEDURE [fontName:
REF, deviceType: DeviceType, transformationProc: TransformationProc] = {
name: ROPE ← IF ISTYPE[fontName, REF TEXT] THEN TrustAsRope[fontName] ELSE NARROW[fontName];
TestForMatch: UFFontDirReader.ExamineProc = {
IF deviceType # candidateDeviceType THEN RETURN [FALSE];
IF NOT name.Equal[RefText.TrustTextAsRope[candidateFontName], FALSE] THEN RETURN [FALSE];
IF candidateSize=0 AND candidateRotation=-360 THEN candidateRotation𡤀
RETURN [transformationProc[[scale: candidateSize, rotation: candidateRotation]]];
};
IgnoreResult: UFFontDirReader.ResultProc = {quit ← TRUE};
UFFontDirReader.EnumerateFontDirectory[fontDirFileName, TestForMatch, IgnoreResult];
};
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:
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];
};
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 ← pZone.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 [Vec] = {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]]};
DrawChar: PUBLIC PROCEDURE [font: FONT, char: CHAR, context: Context] = {IF char IN [font.bc..font.ec] THEN font.fontGraphicsClass.drawCharProc[font, char, context]};
Width: PUBLIC PROCEDURE [font: FONT, char: CHAR] RETURNS [REAL] = {box: Box ← FormattingBox[font, char]; RETURN[box.xmax-box.xmin]};
END.