Not all programming languages are designed for full-fledged software development efforts. There are many “little languages” designed for
Postscript is a special-purpose language for preparing documents that mix text and graphics.
Postscript has the simplest grammar you will ever see:
<program> :== <program> <term> |
<term> ::= identifier | number | string
That’s it!
A Postscript program is just a list of terms, each of which can be am identifier, a number, or a string.
Executing Postscript Programs
Later in this section, I will provide links to several simple Postscript programs. You can also find more at various on-line resources such as this tutorial.
The easiest way to execute a Postscript program is using the open-source program ghostscript. This provides the command-line program gs
. On most Linux systems, if you install gs
, you can run the program and view the results by giving the Postscript file name, e.g.,
gs box1.ps
On Windows, you can install both ghostscript and the gsview viewer.
With so much emphasis on the stack, it’s not surprising that a number of the common operators are for manipulating or rearranging items on the stack:
Many of the most common operators in Postscript are for manipulating data on the stack:
inputs | op | outputs | description |
---|---|---|---|
obj1 | pop | Discard the top element from the stack. | |
obj1 obj2 | exch | obj2 obj1 | Swap the top two elements on the stack. |
obj1 | dup | obj1 obj1 | Copy the top element on the stack. |
objn … obj0 i | index | objn … obj0 obji | Copies the ith element down on the stack, pushing the copy on top. |
obj1 … objn n | copy | obj1 … objn obj1…objn | Copies the top n elements on the stack. |
Postscript data types start with numbers (integer and floating point). In addition to the add
and mul
operators already seen, there are
inputs | op | outputs |
---|---|---|
num1 num2 | add | sum |
num1 num2 | sub | difference |
num1 num2 | mul | product |
num1 num2 | div | quotient |
int1 int2 | idiv | quotient |
int1 int2 | mod | remainder |
base exponent | exp | power |
num1 | abs | absolute-value |
num1 | neg | negation |
num1 | ceiling | num2 |
num1 | floor | num2 |
num1 | round | num2 |
num1 | sqrt | num2 |
num1 | atan | num2 |
num1 | cos | num2 |
num1 | sin | num2 |
num1 | ln | num2 |
num1 | log | num2 |
This allows us to bind values to names.
Assignment is accomplished via the def
operator. For example:
/x 42 def
assigned the value 42 to the variable x
.
Functions are declared by assigning a procedure to a variable, e.g.,
/double { 2 * } def
All parameters are passed on the stack.
The if
operator introduces an if-then construct.
Example: x 0 lt { /x x neg def } if
The x 0 lt
evaluates a condition (is x<0?), leaving a boolean on the stack. The {...}
is a procedure that becomes the then-part of the if. The if
operator checks the boolean value and decides whether to execute the then-part procedure or to simply discard it.
Similarly, we have an ifelse
operator that takes two procedures, one representing the then-part and the next representing the else=-part.
Example: x 0 lt { /absx x neg def} { /absx x def } ifelse
Question: How would you define your own absolute value function?
/absolute
{ dup 0 lt { neg } if }
def
A simple bounded loop is provided by the repeats
operator. For example,
16 { 2 mul } repeats
would execute the procedure inside the { }
sixteen times.
Unbounded loops are provided by the loop
operator, which repeats a procedure until the procedure invokes an `exit operator.
E.g.,
{ 1 sub % subtract 1 from top item
dup 0 le { exit } if % until it becomes 0
} loop
There is also a for-loop-like construction
inputs | op | outputs | description |
---|---|---|---|
init incr limit proc | for |
start incr limit { ... } for
is equivalent to the C++ code:
for (tmp = init; tmp <= limit; tmp += incr)
{
push tmp;
proc;
}
Now, having established that Postscript has sequence, selection, and iteration, we could stop now and safely conclude that Postscript is Turing complete. Actually, it has recursion as well, so it’s doubly-qualified.
But, in case you’re wondering just why Postscript was useful for driving printers, let’s talk about the fun stuff.
You can draw (stroke) the path so that it appears on the printed page.
If the path is “closed” (starts and ends at the same point), you can fill the interior of the path with a color or pattern.
If the path is “closed”, you can use it as a “clipping” boundary for future drawing. It then functions as a kind of window so that any future drawing operations are only visible if they lie within the window.
Graphics in Postscript use a basic coordinate system that lower-left corner of the paper as (0,0).
showpage
operator.For convenience, I will use the following function to convert from inches to points:
/inch { 72 mul } def
E.g, 1.5 inch
puts 108 on the stack.
The basic steps in drawing something are:
newpath
stroke
)fill
)clip
)Drawing Lines
inputs | op | outputs | description |
---|---|---|---|
x y | moveto | Sets the current position to (x,y). Does not affect the path. | |
x y | lineto | Adds to the current path a line segment from the current position to (x,y). Then makes (x,y) the current position. | |
x y | rlineto | Adds to the current path a line segment from the current position (xc,yc) to (xc+x,yc+y). Then makes to (xc+x,yc+y) the current position. | |
closepath | Adds a line segment from the current position to the start of the path, marking the path as closed. |
A simple Postscript program to draw a box:
%!
%
% Draw a simple box.
%
/inch { 72 mul } def
newpath
0 0 moveto
0 1 inch lineto
1 inch 1 inch lineto
1 inch 0 lineto
0 0 lineto
stroke
showpage
Even with just lines, we can create some elaborate effects:
%!
newpath
0 200 % x, y
20
{
exch
dup 0 moveto
exch
dup 0 exch lineto
stroke
10 sub exch
10 add exch
} repeat
showpage
Here is a function to draw a box at the current position:
%!
/inch { 72 mul } def
/box {0 1 inch rlineto
1 inch 0 rlineto
0 -1 inch rlineto
closepath } def
newpath
0 0 moveto box
1 inch 1 inch moveto box
1.5 inch 0.5 inch moveto box
stroke showpage
After the declaration of the function /box
, we call it three times.
Colors can be specified in RGB form, three values ranging from 0 to 1 each, for Red, Blue, and Green intensity.
inputs | op | outputs | description |
---|---|---|---|
red green blue | setrgbcolor | Set current color, where each value ranges from 0 to 1. | |
currentrgbcolor | red green blue | get the current color |
We can add a bit of color to our earlier examples:
%!
newpath
0 200 % x, y
1 1 20
{ % red = 1-0.05i
dup 0.05 mul 1 exch sub
% green = 0.05i
exch 0.05 mul
% blue = 0
0 setrgbcolor
exch
dup 0 moveto
exch
dup 0 exch lineto
stroke
10 sub exch
10 add exch
} for
showpage
We can fill in a closed shape with the current color by using
inputs | op | outputs | description |
---|---|---|---|
fill |
instead of stroke
.
%!
/box {dup 0 rlineto
dup 0 exch rlineto
neg 0 rlineto
closepath } def
/irnd {rand exch mod} def
/rrnd {rand 1000 mod 1000 div} def
usertime srand
10 {
newpath
100 irnd 200 irnd moveto
100 irnd box
rrnd rrnd rrnd setrgbcolor
fill
} repeat
showpage
This actually uses a random number generator to select the box coordinates and colors, so it actually produces slightly different results each time it is executed.
inputs | op | outputs | description |
---|---|---|---|
x y r ang1 ang2 | arc | Adds to the path a counterclockwise arc of a circle |
where (x,y) is the center of the circle, r is the radius, and the portion of the arc drawn is from angle ang1 to ang2 (measured in degrees).
%!
newpath
10 10 360
{
100 100 % x, y
2 index 4 div % r
0 % ang1
4 index % ang2
arc
stroke
} for
showpage
One must often be careful not to go outside the bounds of a drawing area. One way to control this is to define a clipping path, beyond which graphics will not be drawn.
This is done with the operator clip
.
%!
newpath
30 30 25 0 360 arc clip
newpath
0 200 % x, y
1 1 20
{
dup 0.05 mul 1 exch sub
exch 0.05 mul
0 setrgbcolor
exch
dup 0 moveto
exch
dup 0 exch lineto
stroke
10 sub exch
10 add exch
} for
showpage
In the first few lines, we create a circle as a path, then use the clip
author to make that circle the limits for all subsequent graphics. The rest of the program is the same colored “fan” of lines that we demonstrated earlier.