<> <> <> <> DIRECTORY Atom USING [GetPName, GetProp, PutProp], Font, 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 Font = BEGIN OPEN Font; 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]]; <> <> <> <= 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 { <> 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]; <> }; }; 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.