In the last chapter, we have learnt to run PostScript code to define fonts. In this chapter, we will use the fonts.
findfont retrieves a font we have earlier defined width definefont and puts it on a stack. scalefont sets the size and setfont makes it the current font.
The operator show creates the path for the string and fills it. It first creates a backup of the current transformation matrix. Then it multiplies the FontMatrix with the size and with the transformation matrix. Now we are in user space for the font which is 2048 times enlarged. We make the font the current dict and loop through the characters: We put the font to the stack, the character code to the stack and invoke the BuildChar operator.
The BuildChar operator is provided by the font. It looks up the glyph, creates the path and fills it. It gives a feedback on geometry with the setcachedevice. This is an array with offset x and y and also the bottom left and top right coordinates. We use it to move the transformation matrix.
We also need to implement some operators that the font uses: get for dictionary, known, copy and exec.
Run
operators.findfont = function(context) {
const [n] = context.pop("name");
if (!n) return context;
const font = context.fontdict[n.value];
context.stack.push(font);
return context;
};
operators.scalefont = function(context) {
const [scale, font] = context.pop("number","dictionary");
if (!font) return context;
context.graphics.size = scale.value;
context.stack.push(font);
return context;
};
operators.setcachedevice = function(context) {
const [ury, urx, lly, llx, wy, wx] = context.pop("number","number","number","number","number","number");
const cd = [wx.value, wy.value, llx.value, lly.value, urx.value, ury.value ];
context.graphics.cachedevice = cd;
return context;
};
operators.setfont = function(context) {
const [font] = context.pop("dictionary");
context.graphics.font = font.value.FontName.value;
return context;
};
matrixMultiplication = function (a, b) {
const c = new Array(6);
c[0] = a[0] * b[0] + a[1] * b[2] ;
c[1] = a[0] * b[1] + a[1] * b[3] ;
c[2] = a[2] * b[0] + a[3] * b[2] ;
c[3] = a[2] * b[1] + a[3] * b[3] ;
c[4] = a[4] * b[0] + a[5] * b[2] ;
c[5] = a[4] * b[1] + a[5] * b[3] ;
return c;
}
operators.show = function(context) {
const [s] = context.pop("string");
if (!s) return context;
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;
}
operators.get = function(context) {
const [b, a] = context.pop("number,name", "array,string,dictionary");
if (!b) return context;
if (a.type == "dictionary") {
if (b.type == "name") {
const elem = a.value[b.value];
if (!elem) return context.error("undefined");
context.stack.push(elem);
return context;
} else {
return context.error("typeerror");
}
}
if (b.value < 0) return context.error("rangerror");
if (b.value >= a.value.length) return context.error("rangerror");
if (a.type == "array") {
const elem = a.value[b.value];
if (elem.reference) elem.reference.inc();
context.stack.push(elem);
} else {
context.stack.push(new rpnNumber(a.value.charCodeAt(b.value)));
}
a.reference.dec();
return context;
};
unitTest("/foo 1 def currentdict /foo get", "1")
unitTest("/foo 1 def 1 dict /foo get", "!undefined")
operators.exec = function(context) {
const [e] = context.pop("procedure");
if (!e) return context.error("xchec");
context = rpn(e.value,context);
return context;
};
unitTest("2 { 3 mul } exec ","6");
unitTest("/tri { 3 mul } def 2 currentdict /tri get exec ","6");
operators.copy = function(context) {
const [n] = context.pop("number");
if (!n) return context.error("stackunderflow");
if (n.value < 1) return context.error("rangerror");
if (n.value > context.stack.length) return context.error("stackunderflow");
const arr = [];
for (var i = 0; i < n.value; i++ ) {
arr.push(context.stack.pop());
}
arr.reverse();
context.stack = context.stack.concat(arr).concat(arr);
return context;
};
unitTest("1 2 3 3 copy","1 2 3 1 2 3");
operators.known = function(context) {
const [b, a] = context.pop("name", "dictionary");
if (!b) return context;
const elem = a.value[b.value];
if (elem) {
context.stack.push(new rpnNumber(1));
} else {
context.stack.push(new rpnNumber(0));
}
return context;
};
unitTest("/foo 1 def currentdict /foo known", "1")
unitTest("/foo 1 def 1 dict /foo known", "0")
Test
Run
postScriptEditor(`(CMUSerif-Roman) run
/CMUSerif-Roman findfont 300 scalefont setfont
0 100 moveto 0.9 setgray (Post) show
/CMUSerif-Roman findfont 60 scalefont setfont
100 50 translate 60 rotate 0.7 setgray
7 { 0 0 moveto (PostScript) show
-10 rotate
currentgray 0.1 sub setgray } repeat
`);
Seems to work. We are at 1859 lines ps20240804.js
Just to check if the minimal HTML page still works, here is minimal2.html with 29 lines of HTML.
My Journey to PostScript