Introduction to Postscript
CptS 355 - Programming Language Design Washington State University |
|
PostScriptPostScript is a stack-based language using postfix notation. There are several stacks, but only four are of interest to us
Basic (Operand) Stack Manipulations
3 4 addCompute (3 + 4) * 2 3 4 add 2 mulCompute 3 + (4 * 2) 3 4 2 mul addCompute 3x^2 + y^4, assuming x and y are defined names. 3 x x mul mul y y mul dup mul addNext 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 exchWhat will leave 1 2 on the stack?
1 2 3 popWhat will leave 1 2 3 3 on the stack?
1 2 3 dupWhat will leave 1 2 2 3 3 on the stack?
1 2 3 exch dup 3 2 rollWhat will leave 1 2 3 1 2 3 on the stack?
1 2 3 count copyWhat 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 OperatorsPostScript has operators that perform like control structures in other languages. The first operator isif. We first have to be able to evaluate conditions.
0 1 eq % Does 0 == 1? Evaluates to false. x 1 eq % Does x == 1? push true on stack else push false 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 branches. 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
The for operator takes an initial value, an increment, a limit, and a code
array on the
top the stack. It executes the code array multiple times each time with
pushing a different value of the control value on the top of the stack.
Warning, the for loop is a bit tricky because it puts the loop control value on the
top of the stack for each iteration; You have to remove it explicitly if that's what you want.
% 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
% Instead,
1
1
5
{}
for
% 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 The Current Page and PathDrawing occurs on an imaginary page with unlimited coordinates. The origin is the lower-left of the page when printed/displayed via ashowpage. To draw
x-coordinate y-coordinate movetoThe page origin, point (0, 0), is the lower left corner of the page. Each point is 1/72 of an inch. So 72 72 movetowill 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.
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) showYou 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) showYou 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 DictionariesEverything 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 defIt 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 defWe can use this fact to define a name to be whatever the top value on the stack is. /x exch defSo 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 styleReadibility 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 "logical operation" per line. Indentation can also improve readability; indent
pop % pop the top of the stackConsider 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
{1} % Base case, factorial(0) = 1
% Recursive case, factorial(x) = factorial(x-1)*x
{x 1 sub factorial x mul}
ifelse
end
} def
|
|