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];
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: BOOL ← TRUE; -- 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:
BOOL ←
TRUE;
-- 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: BOOL ← FALSE;
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 BYTE ← ALL[LAST[BYTE]];
setec: ARRAY CharSet OF BYTE ← ALL[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: WORD ← LOOPHOLE[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];