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
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: BOOLEANFALSE,
hasAChar: BOOLEANFALSE,
curCharCode: CHAR,
savedCharRep: RasterFontIO.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: 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: BOOLEANFALSE] ~ {
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: BOOLEANFALSE] ~ {
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: 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.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: ROPENIL;
cp: FS.ComponentPositions;
bad: BOOLEANFALSE;
extension: ROPENIL;
[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 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: 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: ROPENIL] = {
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: 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: 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<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 [RasterFontIO.InternalFontRep ← font^];
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: RasterFontIO.InternalCharRep] RETURNS [RasterFontIO.InternalCharRep] ~ {
new: RasterFontIO.InternalCharRep;
window: ImagerPixelMap.DeviceRectangle;
charRep.pixels ← ImagerPixelMap.Trim[charRep.pixels, 0];
window ← ImagerPixelMap.Window[charRep.pixels];
new.fWidth ← charRep.fWidth+1;
new.sWidth ← charRep.sWidth;
new.pixels ← ImagerPixelMap.Create[0, [window.sMin, window.fMin, window.sSize, window.fSize+1]];
ImagerPixelMap.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 [RasterFontIO.InternalFontRep ← font^];
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]
};
ComplementChar: PROC [charRep: RasterFontIO.InternalCharRep] RETURNS [RasterFontIO.InternalCharRep] ~ {
new: RasterFontIO.InternalCharRep;
window: ImagerPixelMap.DeviceRectangle;
window ← ImagerPixelMap.Window[charRep.pixels];
window.fMin ← 0;
window.fSize ← Real.Round[charRep.fWidth];
new.fWidth ← charRep.fWidth;
new.sWidth ← charRep.sWidth;
new.pixels ← ImagerPixelMap.Create[0, window];
ImagerPixelMap.Clear[new.pixels];
ImagerPixelMap.Fill[dest: new.pixels, area: window, value: 1];
new.pixels.Transfer[charRep.pixels, [and, complement]];
RETURN [new]
};
MakeComplementaryFont: PUBLIC PROC [font: InternalFont] RETURNS [InternalFont] ~ {
new: InternalFont ← NEW [RasterFontIO.InternalFontRep ← font^];
new.defaultChar ← ComplementChar[font.defaultChar];
FOR c: CHAR IN CHAR DO
IF font.charRep[c] = font.defaultChar THEN new.charRep[c] ← new.defaultChar
ELSE new.charRep[c] ← ComplementChar[font.charRep[c]];
ENDLOOP;
RETURN [new]
};
deltaX: INTEGER ← 0;
deltaY: INTEGER ← 0;
MakeTranslatedFont: PROC [font: InternalFont] RETURNS [InternalFont] ~ {
new: InternalFont ← NEW [RasterFontIO.InternalFontRep ← font^];
new.defaultChar.pixels.fOrigin ← new.defaultChar.pixels.fOrigin+deltaX;
new.defaultChar.pixels.sOrigin ← new.defaultChar.pixels.sOrigin-deltaY;
FOR c: CHAR IN CHAR DO
new.charRep[c].pixels.fOrigin ← new.charRep[c].pixels.fOrigin+deltaX;
new.charRep[c].pixels.sOrigin ← new.charRep[c].pixels.sOrigin-deltaY;
ENDLOOP;
RETURN [new]
};
FontSlantCommand: Commander.CommandProc ~ {
ModifyFont[cmd, MakeSlantedFont];
};
FontBoldCommand: Commander.CommandProc ~ {
ModifyFont[cmd, MakeBoldFont];
};
FontComplementCommand: Commander.CommandProc ~ {
ModifyFont[cmd, MakeComplementaryFont];
};
FontTranslateCommand: Commander.CommandProc ~ {
ModifyFont[cmd, MakeTranslatedFont];
};
FontWidthsCopyCommand: Commander.CommandProc ~ {
stream: IO.STREAMIO.RIS[cmd.commandLine];
outputName: ROPE ← GetToken[stream];
fontFileType: FontFileType ← FontFileNameType[outputName];
gets: ROPE ← GetToken[stream];
inputName: ROPE ← GetToken[stream];
font: InternalFont;
otherFont: 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[outputName !
RasterFontIO.FormatError => {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.STREAMIO.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.STREAMIO.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.STREAMIO.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.