DIRECTORY Char, Imager USING [ConcatT, Context, Error, MaskBitmap, MaskRectangle], ImagerBackdoor USING [GetTransformation], ImagerBox USING [BoundingBox, Box, BoxFromExtents, BoxFromRectangle, Extents, ExtentsFromBox, RectangleFromBox], ImagerFont USING [CorrectionType, nullXChar, XChar], ImagerMaskCache USING [CharMask, MakeCharMaskProc, RasterCharMaskFromSampleMap], ImagerSample USING [Box, GetBase, ObtainScratchMap, RasterSampleMap, ReleaseScratchMap, SampleMap], ImagerSys, ImagerTransformation, ImagerTypeface USING [Creator, CreatorFromFileExtension, RegisterCreator, Typeface, TypefaceClass, TypefaceClassRep, TypefaceFromFont, TypefaceRep], IO USING [STREAM], PrePressFontFormat USING [CharacterData, CharacterIndexEntry, IXHeader, IXType, missingCharacter, missingFilePos, NameIndexEntry, RasterDefn, RelFilePos], Prop USING [PropList], Real USING [Floor, FScale, PairToReal], Rope USING [Cat, Equal, Fetch, FromProc, Flatten, Replace, ROPE, Size, Substr, UnsafeMoveChars], Vector2 USING [VEC]; ImagerCDTypefaceImpl: CEDAR PROGRAM IMPORTS Char, Imager, ImagerBackdoor, ImagerBox, ImagerMaskCache, ImagerSample, ImagerSys, ImagerTransformation, ImagerTypeface, Real, Rope ~ BEGIN VEC: TYPE ~ Vector2.VEC; ROPE: TYPE ~ Rope.ROPE; Extents: TYPE ~ ImagerBox.Extents; Transformation: TYPE ~ ImagerTransformation.Transformation; Typeface: TYPE ~ ImagerTypeface.Typeface; XChar: TYPE ~ ImagerFont.XChar; nullXChar: XChar ~ ImagerFont.nullXChar; substituteChar: XChar ~ Char.Make[set: 360B, code: 312B]; Data: TYPE ~ REF DataRep; DataRep: TYPE ~ RECORD [ setTable: SetTable ¬ NIL, amplifySpace: BOOL ¬ FALSE, groups: LIST OF GroupData ¬ NIL ]; CharSet: TYPE ~ Char.CharSet[0..254]; 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, bb: ImagerBox.Box]; Representation: TYPE ~ { spline, alignedRaster, packedRaster }; GroupData: TYPE ~ REF GroupDataRep; GroupDataRep: TYPE ~ RECORD [ base: ROPE ¬ NIL, set: BYTE ¬ 0, bc, ec: BYTE ¬ 0, representation: Representation, dataByteOffset: INT ¬ 0, directoryByteOffset: INT ¬ 0, dataByteLength: INT ¬ 0, pixelToChar: Transformation ¬ NIL ]; MalformedCDFont: ERROR ~ CODE; Assert: PROC [truth: BOOL] ~ INLINE { IF NOT truth THEN ERROR MalformedCDFont }; FileBytes: PROC [fileWords: INT] RETURNS [INT] ~ INLINE {RETURN [fileWords*2]}; RawFetch: UNSAFE PROC [base: ROPE, byteOffset: INT, destination: LONG POINTER, nBytes: NAT] ~ UNCHECKED { bytesMoved: INT ~ Rope.UnsafeMoveChars[block: [base: destination, startIndex: 0, count: nBytes], rope: base, start: byteOffset]; Assert[bytesMoved = nBytes]; }; IXHeader: TYPE ~ RECORD [type: PrePressFontFormat.IXType, length: [0..7777B]]; ReadIXHeader: PROC [base: ROPE, byteOffset: INT] RETURNS [ix: IXHeader] = { bytes: MACHINE DEPENDENT RECORD [a, b, c, d: BYTE] ¬ LOOPHOLE[0]; header: PrePressFontFormat.IXHeader; bytes.c ¬ LOOPHOLE[Rope.Fetch[base: base, index: byteOffset]]; bytes.d ¬ LOOPHOLE[Rope.Fetch[base: base, index: byteOffset+1]]; header ¬ LOOPHOLE[bytes]; ix.type ¬ header.type; ix.length ¬ header.length; }; RasterDefn: TYPE ~ RECORD [raster: [0..77B], lines: [0..1777B]]; ReadRasterDefn: PROC [base: ROPE, byteOffset: INT] RETURNS [rd: RasterDefn] = { bytes: MACHINE DEPENDENT RECORD [a, b, c, d: BYTE] ¬ LOOPHOLE[0]; raster: PrePressFontFormat.RasterDefn; bytes.c ¬ LOOPHOLE[Rope.Fetch[base: base, index: byteOffset]]; bytes.d ¬ LOOPHOLE[Rope.Fetch[base: base, index: byteOffset+1]]; raster ¬ LOOPHOLE[bytes]; rd.raster ¬ raster.raster; rd.lines ¬ raster.lines; }; CDCreate: PROC [stream: IO.STREAM] RETURNS [Typeface] ~ { name: ROPE ~ ImagerSys.StreamFileName[stream]; base: ROPE ~ ImagerSys.RopeFromStream[stream]; RETURN [TypefaceFromRopes[LIST[base] ! MalformedCDFont => ERROR Imager.Error[[$malformedFont, Rope.Cat["Font file ", name, " is malformed."], LIST[[$name, name]]]]]]; }; 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. NameTable: TYPE ~ REF NameTableRep; NameTableRep: TYPE ~ RECORD [SEQUENCE n: NAT OF ROPE]; DecodedFamily: TYPE ~ RECORD [ familySansCharSet: ROPE ¬ NIL, hasCharSet: BOOL ¬ FALSE, charSet: BYTE ¬ 0 ]; DecodeFamily: PROC [family: ROPE] RETURNS [DecodedFamily] ~ { size: NAT ~ Rope.Size[family]; charSet: BYTE ¬ 0; hasCharSet: BOOL ¬ FALSE; Assert[family # NIL]; IF size >= 3 THEN { hasCharSet ¬ TRUE; FOR i: INT IN [size-3 .. size) DO c: CHAR ~ Rope.Fetch[family, i]; IF c IN ['0..'7] THEN {charSet ¬ charSet*8 + (c-'0)} ELSE {hasCharSet ¬ FALSE; charSet ¬ 0; EXIT}; ENDLOOP; }; RETURN [[ familySansCharSet: IF hasCharSet THEN Rope.Substr[family, 0, size-3] ELSE family, hasCharSet: hasCharSet, charSet: charSet ]] }; TypefaceFromRopes: PROC [contents: LIST OF ROPE] RETURNS [Typeface] ~ { data: Data ~ NEW[DataRep ¬ []]; ix: IXHeader; indexFace: BYTE ¬ 0; indexFamilySansCharSet: ROPE ¬ NIL; groups: LIST OF GroupData ¬ NIL; ValidateFamilyAndFace: PROC [newFace: BYTE, newFamily: ROPE] ~ { IF indexFamilySansCharSet = NIL THEN { -- first time through indexFamilySansCharSet ¬ newFamily; indexFace ¬ newFace; RETURN; }; Assert[indexFace = newFace]; Assert[Rope.Equal[newFamily, indexFamilySansCharSet, FALSE]]; }; FOR each: LIST OF ROPE ¬ contents, each.rest UNTIL each = NIL DO base: ROPE ~ each.first; byteOffset: INT ¬ 0; nameTable: NameTable ¬ NIL; maxNameCode: CARDINAL ¬ 0; DO -- read the index part to find max name code headerBytes: NAT ~ BYTES[PrePressFontFormat.IXHeader]; ix ¬ ReadIXHeader[base, byteOffset]; SELECT ix.type FROM end => EXIT; name => { name: PrePressFontFormat.NameIndexEntry; nameBytes: NAT ~ BYTES[PrePressFontFormat.NameIndexEntry]; TRUSTED { RawFetch[base, byteOffset+headerBytes, @name, nameBytes] }; maxNameCode ¬ MAX[maxNameCode, name.code]; }; ENDCASE => NULL; byteOffset ¬ byteOffset + FileBytes[ix.length]; ENDLOOP; nameTable ¬ NEW[NameTableRep[maxNameCode+1]]; byteOffset ¬ 0; DO -- read the index part to get the name codes headerBytes: NAT ~ BYTES[PrePressFontFormat.IXHeader]; ix ¬ ReadIXHeader[base, byteOffset]; SELECT ix.type FROM end => EXIT; name => { name: PrePressFontFormat.NameIndexEntry; nameBytes: NAT ~ BYTES[PrePressFontFormat.NameIndexEntry]; i: NAT ¬ 0; P: PROC RETURNS [CHAR] ~ {RETURN [VAL[name.chars[i ¬ i + 1]]]}; TRUSTED { RawFetch[base, byteOffset+headerBytes, @name, nameBytes] }; nameTable[name.code] ¬ Rope.FromProc[len: name.chars[0], p: P]; }; ENDCASE => NULL; byteOffset ¬ byteOffset + FileBytes[ix.length]; ENDLOOP; byteOffset ¬ 0; DO -- read the index part headerBytes: NAT ~ BYTES[PrePressFontFormat.IXHeader]; ix ¬ ReadIXHeader[base, byteOffset]; SELECT ix.type FROM end => EXIT; name => NULL; spline => NULL; -- We don't want to deal with BCPL reals. character => { index: PrePressFontFormat.CharacterIndexEntry; cixBytes: NAT ~ BYTES[PrePressFontFormat.CharacterIndexEntry]; group: GroupData ~ NEW[GroupDataRep]; charUnitsPerResolutionUnit: REAL ¬ 0; decodedFamily: DecodedFamily ¬ []; group.representation ¬ alignedRaster; group.set ¬ 0; Assert[FileBytes[ix.length] = headerBytes + cixBytes]; TRUSTED { RawFetch[base, byteOffset+headerBytes, @index, cixBytes] }; IF checkForBogusResolution THEN { IF ((index.resolutionX = index.resolutionY) AND index.resolutionX # 0) OR (index.resolutionX # 0 AND (index.resolutionX MOD 1500 = 0)) OR (SELECT index.resolutionX FROM 3840, 2000, 2540, 2400, 720 => TRUE ENDCASE => FALSE) THEN NULL ELSE index.resolutionX ¬ 3000 }; IF tryForCharSet THEN { IF index.resolutionY MOD 10 = 0 AND index.resolutionY/10 IN CharSet THEN { group.set ¬ index.resolutionY/10; index.resolutionY ¬ index.resolutionX; }; }; Assert[index.bc <= index.ec]; group.base ¬ base; group.bc ¬ index.bc; group.ec ¬ index.ec; group.dataByteOffset ¬ FileBytes[index.segmentSA]; group.dataByteLength ¬ FileBytes[index.segmentLength]; Assert[group.dataByteOffset + group.dataByteLength <= Rope.Size[base]]; group.directoryByteOffset ¬ group.dataByteOffset + (index.ec-index.bc+1) * BYTES[PrePressFontFormat.CharacterData]; charUnitsPerResolutionUnit ¬ 25400.0/index.size; -- units of resolution are dots per 10 inches group.pixelToChar ¬ ImagerTransformation.Scale2[[ charUnitsPerResolutionUnit/index.resolutionX, charUnitsPerResolutionUnit/index.resolutionY ]]; IF index.rotation#0 THEN group.pixelToChar.ApplyPreRotate[-index.rotation/60.0]; decodedFamily ¬ DecodeFamily[nameTable[index.family]]; ValidateFamilyAndFace[newFace: index.face, newFamily: decodedFamily.familySansCharSet]; IF decodedFamily.hasCharSet THEN group.set ¬ decodedFamily.charSet; { charDataEntryBytes: NAT ~ BYTES[PrePressFontFormat.CharacterData]; dirEntryBytes: NAT ~ BYTES[PrePressFontFormat.RelFilePos]; len: NAT ~ (group.ec-group.bc+1) * (charDataEntryBytes+dirEntryBytes); group.base ¬ Rope.Replace[base: base, start: group.dataByteOffset, len: len, with: Rope.Flatten[base: base, start: group.dataByteOffset, len: len]]; }; groups ¬ CONS[group, groups]; }; ENDCASE => ERROR MalformedCDFont; -- unexpected ix type byteOffset ¬ byteOffset + FileBytes[ix.length]; ENDLOOP; ENDLOOP; Assert[indexFamilySansCharSet # NIL]; data.groups ¬ SortGroups[groups]; data.setTable ¬ BuildSetTable[data.groups]; data.amplifySpace ¬ indexFace < 18; -- 40B is an ordinary character in TEX fonts RETURN[NEW[ImagerTypeface.TypefaceRep ¬ [class: cdClass, data: data, propList: PropListFromGroups[data.groups]]]]; }; PropListFromGroups: PROC [groups: LIST OF GroupData] RETURNS [Prop.PropList] ~ { list: LIST OF REF ¬ NIL; FOR tail: LIST OF GroupData ¬ groups, tail.rest UNTIL tail = NIL DO m: Transformation ¬ tail.first.pixelToChar; IF m # NIL AND NOT ImagerTransformation.Singular[m] THEN { FOR t: LIST OF REF ¬ list, t.rest UNTIL t = NIL DO IF ImagerTransformation.Equal[m, NARROW[t.first]] THEN { m ¬ NIL; EXIT; }; ENDLOOP; IF m # NIL THEN list ¬ CONS[m, list]; }; ENDLOOP; RETURN [IF list = NIL THEN NIL ELSE LIST[[$pixelArrayTransformationUses, list]]]; }; InOrder: PROC [a, b: GroupData] RETURNS [BOOL] ~ { SELECT TRUE FROM a.set # b.set => RETURN [a.set < b.set]; a.representation # b.representation AND (a.representation=spline OR b.representation=spline) => RETURN [a.representation < b.representation]; ENDCASE => { sva: VEC ~ ImagerTransformation.SingularValues[a.pixelToChar]; svb: VEC ~ ImagerTransformation.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: REF PACKED ARRAY CharSet OF BYTE ¬ NEW[PACKED ARRAY CharSet OF BYTE ¬ ALL[LAST[BYTE]]]; setec: REF PACKED ARRAY CharSet OF BYTE ¬ NEW[PACKED 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 SELECT group.representation FROM spline => ERROR MalformedCDFont; alignedRaster => { cd: PrePressFontFormat.CharacterData ~ GetCharacterData[group, code]; IF cd.bbdy#PrePressFontFormat.missingCharacter THEN { wx: REAL ~ Real.PairToReal[Real.Floor[Real.FScale[cd.wx, -16]], 0]; wy: REAL ~ Real.PairToReal[Real.Floor[Real.FScale[cd.wy, -16]], 0]; e: VEC ~ ImagerTransformation.TransformVec[group.pixelToChar, [wx, wy]]; box: ImagerBox.Box ~ ImagerBox.BoxFromRectangle[ImagerTransformation.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, bb: box]] ELSE { charMetrics.bb ¬ ImagerBox.BoundingBox[charMetrics.bb, box]; charMetrics.escapement ¬ e; -- this is clearly the wrong thing to do but for the sunbeam trouble it's the only thing that will work }; }; }; ENDCASE => ERROR; ENDLOOP; ENDLOOP; setbc ¬ NIL; setec ¬ NIL; -- Local use only; an explicit FREE here would work RETURN [setTable] }; TransformBox: PROC [m: Transformation, box: ImagerBox.Box] RETURNS [ImagerBox.Box] ~ { RETURN [ImagerBox.BoxFromRectangle[ImagerTransformation.TransformRectangle[m, ImagerBox.RectangleFromBox[box]]]] }; GetCharacterData: PROC [group: GroupData, code: BYTE] RETURNS [PrePressFontFormat.CharacterData] ~ { characterData: PrePressFontFormat.CharacterData; cdBytes: INT ~ BYTES[PrePressFontFormat.CharacterData]; IF group.representation # alignedRaster THEN ERROR; characterData.bbdy ¬ PrePressFontFormat.missingCharacter; IF code IN [group.bc..group.ec] THEN TRUSTED { RawFetch[group.base, group.dataByteOffset + (code-group.bc) * cdBytes, @characterData, cdBytes] }; RETURN [characterData]; }; CDContains: PROC [self: Typeface, char: ImagerFont.XChar] RETURNS [BOOL] ~ { data: Data ~ NARROW[self.data]; charTable: CharTable ~ data.setTable[Char.Set[char]]; IF charTable = NIL OR Char.Code[char] NOT IN [charTable.bc..charTable.ec] THEN RETURN [FALSE]; RETURN [charTable[Char.Code[char]-charTable.bc] # NIL]; }; CDNextChar: PROC [self: Typeface, char: ImagerFont.XChar] RETURNS [ImagerFont.XChar] ~ { data: Data ~ NARROW[self.data]; next: XChar ¬ IF char = nullXChar THEN Char.Make[0, 0] ELSE SUCC[char]; charTable: CharTable ¬ IF Char.Set[next] IN CharSet THEN data.setTable[Char.Set[next]] ELSE NIL; UNTIL next = nullXChar OR (charTable # NIL AND Char.Code[next] IN [charTable.bc..charTable.ec] AND charTable[Char.Code[next]-charTable.bc] # NIL) DO SELECT TRUE FROM (charTable=NIL) OR (Char.Code[next]>charTable.ec) => { next ¬ next.SUCC; IF Char.Set[next] > CharSet.LAST THEN {next ¬ nullXChar; EXIT}; charTable ¬ data.setTable[Char.Set[next]]; }; (Char.Code[next] < charTable.bc) => next ¬ Char.Make[Char.Set[next], charTable.bc]; ENDCASE => next ¬ next.SUCC; ENDLOOP; RETURN [next]; }; CDEscapement: PROC [self: Typeface, char: ImagerFont.XChar] RETURNS [VEC] ~ { data: Data ~ NARROW[self.data]; charTable: CharTable ~ data.setTable[Char.Set[char]]; IF charTable # NIL AND Char.Code[char] IN [charTable.bc..charTable.ec] THEN { charMetrics: CharMetrics ~ charTable[Char.Code[char]-charTable.bc]; IF charMetrics # NIL THEN RETURN [charMetrics.escapement] }; RETURN[[0.5, 0]]; }; CDAmplified: PROC [self: Typeface, char: ImagerFont.XChar] RETURNS [BOOL] ~ { data: Data ~ NARROW[self.data]; RETURN [data.amplifySpace AND char = Char.Make[set: 0, code: 40B]]; }; CDCorrection: PROC [self: Typeface, char: ImagerFont.XChar] RETURNS [ImagerFont.CorrectionType] ~ { data: Data ~ NARROW[self.data]; IF CDContains[self, char] THEN { IF data.amplifySpace AND char = Char.Make[set: 0, code: 40B] THEN RETURN[space]; RETURN[mask]; }; RETURN[none]; }; CDBoundingBox: PROC [self: Typeface, char: ImagerFont.XChar] RETURNS [Extents] ~ { data: Data ~ NARROW[self.data]; charTable: CharTable ~ data.setTable[Char.Set[char]]; IF charTable # NIL AND Char.Code[char] IN [charTable.bc..charTable.ec] THEN { charMetrics: CharMetrics ~ charTable[Char.Code[char]-charTable.bc]; IF charMetrics # NIL THEN RETURN [ImagerBox.ExtentsFromBox[charMetrics.bb]]; }; IF char # substituteChar THEN RETURN [CDBoundingBox[self, substituteChar]]; RETURN[[leftExtent: -0.05, rightExtent: 0.45, descent: 0, ascent: 0.6]]; }; CDFontBoundingBox: PROC [self: Typeface] RETURNS [Extents] ~ { data: Data ~ NARROW[self.data]; setTable: SetTable ~ data.setTable; bb: ImagerBox.Box ¬ ImagerBox.BoxFromExtents[CDBoundingBox[self, substituteChar]]; 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 { b: ImagerBox.Box ~ charMetrics.bb; bb ¬ [xmin: MIN[bb.xmin, b.xmin], ymin: MIN[bb.ymin, b.ymin], xmax: MAX[bb.xmax, b.xmax], ymax: MAX[bb.ymax, b.ymax]] }; ENDLOOP; }; ENDLOOP; RETURN [ImagerBox.ExtentsFromBox[bb]] }; CDKern: PROC [self: Typeface, char, successor: ImagerFont.XChar] RETURNS [VEC] ~ { RETURN[[0, 0]] }; CDNextKern: PROC [self: Typeface, char, successor: ImagerFont.XChar] RETURNS [ImagerFont.XChar] ~ { RETURN[ImagerFont.nullXChar] }; CDLigature: PROC [self: Typeface, char, successor: ImagerFont.XChar] RETURNS [ImagerFont.XChar] ~ { RETURN[ImagerFont.nullXChar] }; CDNextLigature: PROC [self: Typeface, char, successor: ImagerFont.XChar] RETURNS [ImagerFont.XChar] ~ { RETURN[ImagerFont.nullXChar] }; GetBitmap: PROC [group: GroupData, code: BYTE] RETURNS [ImagerSample.RasterSampleMap] ~ { sampleMap: ImagerSample.RasterSampleMap ¬ NIL; cd: PrePressFontFormat.CharacterData ~ GetCharacterData[group, code]; IF cd.bbdy#PrePressFontFormat.missingCharacter THEN TRUSTED { rfpBytes: INT ~ BYTES[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[rfp]; rdBytes: INT ~ BYTES[PrePressFontFormat.RasterDefn]; rd: RasterDefn ¬ ReadRasterDefn[group.base, group.directoryByteOffset+relativeByte]; 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 ¬ ImagerSample.ObtainScratchMap[ box: [min: [s: cd.bbox, f: cd.bboy], max: [s: cd.bbdx+cd.bbox, f: cd.bbdy+cd.bboy]], bitsPerLine: rd.raster*16, -- this is really 16, not bitsPerWord! bitsPerSample: 1 ]; TRUSTED {RawFetch[group.base, group.directoryByteOffset+relativeByte+rdBytes, ImagerSample.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: ImagerFont.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[char] THEN { SELECT group.representation FROM spline => ERROR MalformedCDFont; alignedRaster => { cd: PrePressFontFormat.CharacterData ~ GetCharacterData[group, Char.Code[char]]; IF cd.bbdy#PrePressFontFormat.missingCharacter THEN { IF t # NIL THEN { composite: Transformation ¬ ImagerTransformation.Concat[group.pixelToChar, t]; f: ImagerTransformation.FactoredTransformation ~ ImagerTransformation.Factor[composite]; ImagerTransformation.Destroy[composite]; composite ¬ NIL; best ¬ group; -- so that there will be an answer in any case, preferring the highest resolution. 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 EXIT; }; }; }; ENDCASE => ERROR; }; ENDLOOP; IF best = NIL THEN ERROR; -- CDContains lied! RETURN [best]; }; GetClientToDevice: PROC [context: Imager.Context] RETURNS [t: Transformation ¬ NIL] ~ { t ¬ ImagerBackdoor.GetTransformation[context: context, from: client, to: device ! Imager.Error => CONTINUE]; }; CDMask: PROC [self: Typeface, char: ImagerFont.XChar, context: Imager.Context] ~ { data: Data ~ NARROW[self.data]; IF CDContains[self, char] THEN { t: Transformation ¬ GetClientToDevice[context]; group: GroupData ~ FindBestGroup[data, char, t]; IF t # NIL THEN { ImagerTransformation.Destroy[t]; t ¬ NIL}; SELECT group.representation FROM spline => ERROR MalformedCDFont; alignedRaster => { bitmap: ImagerSample.SampleMap ¬ GetBitmap[group, Char.Code[char]]; IF bitmap # NIL THEN { Imager.ConcatT[context, group.pixelToChar]; Imager.MaskBitmap[context: context, bitmap: bitmap, referencePoint: [0, 0], scanMode: [slow: right, fast: up], position: [0, 0]]; ImagerSample.ReleaseScratchMap[bitmap]; }; }; ENDCASE => ERROR; } ELSE { IF char # substituteChar THEN CDMask[self, substituteChar, context] ELSE Imager.MaskRectangle[context, [0.05, 0, 0.4, 0.6]]; }; }; tolerance: REAL ¬ 0.01; -- allow up to 1% error in mask size to hit the fast case CloseToIdentity: PROC [t: Transformation] RETURNS [BOOL] = { IF NOT (t.integerTrans AND t.tx = 0 AND t.ty = 0) THEN RETURN[FALSE]; IF t.form = 3 THEN RETURN[TRUE]; IF t.form = 1 AND MAX[ABS[t.a - 1.0], ABS[t.e - 1.0]] <= tolerance THEN RETURN[TRUE]; RETURN[FALSE]}; CDMakeCharMask: ImagerMaskCache.MakeCharMaskProc = { self: Typeface = ImagerTypeface.TypefaceFromFont[font]; data: Data = NARROW[self.data]; charMask: ImagerMaskCache.CharMask ¬ NIL; IF CDContains[self, char] THEN { group: GroupData = FindBestGroup[data, char, font.charToClient]; t: Transformation ¬ NIL; t ¬ ImagerTransformation.Concat[group.pixelToChar, font.charToClient]; IF CloseToIdentity[t] THEN SELECT group.representation FROM alignedRaster => { bitmap: ImagerSample.SampleMap ¬ GetBitmap[group, Char.Code[char]]; IF bitmap # NIL THEN { charMask ¬ ImagerMaskCache.RasterCharMaskFromSampleMap[ bitmap]; ImagerSample.ReleaseScratchMap[bitmap]; }; }; ENDCASE => NULL; }; RETURN[charMask]; }; cdClass: ImagerTypeface.TypefaceClass ~ NEW [ImagerTypeface.TypefaceClassRep ¬ [ type: $CD, Contains: CDContains, NextChar: CDNextChar, Escapement: CDEscapement, Amplified: CDAmplified, Correction: CDCorrection, BoundingBox: CDBoundingBox, FontBoundingBox: CDFontBoundingBox, Ligature: CDLigature, NextLigature: CDNextLigature, Kern: CDKern, NextKern: CDNextKern, Mask: CDMask, MakeCharMask: CDMakeCharMask ]]; ImagerTypeface.RegisterCreator[ImagerTypeface.CreatorFromFileExtension["AC", CDCreate]]; ImagerTypeface.RegisterCreator[ImagerTypeface.CreatorFromFileExtension["CD", CDCreate]]; ImagerTypeface.RegisterCreator[ImagerTypeface.CreatorFromFileExtension["cd-300", CDCreate]]; -- old name for cd-bitmaps ImagerTypeface.RegisterCreator[ImagerTypeface.CreatorFromFileExtension["cd-bitmaps", CDCreate]]; -- new name for cd-300 (product fonts) END.  ImagerCDTypefaceImpl.mesa Copyright Σ 1986, 1987, 1988, 1989, 1990, 1991, 1992 by Xerox Corporation. All rights reserved. Michael Plass, August 19, 1992 10:28 am PDT Doug Wyatt, January 24, 1987 9:57:25 pm PST Tim Diebert: April 24, 1989 2:34:54 pm PDT Russ Roberts (RRR), May 3, 1990 9:02 pm GMT 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; This procedure examines a family name for the the presence of character set information. TRUSTED { RawFetch[base, byteOffset, @ix, headerBytes] }; TRUSTED { RawFetch[base, byteOffset, @ix, headerBytes] }; TRUSTED { RawFetch[base, byteOffset, @ix, headerBytes] }; The following makes a flat representation for the directory portion of the rope representation, to avoid having to do IO to get at the metrics. It is strictly an optimization. Computes the pixelArrayTransformationUses property wx: REAL ~ Real.FScale[cd.wx, -16]; wy: REAL ~ Real.FScale[cd.wy, -16]; RawFetch[group.base, group.directoryByteOffset+relativeByte, @rd, rdBytes]; t may be NIL, in which case we only try for splines. returns NIL if the context does not have a client-to-device transformation [parameters: Parameters, font: Font, char: XChar] RETURNS [CharMask] ΚX–(cedarcode) style•NewlineDelimiter ™codešœ™Kšœ ΟeœU™`K™+Kšœ+™+K™*K™+K™—šΟk ˜ K˜Kšœžœ6˜BKšœžœ˜)Kšœ žœa˜pKšœ žœ$˜4Kšœžœ;˜PKšœ žœQ˜cKšœ ˜ K˜Kšœžœ€˜”Kšžœžœžœ˜Kšœžœ‚˜šKšœžœ ˜Kšœžœ˜'Kšœžœ1žœ!˜`Kšœžœžœ˜—K˜KšΠlnœžœž˜#Kšžœ„˜‹šœž˜K˜Kšžœžœ žœ˜Kšžœžœžœ˜Kšœ žœ˜"Kšœžœ'˜;Kšœ žœ˜)Kšœžœ˜Kšœ(˜(šœ9˜9K˜K˜—Kšœžœžœ ˜šœ žœžœ˜Kšœžœ˜Kšœžœžœ˜Kšœžœžœ ž˜Kšœ˜K˜—Kšœ žœ˜%Kšœ žœžœ ˜!š œ žœžœ žœ žœžœ˜:K˜—Kšœ žœžœ˜#š œžœžœ žœžœžœžœ˜QK˜—Kšœ žœžœ˜'šœžœžœžœ˜CK˜—Kšœžœ+˜?Kšœ žœžœ˜#šœžœžœ˜Kšœžœžœ˜Kšœžœ˜Kšœžœ˜K˜Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœž˜!K˜——˜šΟnœžœžœ˜K˜—š œžœ žœžœžœžœžœžœ˜PK˜—š  œžœ žœžœžœžœžœ˜OKšœLΟbœ9™ˆK˜—š œžœžœžœžœžœžœ žœž œ˜iK•StartOfExpansionM[buffer: REF TEXT, rope: ROPE, start: INT _ 0, len: INT _ 2147483647]šœ žœq˜€Kšœ˜Kšœ˜—K˜Kšœ žœžœ7˜Nš   œžœžœžœžœ˜KKš œžœž œžœžœžœ˜AKšœ$˜$K– [base: ROPE, index: INT _ 0]šœ žœ,˜>K– [base: ROPE, index: INT _ 0]šœ žœ.˜@Kšœ žœ˜Kšœ1˜1Kšœ˜K˜—Kšœ žœžœ'˜@š  œžœžœžœžœ˜OKš œžœž œžœžœžœ˜AKšœ&˜&K– [base: ROPE, index: INT _ 0]šœ žœ,˜>K– [base: ROPE, index: INT _ 0]šœ žœ.˜@Kšœ žœ˜Kšœ3˜3Kšœ˜—K˜š œžœ ž œžœ˜9Kšœžœ$˜.Kšœžœ$˜.šžœžœ˜$Kšœžœg˜—Kšœ˜K˜—KšœžœžœΟcΊ˜ΰšœžœžœ’…˜‘K˜—Kšœ žœžœ˜#š œžœžœžœžœžœžœ˜6K˜—šœžœžœ˜Kšœžœžœ˜Kšœ žœžœ˜Kšœ žœ˜Kšœ˜K˜—š  œžœ žœžœ˜=KšœX™XKšœžœ˜Kšœ žœ˜Kšœ žœžœ˜Kšœžœ˜šžœ žœ˜Kšœ žœ˜šžœžœžœž˜!Kšœžœ˜ šžœžœ ˜Kšžœ˜#Kšžœžœžœ˜-—Kšžœ˜—Kšœ˜—šžœ˜ Kšœžœ žœ žœ˜QKšœ˜Kšœ˜Kšœ˜—Kšœ˜K˜—š  œžœ žœžœžœžœ˜GKšœ žœ˜K˜ Kšœ žœ˜Kšœžœžœ˜#Kšœžœžœ žœ˜ š œžœ žœ žœ˜@šžœžœžœ’˜˜EKšœžœ˜*K˜—Kšžœžœ˜—Kšœ/˜/Kšžœ˜—Kšœ žœ˜-Kšœ˜šžœ’,˜/Kšœ žœžœ˜7Kšœ$˜$Kšžœ2™9šžœ ž˜Kšœžœ˜ ˜ K˜(Kšœ žœžœ$˜:Kšœžœ˜ Kš  œžœžœžœžœžœ˜?Kšžœ>˜EKšœ?˜?K˜—Kšžœžœ˜—Kšœ/˜/Kšžœ˜—Kšœ˜šžœ’˜Kšœ žœžœ˜7Kšœ$˜$Kšžœ2™9šžœ ž˜Kšœžœ˜ Kšœžœ˜ Kšœ žœ’)˜9˜K˜.Kšœ žœžœ)˜?Kšœžœ˜%Kšœžœ˜%Kšœ"˜"Kšœ%˜%Kšœ˜Kšœ6˜6Kšžœ>˜Ešžœžœ˜!Kšžœ*žœžœžœžœ žœžœžœ žœžœžœžœžœžœ˜†Kšœ˜—šžœžœ˜š žœžœžœžœ žœ˜JKšœ!˜!Kšœ&˜&Kšœ˜—Kšœ˜—Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ2˜2Kšœ6˜6KšœG˜GKšœKžœ#˜sKšœ1’-˜^šœ1˜1K˜-K˜,K˜—Kšžœžœ8˜PKšœ6˜6KšœW˜WKšžœžœ#˜Cšœ˜K–9[base: ROPE, start: INT _ 0, len: INT _ 2147483647]™°Kšœžœžœ#˜BKšœžœžœ ˜:Kšœžœ>˜FKšœ”˜”Kšœ˜—Kšœ žœ˜K˜—Kšžœžœ’˜7—Kšœ/˜/Kšžœ˜—Kšžœ˜—Kšœ žœ˜%Kšœ!˜!Kšœ+˜+Kšœ$’$Ρcdl’˜PKšžœžœh˜rK˜K˜—š  œžœ žœžœ žœ˜PK™2Kš œžœžœžœžœ˜š žœžœžœžœžœž˜CK˜+š žœžœžœžœ"žœ˜:š žœžœžœžœžœžœž˜2šžœžœ žœ˜8Kšœžœ˜Kšžœ˜Kšœ˜—Kšžœ˜—Kšžœžœžœžœ ˜%K˜—Kšžœ˜—Kšžœžœžœžœžœžœžœ)˜QK˜—K˜š œžœžœžœ˜2šžœžœž˜Kšœžœ˜(Kšœ$žœžœžœ'˜šžœ˜ Kšœžœ6˜>Kšœžœ6˜>Kšžœžœžœžœ˜$Kš žœžœ žœžœžœ˜6Kšžœžœ˜Kšœ˜——Kšœ˜K˜—š  œžœ žœžœ žœžœžœ˜LKšœ žœžœ˜'Kšœžœžœ žœ˜šžœžœž˜Kšœžœžœ˜*Kšœ&˜&Kšœžœžœ˜Kšœžœžœ žœ˜Kš žœžœžœžœžœžœ˜Kšžœž˜ Kšžœ,˜0Kšžœ3˜7—Kšœ˜Kšžœ˜—Kšžœ˜ Kšœ˜K˜—š   œžœ žœžœ žœ˜FKšœžœžœžœ˜1Kšœžœžœžœ žœžœžœžœžœ žœžœžœžœžœ˜^Kšœžœžœžœ žœžœžœžœžœ žœžœžœžœžœ˜_š žœžœžœžœžœž˜CKšœ˜Kšœžœ ˜Kšœžœ˜)Kšœžœ˜)Kšžœ˜—šžœžœ ž˜šžœžœ˜"Kšœžœ(˜BKšœ˜Kšœ˜Kš žœžœžœžœžœžœ˜@Kšœ˜Kšœ˜—Kšžœ˜—š žœžœžœžœžœž˜CKšœ˜Kšœ+˜+šžœžœžœž˜)šžœž˜ Kšœ žœ˜ ˜KšœE˜Ešžœ-žœ˜5Kšœžœ™#Kšœžœ™#Kšœžœ;˜CKšœžœ;˜CKšœžœB˜HKšœž˜žKšœ8˜8šžœž˜Kšžœ žœ,˜Sšžœ˜Kšœ<˜Kšœ žœ ˜Kšœ#˜#KšœR˜Ršžœžœ ž˜Kšœ%˜%šžœ žœžœ˜šžœžœžœž˜1Kšœ8˜8šžœžœžœ˜Kšœ"˜"Kš œ žœžœžœžœ˜uKšœ˜—Kšžœ˜—Kšœ˜—Kšžœ˜—Kšžœ˜%K˜—K˜Kš  œžœ5žœžœžœ ˜dK˜Kš  œžœ5žœžœ˜ƒK˜Kš  œžœ5žœžœ˜ƒK˜Kš œžœ5žœžœ˜‡K˜š  œžœžœžœ#˜YKšœ*žœ˜.KšœE˜Ešžœ-žœžœ˜=Kšœ žœžœ ˜5Kšœ žœ6˜EKšœ#˜#Kšžœ5˜<šžœ)žœ˜1Kšœžœ˜#Kšœ žœžœ ˜4KšœT˜TKšœK™KKšœžœ˜7šžœ žœ žœ˜!Kšœ žœ žœ˜7šœ*˜*KšœT˜TK˜AK˜K˜—Kšžœ|˜ƒKšœ˜—Kšœ˜—Kšœ˜—Kšžœ ˜Kšœ˜K˜—š  œžœ žœžœžœ˜,Kšžœ žœžœ˜0Kšžœžœžœ˜3Kšžœžœ˜"Kšœ˜K˜—Kšœ žœ ˜Kšœ žœ ˜š  œžœ9žœ˜cK™4Kšœžœ˜š žœžœžœ$žœžœž˜HKšœ˜šžœžœ˜$šžœž˜ Kšœ žœ˜ šœ˜KšœP˜Pšžœ-žœ˜5šžœžœžœ˜KšœN˜NKšœX˜XKšœ5žœ˜9Kšœ’R˜`Kšžœ žœ žœžœžœžœžœžœžœžœ˜wKšœ˜—Kšœ˜—Kšœ˜—Kšžœžœ˜—Kšœ˜—Kšžœ˜—Kš žœžœžœžœ’˜-Kšžœ˜Kšœ˜K˜—š œžœžœžœ˜WK™JKšœbžœ˜lKšœ˜K˜—š œžœF˜RKšœ žœ ˜šžœ˜šžœ˜Kšœ/˜/Kšœ0˜0Kšžœžœžœ(žœ˜<šžœž˜ Kšœ žœ˜ šœ˜KšœC˜Cšžœ žœžœ˜Kšœ+˜+Kšœ˜Kšœ'˜'Kšœ˜—Kšœ˜—Kšžœžœ˜—Kšœ˜—šžœ˜šžœ˜Kšžœ&˜*Kšžœ4˜8—Kšœ˜——K˜—K˜Kšœ žœ ’9˜R—˜š œžœžœžœ˜