<> <> DIRECTORY ImagerPixelMaps, Rope, RasterFontWriter, IO, FileIO, StrikeFormat, Environment; RasterFontWriterImpl: CEDAR PROGRAM IMPORTS ImagerPixelMaps, IO, FileIO 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.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; }; Load: PUBLIC PROC [fileName: Rope.ROPE] RETURNS [internalFont: InternalFont] ~ TRUSTED { file: IO.STREAM _ FileIO.Open[fileName]; ReadBlock: UNSAFE PROC [dest: LONG POINTER, words: CARDINAL, wordOffset: INT _ -1] ~ UNCHECKED { IF wordOffset >= 0 THEN file.SetIndex[wordOffset*Environment.bytesPerWord]; [] _ file.UnsafeGetBlock[[dest, 0, words*Environment.bytesPerWord]]; }; CurWordOffset: PROC RETURNS [wordOffset: INT] ~ CHECKED { wordOffset _ file.GetIndex/Environment.bytesPerWord; }; strike: ImagerPixelMaps.PixelMap; header: StrikeFormat.Header; internalFont _ NEW[InternalFontRep]; ReadBlock[@header, SIZE[StrikeFormat.Header], 0]; 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: StrikeFormat.BoundingBox; body: StrikeFormat.Body; xInSegment, prevXInSegment: CARDINAL; xInSegmentOffset: INT; widthEntryOffset: INT; widthEntry: StrikeFormat.WidthEntry; bodyOffset: INT; ReadBlock[@boundingBox, SIZE[StrikeFormat.BoundingBox]]; bodyOffset _ CurWordOffset[]; ReadBlock[@body, SIZE[StrikeFormat.Body]]; strike _ ImagerPixelMaps.Create[0, [-body.ascent, 0, body.ascent+body.descent, body.raster*Environment.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[StrikeFormat.WidthEntry], widthEntryOffset]; widthEntryOffset _ widthEntryOffset + SIZE[StrikeFormat.WidthEntry]; IF widthEntry # StrikeFormat.nullWidthEntry THEN { IF widthEntry.width > header.maxwidth THEN ERROR FormatError[(widthEntryOffset-SIZE[StrikeFormat.WidthEntry])*Environment.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[StrikeFormat.WidthEntry], widthEntryOffset]; widthEntryOffset _ widthEntryOffset + SIZE[StrikeFormat.WidthEntry]; IF widthEntry # StrikeFormat.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: StrikeFormat.Body; xInSegment, prevXInSegment: CARDINAL; ReadBlock[@body, SIZE[StrikeFormat.Body]]; strike _ ImagerPixelMaps.Create[0, [-body.ascent, 0, body.ascent+body.descent, body.raster*Environment.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; }; widthLimit: INT _ LAST[CARDINAL]; 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; sMin _ MIN[sMin, bb.sMin]; fMin _ MIN[fMin, bb.fMin]; sMax _ MAX[sMax, bb.sMin+bb.sSize]; fMax _ MAX[fMax, bb.fMin+bb.fSize]; totalWidth _ totalWidth + charRep.fWidth; fSizeStrike _ fSizeStrike + bb.fSize; IF charRep.fWidth > widthLimit THEN ERROR; maxWidth _ MAX[charRep.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 _ LAST[INTEGER]; sMax _ fMax _ FIRST[INTEGER]; FOR char: CHAR IN [bc..ec] DO IF internalFont.charRep[char] # internalFont.defaultChar THEN ProcessChar[internalFont.charRep[char]]; ENDLOOP; ProcessChar[internalFont.defaultChar]; }; WriteKernedStrike: PUBLIC PROC [internalFont: InternalFont, fileName: Rope.ROPE] ~ TRUSTED { file: IO.STREAM _ FileIO.Open[fileName, overwrite]; WriteBlock: UNSAFE PROC [source: LONG POINTER, words: CARDINAL] ~ UNCHECKED { file.UnsafePutBlock[[source, 0, words*Environment.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: StrikeFormat.Header; boundingBox: StrikeFormat.BoundingBox _ [fbbox: fMin, fbboy: -sMax, fbbdx: fMax-fMin, fbbdy: sMax-sMin]; body: StrikeFormat.Body; strike: ImagerPixelMaps.PixelMap; widthEntry: StrikeFormat.WidthEntry; f: INTEGER; strike _ ImagerPixelMaps.Create[0, [-ascent, 0, ascent+descent, strikeWidth]]; 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]; f _ f + pixels.fSize; }; ENDLOOP; strike.Transfer[internalFont.defaultChar.pixels.ShiftMap[0, f]]; 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[StrikeFormat.Body]+(max-min+3)*SIZE[CARDINAL]; body.ascent _ ascent; body.descent _ descent; body.xoffset _ 0; body.raster _ strike.refRep.rast; WriteBlock[@header, SIZE[StrikeFormat.Header]]; WriteBlock[@boundingBox, SIZE[StrikeFormat.BoundingBox]]; bodyStart _ file.GetIndex; WriteBlock[@body, SIZE[StrikeFormat.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 + internalFont.charRep[c].pixels.fSize; }; WriteBlock[@strikeWidth, SIZE[CARDINAL]]; ENDLOOP; strikeWidth _ strikeWidth + internalFont.defaultChar.pixels.fSize; WriteBlock[@strikeWidth, SIZE[CARDINAL]]; IF file.GetIndex-bodyStart # body.length*Environment.bytesPerWord THEN ERROR; FOR c: CHAR IN [min..max] DO IF internalFont.charRep[c] = internalFont.defaultChar THEN { widthEntry _ StrikeFormat.nullWidthEntry } ELSE { widthEntry.offset _ internalFont.charRep[c].pixels.Window.fMin-fMin; widthEntry.width _ internalFont.charRep[c].fWidth; }; WriteBlock[@widthEntry, SIZE[StrikeFormat.WidthEntry]]; ENDLOOP; widthEntry.offset _ internalFont.defaultChar.pixels.Window.fMin-fMin; widthEntry.width _ internalFont.defaultChar.fWidth; WriteBlock[@widthEntry, SIZE[StrikeFormat.WidthEntry]]; }; file.Close; }; WriteStrike: PUBLIC PROC [internalFont: InternalFont, fileName: Rope.ROPE] ~ TRUSTED { file: IO.STREAM _ FileIO.Open[fileName, overwrite]; WriteBlock: UNSAFE PROC [source: LONG POINTER, words: CARDINAL] ~ UNCHECKED { file.UnsafePutBlock[[source, 0, words*Environment.bytesPerWord]]; }; header: StrikeFormat.Header; strike: ImagerPixelMaps.PixelMap; body: StrikeFormat.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]]; 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 + 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[StrikeFormat.Body]+(max-min+1+2)*SIZE[CARDINAL]; body.ascent _ ascent; body.descent _ descent; body.xoffset _ 0; body.raster _ strike.refRep.rast; WriteBlock[@header, SIZE[StrikeFormat.Header]]; bodyStart _ file.GetIndex; WriteBlock[@body, SIZE[StrikeFormat.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 + internalFont.charRep[c].fWidth; }; WriteBlock[@strikeWidth, SIZE[CARDINAL]]; ENDLOOP; strikeWidth _ strikeWidth + internalFont.defaultChar.fWidth; WriteBlock[@strikeWidth, SIZE[CARDINAL]]; IF file.GetIndex-bodyStart # body.length*Environment.bytesPerWord THEN ERROR; }; 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.