FontEditImpl.mesa
Michael Plass, January 13, 1984 2:43 pm
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: BOOLEANFALSE,
hasAChar: BOOLEANFALSE,
curCharCode: CHAR,
savedCharRep: RasterFontWriter.InternalCharRep,
bitmapViewer: ViewerClasses.Viewer,
textBaseline: INTEGER ← 4,
textDepth: INTEGER ← 2,
text: ROPE,
paintProcess: PROCESSNIL,
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 TEXTNEW[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: BOOLEANFALSE] ~ {
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: BOOLEANFALSE] ~ {
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: BOOLEANTRUE] ~ {
data: Data ← NARROW[viewer.data];
text: REF TEXTNEW[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 TEXTNEW[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: CHARIF 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𡤀] ~ {
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<sMax DO
new.pixels.Clip[[s, fMin, 3, fMax-fMin]].Transfer[charRep.pixels.ShiftMap[0, shift]];
s ← s + 3;
shift ← shift - 1;
ENDLOOP;
};
RETURN [new]
};
MakeSlantedFont: PUBLIC PROC [font: InternalFont] RETURNS [InternalFont] ~ {
new: InternalFont ← NEW [RasterFontWriter.InternalFontRep];
new.defaultChar ← SlantChar[font.defaultChar];
FOR c: CHAR IN CHAR DO
IF font.charRep[c] = font.defaultChar THEN new.charRep[c] ← new.defaultChar
ELSE new.charRep[c] ← SlantChar[font.charRep[c]];
ENDLOOP;
RETURN [new]
};
BoldChar: PROC [charRep: RasterFontWriter.InternalCharRep] RETURNS [RasterFontWriter.InternalCharRep] ~ {
new: RasterFontWriter.InternalCharRep;
window: ImagerPixelMaps.DeviceRectangle;
charRep.pixels ← ImagerPixelMaps.Trim[charRep.pixels, 0];
window ← ImagerPixelMaps.Window[charRep.pixels];
new.fWidth ← charRep.fWidth+1;
new.sWidth ← charRep.sWidth;
new.pixels ← ImagerPixelMaps.Create[0, [window.sMin, window.fMin, window.sSize, window.fSize+1]];
ImagerPixelMaps.Clear[new.pixels];
new.pixels.Transfer[charRep.pixels];
new.pixels.Transfer[charRep.pixels.ShiftMap[0, 1], [or, null]];
RETURN [new]
};
MakeBoldFont: PUBLIC PROC [font: InternalFont] RETURNS [InternalFont] ~ {
new: InternalFont ← NEW [RasterFontWriter.InternalFontRep];
new.defaultChar ← BoldChar[font.defaultChar];
FOR c: CHAR IN CHAR DO
IF font.charRep[c] = font.defaultChar THEN new.charRep[c] ← new.defaultChar
ELSE new.charRep[c] ← BoldChar[font.charRep[c]];
ENDLOOP;
RETURN [new]
};
FontSlantCommand: Commander.CommandProc ~ {
ModifyFont[cmd, MakeSlantedFont];
};
FontBoldCommand: Commander.CommandProc ~ {
ModifyFont[cmd, MakeBoldFont];
};
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.STREAMIO.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.STREAMIO.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.STREAMIO.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.