Today, we test the exercices of the excellent book Understanding PostScript programming by David A. Holzgang which made me learn PostScript for the first time in the eighties. All exercices work on a page A4 (595 * 842 pixel)
Page 33.
Page 52.
Run
postScriptEditor(`
110 dict begin /width 595 def /height 842 def /canvas 1 def /raw 0 def currentdict setpagedevice end
/FirstColumnStart 72 def
/SecondColumnStart 4 72 mul def
/VerticalStart 9.2 72 mul def
/LineSpace 14 def
/ParaSpace 28 def
/CMUSerif-Roman findfont 12 scalefont setfont
/NextLine VerticalStart def
SecondColumnStart NextLine moveto
(The quick brown fox jumps over the lazy dog) show
/NextLine NextLine LineSpace sub def
SecondColumnStart NextLine moveto
(dog and then the quick brown fox jumps over that) show
/NextLine NextLine LineSpace sub def
SecondColumnStart NextLine moveto
(lazy dog again and again... but the) show
/NextLine NextLine LineSpace sub def
SecondColumnStart NextLine moveto
(dog just doesn't pay attention) show
/NextLine NextLine ParaSpace sub def
/SavePara NextLine def
SecondColumnStart NextLine moveto
(Here is the quick brown fox playing the same) show
/NextLine NextLine LineSpace sub def
SecondColumnStart NextLine moveto
(games again in a second paragraph. You) show
/NextLine NextLine LineSpace sub def
SecondColumnStart NextLine moveto
(can easily see how this could go on and on and ...) show
/CMUSerif-Italic findfont 12 scalefont setfont
/NextLine VerticalStart def
FirstColumnStart NextLine moveto
(This is a classic text) show
/NextLine NextLine LineSpace sub def
FirstColumnStart NextLine moveto
(and commentary layout) show
/NextLine SavePara def
FirstColumnStart NextLine moveto
(using two fonts) show
/NextLine NextLine LineSpace sub def
FirstColumnStart NextLine moveto
(Times Roman and Times Italic) show
showpage
`);
Page 82. For the parser: a number can also start with a period.
Run
postScriptEditor(`
110 dict begin /width 595 def /height 842 def /canvas 1 def /raw 0 def currentdict setpagedevice end
/inch { 72 mul } def
/advanceline { /nextline nextline linespace sub def } def
/advancepara { /nextline nextline paraspace sub def } def
/centertext { dup stringwidth pop 2 div
rightmargin leftmargin sub 2 div
exch sub
leftmargin add
nextline moveto
show } def
/bodytext { secondcolumn nextline moveto show } def
/rightjustifytext { dup stringwidth pop
rightcolumn exch sub
nextline moveto
show } def
/topstart 9.5 inch def
/bodystart 8.5 inch def
/linespace 14 def
/paraspace 28 def
/leftmargin .5 inch def
/rightmargin 8 inch def
/rightcolumn 2.5 inch def
/secondcolumn 3 inch def
/CMUSerif-Bold findfont 14 scalefont setfont
/nextline topstart def
(ACME WIDGETS INCORPORATED) centertext
/nextline nextline 20 sub def
(Fiscal Year 1986) centertext
/CMUSerif-Roman findfont 12 scalefont setfont
/nextline bodystart def
(Acme Widgets war founded in 1952 by Dippy and Daffy Acme) bodytext advanceline
(to produce hight technnology widgets for the booming areospace) bodytext advanceline
(industry. Acme was quickly recognized as being the best) bodytext advanceline
(technology and mandufactoring methods has kept Acme Widgets) bodytext advanceline
(in the forefront of this industry.) bodytext advancepara
/saveparaone nextline def
(Acme Widgets sells 72% of all widgets produced in the) bodytext advanceline
(United States and it has 39% of the growing international) bodytext advanceline
(market.) bodytext advancepara
/saveparatwo nextline def
(Headquarters: Burbank, California) bodytext advanceline
(Domestic sales offices in Seattle, WA: Dallas, TX,) bodytext advanceline
(and Washington, D.C.) bodytext advancepara
/saveparathree nextline def
($223 million in gross sales for fiscal 1985:) bodytext advanceline
($248 million in gross sales projected for fiscal 1986.) bodytext advanceline
(and Washington, D.C.) bodytext advancepara
/CMUSerif-Bold findfont 12 scalefont setfont
/nextline bodystart def
(History:) rightjustifytext
/nextline saveparaone def
(Market position:) rightjustifytext
/nextline saveparatwo def
(Offices:) rightjustifytext
/nextline saveparathree def
(Financial position:) rightjustifytext
showpage
`);
Page 123. Here rpnCanvasDevice, rpnSVGDevice and rpnPDFDevice closed the stroke path automatically. Also, the size of the PDF was not adapted.
Run
postScriptEditor(`
10 dict begin /width 595 def /height 842 def /canvas 1 def /raw 1 def /svg 1 def /pdf 1 def /pdfurl 1 def currentdict setpagedevice end
/inch { 72 mul } def
3 inch 6 inch moveto 5 inch 6 inch lineto
0.1 inch setlinewidth
3 inch 6 inch moveto 3 inch 8 inch lineto
stroke
8 inch 6 inch moveto
6 inch 6 inch lineto 6 inch 8 inch lineto
stroke
showpage
`);
Page 125.
Run
postScriptEditor(`
110 dict begin /width 595 def /height 842 def /canvas 1 def /raw 0 def currentdict setpagedevice end
/inch { 72 mul } def
3 inch 5 inch moveto
4 inch 6 inch lineto
4 inch 5 inch moveto
3 inch 6 inch lineto
stroke
6 inch 5 inch moveto
7 inch 6 inch lineto
7 inch 5 inch moveto
6 inch 6 inch lineto
stroke
showpage
`);
Page 129.
Run
postScriptEditor(`
110 dict begin /width 595 def /height 842 def /canvas 1 def /raw 0 def currentdict setpagedevice end
/inch { 72 mul } def
newpath
3 inch 5 inch moveto
3 inch 6 inch lineto
4 inch 6 inch lineto
4 inch 5 inch lineto
closepath
stroke
6 inch 5 inch moveto
7 inch 6 inch lineto
6 inch 7 inch lineto
5 inch 6 inch lineto
closepath
stroke
newpath
3 inch 2 inch moveto
0 1 inch rlineto
1 inch 0 rlineto
0 -1 inch rlineto
closepath
3 inch 0 inch rmoveto
1 inch 1 inch rlineto
-1 inch 1 inch rlineto
-1 inch -1 inch rlineto
closepath
stroke
showpage
`);
Page 136.
Run
postScriptEditor(`
110 dict begin /width 595 def /height 842 def /canvas 1 def /raw 0 def currentdict setpagedevice end
/inch { 72 mul } def
% height base righttriangle
/righttriangle
{
/base exch def
/hgt exch def
base hgt rlineto
0 hgt neg rlineto
closepath
} def
% height base isotriangle
/isotriangle
{
/base exch def
/hgt exch def
/halfbase base 2 div def
halfbase hgt rlineto
halfbase hgt neg rlineto
closepath
} def
newpath
2 inch 5 inch moveto
1.5 inch 1 inch righttriangle
fill
5 inch 5 inch moveto
1.5 inch 1.2 inch isotriangle
fill
showpage
`);
Page 140.
Run
postScriptEditor(`
10 dict begin /width 595 def /height 842 def /canvas 1 def /raw 0 def currentdict setpagedevice end
/inch { 72 mul } def
% height base righttriangle
/righttriangle
{
/base exch def
/hgt exch def
base hgt rlineto
0 hgt neg rlineto
closepath
} def
% height base isotriangle
/isotriangle
{
/base exch def
/hgt exch def
/halfbase base 2 div def
halfbase hgt rlineto
halfbase hgt neg rlineto
closepath
} def
% dim squarebox
/squarebox
{
/dim exch def
0 dim rlineto
dim 0 rlineto
0 dim neg rlineto
closepath
} def
newpath
2 inch 5 inch moveto
1 inch squarebox
fill
0.25 setgray
2.5 inch 5 inch moveto
2 inch 2 inch isotriangle
fill
4 inch 5 inch moveto
1 inch squarebox
0.75 setgray
fill
showpage
`);
Page 142.
Run
postScriptEditor(`
10 dict begin /width 595 def /height 842 def /canvas 1 def /raw 0 def currentdict setpagedevice end
/inch { 72 mul } def
% height base righttriangle
/righttriangle
{
/base exch def
/hgt exch def
base hgt rlineto
0 hgt neg rlineto
closepath
} def
% height base isotriangle
/isotriangle
{
/base exch def
/hgt exch def
/halfbase base 2 div def
halfbase hgt rlineto
halfbase hgt neg rlineto
closepath
} def
% dim squarebox
/squarebox
{
/dim exch def
0 dim rlineto
dim 0 rlineto
0 dim neg rlineto
closepath
} def
newpath
3 inch 5 inch moveto
2 inch squarebox
0 setgray
fill
3 inch 5 inch moveto
2 inch 2 inch righttriangle
1 setgray
fill
showpage
`);
Page 154. For this exercise, we need to add an arc operator do draw circles. There is an arc operator that draws arcs segments from the center and then there is the arcto operator that draws arcs that integrate in paths. Anyway we will simulate the arc with Bezier.
Run
operators.arc = function(context) {
const [angle2, angle1, radius, centery, centerx] = context.pop("number","number","number","number","number");
if (!centerx) return context;
// assure angle2 > angle1!
while (angle2.value <= angle1.value) angle2.value += 360;
console.log(angle1.value)
console.log(angle2.value)
// approximation works for 90 dregrees max, we split
var first = true;
var target = angle1.value;
const subpath = [];
while(angle1.value < angle2.value) {
target = angle1.value + Math.min(90, angle2.value - target);
// starting point
let [x1, y1] = [centerx.value + radius.value * Math.cos(angle1.value * Math.PI/180), centery.value + radius.value * Math.sin(angle1.value * Math.PI/180)];
// end point
let [x4, y4] = [centerx.value + radius.value * Math.cos(target * Math.PI/180), centery.value + radius.value * Math.sin(target * Math.PI/180)];
// control points
let lt = radius.value * (target-angle1.value)/90 * 4 /3 * (Math.sqrt(2)-1) ;
let [x2, y2] = [x1 + lt * Math.cos((angle1.value + 90)*Math.PI/180), y1 + lt * Math.sin((angle1.value + 90)*Math.PI/180)];
let [x3, y3] = [x4 + lt * Math.cos((target - 90)*Math.PI/180), y4 + lt * Math.sin((target - 90)*Math.PI/180)];
let [x1t, y1t] = context.transform(x1, y1);
let [x2t, y2t] = context.transform(x2, y2);
let [x3t, y3t] = context.transform(x3, y3);
let [x4t, y4t] = context.transform(x4, y4);
if (context.graphics.current.length) {
subpath.push(["L", context.graphics.current[0], context.graphics.current[1], x1t, y1t]);
}
subpath.push(["C", x1t, y1t, x2t, y2t, x3t, y3t, x4t, y4t]);
angle1.value += 90;
context.graphics.current = [ x4t, y4t ];
}
context.graphics.path.push(subpath);
return context;
};
Run
postScriptEditor(`
10 dict begin /width 595 def /height 842 def /canvas 1 def /raw 0 def currentdict setpagedevice end
/inch { 72 mul } def
newpath
0 setgray
5 inch 5 inch moveto
4 inch 5 inch 3 inch 45 135 arc
stroke
5 inch 2 inch moveto
4 inch 2 inch 3 inch 45 135 arc
closepath
stroke
showpage
`);
Page 157
Run
postScriptEditor(`10 dict begin /width 595 def /height 842 def /canvas 1 def /raw 0 def currentdict setpagedevice end
/inch { 72 mul } def
/squarebox { /dim exch def
0 dim rlineto
dim 0 rlineto
0 dim neg rlineto
closepath } def
/circle { 0 360 arc } def
newpath
4 inch 7 inch 2 inch circle
.25 setgray
stroke
3 inch 2 inch moveto
2 inch squarebox
fill
4 inch 3 inch 1 inch circle
1 setgray
fill
showpage
`);
For the the next exercise we need the operator arcto that is used for rounded rectangles.
Run
operators.arcto = function(context) {
const [r, y2, x2, y1, x1] = context.pop("number","number","number","number","number");
if (!x1) return context;
if (!context.graphics.current.length) {
context.error("nocurrentpoint");
return context;
}
const [x0, y0] = context.itransform(context.graphics.current[0], context.graphics.current[1]);
var a1 = Math.atan2(y1.value - y0, x1.value - x0);
if (a1 < 0) a1 += 2 * Math.PI;
const xt1 = x1.value - Math.cos(a1) * r.value;
const yt1 = y1.value - Math.sin(a1) * r.value;
var a2 = Math.atan2(y2.value - y1.value, x2.value - x1.value);
if (a2 < 0) a2 += 2 * Math.PI;
const xt2 = x1.value + Math.cos(a2) * r.value;
const yt2 = y1.value + Math.sin(a2) * r.value;
const lt = Math.abs(r.value * 4 /3 * (Math.sqrt(2)-1)) ;
const xc2 = xt1 + Math.cos(a1) * lt;
const yc2 = yt1 + Math.sin(a1) * lt;
const xc3 = xt2 - Math.cos(a2) * lt;
const yc3 = yt2 - Math.sin(a2) * lt;
const subpath = context.graphics.path.pop();
const [x1p, y1p] = context.transform(xt1, yt1);
const [x2p, y2p] = context.transform(xc2, yc2);
const [x3p, y3p] = context.transform(xc3, yc3);
const [x4p, y4p] = context.transform(xt2, yt2);
subpath.push(["L", context.graphics.current[0], context.graphics.current[1], x1p, y1p]);
subpath.push(["C", x1p, y1p, x2p, y2p, x3p, y3p, x4p, y4p]);
context.graphics.path.push(subpath);
context.graphics.current = [x4p, y4p];
context.stack.push(new rpnNumber(xt1));
context.stack.push(new rpnNumber(yt1));
context.stack.push(new rpnNumber(xt2));
context.stack.push(new rpnNumber(yt1));
return context;
};
Page 163
Run
postScriptEditor(`10 dict begin /width 595 def /height 842 def /canvas 1 def /raw 0 def currentdict setpagedevice end
/inch { 72 mul } def
/squarebox { /dim exch def
0 dim rlineto
dim 0 rlineto
0 dim neg rlineto
closepath } def
/screenbox { /dim exch def
currentpoint
/ypos exch def
/xpos exch def
0.5 inch 0 rmoveto
xpos dim add ypos
xpos dim add ypos dim add
.25 inch arcto 4 { pop } repeat
xpos dim add ypos dim add
xpos ypos dim add
.25 inch arcto 4 { pop } repeat
xpos ypos dim add
xpos ypos
.25 inch arcto 4 { pop } repeat
xpos ypos
xpos dim add ypos
.25 inch arcto 4 { pop } repeat
closepath } def
newpath
3 inch 7 inch moveto
2 inch screenbox
stroke
3 inch 3 inch moveto
2 inch squarebox
.25 setgray
fill
3.25 inch 3.25 inch moveto
1.5 inch screenbox
.8 setgray
fill
showpage
`);
Page 170
Run
postScriptEditor(`10 dict begin /width 595 def /height 842 def /canvas 1 def /raw 0 def currentdict setpagedevice end
/inch { 72 mul } def
/squarebox { /dim exch def
0 0 moveto
0 dim rlineto
dim 0 rlineto
0 dim neg rlineto
closepath } def
/screenbox { /dim exch def
0.5 inch 0 moveto
dim 0 dim dim 0.2 inch arcto 4 { pop } repeat
dim dim 0 dim 0.2 inch arcto 4 { pop } repeat
0 dim 0 0 0.2 inch arcto 4 { pop } repeat
0 0 dim 0 0.2 inch arcto 4 { pop } repeat
closepath } def
1 setlinewidth
newpath
0.25 setgray
1 inch 1 inch translate
1.5 inch screenbox
fill
2 inch 2 inch translate
1.5 inch screenbox
fill
newpath
2 inch 2 inch translate
1.5 inch screenbox
stroke
showpage
`);
Page 173
Run
postScriptEditor(`10 dict begin /width 595 def /height 842 def /canvas 1 def /raw 0 def currentdict setpagedevice end
/inch { 72 mul } def
/mark { 0 setgray newpath 0 0 0 0 3 0 360 arc fill } def
/squarebox { /dim exch def
0 dim rlineto
dim 0 rlineto
0 dim neg rlineto
closepath } def
/screenbox { /dim exch def
0.5 inch 0 moveto
dim 0 dim dim 0.25 inch arcto 4 { pop } repeat
dim dim 0 dim 0.25 inch arcto 4 { pop } repeat
0 dim 0 0 0.25 inch arcto 4 { pop } repeat
0 0 dim 0 0.25 inch arcto 4 { pop } repeat
closepath } def
newpath
1 inch 1 inch translate
30 rotate
1.5 inch screenbox
stroke
4 inch 2 inch translate
1.5 inch screenbox
stroke
2 inch 2 inch moveto
15 rotate
2 inch squarebox
stroke
showpage
`);
Page 175
Run
postScriptEditor(`10 dict begin /width 595 def /height 842 def /canvas 1 def /raw 0 def currentdict setpagedevice end
/inch { 72 mul } def
/squarebox { /dim exch def
0 dim rlineto
dim 0 rlineto
0 dim neg rlineto
closepath } def
newpath
1 inch 1 inch scale
2 2 moveto
2 squarebox
fill
4 6 moveto
0.5 2 scale
2 squarebox
.25 setgray
fill
showpage
`);
Page 181
Run
postScriptEditor(`10 dict begin /width 595 def /height 842 def /canvas 1 def /raw 0 def currentdict setpagedevice end
/inch { 72 mul } def
/mark { 0 setgray newpath 0 0 0 0 3 0 360 arc fill } def
/screenbox {
0.5 0 moveto
1 0 1 1 0.25 arcto 4 { pop } repeat
1 1 0 1 0.25 arcto 4 { pop } repeat
0 1 0 0 0.25 arcto 4 { pop } repeat
0 0 1 0 0.25 arcto 4 { pop } repeat
closepath } def
gsave
newpath
3 inch 8 inch translate
1 inch 1 inch scale
screenbox
.25 setgray
fill
grestore
gsave
5 inch 8 inch translate
2 inch 1 inch scale
screenbox
fill
grestore
gsave
3 inch 3 inch translate
2 inch 2 inch scale
screenbox
gsave
.5 setgray
fill
grestore
1 2 inch div setlinewidth
stroke
grestore
showpage
`);
For the next exercice, we need the count operator that returns the stack count.
Run
operators.count = function(context) {
context.stack.push(new rpnNumber(context.stack.length));
return context;
};
operators.newpath = function(context) {
context.path = [];
context.graphics.current = [];
return context;
}
Page 218
Run
postScriptEditor(`10 dict begin /width 595 def /height 842 def /canvas 1 def /raw 0 def currentdict setpagedevice end
% prologue
% support procedures
/inch { 72 mul } def
/line { 12 mul } def
/halfinch { 36 mul } def
/centertext { /right exch def /left exch def
dup
stringwidth pop 2 div
right left sub 2 div
exch sub left add
cline moveto
show
} def
/rightjustifytext { /rightcolumn exch def
dup
stringwidth pop
rightcolumn exch sub
cline moveto show } def
/screenbox { /dimy exch def /dimx exch def
newpath
.5 inch 0 moveto
dimx 0 dimx dimy .25 inch arcto 4 {pop} repeat
dimx dimy 0 dimy .25 inch arcto 4 {pop} repeat
0 dimy 0 0 .25 inch arcto 4 {pop} repeat
0 0 dimx 0 .25 inch arcto 4 {pop} repeat
closepath } def
/cornerbox { /dimy exch def /dimx exch def
newpath
0 0 moveto 0 12 rlineto 0 0 moveto 12 0 rlineto
dimx 0 moveto 0 12 rlineto dimx 0 moveto -12 0 rlineto
0 dimy moveto 0 -12 rlineto 0 dimy moveto 12 0 rlineto
dimx dimy moveto 0 -12 rlineto dimx dimy moveto -12 0 rlineto stroke } def
/sun { /radius exch def newpath 0 0 radius 0 360 arc } def
/hill { /base exch def /hgt exch def /halfbase base 2 div def
0 0 moveto halfbase hgt lineto base 0 lineto } def
/verticallines {
newpath
144 47 line moveto 144 12.5 line lineto
317 47 line moveto 317 12.5 line lineto
365 47 line moveto 365 12.5 line lineto
413 47 line moveto 413 12.5 line lineto
467 47 line moveto 467 12.5 line lineto
stroke } def
% elemments
/graphic {
gsave
3 0 translate 8 8 hill .3 setgray fill
grestore
gsave
14 9 translate 1.5 sun stroke
grestore
gsave
5 6 hill .7 setgray fill 7 0 translate 5 6 hill fill
grestore } def
/logotitles {
/CMUSansSerif-BoldOblique findfont 18 scalefont setfont
/cline 0.75 inch def
(MOUNTAIN SPORTS, LTD.) 0 8 inch centertext
/CMUSansSerif findfont 10 scalefont setfont
/cline 0.5 inch def
(123 Mountain Way, Chatsworth, CA 91311) 0 8 inch centertext
/cline .5 inch 12 sub def
((818) 882-8144 (213) 5.55-1912) 0 8 inch centertext} def
/logo {
logotitles
9.5 9.5 scale
1 9.5 div setlinewidth
graphic } def
/headtitles {
/CMUSansSerif-Bold findfont 15 scalefont setfont
/cline 52 line def
(INVOICE) .5 inch 8 inch centertext
/CMUSansSerif-Bold findfont 12 scalefont setfont
(NO. :) 7 inch rightjustifytext } def
/datatitles {
newpath
.5 inch 45 line moveto 7.5 inch 0 rlineto stroke
.5 inch 45.5 line moveto
/CMUSansSerif findfont 10 scalefont setfont
/cline 45.5 line def
(ITEM NO) 72 144 centertext
(DESCRIPTION) 144 317 centertext
(ORDER) 318 365 centertext
(SHIP) 366 413 centertext
(UNIT #) 414 467 centertext
(EXT. PRICE) 468 540 centertext
} def
/foottitles {
newpath
.5 inch 12.5 line moveto 7.5 inch 0 rlineto stroke
467 12.5 line moveto 467 4 line lineto stroke
/CMUSansSerif-Bold findfont 10 scalefont setfont
/cline 11 line def
(SUBTOTAL) 463 rightjustifytext
/cline 9 line def
(SALES & TAX) 463 rightjustifytext
/cline 7 line def
(SHIPPING) 463 rightjustifytext
/cline 5 line def
(TOTAL) 463 rightjustifytext
} def
/forminvoice {
gsave
.5 inch 55 line translate
logo
grestore
headtitles
gsave
.5 inch 48 line translate
2.5 inch 1 inch cornerbox
grestore
gsave
.5 inch 4 line translate
7.5 inch 43 line screenbox
2 setlinewidth
stroke
grestore
verticallines
datatitles
foottitles
grestore } def
/address {
.6 inch 49 line moveto count 3 gt { show } if
.6 inch 50 line moveto count 2 gt { show } if
.6 inch 51 line moveto count 1 gt { show } if
.6 inch 52 line moveto count 0 gt { show } if } def
/invoicenumber {
/cline 52 line def 8 inch rightjustifytext } def
/date {
/cline 50 line def 8 inch rightjustifytext
} def
/lineone {
1 inch 43 line moveto show /cline 43 line def } def
/nextline {
/cline cline 18 sub def 1 inch cline moveto show } def
/totale {
/cline 5 line def 7.5 inch rightjustifytext
/cline 7 line def 7.5 inch rightjustifytext
/cline 9 line def 7.5 inch rightjustifytext
/cline 11 line def 7.5 inch rightjustifytext } def
% mainstat
forminvoice
(10423)
invoicenumber
/CMUTypewriter-Regular findfont 11 scalefont setfont
(David Holzgang)
(2261 Penfield Ave.)
(Chatsworth, CH 91311)
address
(20-Feb-87)
date
( 12-456 Bodyback - Blue 1 1 28.95 $28.95) lineone
( 28-145 Wool socks - Size 10 4 4 3.95 $15.80) nextline
( 95-004 Strawberries 10 10 1.45 $14.50) nextline
( 95-010 Abricots 5 5 1.29 $6.45) nextline
( 96-148 Chicken Cacciatore 4 4 2.95 $11.80) nextline
( 96-104 Beef Stew 4 3 2.95 $8.85) nextline
( 96-104 Spaghetti with Meat Sauce 2 2 2.95 $5.90) nextline
( 96-242 Trail Mix 4 4 0.98 $3.92) nextline
( 35-129 First Aid Kit 1 1 9.49 $9.49) nextline
($105.66)
($6.87)
($3.42)
($115.95)
totale
showpage
`);
ps20241115.js 3587 lines, 129 KB.
My Journey to PostScript