Now we can measure text it is straightforward to align text to right.
As it is to align center
Run
postScriptEditor(`
(CMUSerif-Roman) run
/CMUSerif-Roman findfont 30 scalefont setfont
20 290 moveto (When shall we three meet again) show
20 255 moveto (In thunder, lightning, or in rain?) show
20 220 moveto (When the hurlyburly's done,) show
20 185 moveto (When the battle's lost and won.) show
/centershow { dup stringwidth -0.5 mul exch -0.5 mul exch rmoveto show } def
300 150 moveto (When shall we three meet again) centershow
300 115 moveto (In thunder, lightning, or in rain?) centershow
300 80 moveto (When the hurlyburly's done,) centershow
300 45 moveto (When the battle's lost and won.) centershow
`);
But to justify, we need a new operator that can modify the width o space. It is widthshow which adds an x-y Offset after each occurence of a chosen character.
Run
operators.widthshow = function(context) {
const [s, ch, cy, cx ] = context.pop("string", "number", "number", "number");
if (!context.graphics.current.length) {
context.stack.push(new rpnError("nocurrentpoint"));
return context;
}
if (!s) return context;
const [cx0, cy0] = context.transform(cx.value, cy.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];
if (c == ch.value) {
context.graphics.matrix[4] += cx0;
context.graphics.matrix[5] += cy0;
}
}
context.dictstack.pop();
context.graphics.current = [ context.graphics.matrix[4], context.graphics.matrix[5] ];
context.graphics.matrix = bak;
return context;
};
But widthshow does not calculate the amount of offset we have to apply. For this, we need to calculate in PostScript width, deduct this from the target width and divide it by the number of spaces. We need the PostScript operators loop, exit amd search for it.
Run
operators.exit = function(context) {
context.error("exit");
return context;
};
operators.loop = function(context) {
const [doit] = context.pop("any");
if (!doit) return context;
while (true) {
context = rpn(doit.value, context);
if (context.lasterror == "exit") {
context.lasterror = "";
context.stack.pop();
return context;
}
if (context.lasterror) {
return context;
}
}
};
operators.search = function(context) {
const [seek, source] = context.pop("string", "string");
if (!seek) return context;
const f = source.value;
const s = seek.value;
const n = f.indexOf(s);
if (n == -1) {
context.stack.push(source);
source.reference.inc();
context.stack.push(new rpnNumber(0));
return context;
}
context.stack.push(new rpnString(f.substring(n+s.length), context.heap));
context.stack.push(seek);
seek.reference.inc();
context.stack.push(new rpnString(f.substring(0,n), context.heap));
context.stack.push(new rpnNumber(1));
return context;
};
This seems to work.
Run
postScriptEditor(`
(CMUSerif-Roman) run
/CMUSerif-Roman findfont 30 scalefont setfont
/countblanks { 10 dict begin /c 0 def { ( ) search { /c c 1 add def pop pop } { pop c end exit } ifelse } loop } def
/justifyshow { /s exch def s stringwidth pop sub s countblanks div 0 32 s widthshow } def
20 150 moveto 500 (When shall we three meet again) justifyshow
20 115 moveto 500 (In thunder, lightning, or in rain?) justifyshow
20 80 moveto 500 (When the hurlyburly's done,) justifyshow
20 45 moveto 500 (When the battle's lost and won.) justifyshow
`);
But what happens in a rotated context? The text is not aligned
Run
postScriptEditor(`
(CMUSerif-Roman) run
/CMUSerif-Roman findfont 30 scalefont setfont
10 rotate
/rightshow { dup stringwidth -1 mul exch -1 mul exch rmoveto show } def
580 150 moveto (When shall we three meet again) rightshow
580 115 moveto (In thunder, lightning, or in rain?) rightshow
580 80 moveto (When the hurlyburly's done,) rightshow
580 45 moveto (When the battle's lost and won.) rightshow
`);
It looks like some older operators were not correct. Stringdwidth should run from the current point and currentpoint should give back the user space coordinates. We add context.itransform() to it. Run the hotfix below and render the above code again.
Run
operators.stringwidth = function(context) {
context = rpn("3 dict begin /fill { } def gsave currentpoint /y0 exch def /x0 exch def show currentpoint y0 sub exch x0 sub exch grestore end", context);
return context;
};
operators.currentpoint = function(context) {
const [x, y] = context.itransform(context.graphics.current[0], context.graphics.current[1]);
context.stack.push(new rpnNumber(x));
context.stack.push(new rpnNumber(y));
return context;
};
The codebase has now 1948 lines ps20240823.js
My Journey to PostScript