DIRECTORY BitmapEdit, Commander, Convert, FS, Graphics, ImagerPixelMaps, Interminal, IO, Menus, MessageWindow, Process, RasterFontWriter, Real, Rope, TIPUser, ViewerClasses, ViewerOps, ViewerTools ; FontEditImpl: CEDAR PROGRAM IMPORTS BitmapEdit, Commander, Convert, FS, Graphics, ImagerPixelMaps, IO, Menus, MessageWindow, Process, RasterFontWriter, Real, Rope, TIPUser, ViewerOps, ViewerTools SHARES ViewerOps ~ BEGIN ROPE: TYPE ~ Rope.ROPE; InternalFont: TYPE ~ RasterFontWriter.InternalFont; Data: TYPE ~ REF DataRep; DataRep: TYPE ~ RECORD [ fileName: ROPE, font: InternalFont, refreshing: BOOLEAN _ FALSE, hasAChar: BOOLEAN _ FALSE, curCharCode: CHAR, savedCharRep: RasterFontWriter.InternalCharRep, bitmapViewer: ViewerClasses.Viewer, textBaseline: INTEGER _ 4, textDepth: INTEGER _ 2, text: ROPE, paintProcess: PROCESS _ NIL, bitsPerInch: REAL _ 72 ]; 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: Graphics.Context, char: RasterFontWriter.InternalCharRep, xMinGarbage: INT] RETURNS [xMaxGood: INT] ~ TRUSTED { mark: Graphics.Mark _ Graphics.Save[context]; xMaxGood _ MAX[Real.RoundLI[Graphics.GetCP[context, TRUE].x]+char.pixels.fSize+char.pixels.fMin+char.pixels.fOrigin, xMinGarbage]; Graphics.SetColor[context, Graphics.white]; Graphics.DrawBox[context, [xMinGarbage, -9999, xMaxGood, 9999]]; Graphics.SetColor[context, Graphics.black]; [] _ Graphics.SetPaintMode[context, transparent]; context.procs.DrawBits[context, char.pixels.refRep.pointer, char.pixels.refRep.rast, 0, char.pixels.fMin, char.pixels.sMin, char.pixels.fSize, char.pixels.sSize, -char.pixels.fOrigin, -char.pixels.sOrigin]; Graphics.SetCP[context, char.fWidth, char.sWidth, TRUE]; Graphics.Restore[context, mark]; }; DrawText: PROC [viewer: ViewerClasses.Viewer, context: Graphics.Context] ~ { data: Data _ NARROW[viewer.data]; mark: Graphics.Mark _ Graphics.Save[context]; 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]; Graphics.SetCP[context, 4, viewer.ch-data.textBaseline]; Graphics.ClipBox[context, [0, viewer.ch-data.textBaseline-data.textDepth, viewer.cw, viewer.ch]]; [] _ data.text.Map[action: Action]; Graphics.SetColor[context, Graphics.white]; Graphics.DrawBox[context, [xMinGarbage, -9999, 9999, 9999]]; Graphics.Restore[context, mark]; }; FindSelectedChar: PROC [viewer: ViewerClasses.Viewer, where: Interminal.MousePosition] RETURNS [ index: INT ] ~ { data: Data _ NARROW[viewer.data]; fMouse: INT _ where.mouseX; cpf: INT _ 4; Action: PROC [c: CHAR] RETURNS [quit: BOOLEAN _ FALSE] ~ { charRep: RasterFontWriter.InternalCharRep _ data.font.charRep[c]; window: ImagerPixelMaps.DeviceRectangle _ charRep.pixels.Window; IF fMouse-cpf IN [window.fMin..window.fMin+window.fSize) THEN RETURN [TRUE]; cpf _ cpf + Real.RoundLI[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.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 _ RasterFontWriter.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]; data.font _ RasterFontWriter.Load[fontFileName ! RasterFontWriter.FormatError => GOTO BadFormat; FS.Error => GOTO NotFound ]; RasterFontWriter.Trim[data.font]; data.fileName _ fontFileName; SetChar[viewer, 'A]; EXITS NotFound => { MessageWindow.Append["File not found: ", TRUE]; MessageWindow.Append[fontFileName, FALSE]; MessageWindow.Blink[]; }; BadFormat => { MessageWindow.Append["Not a legal font file: ", TRUE]; MessageWindow.Append[fontFileName, FALSE]; MessageWindow.Blink[]; }; }; WriteFormatDerivedFromName: PUBLIC PROC [internalFont: InternalFont, fileName: Rope.ROPE, bitsPerInch: REAL] ~ { firstChar: CHAR _ fileName.Fetch[0]; fontFileType: FontFileType _ FontFileNameType[fileName]; IF firstChar = '[ OR firstChar = '/ THEN { MessageWindow.Append["Cannot save remote file: ", TRUE]; MessageWindow.Append[fileName, FALSE]; MessageWindow.Blink[]; RETURN; }; RasterFontWriter.Trim[internalFont]; SELECT fontFileType FROM ac => RasterFontWriter.WriteAC[internalFont, fileName, bitsPerInch]; ks => RasterFontWriter.WriteKernedStrike[internalFont, fileName]; strike => { FOR c: CHAR IN CHAR DO charRep: RasterFontWriter.InternalCharRep _ internalFont.charRep[c]; IF charRep # internalFont.defaultChar THEN { box: ImagerPixelMaps.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; RasterFontWriter.WriteStrike[internalFont, fileName]; }; ENDCASE => { MessageWindow.Append["Cannot figure out what file format to use for ", TRUE]; MessageWindow.Append[fileName, FALSE]; MessageWindow.Blink[]; }; }; Save: ViewerClasses.SaveProc ~ { data: Data _ NARROW[self.data]; StoreCurrent[self]; WriteFormatDerivedFromName[data.font, data.fileName, data.bitsPerInch]; 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] ~ { index: INT _ 0; dotLoc: INT _ -1; extension: ROPE; InvalidChar: PROC [c: CHAR] RETURNS [BOOLEAN] ~ { index _ index + 1; IF c IN ['a..'z] THEN RETURN [FALSE]; IF c IN ['A..'Z] THEN RETURN [FALSE]; IF c IN ['0..'9] THEN RETURN [FALSE]; IF c='. THEN dotLoc _ index-1; IF c='. OR c='- OR c='$ THEN RETURN [FALSE]; RETURN [TRUE]; }; IF rope.Map[action: InvalidChar] THEN RETURN [invalid]; IF index > 256 THEN RETURN [invalid]; IF dotLoc = -1 THEN RETURN [unknown]; extension _ rope.Substr[dotLoc]; 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: RasterFontWriter.InternalCharRep _ data.savedCharRep; badCharRep: RasterFontWriter.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.bitsPerInch _ new; ComputeCaption[viewer]; }; StoreCurrent: PROC [viewer: ViewerClasses.Viewer] ~ { data: Data _ NARROW[viewer.data]; IF data.hasAChar AND data.bitmapViewer.newVersion THEN { charRep: RasterFontWriter.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; }; 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: ImagerPixelMaps.DeviceRectangle _ data.font.charRep[char].pixels.Window; sMax: INTEGER _ window.sMin+window.sSize; fMax: INTEGER _ window.fMin+window.fSize; pixelMap: ImagerPixelMaps.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 _ ImagerPixelMaps.Create[0, window]; pixelMap.Transfer[data.savedCharRep.pixels]; BitmapEdit.SetBitmap[data.bitmapViewer, pixelMap, Real.RoundI[data.savedCharRep.sWidth], Real.RoundI[data.savedCharRep.fWidth]]; 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 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; }; 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); ViewerOps.ResetPaintCache[self, FALSE]; }; IF whatChanged = NIL OR whatChanged = $Text THEN { DrawText[self, context]; }; }; 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: RasterFontWriter.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; }; }; 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 ~ RECORD [startChar, nChars: CARDINAL]; 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: RasterFontWriter.InternalCharRep] RETURNS [RasterFontWriter.InternalCharRep] ~ { new: RasterFontWriter.InternalCharRep; window: ImagerPixelMaps.DeviceRectangle; sMin, fMin, sMax, fMax: INTEGER; charRep.pixels _ ImagerPixelMaps.Trim[charRep.pixels, 0]; window _ ImagerPixelMaps.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 _ ImagerPixelMaps.Create[0, [sMin, fMin, sMax-sMin, fMax-fMin]]; ImagerPixelMaps.Clear[new.pixels]; CHECKED { s: INTEGER _ -3; shift: INTEGER _ 0; WHILE s>sMin DO s _ s - 3; shift _ shift + 1 ENDLOOP; WHILE s= 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 _ stream.GetTokenRope[Break].token; fontFileType: FontFileType _ FontFileNameType[outputName]; gets: ROPE _ stream.GetTokenRope[Break].token; inputName: ROPE _ stream.GetTokenRope[Break].token; font: InternalFont; errIndex: INT _ -1; IF NOT gets.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; }; font _ RasterFontWriter.Load[inputName ! RasterFontWriter.FormatError => {errIndex _ byteIndex; CONTINUE}; FS.Error => CONTINUE ]; IF font = NIL THEN { InputError[cmd.out, inputName, errIndex]; RETURN; }; font _ fontFunction[font]; RasterFontWriter.Trim[font]; WriteFormatDerivedFromName[font, outputName, 384]; cmd.out.PutRope[outputName]; cmd.out.PutRope[" written."]; }; FontMergeCommand: Commander.CommandProc ~ { stream: IO.STREAM _ IO.RIS[cmd.commandLine]; outputName: ROPE _ stream.GetTokenRope[Break].token; fontFileType: FontFileType _ FontFileNameType[outputName]; token: ROPE _ stream.GetTokenRope[Break].token; font: InternalFont _ RasterFontWriter.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 _ stream.GetTokenRope[Break].token; WHILE token # NIL DO inputName: ROPE; inputFont: InternalFont; badSpecIndex: INT _ -1; errIndex: INT _ -1; WHILE token.Equal[";"] OR token.Equal["FONT"] DO token _ stream.GetTokenRope[Break].token; ENDLOOP; IF token = NIL THEN EXIT; inputName _ token; inputFont _ RasterFontWriter.Load[inputName ! RasterFontWriter.FormatError => {errIndex _ byteIndex; CONTINUE}; FS.Error => CONTINUE ]; IF inputFont = NIL THEN { InputError[cmd.out, inputName, errIndex]; RETURN; }; token _ stream.GetTokenRope[Break].token; 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 _ stream.GetTokenRope[Break].token; } ELSE {font _ inputFont}; ENDLOOP; WriteFormatDerivedFromName[font, outputName, 384]; }; FontEditCommand: Commander.CommandProc ~ { stream: IO.STREAM _ IO.RIS[cmd.commandLine]; name: ROPE _ stream.GetTokenRope[Break].token; IF name.Length = 0 THEN cmd.out.PutRope["Please supply a font file name (.ks or .strike)"] ELSE { [] _ Create[name]; }; }; fontEditClass: ViewerClasses.ViewerClass _ NEW[ViewerClasses.ViewerClassRec _ [ paint: FontEditPaintProc, 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)"]; END. @FontEditImpl.mesa Michael Plass, January 13, 1984 2:43 pm Κ­˜J™J™'J˜šΟk ˜ Jšœ ˜ J˜ J˜Jšœ˜Jšœ ˜ J˜J˜ Jšœ˜J˜Jšœ˜Jšœ˜Jšœ˜J˜J˜J˜Jšœ˜Jšœ ˜ J˜ J˜—J˜šœœ˜Jšœ!œœ^˜§Jšœ ˜Jšœ˜—J˜Jšœœœ˜Jšœœ"˜4J˜Jšœœœ ˜šœ œœ˜Jšœ œ˜Jšœ˜Jšœ œœ˜Jšœ œœ˜Jšœ œ˜Jšœ/˜/Jšœ#˜#Jšœœ˜Jšœ œ˜Jšœœ˜ Jšœœœ˜Jšœ œ˜Jšœ˜—Jšœ œY˜jJšœ œ˜šΟnœœœœ˜!Jš œœœœœ˜ šœœœ ˜J˜Jšœ˜—Jšœ˜Jšœ˜Jšœ˜—š žœœRœœ œœ˜ŽJšœ-˜-Jšœ œ&œJ˜‚Jšœ+˜+Jšœ@˜@Jšœ+˜+Jšœ1˜1JšœΞ˜ΞJšœ2œ˜8Jšœ ˜ Jšœ˜—šžœœ>˜LJšœ œ˜!Jšœ-˜-Jšœ œ˜š žœœœœœœ˜:Jšœœœœ˜.JšœI˜IJšœ˜—Jšœ œœœ˜Jšœ˜Jšœ8˜8Jšœa˜aJšœ#˜#Jšœ+˜+Jšœ<˜Jšœ˜Jšœ˜—Jšœ˜Jšœ˜—Jšœœ&˜8šžœœœœ˜>Jšœœ˜Jšœœ˜Jšœ œ˜š ž œœœœœ˜1Jšœ˜Jš œœ œœœ˜%Jš œœ œœœ˜%Jš œœ œœœ˜%Jšœœ˜Jš œœœœœœ˜,Jšœœ˜Jšœ˜—Jšœœœ ˜7Jšœ œœ ˜%Jšœ œœ ˜%Jšœ ˜ šœœ˜Jšœœœ˜-Jšœœœ ˜5Jšœœœ˜-Jšœœ ˜—Jšœ˜—šž œ˜ Jšœœ ˜.Jšœ œ˜!Jšœœ˜Jšœ4˜4šœœ˜ Jšœ>œ˜DJšœ˜Jšœ˜—šœ˜Jšœœœœ˜LJšœ˜Jšœ˜Jšœ˜—Jšœ˜—šž œ˜Jšœœ ˜.Jšœ˜Jšœ˜—šž œ˜Jšœœ ˜.Jšœ œ˜!Jšœœ˜šœœ˜Jšœ3œ˜9Jšœ˜Jšœ˜—šœ˜Jšœ˜Jšœ˜—Jšœ˜—šž œ˜Jšœœ ˜.Jšœ œ˜!Jšœ>˜>Jšœ-˜-Jšœ˜Jšœ1˜1Jšœ.˜.Jšœ"˜"Jšœ˜Jšœ˜—šžœ˜#Jšœœ ˜.Jšœ œ˜!Jšœ œ˜!šœœ˜Jšœ œœœœœœ˜KJšœ˜—šœœ˜Jšœœœœ œœœ˜TJšœ˜—Jšœ˜Jšœ˜—šž œ˜Jšœœ ˜.Jšœ œ˜!Jšœœ˜5Jšœœ˜5Jšœœœ˜YJšœ&œ ˜4Jšœ˜—šž œ˜!Jšœœ ˜.Jšœ œ˜!Jšœ"˜"Jšœ˜Jšœ˜—šž œ˜Jšœœ ˜.Jšœ œ˜!JšœGœ˜QJšœ˜Jšœ˜—šžœ˜Jšœœ ˜.Jšœ œ˜!Jšœœ˜Jšœ<œ˜FJšœ œ>œ˜XJšœ œ˜/Jšœ˜Jšœ˜—šž œ˜Jšœœ ˜.Jšœ œ˜!Jšœœ˜Jšœ<œ˜FJšœ œ>œ˜XJšœ œ˜'Jšœ˜Jšœ˜—šž œœ#˜5Jšœ œ˜!šœœœ˜8Jšœ*˜*Jšœu˜uJšœ(˜(šœœœ˜>Jšœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ˜—Jšœ.˜.Jšœ˜—Jšœœ˜%Jšœ˜—š ž œœœœœ˜6Jš œœœœœ˜Jšœ œ˜Jšœ+˜+šœœ œ˜Jšœ ˜ Jšœ˜Jšœ˜—Jšœ˜Jšœœ˜Jšœœ˜Jšœ˜Jšœ˜Jšœ˜—šžœœ&œ˜