<> <> <> <> <<>> DIRECTORY Basics, Font, FS, ImagerBasic, ImagerMasks, ImagerPixelMaps, ImagerTransform, IO, RasterFontWriter, Real, Rope, UFFileManager, UFPressFontFormat, UFPressFontReader, UFStrikeFormat, VM; RasterFontWriterImpl: CEDAR PROGRAM IMPORTS FS, ImagerMasks, ImagerPixelMaps, ImagerTransform, IO, Real, Rope, UFFileManager, UFPressFontFormat, UFPressFontReader EXPORTS RasterFontWriter ~ BEGIN OPEN RasterFontWriter; FormatError: PUBLIC ERROR [byteIndex: INT] ~ CODE; Create: PUBLIC PROC [defaultBoxBounds: ImagerPixelMaps.DeviceRectangle, defaultWidth: INTEGER] RETURNS [internalFont: InternalFont] ~ { defaultPixels: ImagerPixelMaps.PixelMap _ ImagerPixelMaps.Create[0, defaultBoxBounds]; defaultPixels.Clear; defaultPixels.Fill[defaultBoxBounds, 1]; internalFont _ NEW [InternalFontRep]; internalFont.defaultChar _ [fWidth: defaultWidth, sWidth: 0, pixels: defaultPixels]; FOR char: CHAR IN CHAR DO internalFont.charRep[char] _ internalFont.defaultChar; ENDLOOP; }; LoadAC: PROC [fileName: Rope.ROPE] RETURNS [internalFont: InternalFont] ~ TRUSTED { fileKey: Font.Key _ UFFileManager.KeyOf[fileName]; sizeInMeters: REAL _ UFPressFontReader.Size[[fileKey, 0]]; bc, ec: CHAR; [bc, ec] _ UFPressFontReader.Range[[fileKey, 0]]; internalFont _ Create[[-8, 1, 8, 8], 10]; internalFont.family _ UFPressFontReader.Family[[fileKey, 0]]; internalFont.face _ UFPressFontReader.Face[[fileKey, 0]]; internalFont.bitsPerEmQuad _ sizeInMeters*UFPressFontReader.Resolution[[fileKey, 0]].xRes/0.0254; FOR char: CHAR IN [bc..ec] DO info: UFPressFontReader.CharInfo _ UFPressFontReader.GetCharInfo[[fileKey, 0], char]; pixelArray: ImagerBasic.PixelArray _ UFPressFontReader.GetCharRaster[[fileKey, 0], char]; IF pixelArray # NIL THEN { mask: ImagerMasks.Mask _ ImagerMasks.FromPixelArray[pixelArray, pixelArray.m.Concat[ImagerTransform.Rotate[90]]]; bb: ImagerMasks.Mask _ ImagerMasks.FromRectangle[ImagerMasks.BoundingBox[mask]]; pixelMap: ImagerPixelMaps.PixelMap _ ImagerMasks.ToPixelMap[mask, bb]; internalFont.charRep[char] _ [ fWidth: info.widthX*internalFont.bitsPerEmQuad, sWidth: -info.widthY*internalFont.bitsPerEmQuad, pixels: pixelMap ]; }; ENDLOOP; }; Load: PUBLIC PROC [fileName: Rope.ROPE] RETURNS [internalFont: InternalFont] ~ TRUSTED { file: IO.STREAM _ FS.StreamOpen[fileName]; ReadBlock: UNSAFE PROC [dest: LONG POINTER, words: CARDINAL, wordOffset: INT _ -1] ~ UNCHECKED { IF wordOffset >= 0 THEN file.SetIndex[wordOffset*Basics.bytesPerWord]; [] _ file.UnsafeGetBlock[[dest, 0, words*Basics.bytesPerWord]]; }; CurWordOffset: PROC RETURNS [wordOffset: INT] ~ CHECKED { wordOffset _ file.GetIndex/Basics.bytesPerWord; }; strike: ImagerPixelMaps.PixelMap; header: UFStrikeFormat.Header; internalFont _ NEW[InternalFontRep]; ReadBlock[@header, SIZE[UFStrikeFormat.Header], 0]; IF header.format.oneBit # T AND header.format.unused # 0 THEN { IO.Close[file]; RETURN [LoadAC[fileName]]; }; IF header.format.oneBit # T THEN ERROR FormatError[0]; IF header.format.index # F THEN ERROR FormatError[0]; IF header.format.unused # 0 THEN ERROR FormatError[1]; IF header.format.kerned = T THEN { boundingBox: UFStrikeFormat.BoundingBox; body: UFStrikeFormat.Body; xInSegment, prevXInSegment: CARDINAL; xInSegmentOffset: INT; widthEntryOffset: INT; widthEntry: UFStrikeFormat.WidthEntry; bodyOffset: INT; ReadBlock[@boundingBox, SIZE[UFStrikeFormat.BoundingBox]]; bodyOffset _ CurWordOffset[]; ReadBlock[@body, SIZE[UFStrikeFormat.Body]]; strike _ ImagerPixelMaps.Create[0, [-body.ascent, 0, body.ascent+body.descent, body.raster*Basics.bitsPerWord]]; ReadBlock[strike.refRep.pointer, strike.refRep.words]; widthEntryOffset _ CurWordOffset[] + (header.max-header.min+3)*SIZE[CARDINAL]; IF widthEntryOffset # bodyOffset + body.length THEN ERROR FormatError[bodyOffset*SIZE[CARDINAL]]; ReadBlock[@prevXInSegment, SIZE[CARDINAL]]; xInSegmentOffset _ CurWordOffset[]; FOR char: CHAR IN [header.min..header.max] DO charPixels: ImagerPixelMaps.PixelMap _ strike; ReadBlock[@xInSegment, SIZE[CARDINAL], xInSegmentOffset]; xInSegmentOffset _ xInSegmentOffset + SIZE[CARDINAL]; ReadBlock[@widthEntry, SIZE[UFStrikeFormat.WidthEntry], widthEntryOffset]; widthEntryOffset _ widthEntryOffset + SIZE[UFStrikeFormat.WidthEntry]; IF widthEntry # UFStrikeFormat.nullWidthEntry THEN { IF widthEntry.width > header.maxwidth THEN ERROR FormatError[(widthEntryOffset-SIZE[UFStrikeFormat.WidthEntry])*Basics.bytesPerWord]; charPixels.fOrigin _ widthEntry.offset+boundingBox.fbbox-prevXInSegment; charPixels.fMin _ prevXInSegment; charPixels.fSize _ xInSegment-prevXInSegment; internalFont.charRep[char] _ [ sWidth: 0, fWidth: widthEntry.width, pixels: charPixels ]; }; prevXInSegment _ xInSegment; ENDLOOP; ReadBlock[@xInSegment, SIZE[CARDINAL], xInSegmentOffset]; xInSegmentOffset _ xInSegmentOffset + SIZE[CARDINAL]; ReadBlock[@widthEntry, SIZE[UFStrikeFormat.WidthEntry], widthEntryOffset]; widthEntryOffset _ widthEntryOffset + SIZE[UFStrikeFormat.WidthEntry]; IF widthEntry # UFStrikeFormat.nullWidthEntry THEN { strike.fOrigin _ widthEntry.offset+boundingBox.fbbox-prevXInSegment; strike.fMin _ prevXInSegment; strike.fSize _ xInSegment-prevXInSegment; internalFont.defaultChar _ [ sWidth: 0, fWidth: widthEntry.width, pixels: strike ]; } ELSE ERROR FormatError[file.GetIndex]; } ELSE { body: UFStrikeFormat.Body; xInSegment, prevXInSegment: CARDINAL; ReadBlock[@body, SIZE[UFStrikeFormat.Body]]; strike _ ImagerPixelMaps.Create[0, [-body.ascent, 0, body.ascent+body.descent, body.raster*Basics.bitsPerWord]]; ReadBlock[strike.refRep.pointer, strike.refRep.words]; ReadBlock[@prevXInSegment, SIZE[CARDINAL]]; FOR char: CHAR IN [header.min..header.max] DO charPixels: ImagerPixelMaps.PixelMap _ strike; ReadBlock[@xInSegment, SIZE[CARDINAL]]; IF xInSegment>prevXInSegment THEN { charPixels.fOrigin _ -prevXInSegment; charPixels.fMin _ prevXInSegment; charPixels.fSize _ xInSegment-prevXInSegment; IF charPixels.fSize > header.maxwidth THEN ERROR FormatError[file.GetIndex-2]; internalFont.charRep[char] _ [ sWidth: 0, fWidth: charPixels.fSize, pixels: charPixels ]; }; prevXInSegment _ xInSegment; ENDLOOP; ReadBlock[@xInSegment, SIZE[CARDINAL]]; strike.fOrigin _ -prevXInSegment; strike.fMin _ prevXInSegment; strike.fSize _ xInSegment-prevXInSegment; internalFont.defaultChar _ [ sWidth: 0, fWidth: strike.fSize, pixels: strike ]; }; FOR char: CHAR IN CHAR DO IF internalFont.charRep[char].pixels.refRep = NIL THEN { internalFont.charRep[char] _ internalFont.defaultChar; }; ENDLOOP; IF file.GetIndex # file.GetLength THEN ERROR FormatError[file.GetIndex]; file.Close; }; ComputeFontMetrics: PUBLIC PROC [internalFont: InternalFont] RETURNS [bc, ec: CHAR, sMin, fMin, sMax, fMax: INTEGER, maxWidth, totalWidth, fSizeStrike: CARDINAL] ~ { ProcessChar: PROC [charRep: InternalCharRep] ~ { bb: ImagerPixelMaps.DeviceRectangle _ charRep.pixels.Window; fWidth: INT _ Real.RoundLI[charRep.fWidth]; IF bb.sSize > 0 AND bb.fSize > 0 THEN { sMin _ MIN[sMin, bb.sMin]; fMin _ MIN[fMin, bb.fMin]; sMax _ MAX[sMax, bb.sMin+bb.sSize]; fMax _ MAX[fMax, bb.fMin+bb.fSize]; fSizeStrike _ fSizeStrike + bb.fSize; }; totalWidth _ totalWidth + fWidth; maxWidth _ MAX[fWidth, maxWidth]; }; bc _ '\000; ec _ '\377; WHILE bc < '\377 AND internalFont.charRep[bc] = internalFont.defaultChar DO bc _ bc + 1 ENDLOOP; WHILE ec > '\000 AND internalFont.charRep[ec] = internalFont.defaultChar DO ec _ ec - 1 ENDLOOP; maxWidth _ totalWidth_ fSizeStrike _ 0; sMin _ fMin _ sMax _ fMax _ 0; FOR char: CHAR IN [bc..ec] DO IF internalFont.charRep[char] # internalFont.defaultChar THEN ProcessChar[internalFont.charRep[char]]; ENDLOOP; ProcessChar[internalFont.defaultChar]; }; ComputeWidthEntry: PROC [charRep: InternalCharRep, fMinFont: INTEGER] RETURNS [widthEntry: UFStrikeFormat.WidthEntry] ~ { window: ImagerPixelMaps.DeviceRectangle _ charRep.pixels.Window; IF window.sSize = 0 OR window.fSize = 0 THEN window.fMin _ 0; IF window.fMin-fMinFont < 0 THEN ERROR; widthEntry.offset _ window.fMin-fMinFont; widthEntry.width _ Real.RoundLI[charRep.fWidth]; }; WriteKernedStrike: PUBLIC PROC [internalFont: InternalFont, fileName: Rope.ROPE] ~ TRUSTED { file: IO.STREAM _ FS.StreamOpen[fileName, $create]; WriteBlock: UNSAFE PROC [source: LONG POINTER, words: CARDINAL] ~ UNCHECKED { file.UnsafePutBlock[[source, 0, words*Basics.bytesPerWord]]; }; min, max: CHAR; sMin, fMin, sMax, fMax: INTEGER; maxwidth, strikeWidth: CARDINAL; [min, max, sMin, fMin, sMax, fMax, maxwidth, ----, strikeWidth] _ ComputeFontMetrics[internalFont]; IF max>=min THEN { ascent: NAT _ -sMin; descent: NAT _ sMax; bodyStart: INT; header: UFStrikeFormat.Header; boundingBox: UFStrikeFormat.BoundingBox _ [fbbox: fMin, fbboy: -sMax, fbbdx: fMax-fMin, fbbdy: sMax-sMin]; body: UFStrikeFormat.Body; strike: ImagerPixelMaps.PixelMap; widthEntry: UFStrikeFormat.WidthEntry; f: INTEGER; strike _ ImagerPixelMaps.Create[0, [-ascent, 0, ascent+descent, strikeWidth]]; strike.Clear; f _ 0; FOR c: CHAR IN [min..max] DO IF internalFont.charRep[c] # internalFont.defaultChar THEN { pixels: ImagerPixelMaps.PixelMap _ internalFont.charRep[c].pixels; pixels _ pixels.ShiftMap[0, f-pixels.Window.fMin]; strike.Transfer[pixels]; IF pixels.sSize # 0 THEN f _ f + pixels.fSize; }; ENDLOOP; strike.Transfer[internalFont.defaultChar.pixels.ShiftMap[0, f-internalFont.defaultChar.pixels.Window.fMin]]; header.format _ [oneBit: T, index: F, fixed: F, kerned: T, unused: 0]; header.min _ min; header.max _ max; header.maxwidth _ maxwidth; body.length _ strike.refRep.words+SIZE[UFStrikeFormat.Body]+(max-min+3)*SIZE[CARDINAL]; body.ascent _ ascent; body.descent _ descent; body.xoffset _ 0; body.raster _ strike.refRep.rast; WriteBlock[@header, SIZE[UFStrikeFormat.Header]]; WriteBlock[@boundingBox, SIZE[UFStrikeFormat.BoundingBox]]; bodyStart _ file.GetIndex; WriteBlock[@body, SIZE[UFStrikeFormat.Body]]; WriteBlock[strike.refRep.pointer, strike.refRep.words]; strikeWidth _ 0; WriteBlock[@strikeWidth, SIZE[CARDINAL]]; FOR c: CHAR IN [min..max] DO charRep: InternalCharRep _ internalFont.charRep[c]; IF charRep # internalFont.defaultChar THEN { IF charRep.pixels.sSize # 0 THEN strikeWidth _ strikeWidth + charRep.pixels.fSize; }; WriteBlock[@strikeWidth, SIZE[CARDINAL]]; ENDLOOP; IF internalFont.defaultChar.pixels.sSize # 0 THEN strikeWidth _ strikeWidth + internalFont.defaultChar.pixels.fSize; WriteBlock[@strikeWidth, SIZE[CARDINAL]]; IF file.GetIndex-bodyStart # body.length*Basics.bytesPerWord THEN ERROR; FOR c: CHAR IN [min..max] DO widthEntry _ IF internalFont.charRep[c] = internalFont.defaultChar THEN widthEntry _ UFStrikeFormat.nullWidthEntry ELSE ComputeWidthEntry[internalFont.charRep[c], fMin]; WriteBlock[@widthEntry, SIZE[UFStrikeFormat.WidthEntry]]; ENDLOOP; widthEntry _ ComputeWidthEntry[internalFont.defaultChar, fMin]; WriteBlock[@widthEntry, SIZE[UFStrikeFormat.WidthEntry]]; }; file.Close; }; WriteStrike: PUBLIC PROC [internalFont: InternalFont, fileName: Rope.ROPE] ~ TRUSTED { file: IO.STREAM _ FS.StreamOpen[fileName, $create]; WriteBlock: UNSAFE PROC [source: LONG POINTER, words: CARDINAL] ~ UNCHECKED { file.UnsafePutBlock[[source, 0, words*Basics.bytesPerWord]]; }; header: UFStrikeFormat.Header; strike: ImagerPixelMaps.PixelMap; body: UFStrikeFormat.Body; min, max: CHAR; sMin, fMin, sMax, fMax: INTEGER; maxwidth, strikeWidth: CARDINAL; [min, max, sMin, fMin, sMax, fMax, maxwidth, strikeWidth, ----] _ ComputeFontMetrics[internalFont]; IF max>=min THEN { ascent: NAT _ -sMin; descent: NAT _ sMax; bodyStart: INT; strike _ ImagerPixelMaps.Create[0, [-ascent, 0, ascent+descent, strikeWidth]]; strike.Clear; strikeWidth _ 0; FOR c: CHAR IN [min..max] DO IF internalFont.charRep[c] # internalFont.defaultChar THEN { strike.Transfer[internalFont.charRep[c].pixels.ShiftMap[0, strikeWidth]]; strikeWidth _ strikeWidth + Real.RoundLI[internalFont.charRep[c].fWidth]; }; ENDLOOP; strike.Transfer[internalFont.defaultChar.pixels.ShiftMap[0, strikeWidth]]; header.format _ [oneBit: T, index: F, fixed: F, kerned: F, unused: 0]; header.min _ min; header.max _ max; header.maxwidth _ maxwidth; body.length _ strike.refRep.words+SIZE[UFStrikeFormat.Body]+(max-min+1+2)*SIZE[CARDINAL]; body.ascent _ ascent; body.descent _ descent; body.xoffset _ 0; body.raster _ strike.refRep.rast; WriteBlock[@header, SIZE[UFStrikeFormat.Header]]; bodyStart _ file.GetIndex; WriteBlock[@body, SIZE[UFStrikeFormat.Body]]; WriteBlock[strike.refRep.pointer, strike.refRep.words]; strikeWidth _ 0; WriteBlock[@strikeWidth, SIZE[CARDINAL]]; FOR c: CHAR IN [min..max] DO IF internalFont.charRep[c] # internalFont.defaultChar THEN { strikeWidth _ strikeWidth + Real.RoundLI[internalFont.charRep[c].fWidth]; }; WriteBlock[@strikeWidth, SIZE[CARDINAL]]; ENDLOOP; strikeWidth _ strikeWidth + Real.RoundLI[internalFont.defaultChar.fWidth]; WriteBlock[@strikeWidth, SIZE[CARDINAL]]; IF file.GetIndex-bodyStart # body.length*Basics.bytesPerWord THEN ERROR; }; file.Close; }; WriteNameEntry: PROC [file: IO.STREAM, code: UFPressFontFormat.FontCode, name: Rope.ROPE] ~ TRUSTED { WriteBlock: UNSAFE PROC [source: LONG POINTER, words: CARDINAL] ~ UNCHECKED { file.UnsafePutBlock[[source, 0, words*Basics.bytesPerWord]]; }; shortenedName: Rope.ROPE _ name.Substr[len: 19]; length: NAT _ shortenedName.Length; hdr: UFPressFontFormat.IndexHeader _ [type: name, length: 12]; WriteBlock[@hdr, SIZE[UFPressFontFormat.IndexHeader]]; file.PutChar['\000]; file.PutChar['\000+code]; file.PutChar['\000+length]; file.PutRope[shortenedName]; WHILE length < 19 DO file.PutChar['\000]; length _ length + 1 ENDLOOP; }; WriteAC: PUBLIC PROC [internalFont: InternalFont, fileName: Rope.ROPE, bitsPerInch: REAL _ 384.0] ~ TRUSTED { micaSize: REAL _ internalFont.bitsPerEmQuad/bitsPerInch*2540.0; bitsPerInchX: REAL _ bitsPerInch; bitsPerInchY: REAL _ bitsPerInch; file: IO.STREAM _ FS.StreamOpen[fileName, $create]; WriteBlock: UNSAFE PROC [source: LONG POINTER, words: INT] ~ UNCHECKED { file.UnsafePutBlock[[source, 0, words*Basics.bytesPerWord]]; }; CurWordOffset: PROC RETURNS [wordOffset: INT] ~ CHECKED { wordOffset _ file.GetIndex/Basics.bytesPerWord; }; min, max: CHAR; endIndexEntry: UFPressFontFormat.IndexHeader _ [type: end, length: SIZE[UFPressFontFormat.IndexHeader]]; charsIndexEntryByteLoc: INT; charsIndexEntry: UFPressFontFormat.RawIndex.chars; pixelMap: ImagerPixelMaps.PixelMap _ ImagerPixelMaps.Create[0, [0,0,16,16]]; pixelMap.Clear; [min, max, ----, ----, ----, ----, ----, ----, ----] _ ComputeFontMetrics[internalFont]; charsIndexEntry _ [ hdr: [type: chars, length: SIZE[UFPressFontFormat.RawIndex.chars]], variantPart: chars[ family: 0, face: internalFont.face, bc: min, ec: max, size: Real.RoundLI[micaSize], rotation: 0, startaddress: [0,0], length: [0,0], resolutionx: Real.RoundLI[bitsPerInchX*10], resolutiony: Real.RoundLI[bitsPerInchY*10] ] ]; WriteNameEntry[file, 0, internalFont.family]; charsIndexEntryByteLoc _ file.GetIndex; IF max>=min THEN { WriteBlock[@charsIndexEntry, charsIndexEntry.hdr.length]; }; WriteBlock[@endIndexEntry, SIZE[UFPressFontFormat.IndexHeader]]; IF max>=min THEN { startaddress: INT _ CurWordOffset[]; directoryStart, filePos: INT; missingCharData: UFPressFontFormat.BoundingBox _ [xwidth: [0, 0], ywidth: [0, 0], BBox: 0, BBoy: 0, BBdx: 0, BBdy: LAST[CARDINAL]]; missingCharDirectoryMarker: UFPressFontFormat.bcplLONGCARDINAL _ [LAST[CARDINAL], LAST[CARDINAL]]; FOR c: CHAR IN [min..max] DO IF internalFont.charRep[c] = internalFont.defaultChar THEN { WriteBlock[@missingCharData, SIZE[UFPressFontFormat.BoundingBox]]; } ELSE { charRep: InternalCharRep ~ internalFont.charRep[c]; window: ImagerPixelMaps.DeviceRectangle _ charRep.pixels.Window; xwidth: UFPressFontFormat.Fraction ~ Real.RoundLI[charRep.fWidth * 65536]; ywidth: UFPressFontFormat.Fraction ~ Real.RoundLI[-charRep.sWidth * 65536]; charData: UFPressFontFormat.BoundingBox _ [ xwidth: UFPressFontFormat.MesaToBcplFraction[xwidth], ywidth: UFPressFontFormat.MesaToBcplFraction[ywidth], BBox: window.fMin, BBoy: -window.sSize-window.sMin, BBdx: window.fSize, BBdy: window.sSize ]; WriteBlock[@charData, SIZE[UFPressFontFormat.BoundingBox]]; }; ENDLOOP; directoryStart _ CurWordOffset[]; filePos _ directoryStart + (max-min+1)*SIZE[UFPressFontFormat.bcplLONGCARDINAL]; FOR c: CHAR IN [min..max] DO charRep: InternalCharRep ~ internalFont.charRep[c]; IF charRep = internalFont.defaultChar THEN { WriteBlock[@missingCharDirectoryMarker, SIZE[UFPressFontFormat.bcplLONGCARDINAL]]; } ELSE { relFilePos: UFPressFontFormat.bcplLONGCARDINAL _ UFPressFontFormat.MesaToBcplLongCardinal[filePos-directoryStart]; bbdx: NAT ~ charRep.pixels.fSize; bbdy: NAT ~ charRep.pixels.sSize; bbdyW: NAT ~ (bbdy+Basics.bitsPerWord-1)/Basics.bitsPerWord; WriteBlock[@relFilePos, SIZE[UFPressFontFormat.bcplLONGCARDINAL]]; filePos _ filePos + 1 + INT[bbdyW]*bbdx; }; ENDLOOP; IF CurWordOffset[] # directoryStart + (max-min+1)*SIZE[UFPressFontFormat.bcplLONGCARDINAL] THEN ERROR; FOR c: CHAR IN [min..max] DO charRep: InternalCharRep ~ internalFont.charRep[c]; IF charRep # internalFont.defaultChar THEN { bbdx: NAT ~ charRep.pixels.fSize; bbdy: NAT ~ charRep.pixels.sSize; bbdyW: NAT ~ (bbdy+Basics.bitsPerWord-1)/Basics.bitsPerWord; rasterDef: MACHINE DEPENDENT RECORD [bbdyW: [0..64), bbdy: [0..1024)] _ [bbdyW, bbdx]; WriteBlock[@rasterDef, 1]; pixelMap _ ImagerPixelMaps.Rotate[charRep.pixels, pixelMap.refRep]; IF pixelMap.refRep.lines # bbdx THEN ERROR; IF pixelMap.refRep.rast # bbdyW THEN ERROR; WriteBlock[pixelMap.refRep.pointer, INT[bbdyW]*bbdx]; }; ENDLOOP; IF filePos # CurWordOffset[] THEN ERROR; charsIndexEntry.startaddress _ LOOPHOLE[UFPressFontFormat.MesaToBcplLongCardinal[startaddress]]; charsIndexEntry.length _ UFPressFontFormat.MesaToBcplLongCardinal[CurWordOffset[] - startaddress]; file.SetIndex[charsIndexEntryByteLoc]; WriteBlock[@charsIndexEntry, charsIndexEntry.hdr.length]; }; file.Close; }; Trim: PUBLIC PROC [internalFont: InternalFont] ~ { oldDefault: InternalCharRep _ internalFont.defaultChar; internalFont.defaultChar.pixels _ internalFont.defaultChar.pixels.Trim[0]; FOR c: CHAR IN CHAR DO IF internalFont.charRep[c] = oldDefault THEN internalFont.charRep[c] _ internalFont.defaultChar ELSE internalFont.charRep[c].pixels _ internalFont.charRep[c].pixels.Trim[0]; ENDLOOP; }; END.