ImagerACTypefaceImpl.mesa
Copyright Ó 1986 by Xerox Corporation. All rights reserved.
Doug Wyatt, May 29, 1985 10:58:54 am PDT
Michael Plass, December 2, 1986 2:33:27 pm PST
DIRECTORY
Basics USING [BYTE, bytesPerWord],
FS USING [OpenFile, StreamFromOpenFile],
II,
IIBackdoor,
IIBox,
IIFont USING [CorrectionType, Extents, nullXChar, XChar],
IISample,
IITransformation,
IITypeface USING [Register, Typeface, TypefaceClass, TypefaceClassRep, TypefaceRep],
PrePressFontFormat USING [CardFromBcpl, CharacterData, CharacterIndexEntry, IntFromBcpl, IXHeader, missingCharacter, missingFilePos, NameIndexEntry, RasterDefn, RelFilePos],
PrincOpsUtils,
Real,
RefText USING [ObtainScratch, ReleaseScratch],
Rope,
RopeFile,
Vector2 USING [VEC];
IIACTypefaceImpl: CEDAR PROGRAM
IMPORTS FS, II, IIBackdoor, IIBox, IISample, IITransformation, IITypeface, PrePressFontFormat, PrincOpsUtils, Real, RefText, Rope, RopeFile
~ BEGIN
BYTE: TYPE ~ [0..377B];
VEC: TYPE ~ Vector2.VEC;
ROPE: TYPE ~ Rope.ROPE;
Extents: TYPE ~ IIFont.Extents;
Transformation: TYPE ~ IITransformation.Transformation;
nan: REAL ~ Real.NonTrappingNaN;
Data: TYPE ~ REF DataRep;
DataRep: TYPE ~ RECORD [
setTable: SetTable ← NIL,
amplifySpace: BOOLFALSE,
groups: LIST OF GroupData ← NIL
];
CharSet: TYPE ~ BYTE;
SetTable: TYPE ~ REF SetTableRep;
SetTableRep: TYPE ~ ARRAY CharSet OF CharTable ← ALL[NIL];
CharTable: TYPE ~ REF CharTableRep;
CharTableRep: TYPE ~ RECORD [bc, ec: BYTE, s: SEQUENCE size: NAT OF CharMetrics];
CharMetrics: TYPE ~ REF CharMetricsRep;
CharMetricsRep: TYPE ~ RECORD [escapement: VEC, extents: Extents];
GroupData: TYPE ~ REF GroupDataRep;
GroupDataRep: TYPE ~ RECORD [
base: ROPENIL,
set: BYTE ← 0,
bc, ec: BYTE ← 0,
charDataByteOffset: INT ← 0,
directoryByteOffset: INT ← 0,
pixelToChar: Transformation ← NIL
];
MalformedACFont: ERROR ~ CODE;
Assert: PROC [truth: BOOL] ~ --INLINE-- { IF NOT truth THEN ERROR MalformedACFont };
Bytes: PROC [wordSize: NAT] RETURNS [CARDINAL] ~ --INLINE-- {RETURN [wordSize*2]};
check: [2..2] ~ Basics.bytesPerWord;
Dragon conversion note; the construct Bytes[SIZE[type]] should be changed to work properly for 32-bit machines. We can get by with this here because the press font formats are all based on 16-bit words.
FileBytes: PROC [fileWords: INT] RETURNS [INT] ~ --INLINE-- {RETURN [fileWords*2]};
FileBytes is used to convert a count of 16-bit words into bytes. This does not change even if the machine's word size is other than 16;
RawFetch: UNSAFE PROC [base: ROPE, byteOffset: INT, destination: LONG POINTER, nBytes: NAT] ~ UNCHECKED {
buf: REF TEXT ~ RefText.ObtainScratch[nBytes];
Assert[(byteOffset + nBytes) <= Rope.Size[base]];
[] ← Rope.AppendChars[buffer: buf, rope: base, start: byteOffset, len: nBytes];
PrincOpsUtils.LongCopy[from: LOOPHOLE[buf, LONG POINTER]+SIZE[TEXT[0]], nwords: (nBytes+(Basics.bytesPerWord-1))/Basics.bytesPerWord, to: destination];
RefText.ReleaseScratch[buf];
};
ACCreate: PROC [file: FS.OpenFile] RETURNS [IITypeface.Typeface] ~ {
base: ROPE ~ RopeFile.FromStream[FS.StreamFromOpenFile[file]];
RETURN [TypefaceFromRope[base]]
};
checkForBogusResolution: BOOLTRUE; -- There seem to be print services fonts in circulation that have a smashed resolutionX field; if this bool is true, we do some paranoid checks and smash it to 300bpi if it smells funny.
tryForCharSet: BOOLTRUE; -- The resolutionY field has been re-used in print services fonts to represent the character set; if this bool is true, we use this interpretation after making some reasonableness checks. As long as the resolution is 256 or greater, we can always disambiguate.
TypefaceFromRope: PROC [base: ROPE] RETURNS [IITypeface.Typeface] ~ {
byteOffset: INT ← 0;
data: Data ~ NEW[DataRep ← []];
ix: PrePressFontFormat.IXHeader;
name: PrePressFontFormat.NameIndexEntry;
indexFamily: BYTE;
indexFace: BYTE;
nameFound, indexFound: BOOLFALSE;
segmentByteIndex, segmentBytes: INT ← 0;
charUnitsPerResolutionUnit: REAL ← 0;
pixelToChar: Transformation ← NIL;
groups: LIST OF GroupData ← NIL;
DO -- read the index part
headerBytes: NAT ~ Bytes[SIZE[PrePressFontFormat.IXHeader]];
TRUSTED { RawFetch[base, byteOffset, @ix, headerBytes] };
SELECT ix.type FROM
end => EXIT;
name => {
nameBytes: NAT ~ Bytes[SIZE[PrePressFontFormat.NameIndexEntry]];
Assert[NOT nameFound]; -- don't allow more than one name entry
Assert[FileBytes[ix.length] = headerBytes + nameBytes];
TRUSTED { RawFetch[base, byteOffset+headerBytes, @name, nameBytes] };
nameFound ← TRUE;
IF indexFound THEN Assert[name.code=indexFamily];
};
character => {
index: PrePressFontFormat.CharacterIndexEntry;
cixBytes: NAT ~ Bytes[SIZE[PrePressFontFormat.CharacterIndexEntry]];
group: GroupData ~ NEW[GroupDataRep];
set: CharSet ← 0;
Assert[segmentByteIndex + segmentBytes <= Rope.Size[base]];
Assert[FileBytes[ix.length] = headerBytes + cixBytes];
TRUSTED { RawFetch[base, byteOffset+headerBytes, @index, cixBytes] };
IF checkForBogusResolution THEN {
IF index.resolutionX = 3840 OR index.resolutionX MOD 1500 = 0 THEN NULL ELSE index.resolutionX ← 3000
};
IF tryForCharSet THEN {
IF index.resolutionY MOD 10 = 0 AND index.resolutionY/10 IN CharSet THEN {
set ← index.resolutionY/10;
index.resolutionY ← index.resolutionX;
};
};
Assert[index.bc <= index.ec];
group.base ← base;
group.bc ← index.bc;
group.ec ← index.ec;
segmentByteIndex ← FileBytes[PrePressFontFormat.CardFromBcpl[index.segmentSA]];
segmentBytes ← FileBytes[PrePressFontFormat.CardFromBcpl[index.segmentLength]];
group.charDataByteOffset ← segmentByteIndex;
group.directoryByteOffset ← segmentByteIndex + (index.ec-index.bc+1) * Bytes[SIZE[PrePressFontFormat.CharacterData]];
charUnitsPerResolutionUnit ← 25400.0/index.size; -- units of resolution are dots per 10 inches
group.pixelToChar ← IITransformation.Scale2[[
charUnitsPerResolutionUnit/index.resolutionX,
charUnitsPerResolutionUnit/index.resolutionY
]];
IF index.rotation#0 THEN group.pixelToChar.ApplyPreRotate[-index.rotation/60.0];
group.set ← set;
IF indexFound THEN Assert[indexFamily=index.family AND indexFace = index.face];
indexFamily ← index.family;
indexFace ← index.face;
indexFound ← TRUE;
IF nameFound THEN Assert[name.code=indexFamily];
groups ← CONS[group, groups];
};
ENDCASE => ERROR MalformedACFont; -- unexpected ix type
byteOffset ← byteOffset + FileBytes[ix.length];
ENDLOOP;
Assert[nameFound AND indexFound];
data.groups ← SortGroups[groups];
data.setTable ← BuildSetTable[data.groups];
IF data.setTable[0] # NIL AND data.setTable[0][40B] # NIL AND data.setTable[0][40B].extents = [0.0, 0.0, 0.0, 0.0] THEN data.amplifySpace ← TRUE;
RETURN[NEW[IITypeface.TypefaceRep ← [class: acClass, data: data]]];
};
InOrder: PROC [a, b: GroupData] RETURNS [BOOL] ~ {
IF a.set < b.set THEN RETURN [TRUE] ELSE {
sva: VEC ~ IITransformation.SingularValues[a.pixelToChar];
svb: VEC ~ IITransformation.SingularValues[b.pixelToChar];
IF sva.x < svb.x THEN RETURN [TRUE];
IF sva.x = svb.x AND sva.y < svb.y THEN RETURN [TRUE];
RETURN [FALSE];
};
};
SortGroups: PROC [groups: LIST OF GroupData] RETURNS [LIST OF GroupData] ~ {
unconsumed: LIST OF GroupData ← groups;
new: LIST OF GroupData ← NIL;
WHILE unconsumed # NIL DO
rest: LIST OF GroupData ~ unconsumed.rest;
current: GroupData ~ unconsumed.first;
a: LIST OF GroupData ← new;
p: LIST OF GroupData ← NIL;
WHILE a#NIL AND NOT InOrder[a.first, current] DO p ← a; a ← a.rest ENDLOOP;
IF p = NIL
THEN { unconsumed.rest ← new; new ← unconsumed }
ELSE { unconsumed.rest ← p.rest; p.rest ← unconsumed };
unconsumed ← rest;
ENDLOOP;
RETURN [new];
};
BuildSetTable: PROC [groups: LIST OF GroupData] RETURNS [SetTable] ~ {
setTable: SetTable ~ NEW[SetTableRep ← ALL[NIL]];
setbc: ARRAY CharSet OF BYTEALL[LAST[BYTE]];
setec: ARRAY CharSet OF BYTEALL[FIRST[BYTE]];
FOR each: LIST OF GroupData ← groups, each.rest UNTIL each = NIL DO
group: GroupData ~ each.first;
set: BYTE ~ group.set;
setbc[set] ← MIN[group.bc, setbc[set]];
setec[set] ← MAX[group.ec, setec[set]];
ENDLOOP;
FOR set: CharSet IN CharSet DO
IF setbc[set] <= setec[set] THEN {
charTable: CharTable ~ NEW[CharTableRep[setec[set]-setbc[set]+1]];
charTable.bc ← setbc[set];
charTable.ec ← setec[set];
FOR i: NAT IN [0..charTable.size) DO charTable[i] ← NIL ENDLOOP;
setTable[set] ← charTable;
};
ENDLOOP;
FOR each: LIST OF GroupData ← groups, each.rest UNTIL each = NIL DO
group: GroupData ~ each.first;
charTable: CharTable ~ setTable[group.set];
FOR code: BYTE IN [group.bc..group.ec] DO
cd: PrePressFontFormat.CharacterData ~ GetCharacterData[group, code];
IF cd.bbdy#PrePressFontFormat.missingCharacter THEN {
wx: REAL ~ Real.FScale[PrePressFontFormat.IntFromBcpl[cd.wx], -16];
wy: REAL ~ Real.FScale[PrePressFontFormat.IntFromBcpl[cd.wy], -16];
e: VEC ~ IITransformation.TransformVec[group.pixelToChar, [wx, wy]];
box: IIBox.Box ~ IIBox.BoxFromRectangle[IITransformation.TransformRectangle[group.pixelToChar, [x: cd.bbox, y: cd.bboy, w: cd.bbdx, h: cd.bbdy]]];
charMetrics: CharMetrics ~ charTable[code-charTable.bc];
IF charMetrics = NIL
THEN charTable[code-charTable.bc] ← NEW [CharMetricsRep ← [escapement: e, extents: IIBox.ExtentsFromBox[box]]]
ELSE charMetrics.extents ← IIBox.ExtentsFromBox[IIBox.BoundingBox[IIBox.BoxFromExtents[charMetrics.extents], box]];
};
ENDLOOP;
ENDLOOP;
RETURN [setTable]
};
GetCharacterData: PROC [group: GroupData, code: BYTE] RETURNS [PrePressFontFormat.CharacterData] ~ {
characterData: PrePressFontFormat.CharacterData;
cdBytes: INT ~ Bytes[SIZE[PrePressFontFormat.CharacterData]];
characterData.bbdy ← PrePressFontFormat.missingCharacter;
IF code IN [group.bc..group.ec]
THEN TRUSTED { RawFetch[group.base, group.charDataByteOffset + (code-group.bc) * cdBytes, @characterData, cdBytes] };
RETURN [characterData];
};
ACContains: PROC [self: IITypeface.Typeface, char: IIFont.XChar] RETURNS [BOOL] ~ {
data: Data ~ NARROW[self.data];
charTable: CharTable ~ data.setTable[char.set];
IF charTable = NIL OR char.code NOT IN [charTable.bc..charTable.ec] THEN RETURN [FALSE];
RETURN [charTable[char.code-charTable.bc] # NIL];
};
ACNextChar: PROC [self: IITypeface.Typeface, char: IIFont.XChar] RETURNS [next: IIFont.XChar] ~ {
Cound be a lot cleverer here!
ch: WORDLOOPHOLE[char, WORD]+1;
UNTIL ACContains[self, LOOPHOLE[ch]] OR ch = WORD.LAST DO
ch ← ch + 1;
ENDLOOP;
next ← LOOPHOLE[ch];
};
ACEscapement: PROC [self: IITypeface.Typeface, char: IIFont.XChar] RETURNS [VEC] ~ {
data: Data ~ NARROW[self.data];
charTable: CharTable ~ data.setTable[char.set];
IF charTable # NIL AND char.code IN [charTable.bc..charTable.ec] THEN {
charMetrics: CharMetrics ~ charTable[char.code-charTable.bc];
IF charMetrics # NIL THEN RETURN [charMetrics.escapement]
};
RETURN[[0.5, 0]];
};
ACAmplified: PROC [self: IITypeface.Typeface, char: IIFont.XChar] RETURNS [BOOL] ~ {
data: Data ~ NARROW[self.data];
RETURN [data.amplifySpace AND char = [set: 0, code: 40B]];
};
ACCorrection: PROC [self: IITypeface.Typeface, char: IIFont.XChar] RETURNS [IIFont.CorrectionType] ~ {
data: Data ~ NARROW[self.data];
IF ACContains[self, char] THEN {
IF data.amplifySpace AND char = [set: 0, code: 40B] THEN RETURN[space];
RETURN[mask];
};
RETURN[none];
};
ACBoundingBox: PROC [self: IITypeface.Typeface, char: IIFont.XChar] RETURNS [IIFont.Extents] ~ {
data: Data ~ NARROW[self.data];
charTable: CharTable ~ data.setTable[char.set];
IF charTable # NIL AND char.code IN [charTable.bc..charTable.ec] THEN {
charMetrics: CharMetrics ~ charTable[char.code-charTable.bc];
IF charMetrics # NIL THEN RETURN [charMetrics.extents];
};
RETURN[[leftExtent: -0.05, rightExtent: 0.45, descent: 0, ascent: 0.6]];
};
ACFontBoundingBox: PROC [self: IITypeface.Typeface] RETURNS [IIFont.Extents] ~ {
data: Data ~ NARROW[self.data];
setTable: SetTable ~ data.setTable;
bb: IIBox.Box ← [xmin: 0.05, ymin: 0.0, xmax: 0.45, ymax: 0.6];
FOR set: CharSet IN CharSet DO
charTable: CharTable ~ setTable[set];
IF charTable # NIL THEN {
FOR code: BYTE IN [charTable.bc..charTable.ec] DO
charMetrics: CharMetrics ~ charTable[code-charTable.bc];
IF charMetrics # NIL THEN {
box: IIBox.Box ~ IIBox.BoxFromExtents[charMetrics.extents];
bb ← IIBox.BoundingBox[bb, box];
};
ENDLOOP;
};
ENDLOOP;
RETURN [IIBox.ExtentsFromBox[bb]]
};
ACKern: PROC [self: IITypeface.Typeface, char, successor: IIFont.XChar] RETURNS [VEC] ~ { RETURN[[0, 0]] };
ACNextKern: PROC [self: IITypeface.Typeface, char, successor: IIFont.XChar] RETURNS [IIFont.XChar] ~ { RETURN[IIFont.nullXChar] };
ACLigature: PROC [self: IITypeface.Typeface, char, successor: IIFont.XChar] RETURNS [IIFont.XChar] ~ { RETURN[IIFont.nullXChar] };
ACNextLigature: PROC [self: IITypeface.Typeface, char, successor: IIFont.XChar] RETURNS [IIFont.XChar] ~ { RETURN[IIFont.nullXChar] };
GetBitmap: PROC [group: GroupData, code: BYTE] RETURNS [IISample.SampleMap] ~ {
sampleMap: IISample.SampleMap ← NIL;
cd: PrePressFontFormat.CharacterData ~ GetCharacterData[group, code];
IF cd.bbdy#PrePressFontFormat.missingCharacter THEN TRUSTED {
rfpBytes: INT ~ Bytes[SIZE[PrePressFontFormat.RelFilePos]];
rfpBytePos: INT ~ group.directoryByteOffset+(code-group.bc)*rfpBytes;
rfp: PrePressFontFormat.RelFilePos;
TRUSTED {RawFetch[group.base, rfpBytePos, @rfp, rfpBytes] };
IF rfp # PrePressFontFormat.missingFilePos THEN {
relativeByte: INT ~ FileBytes[PrePressFontFormat.IntFromBcpl[rfp]];
rdBytes: INT ~ Bytes[SIZE[PrePressFontFormat.RasterDefn]];
rd: PrePressFontFormat.RasterDefn;
RawFetch[group.base, group.directoryByteOffset+relativeByte, @rd, rdBytes];
Assert[rd.lines=cd.bbdx AND rd.raster=(cd.bbdy+15)/16];
IF cd.bbdx>0 AND cd.bbdy>0 THEN {
rasterBytes: INT ~ FileBytes[LONG[rd.raster]*rd.lines];
sampleMap ← IISample.ObtainScratchMap[
box: [min: [s: cd.bbox, f: cd.bboy], max: [s: cd.bbdx+cd.bbox, f: cd.bbdy+cd.bboy]],
bitsPerLine: raster*16, -- this is really 16, not bitsPerWord!
bitsPerSample: 1
];
TRUSTED {RawFetch[group.base, group.directoryByteOffset+relativeByte+rdBytes, IISample.GetBase[sampleMap].word, rasterBytes] };
};
};
};
RETURN [sampleMap];
};
Right: PROC [angle: REAL] RETURNS [BOOL] ~ {
WHILE angle < 0 DO angle ← angle + 90.0 ENDLOOP;
WHILE angle > 45.0 DO angle ← angle - 90.0 ENDLOOP;
RETURN [ABS[angle] <= 1.666667E-2]
};
minScale: REAL ← 0.9875;
maxScale: REAL ← 1.0125;
FindBestGroup: PROC [data: Data, char: IIFont.XChar, t: Transformation] RETURNS [GroupData] ~ {
best: GroupData ← NIL;
FOR each: LIST OF GroupData ← data.groups, each.rest UNTIL each = NIL DO
group: GroupData ~ each.first;
IF group.set = char.set THEN {
cd: PrePressFontFormat.CharacterData ~ GetCharacterData[group, char.code];
IF cd.bbdy#PrePressFontFormat.missingCharacter THEN {
composite: Transformation ~ IITransformation.Concat[group.pixelToChar, t];
f: IITransformation.FactoredTransformation ~ IITransformation.Factor[composite];
IITransformation.Destroy[composite];
IF Right[f.r1] AND Right[f.r2] AND ABS[f.s.x] IN [minScale..maxScale] AND ABS[f.s.y] IN [minScale..maxScale] THEN {best ← group; EXIT};
IF best = NIL THEN best ← group;
};
};
ENDLOOP;
IF best = NIL THEN ERROR; -- ACContains lied!
RETURN [best];
};
ACMask: PROC [self: IITypeface.Typeface, char: IIFont.XChar, context: II.Context] ~ {
data: Data ~ NARROW[self.data];
IF ACContains[self, char]
THEN {
t: Transformation ~ IIBackdoor.GetTransformation[context: context, from: client, to: device];
group: GroupData ~ FindBestGroup[data, char, t];
bitmap: IISample.SampleMap ← GetBitmap[group, char.code];
IITransformation.Destroy[t];
IF bitmap # NIL THEN {
II.ConcatT[context, group.pixelToChar];
II.MaskBitmap[context: context, bitmap: bitmap, referencePoint: [0, 0], scanMode: [slow: right, fast: up], position: [0, 0]];
IISample.ReleaseScratchMap[bitmap];
};
}
ELSE II.MaskRectangle[context, [0.05, 0, 0.4, 0.6]];
};
acClass: IITypeface.TypefaceClass ~ NEW[IITypeface.TypefaceClassRep ← [
type: $AC,
Contains: ACContains,
NextChar: ACNextChar,
Escapement: ACEscapement,
Amplified: ACAmplified,
Correction: ACCorrection,
BoundingBox: ACBoundingBox,
FontBoundingBox: ACFontBoundingBox,
Ligature: ACLigature,
NextLigature: ACNextLigature,
Kern: ACKern,
NextKern: ACNextKern,
Mask: ACMask
]];
IITypeface.Register["AC", ACCreate];
END.