PostScript as a Programming Language

5.9 PostScript as a Programming Language

PostScript has all the programming functionality you would expect from an HP-caclulator, plus some interesting features hard to find in other languages. There are variables, loops, subroutines (of a sort), and an advanced idea for the scope of variables.

Variables in PostScript are defined with the def operator. To create a variable called pi, you would use:

/pi
3.1416
def
Notice the slash before the variable name. You need this only when you define the variable. Thereafter, you would get the area of a circle of radius 4 like this: 4 4 mul pi mul. You use the slash to refer to the variable's name, not its value.

Subroutines are made exactly the same way. However, you normally want more than one command in a subroutine. To do this, you group commands with curly braces. The following defines a subroutine that makes a filled circle:

/filledcircle {
gsave
newpath
0 0 1 0 360 arc
fill
grestore
}
def
This isn't very interesting, however, since this is a very limited subroutine. We can make it take arguments by making it use the previous elements on the stack. So we could set the radius of the circle like this:
/filledcircle {
/radius exch def
gsave
newpath
0 0 radius 0 360 arc
fill
grestore
}
def
Now, to make a filled circle of radius 1, you would use 1 filledcircle. Notice how exch is used in the variable definition. The stack starts with (if our argument is 1)
1
/radius
After the exch we have
/radius
1
and now the def command will work.

There still is something not quite ideal about our subroutine - the radius variable is global! You might be overwriting a value used somewhere else by other code. To fix this, you need to use a dictionary. This an advanced concept of variable scope that you will only learn a tiny bit of here. At the beginning of your subroutine, you can define a new dictionary with n variables using n dict begin. This says, for our purposes, that the next n variables defined will be local variables. They have scope until the next end statement. So here is our routine again. This time it takes three arguments: x center, y center, and radius.

/filledcircle {
3 dict begin
/radius exch def
/y exch def
/x exch def
gsave
newpath
x y radius 0 360 arc
fill
grestore
end
}
def
Notice how gsave and grestore are used here. They allow the circle to be drawn without disrupting any path that might be in progress.

Let us now turn to making a "target" with our filled circles. This will be a set of nested circles with different grey values. Obviously we will use a loop to do this, in this case a for loop. The for loop takes four arguments: an initial value for the loop variable, a step, a final value, and code to repeat. During each iteration of the loop, the current value of the loop variable is pushed on the stack, and then the code is repeated. This loop makes a target with 4 circles:

4 -1 1 {
gsave
dup
1 sub 4 div setgray
0 exch 0 exch 4 div filledcircle
grestore
} for
We can put this all together in a routine that takes a position, radius and number of circles as its argument.
/target {
5 dict begin
/count exch def
/radius exch def
/y exch def
/x exch def

count -1 1 {
/level exch def
gsave

level 1 sub count div setgray
x y radius level count div mul filledcircle
grestore
} for
end 
}
def
Here is the output of 0 0 1 10 target:
Next

David Maxwell, who is still writing this, would like to hear your comments and suggestions. And remember, parts of this manual are based on P.J. Weingartner's work: A First Guide to PostScript.