|
|
|
PostScript
PostScript is a stack-based language using postfix notation.
There are several stacks, but only three are of interest to us
- the stack - there is one primary stack used for most computation
- graphics stack - a stack used to save and restore graphics
environments
- dictionary stack - a stack used to save and restore
dictionaries. All names in the program are defined and
their definition placed into a dictionary
Basic Stack Operations
- constants - dynamic typing, most primitive types, array composite type
3 % push the integer 3 onto a stack
(hello there) % push the string "hello there" onto a stack
3.3 % push the real number 3.3
false % push the boolean value false onto the stack
{3} % push code onto the stack, don't evaluate
/x % push the name x onto the stack
x % evaluate the name x
- many stack operations - take values from stack, do something, push the
result
3 4 add % op1 op2 add --> op1+op2, e.g., 3 + 4
5 2 sub % op1 op2 sub --> op1-op2, e.g., 5 - 2
4 3 mul % op1 op2 mul --> op1*op2, e.g., 4 * 3
Here are some operations that manipulate the stack directly
dup % duplicate the top value on the stack
pop % pop the top value from the stack
stack % display the contents of the stack
count % push a count of how many values are on the stack
exch % exchange the top two stack values
4 2 roll % move the top 2 values on the stack into the 4th
% stack position from the top
count -1 roll % move the top stack value to the bottom
4 index % copy the 4th stack value (from top) onto the top
3 index % copy the 3rd stack value (from top) onto the top
3 copy % copy the top three stack values onto the stack
2 copy % copy the top two stack values onto the stack
count copy % copy the entire stack on itself!
Please refer to your PostScript cheatsheets for more operations.
Let's do a few examples to get a feel for how it works. Compute 3 + 4 and
leave the result on the stack
3 4 add
Compute (3 + 4) * 2
3 4 add 2 mul
Compute 3 + (4 * 2)
3 4 2 mul add
Compute 3x^2 + y^4, assuming x and y are defined names.
3 x x mul mul y y y y mul mul mul add
Next let's do some stack manipulations. For each of the following we will
push 1 2 3 onto the stack first. What will leave 1 3 2 on the stack?
1 2 3 exch
What will leave 1 2 on the stack?
1 2 3 pop
What will leave 1 2 3 3 on the stack?
1 2 3 dup
What will leave 1 2 2 3 3 on the stack?
1 2 3 exch dup 3 2 roll
What will leave 1 2 3 1 2 3 on the stack?
1 2 3 count copy
What will leave 1 2 3 3 2 1 on the stack?
1 2 3
dup % duplicate the 3
2 index % copy the 2 from the 2nd stack position
4 index % copy the 1 from the 4th stack position
Basic Control Operators
PostScript has operators that perform as "control structures". The first
operator is if. We first have to evaluate conditions.
0 1 eq % Does 0 == 1? Evaluates to false.
x 1 eq % Does x == 1?
x 1 lt % Is x < 1?
x 4 4 mul lt % Is x < (4 * 4) ?
x 1 eq x 2 eq or % Does (x == 1) or (x == 2)?
x 2 gt not x 1 gt or % Does (x > 2) => (x > 1)?
The if operator pops the top two values from the stack. If the
2nd to top value is true then it executes the top value (which
has to be code). For example, let's push 3 on the stack if x == 0.
x 1 eq % first evaluate the condition
{3} % next push the code for the "true" branch
if % finally, evaluate the if
By convention, let's indent the branch. Let's clear the stack if x > 0.
x 1 gt {clear} if
ifelse just adds a false branch. For example, let's push 3 on
the stack if x == 0, else push 4.
x 1 eq % first evaluate the condition
{3} % next push the code for the "true" branch
{4} % next push the code for the "false" branch
ifelse % finally, evaluate the ifelse
Let's clear the stack if x > 0, else let's copy the stack
x 1 gt {clear} {count copy} ifelse
Nested ifelse's can be tricky. Let's assume we want to do the
following.
if x == 1 then push 5
else { if x == 2 then push 6
else push 7 }
In PostScript we could use the following.
x 1 eq
{5}
{x 2 eq
{6}
{7}
ifelse}
ifelse
There are also looping constructs. The repeat operator is used
for a repeat loop.
% push four copies of 3 onto the stack
4
{3)
repeat
% push five copies of the top of the stack, 1
1
5
{dup}
repeat
% Let's compute until the top of the stack exceeds 5
1
100
{1 add % add one to the top of the stack
dup 5 gt % is it bigger than 5
{exit}
if
}
repeat
The loop operator just keeps looping. You have to add an
explicit exit condition.
% Let's compute until the top of the stack exceeds 5
1
{1 add % add one to the top of the stack
dup 5 gt % is it bigger than 5
{exit}
if
}
loop
WARNING, THE FOR LOOP IS A BIT TRICKY BECAUSE IT HAS THE LOOP COUNT ON THE
TOP OF THE STACK.
% Let's push 1 through 5 on the stack
1 1 5 {dup} for
% Oops, stack has 1 1 2 2 3 3 4 4 5 5
% Let's push five copies of 1 onto the stack
1 1 5 {1} for
% Oops, stack has 1 1 2 1 3 1 4 1 5 1
So "for" loops are confusing if you manipulate the stack inside the loop,
unless the manipulation does not add to the stack.
The Current Page and Path
Drawing occurs on an imaginary page with unlimited coordinates. The
origin is the lower-left of the page when printed/displayed via a
showpage. To draw
- construct a path
newpath % this starts a path, wiping out the previous path
% it is optional
- establish current point, draw from point to point
0 100 moveto % set the current point to 0 100
100 100 lineto % put a line from the current point to 100 100
% current point is now 100 100
100 100 rlineto % put a line relative to the current point
- complete the path to draw a path that connects at each end
closepath % this completes the path by filling in the line
% it is optional
- you have control over linewidth, kind of line, color of line, fill
and fill pattern
10 setlinewidth % set the line width
fillfactor setgray % make it a gray pattern fill
fill % Fill the path on the current page
- stroke the path -- write it to the current page
stroke % this actually puts it into the current page
The drawing operations usually take an x-coordinate and y-coordinate, e.g.,
x-coordinate y-coordinate moveto
The page origin, point (0, 0), is the lower left corner of the page. Each
point is 1/72 of an inch. So
72 72 moveto
will move to the point one inch up and to the right of the origin. If you
want to work in "inches" rather than points, you can do the following.
/inch {72 mul} def
/inches {72 mul} def
1 inch 1 inch moveto
7 inches 1 inch moveto
If you want to work in centimeters
/centimeter {28.3464567 mul} def
/centimeters {28.3464567 mul} def
7 centimeters 1 centimeter moveto
There are three basic transformations that can be applied to the entire
coordinate system, they change coordinates for future drawing operations.
- scale - shrink or enlarge the whole coordinate system
.5 .5 scale % Make everything half as big
.5 1.0 scale % Squish half as big in the x-axis only
- rotate - rotate entire coordinate system in a clockwise direction
45 rotate % rotate by 45 degrees, clockwise
-90 rotate % rotate by 90 degrees, counter clockwise
- translate - move entire coordinate system
200 300 translate % move origin to 200 300
- transformations are cumulative!
200 300 translate % move origin to 200 300
45 rotate % rotate the coordinate system by 45 degrees
.5 .5 scale % scale the translated, rotated coordinates
- can save and restore current graphics state
gsave
200 300 translate % move origin to 200 300
grestore
.5 .5 scale % Make everything half as big
As an example, let's draw a smiley face.
200 200 translate % move origin to near center of page
% First draw the circle for the head.
newpath
0 0 100 0 360 arc % draw a circle of radius 100
closepath stroke
% Next let's draw the left eye
newpath
-35 25 10 0 360 arc fill
closepath stroke
% Draw the right eye
newpath
35 25 10 0 360 arc fill
closepath stroke
% Draw the mouth
0 30 100 220 320 arc
stroke
Another useful command is showpage. It "shows" the current
page, creating a new, blank page to draw on. Drawing text is very easy (if
the PostScript interpreter can find the font). Just move to a desired
location.
100 100 moveto
(hello there) show
You can change/set the font and color (refer to the on-line resources).
/Times-Roman findfont % load times-roman font
12 scalefont % Scale to a 12 point font
setfont
100 100 moveto
(hello there) show
You can shrink or make text large (example from
U of Indiana guide to PostScript).
gsave
100 100 moveto
1 2 scale % Make the text tall
(Tall Text) show % Draw it
grestore
Finally, you'll probably have to use the setgray operator.
0 setgray % set the fill color to black
1 setgray % set the fill color to white
.5 setgray % set the fill color to gray, half black/half white
Dictionaries
Everything that is not a constant is a name, names are bound to meanings in a
dictionary
/joe % push the name joe onto the stack
joe % fetch the meaning of joe from the dictionary
% and evaluate that meaning
The def operator defines a name. The syntax of the operator is
the following.
name code def
It defines the name to have the meaning of the
code. For example.
/joe {3 4 add} def % define joe to be {3 4 add}
% braces mean defer evaluation until used
joe % evaluate 3 4 add
Names have dynamic scope. You can redefine names with impunity.
/sub {3 4 add} def % define sub to be {3 4 add}
sub % evaluate 3 4 add
See if you can guess what the following examples do; but don't do these in
your programs!
/x {1} def
/x {x} def
/x {1} def
/x {/x {1} def} def
/x {1} def
/y {x} def
/x {y} def
Observe that the meaning of a constant is the constant, so
/x {1} def
is really the same as
/x 1 def
We can use this fact to define a name to be whatever the top value on the
stack is.
/x exch def
So if we assume the stack contains 5 6 2, then we push the name
/x, giving us the stack 5 6 2 /x. The
exch operator then exchanges the top two values yielding 5
6 /x 2. Finally def pops two values and defines
/x to have the meaning 2, leaving the stack as
5 6. We can explicitly establish local scope by creating
a local dictionary and using it. The following factorial program is
incorrect because it doesn't establish the correct scope for the local name.
% This factorial is incorrect because it doesn't do
% a local name properly
/factorial {
/x exch def % try to establish local name x, no good!
0 x eq
{1}
{x 1 sub factorial x mul}
ifelse
} def
A solution is to establish a local dictionary on the dictionary stack. Names
are resolved by looking down the dictonary stack until the name is found.
/factorial {
1 dict % create a local dict. that can have 1 entry
begin % push it onto the dictonary stack
/x exch def % establish local name x
0 x eq
{1}
{x 1 sub factorial x mul}
ifelse
end % pop the dictionary stack
} def
A word on PostScript style
Readibility of code is important. Use indentation, names, and comments to
communicate to others. Generally it is nice to not pack too much (or too
little) into a single line. The rule of thumb is one "operation" per line.
Indentation can also improve readility, indent
- inside a begin/end
- the branches of an ifelse
- inside a loop
- inside a def
Finally, comment all defined "names", and "parameters", and important steps
inside a definition as you see fit. Try not to describe what the operation
does (my comments in the PostScript code in these notes are different because
I'm writing for a specific purpose, that is different from most programming).
For example, try to avoid doing
pop % pop the top of the stack
Consider the following factorial function
/factorial {
1 dict begin /x exch def
0 x eq {1} {x 1 sub factorial x mul} ifelse
end
} def
It lacks comments and is "squashed". Below is a more appropriate definition,
stylistically.
% factorial computes the factorial of the top of stack,
% leaving the result on the stack.
/factorial {
1 dict begin
/x exch def
0 x eq
% Base case, factorial(0) = 1
{1}
% Recursive case, factorial(x) = factorial(x-1)*x
{x 1 sub factorial x mul}
ifelse
end
} def
|