FontEditImpl.mesa
Michael Plass, September 14, 1983 11:27 am
DIRECTORY
BitmapEdit,
Commander,
FileIO,
Graphics,
ImagerPixelMaps,
Interminal,
IO,
Menus,
MessageWindow,
Process,
RasterFontWriter,
Real,
Rope,
TIPUser,
ViewerClasses,
ViewerOps,
ViewerTools
;
FontEditImpl: CEDAR PROGRAM
IMPORTS BitmapEdit, Commander, FileIO, Graphics, ImagerPixelMaps, IO, Menus, MessageWindow, Process, RasterFontWriter, Real, Rope, TIPUser, ViewerOps, ViewerTools
SHARES ViewerOps
~ BEGIN
ROPE: TYPE ~ Rope.ROPE;
Data: TYPE ~ REF DataRep;
DataRep: TYPE ~ RECORD [
fileName: ROPE,
font: RasterFontWriter.InternalFont,
refreshing: BOOLEANFALSE,
hasAChar: BOOLEANFALSE,
curCharCode: CHAR,
savedCharRep: RasterFontWriter.InternalCharRep,
bitmapViewer: ViewerClasses.Viewer,
textBaseline: INTEGER ← 4,
textDepth: INTEGER ← 2,
text: ROPE,
paintProcess: PROCESSNIL
];
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 + 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]];
};
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: "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"
]
];
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;
FileIO.OpenFailed => GOTO NotFound
];
RasterFontWriter.Trim[data.font];
data.fileName ← fontFileName;
viewer.name ← Rope.Concat["Font: ", 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[];
};
};
Save: ViewerClasses.SaveProc ~ {
data: Data ← NARROW[self.data];
firstChar: CHAR ← data.fileName.Fetch[0];
fontFileType: FontFileType ← FontFileNameType[data.fileName];
IF firstChar = '[ OR firstChar = '/ THEN {
MessageWindow.Append["Cannot save remote file: ", TRUE];
MessageWindow.Append[data.fileName, FALSE];
MessageWindow.Blink[];
RETURN;
};
StoreCurrent[self];
RasterFontWriter.Trim[data.font];
SELECT fontFileType FROM
ks => RasterFontWriter.WriteKernedStrike[data.font, data.fileName];
strike => {
FOR c: CHAR IN CHAR DO
charRep: RasterFontWriter.InternalCharRep ← data.font.charRep[c];
IF charRep # data.font.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.fSize];
data.font.charRep[c] ← charRep;
};
ENDLOOP;
SetChar[self, data.curCharCode];
RasterFontWriter.WriteStrike[data.font, data.fileName];
};
ENDCASE => {
IF fontFileType = ac THEN
MessageWindow.Append[".ac format not supported: ", TRUE]
ELSE
MessageWindow.Append["Cannot figure out what file format to use for ", TRUE];
MessageWindow.Append[data.fileName, FALSE];
MessageWindow.Blink[];
};
};
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;
viewer.name ← Rope.Concat["Font: ", data.fileName];
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]];
};
};
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];
};
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];
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, data.font.charRep[char].sWidth, 4-data.textBaseline]-1;
window.fMin ← MIN[window.fMin, 0, data.font.charRep[char].fWidth]-1;
sMax ← MAX[sMax, 0, data.font.charRep[char].sWidth, data.textDepth-2]+1;
fMax ← MAX[fMax, 0, 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, data.savedCharRep.sWidth, data.savedCharRep.fWidth];
viewer.name ← Rope.Cat["Font: ", data.fileName, CharEncode[data.curCharCode]];
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];
};
};
};
ENDCASE => NULL;
};
};
Break: PROC [char: CHAR] RETURNS [IO.CharClass] = {
IF char = '← THEN RETURN [break];
IF char = ' OR char = '  OR char = ', OR char = '; OR char = '\n THEN RETURN [sepr];
RETURN [other];
};
FontEditCommand: Commander.CommandProc ~ {
stream: IO.STREAMIO.RIS[cmd.commandLine];
name: ROPE ← stream.GetToken[Break];
IF name.Length = 0 THEN cmd.out.PutRope["Please supply a font file name (.ks or .strike)"]
ELSE {
IF name.Fetch[0] = '[ OR name.Fetch[0] = '/ THEN cmd.out.PutRope["(will not be able to save remote file)"];
[] ← 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."];
END.