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: 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𡤀] ~ {
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.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.