DIRECTORY
IODefs: FROM "iodefs",
StreamDefs: FROM "streamdefs",
StringDefs: FROM "stringdefs",
SystemDefs: FROM "systemdefs",
PressDefs: FROM "pressdefs";

Matrix: PROGRAM IMPORTS IODefs, StreamDefs, StringDefs, SystemDefs, PressDefs =
BEGIN

Font:TYPE = POINTER TO FontBody;
FontBody:TYPE = RECORD[family:STRING, size:CARDINAL ← 0, face:CARDINAL ← 0,
rotation:CARDINAL ← 0, widths:Widths ← NIL];
Widths:TYPE = POINTER TO WidthBody;
WidthBody:TYPE = RECORD[micaX:ARRAY [0..256) OF INTEGER,
bBox:ARRAY [0..4) OF INTEGER];
JustificationMode: TYPE = {leftJustified, rightJustified, centered,
bottomJustified, topJustified};

labelFont, entryFont, titleFont, commentFont, quoteFont: Font ← NIL;
wStream:StreamDefs.StreamHandle; --Fonts.Widths stream
outFile:POINTER TO PressDefs.PressFileDescriptor;
outFileName:STRING = "Matrix.Press";
str:STRING ← [15];
row,column:CARDINAL;
char:CHARACTER;

leftEdge:CARDINAL←4000;
rightEdge:CARDINAL←19000;
bottomEdge:CARDINAL←6000;
topEdge:CARDINAL←24800;
gridWidth:CARDINAL←20;
labelMargin:CARDINAL←200;
titleMargin:CARDINAL←700;
title:STRING = "Alto OS keyboard handler";
comment1:STRING = "Here is one way to type each seven bit character code to the keyboard handler included as part of the Alto";
comment2:STRING = "Operating System, and hence to Alto programs that let the OS handle the keyboard for them (such as OEdit).";
comment3:STRING = "There are often other ways as well; for example, <ctrl><@> gives #000, while <shift><hyphen> gives #030.";

numCols:CARDINAL=8;
numRows:CARDINAL=16;
colWidth:CARDINAL=(rightEdge-leftEdge)/numCols;
rowHeight:CARDINAL=(topEdge-bottomEdge)/numRows;

FindWidths:
PROCEDURE[f:Font]=
BEGIN
success:BOOLEAN;
IF f=NIL THEN ERROR AttemptToFindWidthsOfNIL;
f.widths ← SystemDefs.AllocateHeapNode[SIZE[WidthBody]];
success ← PressDefs.LookupFontName[wStream, f.family, f.face, f.size,
f.rotation, @f.widths.micaX, NIL, @f.widths.bBox];
IF ~success THEN ERROR FontNotInWidthDictionary;
END;
FontNotInWidthDictionary: SIGNAL = CODE;
AttemptToFindWidthsOfNIL: SIGNAL = CODE;

PutString:
PROCEDURE[s:STRING,f:Font,x,y:CARDINAL,m:JustificationMode]=
BEGIN
length:CARDINAL ← 0;
height, i:CARDINAL;
IF f.widths=NIL THEN ERROR FontHasNoWidthData;
FOR i IN [0..s.length) DO
length ← length+f.widths.micaX[s[i]-0C];
ENDLOOP;
height ← f.widths.bBox[3]*8/10;
SELECT m FROM
leftJustified => BEGIN y ← y-height/2 END;
rightJustified => BEGIN y ← y-height/2; x ← x-length END;
centered => BEGIN y ← y-height/2; x ← x-length/2 END;
topJustified => BEGIN y ← y-height; x ← x-length/2 END;
bottomJustified => BEGIN x ← x-length/2 END;
ENDCASE;
PressDefs.SetFont[outFile, f.family, f.size, f.face, f.rotation];
PressDefs.PutText[outFile, s, x, y];
END;
FontHasNoWidthData:SIGNAL = CODE;

InitFont: PROCEDURE[fam:STRING, size:CARDINAL, face,rot:CARDINAL ← 0]
RETURNS [Font] =
BEGIN
f:Font;
f ← SystemDefs.AllocateHeapNode[SIZE[FontBody]];
f↑ ← FontBody[fam,size,face,rot,NIL];
RETURN [f];
END;

PutChar: PROCEDURE[char:CHARACTER, font:Font,x,y:CARDINAL] = INLINE
BEGIN
string:STRING ← [1];
string.length ← 1;
string[0] ← char;
PutString[string,font,x,y,centered];
END;

PutStringChar: PROCEDURE[string1:STRING,font1:Font,char:CHARACTER,
font2:Font,x,y:CARDINAL] = INLINE
BEGIN
string2:STRING ← [1];
string2.length ← 1;
string2[0] ← char;
PutStringString[string1,font1,string2,font2,x,y];
END;

PutStringString: PROCEDURE[string1:STRING,font1:Font,string2:STRING,
font2:Font,x,y:CARDINAL] = INLINE
BEGIN
PutString[string1,font1,x,y+50,bottomJustified];
PutString[string2,font2,x,y,topJustified];
END;

IODefs.WriteLine["Starting:"];
wStream ← StreamDefs.NewWordStream["Fonts.Widths",
StreamDefs.Read];
outFile ← SystemDefs.AllocateHeapNode[SIZE[PressDefs.PressFileDescriptor]];
PressDefs.InitPressFileDescriptor[outFile,outFileName];
labelFont ← InitFont["TimesRoman",10];
entryFont ← InitFont["TimesRoman",10];
titleFont ← InitFont["TimesRoman",18,2];
commentFont ← InitFont["TimesRoman",10];
quoteFont ← InitFont["Gacha",12];

FindWidths[labelFont];
FindWidths[entryFont];
FindWidths[titleFont];
FindWidths[commentFont];
FindWidths[quoteFont];

--Put down the rules of the grid;
FOR row IN [0..numRows]
DO
PressDefs.PutRectangle[outFile,leftEdge,bottomEdge+row*rowHeight,
numCols*colWidth+gridWidth,gridWidth]
ENDLOOP;
FOR column IN [0..numCols]
DO
PressDefs.PutRectangle[outFile,leftEdge+column*colWidth,bottomEdge,
gridWidth,numRows*rowHeight+gridWidth]
ENDLOOP;

--Put numeric labels on the grid, in labelFont;
FOR column IN [0..numCols)
DO
str[0]←(’0)+column;
str.length ← 1;
PutString[str, labelFont, leftEdge+colWidth/2+column*colWidth,
bottomEdge+labelMargin+numRows*rowHeight,bottomJustified];
ENDLOOP;
FOR row IN [0..numRows)
DO
str.length←0;
StringDefs.AppendChar[str,’#];
SELECT row*numCols FROM
IN [0B..7B] => StringDefs.AppendString[str,"00"];
IN [10B..77B] => StringDefs.AppendString[str,"0"];
ENDCASE;
StringDefs.AppendNumber[str,row*numCols,8];
PutString[str, labelFont, leftEdge-labelMargin,
bottomEdge+(numRows-row)*rowHeight-(rowHeight/2), rightJustified];
ENDLOOP;

--put title at the bottom of the page
PutString[title, titleFont, leftEdge+(numCols*colWidth/2),
bottomEdge-titleMargin, topJustified];

--and add comments below it
PutString[comment1,commentFont,leftEdge-1000,bottomEdge*6/10,leftJustified];
PutString[comment2,commentFont,leftEdge-1000,bottomEdge/2,leftJustified];
PutString[comment3,commentFont,leftEdge-1000,bottomEdge*4/10,leftJustified];

--start putting in keys

FOR char IN [0C..177C]
DO BEGIN
l:CARDINAL;
x,y,row,column:CARDINAL;
l←char-0C;
row←l/numCols;
column ← l MOD numCols;
x ← leftEdge+column*colWidth+colWidth/2;
y ← bottomEdge+(numRows-row)*rowHeight-rowHeight/2;
SELECT char FROM
=0C => PutStringString["ctrl",commentFont,"space",commentFont,x,y];
IN [1C..7C] => PutStringChar["ctrl",commentFont,char+140B,entryFont,x,y];
=10C => PutString["bs",commentFont,x,y,centered];
=11C => PutString["tab",commentFont,x,y,centered];
=12C => PutString["lf",commentFont,x,y,centered];
IN [13C..14C]=> PutStringChar["ctrl",commentFont,char+140B,entryFont,x,y];
=15C => PutString["return",commentFont,x,y,centered];
IN [16C..32C]=> PutStringChar["ctrl",commentFont,char+140B,entryFont,x,y];
=33C => PutString["esc",commentFont,x,y,centered];
IN [34C..37C]=> PutStringChar["ctrl",commentFont,char+100B,entryFont,x,y];
=40C => PutString["space",commentFont,x,y,centered];
=41C => PutChar[char,entryFont,x,y];
=42C => PutChar[char,quoteFont,x,y];
IN [43C..46C] => PutChar[char,entryFont,x,y];
=47C => PutChar[char,quoteFont,x,y];
IN [50C..54C] => PutChar[char,entryFont,x,y];
=55C => PutChar[30C,entryFont,x,y];
IN [56C..100C] => PutChar[char,entryFont,x,y];
IN [101C..132C] => PutStringChar["shift",commentFont,char+40B,entryFont,
x,y];
IN [133C..137C] => PutChar[char,entryFont,x,y];
=140C => PutStringString["shift",commentFont,"lf",commentFont,x,y];
IN [141C..176C] => PutChar[char,entryFont,x,y];
=177C => PutString["del",commentFont,x,y,centered];
ENDCASE;
END
ENDLOOP;

PressDefs.WritePage[outFile];

PressDefs.ClosePressFile[outFile];

IODefs.WriteLine[""];
IODefs.WriteLine["Done."]

END.