ImagerFontImpl.mesa
Copyright Ó 1984, 1985, 1986, 1988, 1989, 1990, 1991, 1993 by Xerox Corporation. All rights reserved.
Doug Wyatt, June 5, 1990 4:07 pm PDT
Michael Plass, May 28, 1993 9:30 am PDT
DIRECTORY
Char,
Imager USING [Context],
ImagerBox USING [Box, BoxFromExtents, Extents, Rectangle],
ImagerFont USING [CorrectionType, Font, FontRep, Substitution, Typeface, TypefaceRep, XChar, XCharProc, XStringProc],
ImagerFontWorks USING [MaskChar],
ImagerError USING [Error],
ImagerManhattan USING [BoundingBox, Destroy, Polygon],
ImagerMaskCache USING [CharMask, CharMaskRep, CountRuns, MakeCharMaskProc, Parameters, RasterCharMaskFromManhattan, Run, RunsCharMaskFromManhattan],
ImagerMaskCapture USING [Cant, CaptureManhattan],
ImagerSample USING [WordsForMap],
ImagerSys USING [RawHash],
ImagerTransformation USING [Concat, Copy, Destroy, Equal, PostScale, Scale, Transformation, TransformationRep, TransformRectangle, TransformVec],
ImagerTypeface USING [FindTypeface, InfoTable, InfoTableRep, SelectAlternateTypefaceMetrics, Typeface, TypefaceRep],
Real USING [Ceiling, Floor],
Rope USING [ActionType, FromRefText, Map, ROPE, Substr],
ImagerScaled USING [FromReal],
SF USING [Box, Size],
Vector2 USING [Add, Mul, VEC];
ImagerFontImpl: CEDAR MONITOR
IMPORTS Char, ImagerBox, ImagerFontWorks, ImagerError, ImagerManhattan, ImagerMaskCache, ImagerMaskCapture, ImagerSample, ImagerSys, ImagerTransformation, ImagerTypeface, Real, Rope, ImagerScaled, SF, Vector2
EXPORTS ImagerFont, ImagerFontWorks, ImagerTypeface
~ BEGIN
CorrectionType: TYPE ~ ImagerFont.CorrectionType;
Extents: TYPE ~ ImagerBox.Extents;
Font: TYPE ~ ImagerFont.Font;
FontRep: TYPE ~ ImagerFont.FontRep;
XChar: TYPE ~ ImagerFont.XChar;
XCharProc: TYPE ~ ImagerFont.XCharProc;
XStringProc: TYPE ~ ImagerFont.XStringProc;
Substitution: TYPE ~ ImagerFont.Substitution;
Typeface: TYPE ~ REF TypefaceRep;
TypefaceRep: PUBLIC TYPE ~ ImagerTypeface.TypefaceRep; -- export to ImagerTypeface
Rectangle: TYPE ~ ImagerBox.Rectangle;
ROPE: TYPE ~ Rope.ROPE;
MapRope: PUBLIC PROC [rope: ROPE, start: INT ¬ 0, len: INT ¬ INT.LAST, charAction: XCharProc] ~ {
state: {run, escape, escape2, extended, extended2} ¬ run;
set: Char.CharSet ¬ 0;
byteAction: PROC [c: CHAR] RETURNS [BOOL ¬ FALSE] ~ {
b: BYTE ~ ORD[c];
SELECT state FROM
run => IF b=255 THEN state ¬ escape ELSE { charAction[Char.Make[set: set, code: b]] };
escape => IF b=255 THEN state ¬ escape2 ELSE { set ¬ b; state ¬ run };
escape2 => IF b=0 THEN state ¬ extended ELSE ERROR ImagerError.Error[[$invalidEncoding, "invalid string-body encoding", LIST[[$rope, rope.Substr[start, len]]]]];
extended => IF b=255 THEN state ¬ escape ELSE { set ¬ b; state ¬ extended2 };
extended2 => { charAction[Char.Make[set: set, code: b]]; state ¬ extended };
ENDCASE;
};
[] ¬ Rope.Map[base: rope, start: start, len: len, action: byteAction];
};
MapText: PUBLIC PROC [text: REF READONLY TEXT,
start: NAT ¬ 0, len: NAT ¬ NAT.LAST, charAction: XCharProc] ~ {
rem: NAT ~ text.length-start; -- bounds check
state: {run, escape, escape2, extended, extended2} ¬ run;
set: Char.CharSet ¬ 0;
FOR i: NAT IN[0..MIN[len, rem]) DO
b: BYTE ~ ORD[text[start+i]];
SELECT state FROM
run => IF b=255 THEN state ¬ escape ELSE { charAction[Char.Make[set: set, code: b]] };
escape => IF b=255 THEN state ¬ escape2 ELSE { set ¬ b; state ¬ run };
escape2 => IF b=0 THEN state ¬ extended ELSE ERROR ImagerError.Error[[$invalidEncoding, "invalid string-body encoding", LIST[[$rope, Rope.FromRefText[text].Substr[start, len]]]]];
extended => IF b=255 THEN state ¬ escape ELSE { set ¬ b; state ¬ extended2 };
extended2 => { charAction[Char.Make[set: set, code: b]]; state ¬ extended };
ENDCASE;
ENDLOOP;
};
VEC: TYPE ~ Vector2.VEC;
Transformation: TYPE ~ ImagerTransformation.Transformation;
TransformationRep: TYPE ~ ImagerTransformation.TransformationRep;
identityTransformation: Transformation ~ ImagerTransformation.Scale[1];
hashTableSize: NAT ~ 512;
HashIndex: TYPE ~ [0..hashTableSize);
HashTable: TYPE ~ REF HashTableRep;
HashTableRep: TYPE ~ ARRAY HashIndex OF NodeList;
NodeList: TYPE ~ LIST OF Node;
Node: TYPE ~ RECORD [hash: CARDINAL, font: Font];
hashTable: HashTable ~ NEW[HashTableRep ¬ ALL[NIL]];
entries: INT ¬ 0;
collisions: INT ¬ 0;
misses: INT ¬ 0;
Hash: PROC [typeface: Typeface, m: Transformation] RETURNS [CARDINAL] ~ {
HashRec: TYPE ~ MACHINE DEPENDENT RECORD [
typeface: Typeface,
transformationRep: TransformationRep
];
hashRec: HashRec ¬ [typeface, m­];
TRUSTED { RETURN [ImagerSys.RawHash[[base: LOOPHOLE[@hashRec], startIndex: 0, count: SIZE[HashRec]*BYTES[UNIT]], 0]] };
};
TypefaceFromFont: PUBLIC PROC [font: Font] RETURNS [Typeface] = {
N.B. Exported to ImagerTypeface
Avoids some opaque-type hassles.
RETURN [font.typeface]
};
MakeFont: PUBLIC PROC [typeface: Typeface, m: Transformation] RETURNS [Font] ~ {
N.B. Exported to ImagerTypeface
ENABLE UNWIND => NULL;
hash: CARDINAL ~ Hash[typeface, m];
hashIndex: HashIndex ~ hash MOD hashTableSize;
font: Font ¬ NIL;
head: NodeList ~ hashTable[hashIndex];
UpdateFontList: ENTRY PROC ~ {
FOR each: NodeList ¬ head, each.rest UNTIL each=NIL DO
node: Node ~ each.first;
IF node.hash = hash AND node.font.typeface = typeface AND ImagerTransformation.Equal[node.font.charToClient, m]
THEN { font ¬ node.font; EXIT }
ELSE misses ¬ misses+1;
ENDLOOP;
IF font = NIL THEN {-- not found, so make a new Font
font ¬ NEW[FontRep ¬ [charToClient: ImagerTransformation.Copy[m], typeface: typeface]];
hashTable[hashIndex] ¬ CONS[[hash: hash, font: font], head];
entries ¬ entries+1;
IF head#NIL THEN collisions ¬ collisions+1;
};
};
IF typeface.info = NIL THEN {
info: ImagerTypeface.InfoTable ~ NEW[ImagerTypeface.InfoTableRep ¬ ALL[[]]];
FOR code: BYTE IN BYTE DO
char: XChar ~ Char.Make[set: 0, code: code];
exists: BOOL ~ typeface.class.Contains[typeface, char];
amplified: BOOL ~ typeface.class.Amplified[typeface, char];
correction: CorrectionType ~ typeface.class.Correction[typeface, char];
info[code] ¬ [exists: exists, amplified: amplified, correction: correction];
ENDLOOP;
typeface.info ¬ info; -- A race to fill in typeface.info should be harmless, since the computed value should be equivalent each time.
};
UpdateFontList[];
RETURN [font];
};
FlushFontCaches: PUBLIC ENTRY PROC ~ {
N.B. Exported to ImagerTypeface
hashTable­ ¬ ALL[NIL];
entries ¬ 0;
collisions ¬ 0;
misses ¬ 0;
};
RectangleFromExtents: PROC [e: Extents] RETURNS [Rectangle] ~ INLINE {
RETURN[[x: -e.leftExtent, y: -e.descent, w: e.leftExtent+e.rightExtent, h: e.descent+e.ascent]];
};
ExtentsFromRectangle: PROC [r: Rectangle] RETURNS [Extents] ~ INLINE {
RETURN[[leftExtent: -r.x, rightExtent: r.x+r.w, descent: -r.y, ascent: r.y+r.h]];
};
TransformExtents: PROC [m: Transformation, e: Extents] RETURNS [Extents] ~ {
RETURN[ExtentsFromRectangle[m.TransformRectangle[RectangleFromExtents[e]]]]
};
Find: PUBLIC PROC [name: ROPE, substitution: ImagerFont.Substitution] RETURNS [Font] ~ {
typeface: Typeface ~ ImagerTypeface.FindTypeface[name, substitution]; -- may raise Imager.Error
RETURN[MakeFont[typeface, identityTransformation]];
};
Modify: PUBLIC PROC [font: Font, m: Transformation] RETURNS [modifiedFont: Font] ~ {
modifiedTransformation: Transformation ~ ImagerTransformation.Concat[font.charToClient, m];
modifiedFont ¬ MakeFont[font.typeface, modifiedTransformation];
ImagerTransformation.Destroy[modifiedTransformation];
};
Scale: PUBLIC PROC [font: Font, s: REAL] RETURNS [modifiedFont: Font] ~ {
modifiedTransformation: Transformation ~ ImagerTransformation.PostScale[font.charToClient, s];
modifiedFont ¬ MakeFont[font.typeface, modifiedTransformation];
ImagerTransformation.Destroy[modifiedTransformation];
};
FindScaled: PUBLIC PROC [name: ROPE, s: REAL] RETURNS [Font] ~ {
RETURN[Scale[Find[name, substituteWithWarning], s]];
};
SelectAlternateMetrics: PUBLIC PROC [font: Font, alternateMetrics: ROPE] RETURNS [Font] ~ {
typeface: Typeface ~ font.typeface;
newTypeface: Typeface ~ ImagerTypeface.SelectAlternateTypefaceMetrics[typeface, alternateMetrics]; -- may raise Imager.Error
RETURN[MakeFont[newTypeface, font.charToClient]];
};
Contains: PUBLIC PROC [font: Font, char: XChar] RETURNS [BOOL] ~ {
typeface: Typeface ~ font.typeface;
IF Char.Set[char]=0 AND typeface.info#NIL
THEN RETURN[typeface.info[Char.Code[char]].exists]
ELSE RETURN[typeface.class.Contains[typeface, char]];
};
NextChar: PUBLIC PROC [font: Font, char: XChar] RETURNS [next: XChar] ~ {
typeface: Typeface ~ font.typeface;
RETURN[typeface.class.NextChar[typeface, char]];
};
Escapement: PUBLIC PROC [font: Font, char: XChar] RETURNS [VEC] ~ {
typeface: Typeface ~ font.typeface;
escapement: VEC ~ typeface.class.Escapement[typeface, char];
RETURN[font.charToClient.TransformVec[escapement]];
};
Amplified: PUBLIC PROC [font: Font, char: XChar] RETURNS [BOOL] ~ {
typeface: Typeface ~ font.typeface;
IF Char.Set[char]=0 AND typeface.info#NIL
THEN RETURN[typeface.info[Char.Code[char]].amplified]
ELSE RETURN[typeface.class.Amplified[typeface, char]];
};
Correction: PUBLIC PROC [font: Font, char: XChar] RETURNS [CorrectionType] ~ {
typeface: Typeface ~ font.typeface;
IF Char.Set[char]=0 AND typeface.info#NIL
THEN RETURN[typeface.info[Char.Code[char]].correction]
ELSE RETURN[typeface.class.Correction[typeface, char]];
};
BoundingBox: PUBLIC PROC [font: Font, char: XChar] RETURNS [Extents] ~ {
typeface: Typeface ~ font.typeface;
charExtents: Extents ~ typeface.class.BoundingBox[typeface, char];
RETURN[TransformExtents[font.charToClient, charExtents]];
};
FontBoundingBox: PUBLIC PROC [font: Font] RETURNS [Extents] ~ {
typeface: Typeface ~ font.typeface;
IF typeface.fontExtents = NIL THEN {
A race to fill in typeface.fontExtents should be harmless, since the computed value should be equivalent each time.
typeface.fontExtents ¬ NEW[Extents ¬ typeface.class.FontBoundingBox[typeface]];
};
RETURN[TransformExtents[font.charToClient, typeface.fontExtents­]];
};
Name: PUBLIC PROC [font: Font] RETURNS [ROPE] ~ {
typeface: Typeface ~ font.typeface;
RETURN [typeface.name];
};
Kern: PUBLIC PROC [font: Font, char, successor: XChar] RETURNS [VEC] ~ {
typeface: Typeface ~ font.typeface;
RETURN[typeface.class.Kern[typeface, char, successor]];
};
NextKern: PUBLIC PROC [font: Font, char, successor: XChar] RETURNS [XChar] ~ {
typeface: Typeface ~ font.typeface;
RETURN[typeface.class.NextKern[typeface, char, successor]];
};
Ligature: PUBLIC PROC [font: Font, char, successor: XChar] RETURNS [XChar] ~ {
typeface: Typeface ~ font.typeface;
RETURN[typeface.class.Ligature[typeface, char, successor]];
};
NextLigature: PUBLIC PROC [font: Font, char, successor: XChar] RETURNS [XChar] ~ {
typeface: Typeface ~ font.typeface;
RETURN[typeface.class.NextLigature[typeface, char, successor]];
};
CharMask: TYPE ~ ImagerMaskCache.CharMask;
CharMaskRep: TYPE ~ ImagerMaskCache.CharMaskRep;
viewOrigin: NAT = 10000; -- should agree with ImagerMaskCaptureImpl.viewOrigin
maxSize: REAL = 600.0;
boundsMin: REAL ¬ - REAL[viewOrigin];
boundsMax: REAL ¬ REAL[LAST[INT16]-viewOrigin];
RangeClip: PROC [real: REAL] RETURNS [REAL] ~ {
RETURN[MIN[MAX[real, boundsMin], boundsMax]]
};
CaptureChar: PUBLIC PROC [font: Font, char: XChar, parameters: ImagerMaskCache.Parameters, metricsOnly: BOOL ¬ FALSE] RETURNS [charMask: CharMask ¬ NIL] ~ {
N.B. Exported to ImagerFontWorks
escapement: VEC ~ Escapement[font, char];
bounds: ImagerBox.Box ~ ImagerBox.BoxFromExtents[BoundingBox[font, char]];
Reasonableness check for size of cached object wrt output device
IF ABS[escapement.x] <= 16383.0 AND ABS[escapement.y] <= 16383.0 AND bounds.xmin >= boundsMin AND bounds.ymin >= boundsMin AND bounds.xmax <= boundsMax AND bounds.ymax <= boundsMax THEN {
If OK then check whether we really need a bitmap and whether the mask is too big for the cache (N.B. also good for PostScript setcachedevice operator!)
SELECT TRUE FROM
metricsOnly => NULL;
(bounds.xmax - bounds.xmin <= maxSize AND bounds.ymax - bounds.ymin <= maxSize) => {
typeface: Typeface ~ font.typeface;
IF typeface.class.MakeCharMask # NIL THEN {
charMask ¬ typeface.class.MakeCharMask[parameters, font, char];
};
IF charMask = NIL AND parameters.makeCharMask # NIL THEN {
charMask ¬ parameters.makeCharMask[parameters, font, char];
};
IF charMask = NIL THEN {
charMask ¬ GeneralCaptureCharMask[parameters, font, char];
};
};
ENDCASE => NULL;
};
IF charMask = NIL THEN {
IF metricsOnly
THEN charMask ¬ NEW[CharMaskRep.culled]
ELSE charMask ¬ NEW[CharMaskRep.maskNotCacheable];
charMask.box ¬ [
min: [Real.Floor[RangeClip[bounds.xmin]], Real.Floor[RangeClip[bounds.ymin]]],
max: [Real.Ceiling[RangeClip[bounds.xmax]], Real.Ceiling[RangeClip[bounds.ymax]]]];
};
Only the actual bitmap generation is specific to either the FontSolution or General approach; the remainder of the information is common and is dealt with below.
charMask.font ¬ font;
charMask.char ¬ char;
charMask.escapement.s ¬ ImagerScaled.FromReal[escapement.x];
charMask.escapement.f ¬ ImagerScaled.FromReal[escapement.y];
charMask.flags.amplified ¬ Amplified[font, char];
charMask.flags.correction ¬ Correction[font, char];
charMask.flags.missing ¬ NOT Contains[font, char];
};
GeneralCaptureCharMask: PUBLIC ImagerMaskCache.MakeCharMaskProc = {
N.B. Exported to ImagerTypeface
Operator: PROC [context: Imager.Context] = {
ImagerFontWorks.MaskChar[font, char, context];
};
ok: BOOL ¬ TRUE;
p: ImagerManhattan.Polygon ¬ NIL;
charMask: ImagerMaskCache.CharMask ¬ NIL;
p ¬ ImagerMaskCapture.CaptureManhattan[
operator: Operator, m: NIL,
checkColor: TRUE, parameters: parameters ! ImagerMaskCapture.Cant => {ok ¬ FALSE; CONTINUE}];
IF ok THEN {
bb: SF.Box = ImagerManhattan.BoundingBox[p];
bitmapWords: REAL = ImagerSample.WordsForMap[
size: SF.Size[bb], bitsPerSample: 1];
nRuns: INT = ImagerMaskCache.CountRuns[p];
rasterCost: REAL = parameters.rasterCost.slope*bitmapWords + parameters.rasterCost.offset;
runsCost: REAL = parameters.runsCost.slope*nRuns*WORDS[ImagerMaskCache.Run] + parameters.runsCost.offset;
uncacheableCost: REAL = parameters.uncacheableCost.offset;
min: REAL = MIN[rasterCost, runsCost, uncacheableCost];
SELECT TRUE FROM
(rasterCost=min) => charMask ¬ ImagerMaskCache.RasterCharMaskFromManhattan[p, bb];
(runsCost=min) => charMask ¬ ImagerMaskCache.RunsCharMaskFromManhattan[p, bb, nRuns];
ENDCASE => NULL;
};
ImagerManhattan.Destroy[p];
RETURN [charMask]
};
StringEscapement: PUBLIC PROC [font: Font, string: XStringProc, amplifySpace: REAL] RETURNS [VEC] ~ {
sum: VEC ¬ [0, 0]; -- accumulated in character coordinates
typeface: Typeface ~ font.typeface;
amp: BOOL ~ amplifySpace # 1.0;
charAction: XCharProc ~ {
escapement: VEC ¬ typeface.class.Escapement[typeface, char];
IF amp AND Correction[font, char] = space THEN escapement ¬ Vector2.Mul[escapement, amplifySpace];
sum ¬ Vector2.Add[sum, escapement];
};
string[charAction];
RETURN[font.charToClient.TransformVec[sum]];
};
RopeEscapement: PUBLIC PROC [font: Font, rope: ROPE, start: INT, len: INT, amplifySpace: REAL] RETURNS [VEC] ~ {
string: XStringProc ~ { MapRope[rope, start, len, charAction] };
RETURN[StringEscapement[font, string, amplifySpace]];
};
TextEscapement: PUBLIC PROC [font: Font, text: REF READONLY TEXT, start: NAT, len: NAT, amplifySpace: REAL] RETURNS [VEC] ~ {
string: XStringProc ~ { MapText[text, start, len, charAction] };
RETURN[StringEscapement[font, string, amplifySpace]];
};
StringBoundingBox: PUBLIC PROC [font: Font, string: XStringProc, amplifySpace: REAL] RETURNS [Extents] ~ {
typeface: Typeface ~ font.typeface;
sxmin, symin, sxmax, symax: REAL ¬ 0;
position: VEC ¬ [0, 0];
count: INT ¬ 0;
amp: BOOL ~ amplifySpace # 1.0;
charAction: XCharProc ~ {
charExtents: Extents ~ typeface.class.BoundingBox[typeface, char];
charEscapement: VEC ¬ typeface.class.Escapement[typeface, char];
cxmin: REAL ~ position.x-charExtents.leftExtent;
cxmax: REAL ~ position.x+charExtents.rightExtent;
cymin: REAL ~ position.y-charExtents.descent;
cymax: REAL ~ position.y+charExtents.ascent;
IF count=0
THEN { sxmin ¬ cxmin; sxmax ¬ cxmax; symin ¬ cymin; symax ¬ cymax }
ELSE {
IF cxmin < sxmin THEN sxmin ¬ cxmin;
IF cxmax > sxmax THEN sxmax ¬ cxmax;
IF cymin < symin THEN symin ¬ cymin;
IF cymax > symax THEN symax ¬ cymax;
};
IF amp AND Correction[font, char] = space THEN charEscapement ¬ Vector2.Mul[charEscapement, amplifySpace];
position ¬ Vector2.Add[position, charEscapement];
count ¬ count+1;
};
string[charAction];
RETURN[TransformExtents[font.charToClient, [leftExtent: -sxmin, rightExtent: sxmax, descent: -symin, ascent: symax]]];
};
RopeBoundingBox: PUBLIC PROC [font: Font, rope: ROPE,
start: INT, len: INT, amplifySpace: REAL] RETURNS [Extents] ~ {
string: XStringProc ~ { MapRope[rope, start, len, charAction] };
RETURN[StringBoundingBox[font, string, amplifySpace]];
};
TextBoundingBox: PUBLIC PROC [font: Font, text: REF READONLY TEXT, start: NAT, len: NAT, amplifySpace: REAL] RETURNS [Extents] ~ {
string: XStringProc ~ { MapText[text, start, len, charAction] };
RETURN[StringBoundingBox[font, string, amplifySpace]];
};
END.