<<>> <> <> <> <> <> <> <<>> 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 = { <<[parameters: Parameters, font: Font, char: XChar] RETURNS [CharMask]>> 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.