20081211

Quick PostScript Programming Tutorial



Examples of what you could do after learning PS:
Koch curve, Mandelbrot set (top)
Christmas card full of auto-generated fractals (bottom)

For those of you who don't know, PostScript is a programming language (and also the name of the type of files you write in this language) used as a way to define what a page looks like. A PostScript file contains instructions which create text, or draw images, and these instructions can do quite a lot of work. PostScript is directly processed by almost all laser printers, and if you are a Linux or MacOs user you already have some mean to process and open it. In Windows you can download an install AFPL GhostScript, or visit this page to convert a PS to PDF.

As you may already know, PostScript is a Turing complete programming language. This means that any program you can write in any programming language, you can write in PostScript (albeit it will be slower). As geekish and curious as I am, a few days ago I decided to learn some of its core features, and as I tended to forget new languages almost as quick as I learnt them (Oh Python, where are thou?) I started writing some small reminders. If I forgot, I just read them and ready to go. In this quick tutorial/knowledge-refresher for PostScript programming, I'll sketch some key points and show a pair of examples: a Mandelbrot set and a Koch's snowflake. As a side note, the code for the snowflake is quite general for L-systems."My" code for this system is a rewriting, rescaling and simplifying of the code from Michel Charpentier at this page. When you are through this tutorial, take a look at his code, he is much more clever than I am when PostScript-ing, and his results are awesome.

From now on, PS will mean, of course, PostScript.

PS is stack oriented, based on RPN (reverse polish notation). This means that you have a stack, like a stack of plates, and if you want to multiply 3 by 4, you should write 3 4 mul to push 3 and 4 to the stack (4 being the topmost element now). Mul is the command for multiplying, as you may guess. 3 and 4 get pushed to the stack and then multiplied. Now the instruction mul leaves his result in the stack, ready to be used. Keep how the stack works in mind. When writing some complex formulae or algorithms, the state of the stack is very important, if you start to lose track, write it down on the side. Also, it is more efficient and quicker to just use what you have on the stack and don't rely on named variables.

To make a comment within a PS program, write %, and it is customary to start every PS file with the comment line %!PS which allows all devices to recognize it as PostScript.

There are two separate stacks: a operand stack and a dictionary stack. Well, in fact as xardox from Reddit points out, there are 3 stacks, these two and the function stack: thanks to it you don't need to mess with function call and return addresses in the other two stacks. In the first one, operands (as 3 and 4 in the previous example) are kept. In the second one, names of operators or variables are kept. And as it is a stack, if you define a variable named A twice, when you call it only the most recent (nearer to the top of the stack) will be used.

In fact, defining a variable or defining an operator has the same syntax as defining a variable. /name content def for a variable, /name {operations} def for an operator. I find this very nice (very Lisp-y). Variable and function names are made of combinations of characters (a...z) and numbers, as usual in other programming languages. Curiously enough, you can name a variable 7U if you want, but starting variables with a number is "bad style", usually. Keep it in mind. You may wonder why I wrote /name when defining a variable. Well preceding a name with a / (slash) puts the name itself as operand in the stack, without the slash, PS looks for it in the dictionary stack and puts its corresponding value in the stack. (Thanks to xardox from Reddit for the slash/backslash distinction!).

After so much talking, sure you are wondering How does one draw??. Well, first we have to define a path with newpath, then we issue some movement and tracing commands, then fill or stroke these commands, and finish the path. As a starting point, a square 200 points per side in the bottom left of the page:

newpath
200 200 moveto % as the name says, move the cursor to x y
200 0 rlineto % line from actual point toward the right
% side
0 200 rlineto % the actual point is now in the right side,
% go up
-200 0 rlineto % go left
0 -200 rlineto % ta daa A Square
closepath
stroke
showpage
newpath and closepath enclose a drawing path, and moveto moves the cursor to an starting position. rlineto creates lines in relative coordinates (from the actual point taken as (0,0)), a similar construct with lineto uses global coordinates (and there is also a rmoveto). stroke draws the previous paths, and if we were to put fill instead of stroke, it would be a black square.

Changing colors is as easy as (before stroking or filling) issuing a num setgray for a gray level, where num is between 0 (black) and 1 (white). If you prefer RGB, Red Green Blue setrgbcolor would do the same with RGB values (also ranging from 0 to 1 for each colour component).

Writing is a little trickier in PostScript. First, you have to find a font with name findfont, then numpt scalefont to scale it to a certain number of points, finally setfont. And how do you write? newpath, move to wherever you want and (Text) show. Show acts as both a stroke and closepath, so if you want to change colors or something, do before writing text.

/Times-Roman findfont
20 scalefont
setfont
newpath
200 200 moveto % as the name says, move the cursor to x y
200 0 rlineto % line from actual point toward the right side
0 200 rlineto % the actual point is now in the right side, go up
-200 0 rlineto % go left
0 -200 rlineto
235 295 moveto
(Hi, I'm A Square) show % ta daa A Square
closepath
stroke
showpage
You can rotate, translate or scale your constructs (and if you want to keep your previous transformation safe, issue a gsave before doing anything and a grestore after doing it, this way the initial and final position states are the same). To rotate by 60 degrees our friend A Square, put a 60 rotate command after the first moveto. Also, if you pretended to draw something in the usual orientation, you should issue a gsave before rotating, and a grestore after stroking.

/Times-Roman findfont
20 scalefont
setfont
gsave
newpath
200 200 moveto % as the name says, move the cursor to x y
60 rotate
200 0 rlineto % line from actual point toward the right side
0 200 rlineto % the actual point is now in the right side, go up
-200 0 rlineto % go left
0 -200 rlineto
35 95 rmoveto
(Hi, I'm A Square) show % ta daa A Square
closepath
stroke
grestore
gsave
newpath
0.6 setgray
200 200 moveto
30 rotate
200 0 rlineto
0 200 rlineto
-200 0 rlineto
0 -200 rlineto
35 95 rmoveto
(Hi, I'm A Square) show
closepath
stroke
grestore
newpath
0.8 setgray
200 200 moveto
200 0 rlineto
0 200 rlineto
-200 0 rlineto
0 -200 rlineto
35 95 rmoveto
(Hi, I'm A Square) show
closepath
stroke
showpage
This starts to get annoying, so let's define a square function, which assumes we are already at a point where we will draw a square of a certain side (in the stack).
/box {
dup dup dup % this places 4 instances of the side in the stack
0 rlineto % just one argument, as below we have side x 4
0 exch rlineto % exch swaps the first and second argument
neg 0 rlineto % neg changes the sign
neg 0 exch rlineto % you guess?
} def
and this simplifies the gsave-grestore blocks as
gsave
newpath
0.6 setgray
200 200 moveto
x rotate
200 box
35 95 rmoveto
(Hi, I'm A Square) show
closepath
stroke
grestore
dup is a really useful command, which duplicates the topmost item in the stack, you will be using it a lot. And the same goes for exch, which swaps the first and second items. The most useful one, when doing some "big" code is m n roll... but it is trickier. As an example, if you have n elements in the stack, and want to move the topmost to the bottom (shifting), you should n 1 roll. If instead you want to shift to the other side, n n-1 roll. Have a look at how it works in some references below if you need anything harder, but these two constructs are almost all there is to it. neg, together with add sub mul and div form the core arithmetic structure.

PS of course has a number of flow control structures. The simplest, is repeat. As a simple example, 3 {dup} repeat would result in the first line in our box operator. Enclosing something in braces lets PS know it is a set of instructions, by the way.

The for loop is by far the most interesting and the one you (well, at least me) will be using more often. Its syntax is initial increment final {what to do} for.

for puts the counter on the topmost of the stack, so if you want to have a named variable corresponding to the counter, the first line in the for should be /n exch def. Later on in the final samples I will show some nested for constructs for more entertainment value.

Now comes a classical pair, if and ifelse. They are quite straightforward, condition {true procedure} {false procedure} ifelse and if it were an if, dont write a false procedure.

I think that this is almost all needed to start with PostScript. Maybe in a few days I'll be adding something, or maybe PDF-ing this.

Now, the "hard" examples
And some references

Wikipedia article
L-systems in PostScript
A PostScript web-server
A first guide to PostScript
PostScript Language Reference (Red book)

If you enjoyed this small tutorial, please share its link with your friends! It would made a difference, PostScript is a wonderful programming language which deserves to be more widely known.
Written by Ruben Berenguel