We will install 12 fonts from Computer Modern: Serif, Sans Serif and Typewriter which all have four variants: Roman (or Regular), Italic (or Oblique), Bold, Bold Italic (or Bold Oblique).
We add them all as Type 3 fonts embedded in a JS-file. The font files are quite heavy. I suspect the outlines of our fonts are more complex than they should be. We add them as external Javascript files, but reading 12 font files takes some time.
Now you see the heap and the dictionary fill up. We rewrite our postScriptEditor to make the console more compact.
Run
switchDisplay = function (node) {
if (node.nextSibling.style.display == "block") {
node.nextSibling.style.display = "none";
} else {
node.nextSibling.style.display = "block";
}
}
postScriptEditor = function(code) {
const id = Math.floor(Math.random() * 1000); // create unique id for console.log
const node = document.createElement("DIV"); // build the HTML nodes
node.id = "id" & id;
node.className = "psmain";
const node2 = document.createElement("DIV");
node2.className = "editzone";
node.appendChild(node2);
const node3 = document.createElement("DIV");
node3.className = "editheader";
node3.innerHTML = "PostScript";
node2.appendChild(node3);
const node4 = document.createElement("FORM");
node2.appendChild(node4);
const node5 = document.createElement("BUTTON");
node5.type = "button";
node5.id = "button" + id;
node5.innerHTML = "Run";
node4.appendChild(node5);
const node6 = document.createElement("TEXTAREA");
node6.id = "editor" + id;
node6.className = "pseditor";
node6.innerHTML = code;
node6.rows = code.split(endofline).length + 1;
node4.appendChild(node6);
const node7 = document.createElement("DIV");
node7.id = "console" + id;
node7.className = "jsconsole";
const node8 = document.createElement("CANVAS");
node8.id = "canvas" + id;
node8.className = "jscanvas";
node8.width = 590;
node8.height = 330;
node.appendChild(node8);
node.appendChild(node7);
console.log(node.outerHTML); // add the node to the parent element
const script = `redirectConsole($id);
run$id = function() {
redirectConsole($id);
const width = 590;
const height = 330;
const data = new Uint8ClampedArray(width * height * 4);
// make it white
for(i = 0; i< data.length; i++) data[i] = 255;
const code = document.getElementById("editor$id").value;
var context = new rpnContext;
context.data = data;
context.width = width;
context.height = height;
context = rpn(code, context);
console.log("stack: " + context.stack.reduce((acc,v) => acc + v.dump + " " , " "));
if (context.lasterror) {
console.log("rpn: " + context.currentcode);
}
for (key in context.graphics) {
console.log(key + ": " + context.graphics[key]);
}
for (key in context.fontdict) {
console.log("Font "+key);
}
for (key in context.dict) {
console.log(key + " = " + (context.dict[key].dump));
}
if (context.heap.length) {
console.log("heap:
" + context.heap.reduce( function(acc, v) {
const val = v.value;
if (Array.isArray(val)) {
return acc + " [" + val.reduce( (acc2, v2) => acc2 + v2.dump + " " , " ") + "] "+v.counter+" ";
} else if (val === null) {
return acc + " ";
} else {
return acc + " (" + val + ") "+v.counter;
}
} , "") + "
");
}
const image = new ImageData(context.data, width);
const canvas = document.getElementById("canvas$id");
ctx = canvas.getContext("2d");
ctx.putImageData(image, 0,0);
};
document.getElementById("button$id").onclick = run$id;`.replaceAll("$id", id).replaceAll("$code",code);
const scriptnode = document.createElement("SCRIPT");
scriptnode.innerHTML = script;
document.body.appendChild(scriptnode); // run the script
};
Run
postScriptEditor(`(CMUSerif-BoldItalic) run
(CMUSerif-Roman) run
(CMUSerif-Bold) run
(CMUSerif-Italic) run
(CMUSansSerif) run
(CMUSansSerif-Oblique) run
(CMUSansSerif-Bold) run
(CMUSansSerif-BoldOblique) run
(CMUTypewriter-Regular) run
(CMUTypewriter-Italic) run
(CMUTypewriter-Bold) run
(CMUTypewriter-BoldItalic) run
/CMUSerif-Roman findfont 48 scalefont setfont
0 250 moveto (CMUSerif-Roman) show
/CMUSerif-Italic findfont 48 scalefont setfont
0 200 moveto (Italic) show
/CMUSerif-Bold findfont 48 scalefont setfont
180 200 moveto (Bold) show
/CMUSerif-BoldItalic findfont 48 scalefont setfont
300 200 moveto (BoldItalic) show
/CMUSansSerif findfont 48 scalefont setfont
0 150 moveto (CMUSansSerif) show
/CMUSansSerif-Oblique findfont 48 scalefont setfont
0 100 moveto (Oblique) show
/CMUSansSerif-Bold findfont 48 scalefont setfont
180 100 moveto (Bold) show
/CMUSansSerif-BoldOblique findfont 48 scalefont setfont
300 100 moveto (BoldOblique) show
/CMUTypewriter-Regular findfont 48 scalefont setfont
0 50 moveto (CMUTypewriter-Regular) show
showpage
/CMUTypewriter-Italic findfont 48 scalefont setfont
0 0 moveto (Italic) show
/CMUTypewriter-Bold findfont 48 scalefont setfont
180 0 moveto (Bold) show
/CMUTypewriter-BoldItalic findfont 48 scalefont setfont
300 0 moveto (BoldItalic) show
`);
What are the available letters? There are 255 code points. We make a script to create a special string with all code points. We can do this with the put operator and implementing the string operator that creates a zero byte string of a given length.
Run
operators.string = function(context) {
const [n] = context.pop("number");
if (!n) return context;
if (n.value < 1) {
context.error("limitcheck");
return context;
}
const s = String.fromCharCode(0).repeat(n.value);
context.stack.push(new rpnString(s, context.heap));
return context;
};
That looks like Latin1, not Unicode
Run
postScriptEditor(`(CMUTypewriter-Regular) run
/CMUTypewriter-Regular findfont 20 scalefont setfont
/a 256 string def
0 1 255 { /i exch def a i i put } for
0 1 15 { /y exch def
0 1 15 { /x exch def a x y 16 mul add 1 getinterval x 32 mul 20 add 330 y 20 mul sub moveto show
} for
} for
`);
So to properly show the strings, we need to convert them to Latin1.
Run
operators.show = function(context) {
const [s] = context.pop("string");
if (!s) return context;
const encoder = new TextEncoder("latin1");
s.value = encoder.encode(s.value);
const font = context.fontdict[context.graphics.font];
const bak = context.graphics.matrix.slice();
const matrix = font.value.FontMatrix.value;
const size = context.graphics.size;
var m = context.graphics.matrix.slice();
m = matrixMultiplication(m, [matrix[0].value * size, matrix[1].value * size, matrix[2].value * size, matrix[3].value * size, matrix[4].value, matrix[5].value ]);
m[4] += context.graphics.current[0];
m[5] += context.graphics.current[1];
context.graphics.matrix = m;
context.dictstack.push(font.value);
for(var i = 0; i < s.value.length; i++) {
context.stack.push(font);
const c = s.value.charCodeAt(i);
context.stack.push(new rpnNumber(c));
context = rpn("BuildChar", context);
const x = context.graphics.cachedevice[0];
const y = context.graphics.cachedevice[1];
const m = context.graphics.matrix.slice();
context.graphics.matrix[4] += x*m[0] + y*m[2];
context.graphics.matrix[5] += x*m[1] + y*m[3];
}
context.dictstack.pop();
context.graphics.matrix = bak;
return context;
}
I like using "Genève" and "Zürich" to check encoding.
Run
postScriptEditor(`(CMUSansSerif) run
/CMUSansSerif findfont 96 scalefont setfont
100 100 moveto (Genève) show
100 200 moveto (Zürich) show
`);
Can we write our own font. Yes we can. We use the paths of the characters TAO in the earlier chapters to build a font. We use only the minimal parameters.
Run
postScriptEditor(`11 dict begin
/FontMatrix [0.016 0 0 0.016 0 0 ]readonly def
/FontName /FirstFont def
/Encoding 256 array
0 1 255 { 1 index exch /.notdef put} for
dup 65/A put
dup 79/O put
dup 84/T put
def
/BuildChar { 1 index /Encoding get exch get 1 index /BuildGlyph get exec } bind def
/BuildGlyph { 2 copy exch /CharProcs get exch 2 copy known not { pop /.notdef} if get exch pop 0 exch exec pop pop fill} bind def
/CharProcs 700 dict def
CharProcs begin
/.notdef { 60 0 20 0 0 60 60 setcachedevice
0 0 moveto 0 60 lineto 60 60 lineto 0 60 lineto closepath
} bind def
/T { 60 0 0 0 60 60 setcachedevice
30 0 moveto 40 0 lineto 40 50 lineto 60 50 lineto 60 60 lineto 10 60 lineto 10 50 lineto 30 50 lineto closepath } def
/A { 60 0 0 0 60 60 setcachedevice
0 0 moveto 10 0 lineto 20 20 lineto 40 20 lineto 50 0 lineto 60 0 lineto 30 60 lineto closepath
25 30 moveto 35 30 lineto 30 40 lineto closepath
} def
/O { 50 0 0 0 60 60 setcachedevice
0 0 moveto 30 0 rmoveto 14 0 20 10 20 30 rcurveto
0 20 -6 30 -20 30 rcurveto
-14 0 -20 -10 -20 -30 rcurveto
0 -20 6 -30 20 -30 rcurveto closepath
0 10 rmoveto 7 0 10 7 10 20 rcurveto
0 14 -3 20 -10 20 rcurveto
-7 0 -10 -6 -10 -20 rcurveto
0 -14 6 -20 10 -20 rcurveto closepath
} def
end
currentdict end
/FirstFont exch definefont
/FirstFont findfont 140 scalefont setfont
100 0 moveto gsave 15 rotate 0.7 setgray (ATTO) show grestore
/FirstFont findfont 24 scalefont setfont
150 100 moveto (TATATATATATATATA) show
0 1 12 { 100 0 moveto (T) show 15 rotate } for
`);
Note that this code will probably not work in another PostScript interpreter. We did not really check if all parameters of the font are defined.
The codebase is 1880 lines ps20240805.js The references to the font files areCMUSerif-Roman.js CMUSerif-Bold.js CMUSerif-Italic.js CMUSansSerif.js CMUSansSerif-Oblique.js CMUSansSerif-Bold.js CMUSansSerif-BoldOblique.js CMUTypewriter-Regular.js CMUTypewriter-Italic.js CMUTypewriter-Bold.js CMUTypewriter-BoldItalic.js
My Journey to PostScript