Downloader
Volume Number: 4
Issue Number: 1
Column Tag: Postscript Programming
Overview & Downloader in C 
By Nicholas Pavkovic, Perimeter, Chicago, IL
In this article, I’m going to present a programmer’s overview of PostScript. This
overview includes comparisons between PostScript and QuickDraw, information about
using the stack and a brief explanation of the LaserWriter driver. At the end of the
article, I’ll present the PAP manager calls (which allow direct access to the
LaserWriter over AppleTalk) and C source code for a PostScript downloader.
Overview of PostScript
PostScript is a graphics language that’s designed to set type and graphics on
high-resolution devices such as laser printers and typesetting machines. In most of its
implementations it runs on a dedicated processor that’s contained in or connected to a
printer. Though these printers vary considerably in their capabilities, PostScript is
device-independent, so programs written for one PostScript device will usually run on
another.
In addition to its graphic capabilities, PostScript contains a full complement of
standard language features (floating point arithmetic, string and array manipulation,
etc.) and could conceivably be used for non-graphics applications. It most closely
resembles FORTH in its syntax and overall “feel” (but, thankfully, PostScript carries
no social stigma). However, most FORTH systems provide the option of controlling the
processor at a very low level and PostScript (because it is device-independent) allows
only high-level programming.
PostScript’s design shows the influence of SmallTalk. Though it isn’t really
object-oriented (there is no messaging system and no inheritance), the documentation
describes instances of the data types as “objects,” and the interpreter performs
comprehensive type checking. The influence of SmallTalk can probably be traced to
PARC, where PostScript’s principal designer, John Warnock, worked during the late
70’s and early 80’s. Since SmallTalk and QuickDraw share common ground, Mac
programmers will recognize some PostScript commands.
In the PostScript documentation, the built-in commands are usually referred to
as operators. In this article, they will be referred to in a number of more familiar
ways, such as routines, procedures, functions, etc. The name used doesn’t provide any
additional information about the operator: an operator referred to as a function won’t
necessarily return a value. The data used by operators are technically called operands,
but I often use arguments and parameters instead. It’s good to use the correct
PostScript terminology, but unfamiliar terms can make a new language seem even
more foreign.
PostScript and QuickDraw
To give you a better sense of PostScript’s capabilities, I’d like to cover some of
the similarities and differences between PostScript and QuickDraw. While reading this
section, think of the PostScript function calls as Pascal or C calls in which the
arguments precede the routine names. Note that the PostScript interpreter is
case-sensitive and all of the built-in commands are written with lowercase letters.
Coordinates, Navigation and the Path
Unlike QuickDraw, PostScript uses a Cartesian coordinate system with the origin
located in the lower left hand corner of the page. The x coordinates increase from left to
right and the y coordinates increase from bottom to top. In this coordinate system, the
basic unit is the point which is 1/72” wide (the width of a pixel on the Macintosh
screen). Since PostScript supports floating point, coordinates don’t have to be
integers.
Like QuickDraw, PostScript uses a pen and positions it with the moveto command.
Since the arguments precede the routine, the code to move the pen to position (72, 18)
would read 72 18 moveto. To move the pen relative to its current position, use
rmoveto. Straight line segments are drawn using lineto and rlineto. These commands
are preceded by an x and y coordinate (or offset, if relative movement is involved).
Unlike QuickDraw, line segments are not drawn on the page as the commands are
executed. The segments are accumulated into the current path, a hidden data structure
that keeps track of the lines drawn on the page. Once you’ve created a path, you can
draw (outline) it using the stroke operator or fill its interior using the fill operator
(neither operator takes any arguments). The nature of the fill or the outline depend on
the pen’s characteristics when fill or stroke is called. The PostScript pen’s state
differs somewhat from the QuickDraw pen. The PostScript pen does not have a transfer
mode; whatever you draw will cover anything printed underneath it. And though the
PostScript pen can be set up to draw with a pattern, it’s a somewhat tricky procedure.
Usually you’ll use the PostScript operator setgray, which allows you to fill the path
with different levels of gray. setgray is preceded by the gray level, a decimal that
ranges from 0 (black) to 1 (white).
PostScript’s setlinewidth is the PostScript version of QuickDraw’s PenSize
routine; it’s preceded by the desired line weight (vertical width), which can be
fractional. This command doesn’t alter the horizontal width of the pen. 0 setlinewidth
generates the thinnest line available on the PostScript device you’re using. Lines are
drawn from the center; half of the line’s weight is above the segment specified and half
is below.
fill and stroke have one surprising side-effect: they erase the current path and
make the current pen position indefinite. Because the pen position is indefinite, you’ll
need a moveto to position the pen after you’ve used one of these operators. The erasure
of the current path causes problems if you want to create a figure that’s both stroked
and filled. To do so, you must either draw the object twice (fill it once and stroke it the
second time) or use the commands gsave and grestore. These commands save and
restore the graphics state, which includes the current path and pen state (among other
things). gsave fill grestore stroke fills and strokes the current path.
Once you’ve finished drawing and you want to see your creation, execute
showpage. Up to this point, all drawing has been done on a bit image of the page that
exists only in memory. showpage transfers the memory image to the physical page and
ejects the page.
Setting Text
Before you can draw any text, you must indicate the font that you wish to use. The
commands needed to set the font to 18 point Times-Roman are:
/Times-Roman findfont
12 scalefont
setfont
It’s a little too early to explain exactly why this works, but I will point out that
findfont can be preceded by any valid font name (beginning with a backslash) and
scalefont is preceded by the desired font size. If you don’t know the correct spellings of
the font names, you can find them by executing FontDirectory pop = forall, another
piece of code that will have to remain unexplained. Note that PostScript uses a different
name for each font style, and certain standard Macintosh styles (Outline, Shadow and
Underline) aren’t standard on the LaserWriter.
After you’ve set the font, you’ll need to move into position with moveto. Then use
show to draw the text. show is preceded by the text to be drawn, enclosed in
parentheses, as in (Hello, world) show. The current gray level will be used to draw
the text.
Coordinate Transformations
Before being drawn, every coordinate is transformed by the current
transformation matrix or CTM. To modify the drawing space, you can adjust the CTM
directly with some general matrix operators, or you can avoid the linear algebra and
use the following “convenience” operators:
x y scale Scales the drawing space
Default: 1 1 scale
rotate Rotates the axes
x y translate Translates the origin to the point (x,y)
Note that these operators affect drawing that is done after they are invoked; they
don’t affect any text or graphics that have already been drawn.
What’s Missing?
There are a few features that QuickDraw programmers will miss in PostScript.
As mentioned above, there are no transfer modes; you’ll have to be careful about
the order in which objects are drawn and make use of PostScript’s clip operator.
Though PostScript’s text processing is more sophisticated than QuickDraw’s,
PostScript contains no operators for wrapping text ( there’s no TextBox equivalent).
You can write a simple line-breaking procedure in PostScript, but for reasons of
speed you’ll probably want to do any heavy-duty hyphenation and justification on the
Mac’s processor.
Regions are missing from PostScript, as are most of QuickDraw’s routines for
drawing geometric shapes. In both cases, it’s not difficult to program functional
equivalents.
Visualizing the Stack
Like FORTH, PostScript is a stack-based language. FORTH programmers will find
their experience with FORTH’s stack very useful when learning to use PostScript.
Assembly language programmers will also benefit from their familiarity with
the stack. The stack preparation required by PostScript procedures is conceptually
similar to the setup that occurs in assembler prior calling a Pascal function or on
entry to a definition routine that follows the Pascal calling conventions. In most
respects, however, PostScript and assembler are quite different. The PostScript stack
is “intelligent” in the sense that it keeps track of its elements’ types and sizes.
PostScript relies on it for arithmetic operations that assembler normally handles with
registers, and PostScript provides a full set of methods for changing the order of
elements on the stack. One thing that PostScript doesn’t use its operand stack for is
return addresses, so you don’t have to worry about accidently branching into a data
structure if your calls aren’t set up correctly.
For programmers used to high-level languages, the stack will seem somewhat
cumbersome at first. For example, subtracting 5 from 7 is accomplished with the code
7 5 sub. [If you remember the great HP 65 calculator and it’s reverse polish notation,
then this will appeal to you. -Ed] As each number is read, it’s pushed onto the top of
the stack. The prior contents of the stack are moved downwards. Keeping in mind that
PostScript is interpreting the statement from left to right, consider the following
visual representation of what happens:
As you can see, the sum is pushed onto the stack after the addends are removed. If
you’re actually trying this code out and want the result to appear on your screen, type
= after the code. = pops the result off the top of the stack and echoes it to the output
device (it ends up on your screen).
The way that the stack is used may remind you of a RPN calculator. An associate of
mine who is studying PostScript prefers to think of the stack in terms of shifting
blocks. He’s cut a large piece of erasable whiteboard into rectangular sections, and he
emulates the stack by writing values onto them and shifting their positions. You may
not need to go this far to acquire proficiency with the stack, but you will need to retain
a mental image of the stack while you’re coding. Since many of the simple operators in
PostScript don’t do much to the stack, it’s tempting to look at them simply as functions
preceded by their arguments and forget that the stack exists. Unfortunately, this
approach won’t get you far when you’re trying to understand PostScript’s stack
manipulation operators.
dup duplicates the stack’s top element:
exch reverses the order of the top two elements:
roll rotates the order of elements on the stack. It’s often used to move the
bottommost element to the top of the stack:
roll takes two integer arguments. The first argument determines the number of
elements that are affected by the rotation. The second argument specifies how many
rotations occur and the direction of these rotations. A negative integer will move
elements from the bottom of the stack to the top and a positive integer will move
elements from the top of the stack to the bottom. The code necessary to accomplish the
above rotation would read 3 -1 roll. Three elements are affected by the operation (if
there are any elements beneath the third element, they’ll remain in place). The -1
causes the elements to rotate once, moving the third element to the top of the stack. If
-2 had been the second argument, 9 would have been moved to the top of the stack first
and 8 would have been moved to the top of the stack next.
Obviously, rolls can get quite complex. It’s frustratingly similar to Rubik’s
cube: though it’s easy to get a particular element into place, all the other elements get
moved around in the process. To help keep track of the stack, most programmers rely
on a standard notation that looks like this:
a b c -- b c
The letters denote elements on the stack. The ones placed before the dash
represent the state of the stack before an operation and those following the dash
represent the stack state after the operation. The letter farthest to the right
represents the element that’s on top of the stack. exch’s effect on the stack might be
described by a b -- b a. For some operations, you may want to use descriptive names
instead of letters. The stack effects of add could be written addend1 addend2 -- sum.
The PostScript Language Reference Manual’s reference section describes each
PostScript operator using a variant of this notation. add, for example, contains the
description num1 num2 add sum. The dash has been replaced with the name of the
operator. A short dash is used to represent an empty stack, as in - stroke -. Stroke
takes nothing from the stack and leaves nothing on the stack after execution.
The stack notation can be used to describe the effect of a single instruction or it
can summarize the effect of numerous operations. If I’m writing a tricky program (or
debugging code that I had believed to be simple), I’ll sometimes use the notation to
describe the state of the stack before and after the execution of each line.
Declarations, Types and Dictionaries
Many conventional languages require you to declare a variable’s name and type
before it can be used. In PostScript, an assignment statement serves as the declaration.
The variable’s type is determined by the value you assign to it. For instance, /counter
1 def defines an integer variable named “counter” and assigns it the value 1.
In addition to integers, you can assign floating point values (by including
decimals), strings (using parentheses as delimiters instead of quotation marks) and
arrays (bracketing the elements with ‘[’ and ‘]’). Arrays can be heterogeneous -- the
first element might be an integer, the second a floating point value and the third a
string. This allows the implementation of complex data structures, though you’ll