Postscript is a page-layout language for printers and display devices. It uses a simple model of placing marks on a page, whether they are lines, textures, characters form a font or bit maps. It is platform independent and human-readable and writeable in its ASCII form. It is pretty well universal on all computer platforms, and many laser printers use it (HP uses a similar language called PCL, but even HP printers can use Postscript.)
There are primitive operations for all the common graphic objects and operators, for handling fonts, and for placing these anywhere on an unbounded surface. Advanced Postscript does color, color separations for commercial printing, and has the immediate advantage of being infinitely scalable in all its forms.
The model of computation is that of a stack machine. Operands are pushed onto a stack and popped off by operators when needed. Values can be almost anything, including numbers, strings, blocks, called procedures, and names.
The syntax is strictly postfix, in accordance with the stack architecture (like the HP calculators). There are the following syntactic categories, with examples:
There are in fact several stacks that control Postscript execution, but only the operand stack and the graphics stack are important here. Objects are copied onto a stack if simple, and placed by reference if compound. Basic operations are numeric, either integer or floating point. e.g.
3 5 add
pushes 3 and 5 onto the stack which are then popped by the addition operation. The result is pushed back on the stack.
3 5 add 4 mul 2 div
produces the result 16 on the stack.
Graphics operations are, in general, side-effect operations that leave the stack unchanged. e.g.
(a little string) show
places the characters "a little string" on the page at the current position, using the current font.
12 20 lineto
places a line from the current position to the coordinate position (12, 20) in the current path.
Values can be bound to names with def:
/a 3 def
names the value 3 as a, and
/a { ... } def
names the procedure between the braces as a.
Conditional execution is obtianed with if or ifelse:
a b gt { p } if
will only execute the procedure p if a is greater then b, and
a b gt { q } { q } ifelse
will execute p if a is greater than b and q if not.
Loops are either definite:
1 1 10 { p } for
will execute p 10 times, and
{ b} loop
will execute b until an exit operation is executed, in which case the loop is exited.
The Postscript page is an unbounded plain stretching in the positive x and y directions. The origin is in the bottom left-hand corner (like ordinary graph paper), and all measurements are relative to this. The default unit of measurement is 1 point, or 1/72 inch. This is a traditional typesetters unit, but the units can be changed by scaling to anything you like. For instance,
72 72 scale
will change the units to inches, since now everything is now 72 times as large.
The axes can by moved and rotated using translate and rotate:
2 2 translate
will move the origin to a point 2 units up and 2 units across from the bottom left-hand corner;
90 rotate
will rotate the axes 90( counterclockwise.
Drawing lines is done by placing them in a path first, and then "stroking" the path onto the page:
newpath 100 100 moveto 100 200 lineto 200 200 lineto closepath stroke
draws a right-angled triangle.
Text can be drawn by using show:
/Helvetica findfont 12 scalefont setfont 100 100 moveto (Some Text) show
Draws the text "Some Text" in 12 pt. Helvetica at the postion (100, 100)
The parameters of the graphics state can be saved with gsave and restored with grestore. The state includes the current font, current color, current linewidth, current position and current path.
Every Postscript file must start with a comment which tells the device that the file contains Postscript code:
%!Postscript
Nothing will actually show unless the file contains at least one
showpage
which transmits the page to the devices renderer (Postscript printers convert their pages into raster images for tramsission to the drum).
If a Postscript file is simply sent directly to a Postscript printer, it will execute the program, porduce the page or pages, and print them.
Here is the NMSU logo in Postscript:
%!Postscript %% definitions /outsidecircletext /radius exch def % give the first stack item a name /centerangle exch def % and the second /ptsize exch def % and the third /str exch def % and the fourth /xradius radius ptsize 4 div add def gsave % preserve the current graphics state centerangle str findhalfangle add rotate str {/charcode exch def ( ) dup 0 charcode put outsideplacechar } forall grestore % restore the original graphics state } def /insidecircletext /radius exch def /centerangle exch def /ptsize exch def /str exch def /xradius radius ptsize 3 div sub def gsave centerangle str findhalfangle sub rotate str {/charcode exch def ( ) dup 0 charcode put insideplacechar } forall grestore } def /findhalfangle {stringwidth pop 2 div 2 xradius mul pi mul div 360 mul } def /outsideplacechar {/char exch def /halfangle char findhalfangle def gsave halfangle neg rotate radius 0 translate -90 rotate char stringwidth pop 2 div neg 0 moveto char show grestore halfangle 2 mul neg rotate } def /insideplacechar {/char exch def /halfangle char findhalfangle def gsave halfangle rotate radius 0 translate 90 rotate char stringwidth pop 2 div neg 0 moveto char show grestore halfangle 2 mul rotate } def /pi 3.1415923 def /tri { newpath 2 29 moveto 26 -12 lineto 2 2 lineto closepath } def %% end of definitions gsave 500 700 680 sub currentpoint exch pop add translate 0.8 0.8 scale % first the text around a circular path /Helvetica findfont [14 0 0 12 0 0] makefont setfont % this goes outside the circle (NEW MEXICO STATE) 12 90 38 outsidecircletext % this goes inside /Helvetica findfont [17 0 0 12 0 0] makefont setfont (UNIVERSITY) 12 -90 48 insidecircletext % now the triangles 0 setlinejoin % make sure the lines join neatly 3 setlinewidth % set their width to 3/72 inch tri stroke 120 rotate tri stroke 120 rotate tri stroke grestore showpage
any | pop | -- | discard top element |
any1 any2 | exch | any2 any1 | exchange top tow elements |
any | dup | any any | duplicate top element |
an-1 ... a0 n j | roll | a(j-1) mod n ... a0 an-1 ... aj mod n | roll n elements up j times |
( any1 ... anyn | clear | ( | discard all elements |
num1 num2 | add | sum | num1 plus num2 |
num1 num2 | div | quotient | num1 divided by num2 |
int1 int2 | mod | remainder | int1 mod int2 |
num1 num2 | mul | product | num1 times num2 |
num1 num2 | sub | difference | num1 minus num2 |
num1 | abs | num2 | absolute value of num1 |
num1 | neg | num2 | negative of num1 |
any1 any2 | eq | bool | test equaL |
any1 any2 | ne | bool | test not equal |
num1 num2 | ge | bool | test greater than or equal |
num1 num2 | gt | bool | test greater than |
num1 num2 | le | bool | test less than or equal |
num1 num2 | lt | bool | test less than or equal |
bool1 bool2 | and | bool | logical and |
bool1 | not | bool2 | logical not |
bool1 bool2 | or | bool | logical or |
-- | true | true | push value true |
-- | false | false | push value false |
any | exec | -- | execute object |
boo proc | if | -- | execute proc if bool is true |
bool proc1 proc2 | ifelse | -- | execute proc1 if bool is true, execute proc2 if bool is false |
init incr limit proc | for | -- | execute proc with values from init by steps of incr to limit |
proc | loop | -- | execute proc indefinitely |
-- | exit | -- | exit innermost active loop |
any | = | -- | write text representation of any to standard output |
( any1 ... anyn | stack | ( any1 ... anyn | print stack non-destructively using = |
-- | gsave | -- | push graphics state |
-- | grestore | -- | pop graphics state |
num | setlinewidth | -- | set line width |
tx ty | translate | -- | translate user space by (tx, ty) |
angle | rotate | -- | rotate user space by angle |
-- | newpath | -- | initialize current path to empty |
x y | moveto | -- | set current point to (x, y) |
dx dy | rmoveto | -- | relative to moveto |
x y | lineto | -- | append straight line to (x, y) |
dx dy | rlineto | -- | relative to lineto |
x y r ang1 ang2 | arc | -- | append counterclockwise arc with radius r between ang1 and ang2 |
-- | closepath | -- | connect subpath back to starting point |
-- | fill | -- | fill current path with current color |
-- | stroke | -- | draw line along current path |
key | findfont | font | returns the font called key |
font scale | scalefont | font' | scales a 1 pt. font by scale |
font | setfont | -- | sets font as current in the graphics state |
string | show | -- | paints string on the page |
-- | showpage | -- | tramsit the current page to the printer and reset the page |
Here is the code for the two little quilts, a and b, and a test that draw them:
%!Postscript /bands {newpath 0 0 moveto 0 144 rlineto 144 0 rlineto 0 -144 rlineto closepath 0 144 moveto 144 -144 rlineto 48 144 moveto 96 -96 rlineto 96 144 moveto 48 -48 rlineto 0 0 moveto stroke } def /arcs {newpath 0 0 moveto 0 144 rlineto 144 0 rlineto 0 -144 rlineto closepath 144 0 moveto 0 0 144 0 90 arc 144 48 moveto 48 48 96 0 90 arc 144 96 moveto 96 96 48 0 90 arc 0 0 moveto stroke } def gsave 72 576 translate 1 1 3 {bands 0 -180 translate} for 180 180 translate 1 1 3 {arcs 0 180 translate} for grestore 180 120 translate 0.2 0.2 scale 1 1 4 {-90 rotate bands 0 288 translate} for 1 1 4 {1 1 3 {arcs 90 rotate} for 0 288 translate} for showpage