Postscript Tutorial

The language

Postscript is what computer scientists call a "back-assward" programming language. Another famous "back-assward" language is FORTH. FORTH programmers will feel right at home with postscript. This class of languages should not be confused with languages such as TCL, which are classified as "utterly assinine".

Back-assward languages have, as a central concept, the use of a stack. Think of a stack of books. You can easily put a book on top of the stack, or remove one from the top. Removing or inserting books elsewhere is a pain in the ass, especially if the stack is tall and the book of interest is near the bottom of the stack. In a back-assward language, you never write an expression like "2 + 2", instead you write "2 2 add".

The overriding design principle in back-assward languages is "if it was good enough for the neanderthals, then it is good enough for us". The life of a back-assward interpreter is easy, it pulls something off the top of the stack, figures out what to do with it, and repeats the process. Your job as a back-assward programmer, is to make sure whatever operands the interpreter will need are on top of the stack, in the right order. Once this is done, you put the operator on the stack and hand the mess to the interpreter.

Some examples of basic operators

A percent sign starts a comment in postscript, inline comments are allowed.

Postscript has 262 different operators. We probably won't cover them all here (this is a tutorial, not a reference manual).

Stack operators

If you wanted to write an expression like (2+2)*3 you would write:
2 2 plus 3 mul

Graphics and printers

The only reason I know of for wanting to use postscript is to set up files to be "printed" on a postscript printer. Text and fonts are one aspect of this (to be discussed later). Graphics is another. To generate graphics, you have to bear in mind the concepts of the "current page", "current path", user coordinates, and device coordinates. And lots of other things too. You put lines and curves into the current path, then "stroke" or "fill" them onto the current page. When the current page is ready, you have to "showpage" it, or you never see anything. Lines are the logical thing to start with: To draw a line, think of holding a stick in your hand and drawing in the sand. The "moveto" command moves the stick up in the air and jabs it into the sand at the specified location. The "lineto" command drags the stick through the sand in a straight line from the current position to the specified location. These commands operate in absolute coordinates, but they have flavor that operate relative to the current position, namely "rmoveto" and "rlineto".

The currentpoint operator places the x and y position of the current location on the stack.

Variables and procedures

If you prepend a "/" to some letters like this: "/dog" you put the literal name "dog" on the stack. (Without the preceding slash, postscript tries to find an operator named dog.) This is the setup phase for defining a new variable or procedure. To define and then increment a variable, you do:
/dog 10 def
/dog dog 1 add def
Defining a new procedure has much the same flavor, the only difference being that you wrap the body of the procedure in curly brackets:
/cube { dup dup mul mul } def
The curly bracket syntax is actually an "executable array", which is a single postscript object (and which is used in other situations, such as loops).

Coordinates, units, and such

One day long ago, the postscript authors were sitting around smoking crack. The discussion turned to what units should be used for postscript coordinates. Inches and millimeters were quickly rejected. The suggestion was made to use dots or pixels (postscript printers at that time had 300 dots per inch), but that seemed too hardware specific. Then somebody suggested using a unit that was 1/72 of an inch and that was it!

The default unit of measure in postscript is what you may choose to call "postscript points", of which there are exactly 72 in an inch. They are called "points" due to their accidental similarity to printers points (such as used in TeX), of which there are 72.27 in an inch. TeX calls the latter points (pt), and the former "big points" (bp) in its very meticulous scheme of things. You can probably forget about all of this now, but it is good to know about in case some typesetting purist takes you to task someday.

The default postscript coordinate system has its origin at the bottom left corner of the page. Commands exist to translate, rotate, and scale the postscript coordinate system.

A rotation is specified in degrees, with a positive rotation being counterclockwise.

Postscript has a graphics state stack (limited to 32 entries) that can be used to save and restore the current graphics state. The current path is part of the graphics state, and the goal of doing a graphics state save is often to save the current path so it can later be restored. Notably, the fill operation consumes the current path.

Text

You can produce a string in postscript by enclosing whatever it is you want in parenthesis, like so:
(this is a string)
You can enclose balanced parenthesis in such a string without doing anything special; otherwise you use the backslash to escape them (and to do other special things).

It is also possible to dynamically create strings. You create an emptry string of a certain size (filled with null characters using the "string" operator:

10 string
You encode values into such a string using the cvs operator, this works for any kind of postscript object.
/value 7 def
value 10 string cvs
To find out how big a string will be (which can be useful if you want to do something like center it), you can use the stringwidth operator, which replaces the string with the width and height of the string shown using the current font.

Fonts

There are font families, which are kept in font dictionaries, which need to be scaled. To specify 10 point Times-roman as the default font and print something, you do this:
/Times-Roman findfont 10 scalefont setfont
(Hello World!) show showpage
Postscript is quite clever about how it does font scaling, and you are expected to scale fonts to the size you need.

It is up to you to use "moveto" to start the display of each string. If you want to move to a new line, you decide how to get there and how much space should exist between lines. I have been pleased by moving down 1.25 times the font size, but you can do whatever you want.

Curves

You can draw circles and parts of circles. If you want an ellipse, you have to scale the coordinate system.

Boolean operators

Tests and Loops

The following would count from 1 to 10:
1 1 10 { 3 string cvs show ( ) show } for
This would do the same:
/i 1 def
{ i 3 string cvs show ( ) show
i 10 ge { exit } if
/i i 1 add def
} loop

Some advanced business

Note that Postscript will allow recursive programming.

I ran across this link which demonstrates a couple of nice postscript programs. I reproduce them here, lest they become lost someday.

The first makes use of executable arrays and the "exec" operator:

%!PS-Adobe-3.0 EPSF-3.0
%%BoundingBox: 0 0 170 50

% load standard font
24 /TimesBold findfont exch scalefont setfont
.25 setlinewidth
1 setgray                                 % white

gsave                                     % push graphic state
{                                         % push lambda expression
  gsave                                   % push graphic state
  (transformations) dup                   % duplicate string
  0 0 moveto show                         % print filled
  0 setgray                               % set black
  0 0 moveto true charpath stroke         % print outlined
  grestore                                % pop graphic state
} 
.6 .05 1.1 {                              % loop condition
  setgray                                 % using loop iterator
  .8 dup 13 dup translate scale           % matrix transformation
  -4 rotate
  dup exec                                % execute expression
} for                                     % loop operator
grestore                                  % pop graphic state
exec                                      % finally pop and execute

0 setgray                                 % black
[-1 0 0 1 150 30] concat                  % matrix transformation
(affine) 0 0 moveto show                  % print

showpage
%%EOF
The second produces part of the Koch snowflake using recursion:
%!PS-Adobe-3.0 EPSF-3.0

/koch {
  dup depth le {     % s < depth
    1 add exch % s => s+1
    3 div exch % t => t/3

    % prepare stack:
    % t/3 s+1 60 t/3 s+1 240 t/3 s+1 60 t/3 s+1
    60 3 copy 180 add 3 copy 180 sub 3 copy pop
    koch rotate koch rotate koch rotate koch
  } 
  { pop 0 rlineto }         % draw
  ifelse
} def

4.0 4.0 scale
/depth 5 def
newpath
20 100 moveto
100 1 koch stroke
showpage

Worthy references


Feedback? Questions? Drop me a line!

Tom's Computer Info / tom@mmto.org