DIRECTORY BitmapEdit USING [CreateBitmapViewer, GetBitmap, SetBitmap], Commander USING [CommandProc, Handle, Register], Convert USING [AppendInt, AppendReal, AppendRope, Error, IntFromRope, RealFromRope], FontEdit USING [CharRange], FS USING [ComponentPositions, Error, ExpandName], Imager USING [black, MaskRectangle, ClipRectangleI, Context, DoSave, DoSaveAll, MaskBits, MaskBox, SetColor, SetXY, SetXYRel, Trans, VEC, white], ImagerBackdoor USING [GetCP], ImagerPixelMap USING [Clear, Clip, Create, DeviceRectangle, Fill, PixelMap, ShiftMap, Transfer, Trim, Window], IO USING [Backup, CharClass, EndOf, EndOfStream, GetChar, GetIndex, GetTokenRope, int, PutF, PutFR, PutRope, RIS, rope, SetIndex, STREAM], Menus USING [AppendMenuEntry, ClickProc, CreateEntry, CreateMenu, Menu], MessageWindow USING [Append, Blink], Process USING [MsecToTicks, Pause], RasterFontIO USING [ComputeFontMetrics, Create, FormatError, InternalCharRep, InternalFont, InternalFontRep, Load, Trim, WriteAC, WriteKernedStrike, WriteStrike], Real USING [Round, RoundI], Rope USING [Concat, Equal, Fetch, FromRefText, Length, Map, ROPE, Substr], TIPUser USING [InstantiateNewTIPTable, TIPScreenCoords, TIPScreenCoordsRec], ViewerClasses USING [AdjustProc, NotifyProc, PaintProc, SaveProc, Viewer, ViewerClass, ViewerClassRec], ViewerOps USING [CreateViewer, MoveViewer, PaintViewer, RegisterViewerClass, SaveViewer, SetNewVersion], ViewerTools USING [GetSelectedViewer]; FontEditImpl: CEDAR PROGRAM IMPORTS BitmapEdit, Commander, Convert, FS, Imager, ImagerBackdoor, ImagerPixelMap, IO, Menus, MessageWindow, Process, RasterFontIO, Real, Rope, TIPUser, ViewerOps, ViewerTools EXPORTS FontEdit ~ BEGIN ROPE: TYPE ~ Rope.ROPE; InternalFont: TYPE ~ RasterFontIO.InternalFont; Data: TYPE ~ REF DataRep; DataRep: TYPE ~ RECORD [ fileName: ROPE, font: InternalFont, refreshing: BOOLEAN _ FALSE, hasAChar: BOOLEAN _ FALSE, curCharCode: CHAR, savedCharRep: RasterFontIO.InternalCharRep, bitmapViewer: ViewerClasses.Viewer, textBaseline: INTEGER _ 4, textDepth: INTEGER _ 2, text: ROPE, paintProcess: PROCESS _ NIL ]; defaultText: ROPE _ "the quick brown fox jumps over lazy dogs; THE QUICK BROWN FOX JUMPS OVER LAZY DOGS."; allChars: ROPE _ AllChars[]; AllChars: PROC RETURNS [ROPE] ~ { text: REF TEXT _ NEW[TEXT[256]]; FOR i: NAT IN [0..256) DO text[i] _ '\000 + i; ENDLOOP; text.length _ 256; RETURN [Rope.FromRefText[text]] }; DrawBitmapChar: PROC [context: Imager.Context, char: RasterFontIO.InternalCharRep, xMinGarbage: INT] RETURNS [xMaxGood: INT] ~ { inner: PROC ~ { cp: Imager.VEC ~ ImagerBackdoor.GetCP[context, TRUE]; xMaxGood _ MAX[Real.Round[cp.x]+char.pixels.fSize+char.pixels.fMin+char.pixels.fOrigin, xMinGarbage]; Imager.SetColor[context, Imager.white]; Imager.MaskBox[context, [xMinGarbage, -9999, xMaxGood, 9999]]; Imager.SetColor[context, Imager.black]; Imager.Trans[context]; Imager.MaskBits[context: context, base: char.pixels.refRep.pointer, wordsPerLine: char.pixels.refRep.rast, sMin: char.pixels.sMin, fMin: char.pixels.fMin, sSize: char.pixels.sSize, fSize: char.pixels.fSize, tx: char.pixels.fOrigin+char.pixels.fMin, ty: -(char.pixels.sOrigin+char.pixels.sMin)]; Imager.SetXYRel[context, [char.fWidth, -char.sWidth]]; }; Imager.DoSave[context, inner]; }; DrawText: PROC [viewer: ViewerClasses.Viewer, context: Imager.Context] ~ { data: Data _ NARROW[viewer.data]; proc: PROC ~ { xMinGarbage: INT _ 0; Action: PROC [c: CHAR] RETURNS [quit: BOOLEAN _ FALSE] ~ { IF xMinGarbage > viewer.cw THEN RETURN [TRUE]; xMinGarbage _ DrawBitmapChar[context, data.font.charRep[c], xMinGarbage]; }; IF data.font = NIL THEN RETURN; StoreCurrent[viewer]; Imager.SetXY[context, [4, viewer.ch-data.textBaseline]]; Imager.MaskRectangle[context, [0,viewer.ch-data.textBaseline, 10000, 1]]; Imager.ClipRectangleI[context, 0, viewer.ch-data.textBaseline-data.textDepth, viewer.cw, data.textBaseline+data.textDepth]; [] _ data.text.Map[action: Action]; Imager.SetColor[context, Imager.white]; Imager.MaskBox[context, [xMinGarbage, -9999, 9999, 9999]]; }; Imager.DoSaveAll[context, proc]; }; FindSelectedChar: PROC [viewer: ViewerClasses.Viewer, where: TIPUser.TIPScreenCoordsRec] RETURNS [index: INT] ~ { data: Data _ NARROW[viewer.data]; fMouse: INT _ where.mouseX; cpf: INT _ 4; Action: PROC [c: CHAR] RETURNS [quit: BOOLEAN _ FALSE] ~ { charRep: RasterFontIO.InternalCharRep _ data.font.charRep[c]; window: ImagerPixelMap.DeviceRectangle _ charRep.pixels.Window; IF fMouse-cpf IN [window.fMin..window.fMin+window.fSize) THEN RETURN [TRUE]; cpf _ cpf + Real.Round[charRep.fWidth]; index _ index + 1; }; index _ 0; IF NOT data.text.Map[action: Action] THEN index _ -1; }; RefreshProcess: PROC [viewer: ViewerClasses.Viewer] ~ { data: Data _ NARROW[viewer.data]; WHILE data.refreshing AND NOT viewer.destroyed DO IF data.bitmapViewer.newVersion THEN { ViewerOps.PaintViewer[viewer, client, FALSE, $Text]; ViewerOps.SetNewVersion[viewer]; }; Process.Pause[Process.MsecToTicks[300]]; ENDLOOP; }; GetSelection: PROC RETURNS [selectionContents: ROPE] ~ { selectedViewer: ViewerClasses.Viewer _ ViewerTools.GetSelectedViewer[]; IF selectedViewer = NIL OR selectedViewer.class.get = NIL THEN RETURN [NIL]; selectionContents _ NARROW[selectedViewer.class.get[selectedViewer, $SelChars]]; }; ComputeCaption: PROC [viewer: ViewerClasses.Viewer, paint: BOOLEAN _ TRUE] ~ { data: Data _ NARROW[viewer.data]; text: REF TEXT _ NEW[TEXT[200]]; PutRope: PROC [rope: ROPE] ~ {text _ Convert.AppendRope[text, rope, FALSE]}; PutInt: PROC [int: INT] ~ {text _ Convert.AppendInt[text, int]}; PutReal: PROC [real: REAL] ~ {text _ Convert.AppendReal[text, real]}; PutRope[data.fileName]; PutRope[CharEncode[data.curCharCode]]; PutRope[" ("]; PutRope[data.font.family]; PutRope[" Face"]; PutInt[data.font.face]; PutRope[" "]; PutReal[data.font.bitsPerEmQuad]; PutRope["bpm "]; PutReal[data.font.bitsPerInch]; PutRope["bpi)"]; viewer.name _ Rope.FromRefText[text]; IF paint THEN ViewerOps.PaintViewer[viewer, caption]; text _ NIL; }; Create: PUBLIC PROC [fontFileName: ROPE] RETURNS [viewer: ViewerClasses.Viewer] ~ { menu: Menus.Menu _ Menus.CreateMenu[]; data: Data _ NEW[DataRep]; Menus.AppendMenuEntry[ menu: menu, entry: Menus.CreateEntry[ name: "Undo", proc: UndoButton, documentation: "Undo edits to the current character" ] ]; Menus.AppendMenuEntry[ menu: menu, entry: Menus.CreateEntry[ name: "Get", proc: GetButton, documentation: "Get a raster font" ] ]; Menus.AppendMenuEntry[ menu: menu, entry: Menus.CreateEntry[ name: "Store", proc: StoreButton, documentation: "Store a raster font" ] ]; Menus.AppendMenuEntry[ menu: menu, entry: Menus.CreateEntry[ name: "Save", proc: SaveButton, documentation: "Save the raster font" ] ]; Menus.AppendMenuEntry[ menu: menu, entry: Menus.CreateEntry[ name: "Char", proc: CharButton, documentation: "Edit a new character" ] ]; Menus.AppendMenuEntry[ menu: menu, entry: Menus.CreateEntry[ name: "Next/Prev", proc: NextPrevButton, documentation: "Move to the next or previous char" ] ]; Menus.AppendMenuEntry[ menu: menu, entry: Menus.CreateEntry[ name: "Text", proc: TextButton, documentation: "See what some text looks like" ] ]; Menus.AppendMenuEntry[ menu: menu, entry: Menus.CreateEntry[ name: "Family", proc: FamilyButton, documentation: "Set the family" ] ]; Menus.AppendMenuEntry[ menu: menu, entry: Menus.CreateEntry[ name: "Face", proc: FaceButton, documentation: "Set the face code" ] ]; Menus.AppendMenuEntry[ menu: menu, entry: Menus.CreateEntry[ name: "Em", proc: EmButton, documentation: "Set the number of bits per em" ] ]; Menus.AppendMenuEntry[ menu: menu, entry: Menus.CreateEntry[ name: "Res", proc: ResButton, documentation: "Set the number of bits per inch for AC fonts" ] ]; viewer _ ViewerOps.CreateViewer[$FontEdit, [menu: menu, name: "Font Editor", data: data], FALSE]; data.bitmapViewer _ BitmapEdit.CreateBitmapViewer[4, 4, [border: FALSE, parent: viewer, wh: 20, ww: 20]]; Get[viewer, fontFileName]; IF data.font = NIL THEN data.font _ RasterFontIO.Create[[-5, 1, 6, 4], 6]; data.text _ defaultText; ViewerOps.PaintViewer[viewer, all]; }; Get: PROC [viewer: ViewerClasses.Viewer, fontFileName: ROPE] ~ { data: Data _ NARROW[viewer.data]; Complain: PROC [expl: ROPE] ~ { MessageWindow.Append[expl, TRUE]; MessageWindow.Blink[]; }; fontFileName _ FS.ExpandName[fontFileName ! FS.Error => CONTINUE].fullFName; data.font _ RasterFontIO.Load[fontFileName ! RasterFontIO.FormatError => { Complain[IO.PutFR["Font file format error at byte index %g", IO.int[byteIndex]]]; GOTO Bad }; FS.Error => {Complain[error.explanation]; GOTO Bad}; ]; RasterFontIO.Trim[data.font]; data.fileName _ fontFileName; SetChar[viewer, 'A]; EXITS Bad => NULL; }; WriteFormatDerivedFromName: PUBLIC PROC [internalFont: InternalFont, fileName: Rope.ROPE] ~ { ENABLE FS.Error => { MessageWindow.Append[error.explanation, TRUE]; MessageWindow.Blink[]; GOTO Quit; }; firstChar: CHAR _ fileName.Fetch[0]; fontFileType: FontFileType _ FontFileNameType[fileName]; RasterFontIO.Trim[internalFont]; SELECT fontFileType FROM ac => RasterFontIO.WriteAC[internalFont, fileName]; ks => RasterFontIO.WriteKernedStrike[internalFont, fileName]; strike => { FOR c: CHAR IN CHAR DO charRep: RasterFontIO.InternalCharRep _ internalFont.charRep[c]; IF charRep # internalFont.defaultChar THEN { box: ImagerPixelMap.DeviceRectangle _ charRep.pixels.Window; IF box.fMin < 0 THEN { charRep.pixels _ charRep.pixels.ShiftMap[0, -box.fMin]; box _ charRep.pixels.Window; }; IF box.fMin < 0 THEN ERROR; charRep.fWidth _ MAX[charRep.fWidth, box.fMin+box.fSize]; internalFont.charRep[c] _ charRep; }; ENDLOOP; RasterFontIO.WriteStrike[internalFont, fileName]; }; ENDCASE => { MessageWindow.Append["Cannot figure out what file format to use for ", TRUE]; MessageWindow.Append[fileName, FALSE]; MessageWindow.Blink[]; }; EXITS Quit => NULL; }; Save: ViewerClasses.SaveProc ~ { data: Data _ NARROW[self.data]; StoreCurrent[self]; WriteFormatDerivedFromName[data.font, data.fileName]; SetChar[self, data.curCharCode]; }; GetButton: Menus.ClickProc ~ { viewer: ViewerClasses.Viewer _ NARROW[parent]; data: Data _ NARROW[viewer.data]; rope: ROPE _ GetSelection[]; IF rope.Length = 0 THEN { MessageWindow.Append["Please select a font file name.", TRUE]; MessageWindow.Blink[]; } ELSE Get[viewer, rope]; }; FontFileType: TYPE ~ {invalid, unknown, ac, strike, ks}; FontFileNameType: PROC [rope: ROPE] RETURNS [FontFileType] ~ { fullFName: ROPE _ NIL; cp: FS.ComponentPositions; bad: BOOLEAN _ FALSE; extension: ROPE _ NIL; [fullFName, cp] _ FS.ExpandName[rope ! FS.Error => {bad _ TRUE; CONTINUE}]; IF bad THEN RETURN [invalid]; extension _ fullFName.Substr[cp.ext.start, cp.ext.length]; SELECT TRUE FROM extension.Equal["ac", FALSE] => RETURN [ac]; extension.Equal["strike", FALSE] => RETURN [strike]; extension.Equal["ks", FALSE] => RETURN [ks]; ENDCASE => RETURN [unknown]; }; StoreButton: Menus.ClickProc ~ { viewer: ViewerClasses.Viewer _ NARROW[parent]; data: Data _ NARROW[viewer.data]; rope: ROPE _ GetSelection[]; fontFileType: FontFileType _ FontFileNameType[rope]; IF fontFileType = invalid THEN { MessageWindow.Append["Please select a valid font file name.", TRUE]; MessageWindow.Blink[]; } ELSE { data.fileName _ IF fontFileType = unknown THEN rope.Concat[".ks"] ELSE rope; ComputeCaption[viewer]; ViewerOps.SaveViewer[viewer]; }; }; SaveButton: Menus.ClickProc ~ { viewer: ViewerClasses.Viewer _ NARROW[parent]; ViewerOps.SaveViewer[viewer]; }; CharButton: Menus.ClickProc ~ { viewer: ViewerClasses.Viewer _ NARROW[parent]; data: Data _ NARROW[viewer.data]; rope: ROPE _ GetSelection[]; IF rope.Length = 0 THEN { MessageWindow.Append["Please select a character.", TRUE]; MessageWindow.Blink[]; } ELSE { SetChar[viewer, rope.Fetch[0]]; }; }; UndoButton: Menus.ClickProc ~ { viewer: ViewerClasses.Viewer _ NARROW[parent]; data: Data _ NARROW[viewer.data]; charRep: RasterFontIO.InternalCharRep _ data.savedCharRep; badCharRep: RasterFontIO.InternalCharRep; StoreCurrent[viewer]; badCharRep _ data.font.charRep[data.curCharCode]; data.font.charRep[data.curCharCode] _ charRep; SetChar[viewer, data.curCharCode]; data.savedCharRep _ badCharRep; }; NextPrevButton: Menus.ClickProc ~ { viewer: ViewerClasses.Viewer _ NARROW[parent]; data: Data _ NARROW[viewer.data]; newChar: CHAR _ data.curCharCode; IF mouseButton = blue THEN { IF newChar = FIRST[CHAR] THEN newChar _ LAST[CHAR] ELSE newChar _ newChar-1 }; IF mouseButton = red THEN { IF data.curCharCode = LAST[CHAR] THEN newChar _ FIRST[CHAR] ELSE newChar _ newChar+1 }; SetChar[viewer, newChar]; }; TextButton: Menus.ClickProc ~ { viewer: ViewerClasses.Viewer _ NARROW[parent]; data: Data _ NARROW[viewer.data]; IF mouseButton = red THEN data.text _ GetSelection[]; IF mouseButton = yellow THEN data.text _ defaultText; IF mouseButton = blue THEN data.text _ allChars.Substr[MAX[data.curCharCode-'\000-2, 0]]; ViewerOps.PaintViewer[viewer, client, FALSE, $Text]; }; FamilyButton: Menus.ClickProc ~ { viewer: ViewerClasses.Viewer _ NARROW[parent]; data: Data _ NARROW[viewer.data]; data.font.family _ GetSelection[]; ComputeCaption[viewer]; }; FaceButton: Menus.ClickProc ~ { viewer: ViewerClasses.Viewer _ NARROW[parent]; data: Data _ NARROW[viewer.data]; data.font.face _ Convert.IntFromRope[GetSelection[] ! Convert.Error => CONTINUE]; ComputeCaption[viewer]; }; EmButton: Menus.ClickProc ~ { viewer: ViewerClasses.Viewer _ NARROW[parent]; data: Data _ NARROW[viewer.data]; new: REAL _ -1; new _ Convert.IntFromRope[GetSelection[] ! Convert.Error => CONTINUE]; IF new = -1 THEN new _ Convert.RealFromRope[GetSelection[] ! Convert.Error => CONTINUE]; IF new >= 1 THEN data.font.bitsPerEmQuad _ new; ComputeCaption[viewer]; }; ResButton: Menus.ClickProc ~ { viewer: ViewerClasses.Viewer _ NARROW[parent]; data: Data _ NARROW[viewer.data]; new: REAL _ -1; new _ Convert.IntFromRope[GetSelection[] ! Convert.Error => CONTINUE]; IF new = -1 THEN new _ Convert.RealFromRope[GetSelection[] ! Convert.Error => CONTINUE]; IF new > 0 THEN data.font.bitsPerInch _ new; ComputeCaption[viewer]; }; StoreCurrent: PROC [viewer: ViewerClasses.Viewer] ~ { data: Data _ NARROW[viewer.data]; IF data.hasAChar AND data.bitmapViewer.newVersion THEN { charRep: RasterFontIO.InternalCharRep; [sWidth: charRep.sWidth, fWidth: charRep.fWidth, pixelMap: charRep.pixels] _ BitmapEdit.GetBitmap[data.bitmapViewer]; charRep.pixels _ charRep.pixels.Trim[0]; IF charRep.pixels.sSize = 0 OR charRep.pixels.fSize = 0 THEN { charRep.pixels.sOrigin _ 0; charRep.pixels.fOrigin _ 0; charRep.pixels.sSize _ 0; charRep.pixels.fSize _ 0; }; IF Real.RoundI[data.font.charRep[data.curCharCode].sWidth] = charRep.sWidth AND Real.RoundI[data.font.charRep[data.curCharCode].fWidth] = charRep.fWidth THEN data.font.charRep[data.curCharCode].pixels _ charRep.pixels ELSE data.font.charRep[data.curCharCode] _ charRep; }; data.bitmapViewer.newVersion _ FALSE; }; CharEncode: PROC [char: CHAR] RETURNS [rope: ROPE] ~ { text: REF TEXT _ NEW[TEXT[7]]; charVal: NAT _ char - '\000; text[0] _ text[1] _ text[2] _ text[3] _ ' ; IF char IN [' ..'~] THEN { text[1] _ ''; text[2] _ char; }; text[4] _ '0 + charVal/64; text[5] _ '0 + charVal/8 MOD 8; text[6] _ '0 + charVal MOD 8; text.length _ 7; rope _ Rope.FromRefText[text]; }; SetChar: PROC [viewer: ViewerClasses.Viewer, char: CHAR] ~ { data: Data _ NARROW[viewer.data]; window: ImagerPixelMap.DeviceRectangle _ data.font.charRep[char].pixels.Window; sMax: INTEGER _ window.sMin+window.sSize; fMax: INTEGER _ window.fMin+window.fSize; pixelMap: ImagerPixelMap.PixelMap; data.refreshing _ FALSE; IF data.paintProcess # NIL THEN TRUSTED {[] _ JOIN data.paintProcess}; data.paintProcess _ NIL; StoreCurrent[viewer]; window.sMin _ MIN[window.sMin, 0, Real.RoundI[data.font.charRep[char].sWidth], 4-data.textBaseline]-1; window.fMin _ MIN[window.fMin, 0, Real.RoundI[data.font.charRep[char].fWidth]]-1; sMax _ MAX[sMax, 0, Real.RoundI[data.font.charRep[char].sWidth], data.textDepth-2]+1; fMax _ MAX[fMax, 0, Real.RoundI[data.font.charRep[char].fWidth]]+1; window.sSize _ sMax - window.sMin; window.fSize _ fMax - window.fMin; data.curCharCode _ char; data.hasAChar _ TRUE; data.savedCharRep _ data.font.charRep[char]; pixelMap _ ImagerPixelMap.Create[0, window]; pixelMap.Clear; pixelMap.Transfer[data.savedCharRep.pixels]; { x: INTEGER ~ data.bitmapViewer.wx; y: INTEGER ~ data.bitmapViewer.wy; w: INTEGER ~ data.bitmapViewer.ww; h: INTEGER ~ data.bitmapViewer.wh; ViewerOps.MoveViewer[data.bitmapViewer, 2000, 2000, 0, 0, FALSE]; BitmapEdit.SetBitmap[data.bitmapViewer, pixelMap, Real.RoundI[data.savedCharRep.sWidth], Real.RoundI[data.savedCharRep.fWidth]]; ViewerOps.MoveViewer[data.bitmapViewer, x, y, w, h, FALSE]; }; ComputeCaption[viewer, FALSE]; ViewerOps.PaintViewer[viewer, all]; data.refreshing _ TRUE; data.paintProcess _ FORK RefreshProcess[viewer]; }; FontEditPaintProc: ViewerClasses.PaintProc ~ { data: Data _ NARROW[self.data]; IF whatChanged = NIL OR whatChanged = $Text THEN { DrawText[self, context]; }; quit _ whatChanged = $Text; }; FontEditAdjustProc: ViewerClasses.AdjustProc ~ { data: Data _ NARROW[self.data]; IF data # NIL THEN { data.textBaseline _ 4; data.textDepth _ 2; IF data.font # NIL THEN { bc, ec: CHAR; sMin, fMin, sMax, fMax: INTEGER; maxWidth, totalWidth, fSizeStrike: CARDINAL; [bc, ec, sMin, fMin, sMax, fMax, maxWidth, totalWidth, fSizeStrike] _ data.font.ComputeFontMetrics; data.textBaseline _ 4-sMin; data.textDepth _ 2+sMax; WHILE data.textBaseline+data.textDepth > 2 AND INTEGER[self.ch]-(data.textBaseline+data.textDepth) < sMax-sMin DO data.textBaseline _ data.textBaseline - data.textBaseline/2; data.textDepth _ data.textDepth - data.textDepth/2; ENDLOOP; }; IF data.bitmapViewer#NIL THEN { data.bitmapViewer.wx _ data.bitmapViewer.cx _ 0; data.bitmapViewer.wy _ data.bitmapViewer.cy _ 0; data.bitmapViewer.ww _ data.bitmapViewer.cw _ self.cw; data.bitmapViewer.wh _ data.bitmapViewer.ch _ self.ch-(data.textBaseline+data.textDepth); }; adjusted _ TRUE; }; }; FontEditNotifyProc: ViewerClasses.NotifyProc = { IF ISTYPE[input.first, TIPUser.TIPScreenCoords] THEN { data: Data _ NARROW[self.data]; mousePlace: TIPUser.TIPScreenCoords _ NARROW[input.first]; SELECT input.rest.first FROM $SelectChar => { index: INT _ FindSelectedChar[self, mousePlace^]; IF index >= 0 THEN { char: CHAR _ data.text.Fetch[index]; IF char # data.curCharCode THEN { SetChar[self, char]; }; }; }; $CopyChar => { index: INT _ FindSelectedChar[self, mousePlace^]; IF index >= 0 THEN { char: CHAR _ data.text.Fetch[index]; IF char # data.curCharCode THEN { charRep: RasterFontIO.InternalCharRep _ data.font.charRep[char]; BitmapEdit.SetBitmap[data.bitmapViewer, charRep.pixels, Real.RoundI[charRep.sWidth], Real.RoundI[charRep.fWidth]]; data.font.charRep[data.curCharCode] _ charRep; data.bitmapViewer.newVersion _ TRUE; }; }; }; ENDCASE => NULL; }; }; GetToken: PROC [stream: IO.STREAM] RETURNS [token: ROPE _ NIL] = { token _ stream.GetTokenRope[Break ! IO.EndOfStream => CONTINUE].token; }; Break: PROC [char: CHAR] RETURNS [IO.CharClass] = { IF char = '_ OR char = '; THEN RETURN [break]; IF char = ' OR char = ' OR char = ', OR char = '\n THEN RETURN [sepr]; RETURN [other]; }; SpecError: PUBLIC ERROR [offset: INT] ~ CODE; CharRange: TYPE ~ FontEdit.CharRange; defaultChar: CARDINAL ~ 400B; ParseCharSpec: PUBLIC PROC [stream: IO.STREAM] RETURNS [LIST OF CharRange] ~ { i: NAT _ 0; c: CHAR _ IF stream.EndOf THEN '\377 ELSE stream.GetChar; GetChar: PROC ~ {c _ IF stream.EndOf THEN '\377 ELSE stream.GetChar}; SkipSpaces: PROC ~ {WHILE (c = ', OR c = ' OR c = ' OR c = '\n) DO GetChar[] ENDLOOP}; Char: PROC RETURNS [value: CARDINAL_0] ~ { SkipSpaces[]; IF c # '' THEN ERROR SpecError[stream.GetIndex]; GetChar[]; IF c='\\ THEN { GetChar[]; SELECT c FROM IN ['0..'7] => { WHILE c IN ['0..'7] DO value _ value * 8 + (c-'0); GetChar[]; ENDLOOP; }; '\\ => {value _ '\\-'\000; GetChar[]}; ENDCASE => ERROR SpecError[stream.GetIndex]; } ELSE {value _ c-'\000; GetChar[]}; }; spec: LIST OF CharRange _ NIL; SkipSpaces[]; WHILE c # '\377 DO SELECT c FROM '' => {spec _ CONS[[Char[], 1], spec]}; 'D => { savedIndex: INT _ stream.GetIndex; i: INT _ 0; efault: ROPE ~ "EFAULT"; WHILE i < efault.Length DO GetChar[]; IF c = efault.Fetch[i] THEN i _ i + 1 ELSE EXIT; ENDLOOP; IF i = efault.Length THEN {spec _ CONS[[defaultChar, 1], spec]; GetChar[]} ELSE {stream.SetIndex[savedIndex]; EXIT}; }; '[, '( => { open: CHAR _ c; start, end: CARDINAL; GetChar[]; start _ Char[]; SkipSpaces[]; IF c = '. THEN GetChar[] ELSE ERROR SpecError[stream.GetIndex]; IF c = '. THEN GetChar[] ELSE ERROR SpecError[stream.GetIndex]; end _ Char[]; SkipSpaces[]; IF c = '] OR c = ') THEN { IF open = '( THEN start _ start + 1; IF c = '] THEN end _ end + 1; IF end > start THEN spec _ CONS[[start, end-start], spec]; GetChar[]; } ELSE ERROR SpecError[stream.GetIndex]; }; ENDCASE => EXIT; SkipSpaces[]; ENDLOOP; IF c # '\377 THEN stream.Backup[c]; RETURN [Reverse[spec]] }; Reverse: PROC [charSpec: LIST OF CharRange] RETURNS [reversed: LIST OF CharRange] ~ { WHILE charSpec # NIL DO t: LIST OF CharRange _ charSpec; charSpec _ t.rest; t.rest _ reversed; reversed _ t; ENDLOOP; }; SlantChar: PROC [charRep: RasterFontIO.InternalCharRep] RETURNS [RasterFontIO.InternalCharRep] ~ { new: RasterFontIO.InternalCharRep; window: ImagerPixelMap.DeviceRectangle; sMin, fMin, sMax, fMax: INTEGER; charRep.pixels _ ImagerPixelMap.Trim[charRep.pixels, 0]; window _ ImagerPixelMap.Window[charRep.pixels]; sMin _ window.sMin; fMin _ window.fMin; sMax _ sMin + window.sSize; fMax _ fMin + window.fSize; fMax _ fMax + MAX[0, (-sMin+1)/2]; fMin _ fMin - MAX[0, (sMax+1)/2]; new.fWidth _ charRep.fWidth; new.sWidth _ charRep.sWidth; new.pixels _ ImagerPixelMap.Create[0, [sMin, fMin, sMax-sMin, fMax-fMin]]; ImagerPixelMap.Clear[new.pixels]; CHECKED { s: INTEGER _ -3; shift: INTEGER _ 0; WHILE s>sMin DO s _ s - 3; shift _ shift + 1 ENDLOOP; WHILE s {errIndex _ byteIndex; CONTINUE}; FS.Error => CONTINUE ]; IF font = NIL THEN { InputError[cmd.out, outputName, errIndex]; RETURN; }; otherFont _ RasterFontIO.Load[inputName ! RasterFontIO.FormatError => {errIndex _ byteIndex; CONTINUE}; FS.Error => CONTINUE ]; IF otherFont = NIL THEN { InputError[cmd.out, inputName, errIndex]; RETURN; }; FOR c: CHAR IN CHAR DO IF font.charRep[c] # font.defaultChar AND otherFont.charRep[c] # otherFont.defaultChar THEN { font.charRep[c].sWidth _ otherFont.charRep[c].sWidth; font.charRep[c].fWidth _ otherFont.charRep[c].fWidth }; ENDLOOP; WriteFormatDerivedFromName[font, outputName]; cmd.out.PutRope[outputName]; cmd.out.PutRope[" written."]; }; InputError: PROC [out: IO.STREAM, inputName: ROPE, errIndex: INT] ~ { IF errIndex >= 0 THEN { out.PutF["Format error in %g at byte index %g", IO.rope[inputName], IO.int[errIndex]]; } ELSE { out.PutRope[inputName]; out.PutRope[" not found"]; }; RETURN; }; ModifyFont: PROC [cmd: Commander.Handle, fontFunction: PROC [InternalFont] RETURNS [InternalFont]] ~ { stream: IO.STREAM _ IO.RIS[cmd.commandLine]; outputName: ROPE _ GetToken[stream]; fontFileType: FontFileType _ FontFileNameType[outputName]; gets: ROPE _ GetToken[stream]; inputName: ROPE _ GetToken[stream]; font: InternalFont; errIndex: INT _ -1; IF NOT gets.Equal["_"] THEN { cmd.out.PutRope["Specify output _ input, please"]; RETURN; }; IF fontFileType = invalid OR fontFileType = unknown THEN { cmd.out.PutRope[outputName]; cmd.out.PutRope[": invalid font file name"]; IF fontFileType # invalid THEN cmd.out.PutRope[" (must have .ac, .ks or .strike extension)"]; RETURN; }; font _ RasterFontIO.Load[inputName ! RasterFontIO.FormatError => {errIndex _ byteIndex; CONTINUE}; FS.Error => CONTINUE ]; IF font = NIL THEN { InputError[cmd.out, inputName, errIndex]; RETURN; }; font _ fontFunction[font]; RasterFontIO.Trim[font]; WriteFormatDerivedFromName[font, outputName]; cmd.out.PutRope[outputName]; cmd.out.PutRope[" written.\n"]; }; FontMergeCommand: Commander.CommandProc ~ { stream: IO.STREAM _ IO.RIS[cmd.commandLine]; outputName: ROPE _ GetToken[stream]; fontFileType: FontFileType _ FontFileNameType[outputName]; token: ROPE _ GetToken[stream]; font: InternalFont _ RasterFontIO.Create[[-1,0,1,1],1]; IF NOT token.Equal["_"] THEN { cmd.out.PutRope["Specify output _ input, please"]; RETURN; }; IF fontFileType = invalid OR fontFileType = unknown OR fontFileType = ac THEN { cmd.out.PutRope[outputName]; cmd.out.PutRope[": invalid font file name"]; IF fontFileType # invalid THEN cmd.out.PutRope[" (must have .ks or .strike extension)"]; RETURN; }; token _ GetToken[stream]; WHILE token # NIL DO inputName: ROPE; inputFont: InternalFont; badSpecIndex: INT _ -1; errIndex: INT _ -1; WHILE token.Equal[";"] OR token.Equal["FONT"] DO token _ GetToken[stream]; ENDLOOP; IF token = NIL THEN EXIT; inputName _ token; inputFont _ RasterFontIO.Load[inputName ! RasterFontIO.FormatError => {errIndex _ byteIndex; CONTINUE}; FS.Error => CONTINUE ]; IF inputFont = NIL THEN { InputError[cmd.out, inputName, errIndex]; RETURN; }; token _ GetToken[stream]; IF token.Equal["CHAR"] OR token.Equal["CHARS"] THEN { charSpec: LIST OF CharRange; charSpec _ ParseCharSpec[stream ! SpecError => {badSpecIndex _ offset; CONTINUE}]; IF badSpecIndex # -1 THEN { cmd.out.PutRope["Bad specification: "]; cmd.out.PutRope[cmd.commandLine.Substr[0, badSpecIndex]]; cmd.out.PutRope[" *^* "]; cmd.out.PutRope[cmd.commandLine.Substr[badSpecIndex]]; RETURN; }; FOR cr: LIST OF CharRange _ charSpec, cr.rest UNTIL cr = NIL DO IF cr.first.startChar = defaultChar THEN { FOR c: CHAR IN CHAR DO IF font.charRep[c] = font.defaultChar THEN font.charRep[c] _ inputFont.defaultChar; ENDLOOP; font.defaultChar _ inputFont.defaultChar; } ELSE { FOR c: CHAR IN ['\000+cr.first.startChar..'\000+cr.first.startChar+cr.first.nChars) DO font.charRep[c] _ inputFont.charRep[c]; ENDLOOP; }; ENDLOOP; token _ GetToken[stream]; } ELSE {font _ inputFont}; ENDLOOP; WriteFormatDerivedFromName[font, outputName]; }; FontEditCommand: Commander.CommandProc ~ { stream: IO.STREAM _ IO.RIS[cmd.commandLine]; name: ROPE _ GetToken[stream]; IF name.Length = 0 THEN cmd.out.PutRope["Please supply a font file name (.ks or .strike or .ac)\n"] ELSE { [] _ Create[name]; }; }; fontEditClass: ViewerClasses.ViewerClass _ NEW[ViewerClasses.ViewerClassRec _ [ paint: FontEditPaintProc, adjust: FontEditAdjustProc, notify: FontEditNotifyProc, save: Save, tipTable: TIPUser.InstantiateNewTIPTable["FontEdit.TIP"] ]]; ViewerOps.RegisterViewerClass[$FontEdit, fontEditClass]; Commander.Register["FontEdit", FontEditCommand, "Edit the named strike or kerned-strike font."]; Commander.Register["FontMerge", FontMergeCommand, "Merge font files, e.g. new.ks _ one.strike CHARS ['a..'z]; two.strike CHARS ['0..'1], ['A..'Z]; three.strike CHAR '?; four.ks CHAR DEFAULT;"]; Commander.Register["FontSlant", FontSlantCommand, "Make a slanted font (output _ input)"]; Commander.Register["FontBold", FontBoldCommand, "Make a bold font (output _ input)"]; Commander.Register["FontComplement", FontComplementCommand, "Make a complemented (white chars on black backing) font (output _ input)"]; Commander.Register["FontWidthsCopy", FontWidthsCopyCommand, "Copy widths from another font (output _ input)"]; Commander.Register["FontTranslate", FontTranslateCommand, "Translate each character in the font by the amount (FontEditImpl.deltaX, FontEditImpl.deltaY) (output _ input)"]; END. °FontEditImpl.mesa Copyright (C) 1984, Xerox Corporation. All rights reserved. Michael Plass, May 20, 1985 5:06:52 pm PDT Neil Gunther, October 24, 1985 12:46:45 pm PDT Κ$Ζ˜™J™˜>Kšœ'˜'K•StartOfExpansion’[context: Imager.Context, base: LONG POINTER, wordsPerLine: NAT, sMin: NAT, fMin: NAT, sSize: NAT, fSize: NAT, tx: INTEGER _ 0, ty: INTEGER _ 0]šœ˜Kšœ¦˜¦Kšœ6˜6Kšœ˜—Kšœ˜Kšœ˜K˜—šŸœœ<˜JKšœ œ˜!šœœ˜Kšœ œ˜š Ÿœœœœœœ˜:Kšœœœœ˜.KšœI˜IKšœ˜—Kšœ œœœ˜Kšœ˜Kšœ8˜8KšœI˜IKšœ{˜{Kšœ#˜#Kšœ'˜'Kšœ:˜:Kšœ˜—Kšœ ˜ Kšœ˜K˜—šŸœœCœ œ˜qKšœ œ˜!Kšœœ˜Kšœœ˜ š Ÿœœœœœœ˜:Kšœ=˜=Kšœ?˜?Kš œ œ)œœœ˜LKšœ'˜'Kšœ˜Kšœ˜—Kšœ ˜ Kšœœœ ˜5Kšœ˜K˜—šŸœœ#˜7Kšœ œ˜!šœœœ˜1šœœ˜&Kšœ&œ ˜4Kšœ ˜ Kšœ˜—K˜(Kšœ˜—Kšœ˜K˜—šŸ œœœœ˜8K˜GKšœœœœœœœ˜LKšœœ6˜PKšœ˜K˜—šŸœœ'œœ˜NKšœ œ˜!Kš œœœœœ˜ KšŸœœœ+œ˜LKšŸœœœ*˜@KšŸœœœ,˜EKšœ˜Kšœ&˜&Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ ˜ Kšœ!˜!Kšœ˜Kšœ˜Kšœ˜Kšœ%˜%Kšœœ(˜5Kšœœ˜ Kšœ˜K˜—š Ÿœœœœœ#˜SKšœ&˜&Kšœ œ ˜šœ˜Kšœ ˜ ˜K˜ K˜K˜4K˜—K˜—šœ˜Kšœ ˜ ˜K˜ K˜K˜"K˜—K˜—šœ˜Kšœ ˜ ˜K˜K˜K˜$K˜—K˜—šœ˜Kšœ ˜ ˜K˜ K˜K˜%K˜—K˜—šœ˜Kšœ ˜ ˜K˜ K˜K˜%K˜—K˜—šœ˜Kšœ ˜ ˜K˜K˜K˜2K˜—K˜—šœ˜Kšœ ˜ ˜K˜ K˜K˜.K˜—K˜—šœ˜Kšœ ˜ ˜K˜K˜K˜K˜—K˜—šœ˜Kšœ ˜ ˜K˜ K˜K˜"K˜—K˜—šœ˜Kšœ ˜ ˜K˜ K˜K˜.K˜—K˜—šœ˜Kšœ ˜ ˜K˜ K˜K˜=K˜—K˜—KšœZœ˜aKšœAœ#˜iKšœ˜Kšœ œœ3˜JK˜Kšœ#˜#Kšœ˜K˜—šŸœœ.œ˜@Kšœ œ˜!šŸœœœ˜Kšœœ˜!Kšœ˜Kšœ˜—Kšœœœ œ ˜Lšœ,˜,šœ˜Kšœ œ2œ˜QKšœ˜Kšœ˜—Kšœ(œ˜4K˜—Kšœ˜Kšœ˜K˜Kšœœ˜Kšœ˜K˜—šŸœœœ-œ˜]šœœ ˜Kšœ(œ˜.Kšœ˜Kšœ˜ K˜—Kšœ œ˜$Kšœ8˜8Kšœ ˜ šœ˜Kšœ3˜3Kšœ=˜=šœ ˜ š œœœœ˜Kšœ@˜@šœ$œ˜,Kšœ<˜<šœœ˜Kšœ7˜7Kšœ˜Kšœ˜—Kšœœœ˜Kšœœ%˜9Kšœ"˜"Kšœ˜—Kšœ˜—Kšœ1˜1Kšœ˜—šœ˜ KšœGœ˜MKšœœ˜&Kšœ˜Kšœ˜——Kšœ œ˜Kšœ˜K˜—šŸœ˜ Kšœ œ ˜Kšœ˜Kšœ5˜5Kšœ ˜ Kšœ˜K˜—šŸ œ˜Kšœœ ˜.Kšœ œ˜!Kšœœ˜šœœ˜Kšœ8œ˜>Kšœ˜Kšœ˜—Kšœ˜Kšœ˜K˜—Kšœœ&˜8šŸœœœœ˜>Kšœ œœ˜K–x -- [server: FS.Position, dir: FS.Position, subDirs: FS.Position, base: FS.Position, ext: FS.Position, ver: FS.Position]šœœ˜Kšœœœ˜Kšœ œœ˜Kš œœœœœ˜KKšœœœ ˜Kšœ:˜:šœœ˜Kšœœœ˜,Kšœœœ ˜4Kšœœœ˜,Kšœœ ˜—Kšœ˜K˜—šŸ œ˜ Kšœœ ˜.Kšœ œ˜!Kšœœ˜Kšœ4˜4šœœ˜ Kšœ>œ˜DKšœ˜Kšœ˜—šœ˜Kšœœœœ˜LKšœ˜Kšœ˜Kšœ˜—Kšœ˜K˜—šŸ œ˜Kšœœ ˜.Kšœ˜Kšœ˜K˜—šŸ œ˜Kšœœ ˜.Kšœ œ˜!Kšœœ˜šœœ˜Kšœ3œ˜9Kšœ˜Kšœ˜—šœ˜Kšœ˜Kšœ˜—Kšœ˜K˜—šŸ œ˜Kšœœ ˜.Kšœ œ˜!Kšœ:˜:Kšœ)˜)Kšœ˜Kšœ1˜1Kšœ.˜.Kšœ"˜"Kšœ˜Kšœ˜K˜—šŸœ˜#Kšœœ ˜.Kšœ œ˜!Kšœ œ˜!šœœ˜Kšœ œœœœœœ˜KKšœ˜—šœœ˜Kšœœœœ œœœ˜TKšœ˜—Kšœ˜Kšœ˜K˜—šŸ œ˜Kšœœ ˜.Kšœ œ˜!Kšœœ˜5Kšœœ˜5Kšœœœ˜YKšœ&œ ˜4Kšœ˜K˜—šŸ œ˜!Kšœœ ˜.Kšœ œ˜!Kšœ"˜"Kšœ˜Kšœ˜K˜—šŸ œ˜Kšœœ ˜.Kšœ œ˜!KšœGœ˜QKšœ˜Kšœ˜K˜—šŸœ˜Kšœœ ˜.Kšœ œ˜!Kšœœ˜Kšœ<œ˜FKšœ œ>œ˜XKšœ œ˜/Kšœ˜Kšœ˜K˜—šŸ œ˜Kšœœ ˜.Kšœ œ˜!Kšœœ˜Kšœ<œ˜FKšœ œ>œ˜XKšœ œ˜,Kšœ˜Kšœ˜K˜—šŸ œœ#˜5Kšœ œ˜!šœœœ˜8Kšœ&˜&Kšœu˜uKšœ(˜(šœœœ˜>Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜—šœJœJ˜Kšœ;˜;—Kšœ/˜3Kšœ˜—Kšœœ˜%Kšœ˜K˜—š Ÿ œœœœœ˜6Kš œœœœœ˜Kšœ œ˜Kšœ+˜+šœœ œ˜Kšœ ˜ Kšœ˜Kšœ˜—Kšœ˜Kšœœ˜Kšœœ˜Kšœ˜Kšœ˜Kšœ˜K˜—šŸœœ&œ˜K˜7Kšœ˜ Kšœ˜K˜—šŸœœœœ˜RKšœœ(˜?Kšœ3˜3š œœœœ˜Kšœ$œ!˜KKšœ2˜6Kšœ˜—Kšœ˜ Kšœ˜K˜K˜—Kšœœ˜Kšœœ˜šŸœœœ˜HKšœœ(˜?KšœG˜GKšœG˜Gš œœœœ˜KšœE˜EKšœE˜EKšœ˜—Kšœ˜ Kšœ˜K˜—šŸœ˜+Kšœ!˜!Kšœ˜K˜—šŸœ˜*Kšœ˜Kšœ˜K˜—šŸœ˜0Kšœ'˜'Kšœ˜K˜—šŸœ˜/Kšœ$˜$Kšœ˜K˜—šŸœ˜0Kš œœœœœ˜,Kšœ œ˜$Kšœ:˜:Kšœœ˜Kšœ œ˜#Kšœ˜Kšœ˜Kšœ œ˜šœœœ˜Kšœ2˜2Kšœ˜Kšœ˜—šœœœ˜:Kšœ˜Kšœ,˜,Kšœœ?˜]Kšœ˜Kšœ˜—šœ%˜%Kšœ3œ˜=Kšœ ˜Kšœ˜—šœœœ˜Kšœ*˜*Kš˜Kšœ˜—šœ)˜)Kšœ3œ˜=Kšœ ˜Kšœ˜—šœ œœ˜Kšœ)˜)Kš˜Kšœ˜—š œœœœ˜šœ$œ œ œ˜]Kšœ5˜5Kšœ4˜4Kšœ˜—Kšœ˜—Kšœ-˜-Kšœ˜Kšœ˜Kšœ˜K˜—š Ÿ œœœœ œ œ˜Ešœœ˜Kšœ0œœ˜VKšΟi˜—šœ˜Kšœ˜Kšœ˜K˜—Kš˜Kšœ˜K˜—šŸ œœ'œœ˜fKš œœœœœ˜,Kšœ œ˜$Kšœ:˜:Kšœœ˜Kšœ œ˜#Kšœ˜Kšœ œ˜šœœœ˜Kšœ2˜2Kšœ˜Kšœ˜—šœœœ˜:Kšœ˜Kšœ,˜,Kšœœ?˜]Kšœ˜Kšœ˜—šœ$˜$Kšœ3œ˜=Kšœ ˜Kšœ˜—šœœœ˜Kšœ)˜)Kš˜Kšœ˜—Kšœ˜Kšœ˜Kšœ-˜-Kšœ˜Kšœ˜Kšœ˜K˜—šŸœ˜+Kš œœœœœ˜,Kšœ œ˜$Kšœ:˜:Kšœœ˜Kšœ7˜7šœœœ˜Kšœ2˜2Kšœ˜Kšœ˜—šœœœœ˜OKšœ˜Kšœ,˜,Kšœœ:˜XKšœ˜Kšœ˜—Kšœ˜šœ œ˜Kšœ œ˜Kšœ˜Kšœœ˜Kšœ œ˜šœœ˜0Kšœ˜Kšœ˜—Kšœ œœœ˜Kšœ˜šœ)˜)Kšœ3œ˜=Kšœ ˜Kšœ˜—šœ œœ˜Kšœ)˜)Kš˜Kšœ˜—Kšœ˜šœœœ˜5Kšœ œœ ˜KšœGœ˜Ršœœ˜Kšœ'˜'Kšœ9˜9Kšœ˜Kšœ6˜6Kšœ˜Kšœ˜—š œœœœœ˜?šœ"œ˜*š œœœœ˜Kšœ$œ)˜SKšœ˜—Kšœ)˜)Kšœ˜—šœ˜šœœœF˜VKšœ'˜'Kšœ˜K˜——Kšœ˜—Kšœ˜Kšœ˜—Kšœ˜Kšœ˜—Kšœ-˜-Kšœ˜K˜—šŸœ˜*Kš œœœœœ˜,Kšœœ˜KšœœL˜cšœ˜Kšœ˜Kšœ˜—Kšœ˜K˜—šœ+œ!˜OKšœ˜Kšœ˜Kšœ˜K˜ Kšœ8˜8K˜—Kšœ8˜8K˜`K˜ΑK˜ZK˜UKšœˆ˜ˆK˜nK˜¬—Kšœ˜J˜—…—zZŸΠ