The Postscript Page Description Language

PDF files@

First, Postscript is built on Forth, so it is stack based (parameters are passed on the operand stack, vars are defined on the dictionary stack, etc....) and it is RPN (1 2 add rather than 1 + 2) but it adds many "words" and concepts that expand Forth into a Page Description Langauge.

Basic Language

Text after a % on any line is a comment. %! is a special comment that marks the start of a PS program.

number or radix#number or mantissaEexponent all describe numbers.

(string) describes a string. The ( ) actually place a pointer to the string on the stack, and not the real string.

<hh> describes a binary string from hex digits. For example <414243> defines the string "ABC"

\ooo where ooo is an octal number puts the ASCII character value of that numberon top of stack. \r is the carrage return and so on.

/text defines text as a word in the dictionary at the top of the dictionary stack. It is normally used with def to assign a value to that word. For example: /answer 42 def will make a new word ("answer") which will be defined as 42. Anywhere you would have put a 42, you can now put answer instead. The value assigned to the word is defined by the execution of the words between the /name and def. For example, /halfanswer answer 2 div def adds a new word that has a value of 21.

Squiggly brackets will defer the execution of PostScript until your routine gets called. A /zowie gleek zounds add def takes gleek and adds it to zounds before zowie is defined. A /zowie {gleek zounds add} def takes gleek and adds it to zounds, when zowie gets used. The { } return a pointer to the new function and mark the newly defined word as executable.

Executable words (actually ALL words) can be redefined. Even PS words can be re-defined. For example: /showpage can be re-defined to add any number of effects to the page just before printing.

bind causes all the words in your executable function to be translated into pointers to thier existing definitions. In our previous "zowie" example, gleek, zounds and add are all just text until zowie is called. It is not until that time that PS attempts to look up those words in the dictionary. In this case PS is acting as a pure interpreter. Obviously, this can get a bit slow. If all those words are already in the dictionary at the time zowie is defined, we could do /zowie { gleek zounds add } bind def which would later execute much faster since all the words used in zowie would exist as references rather than as text. PS is now acting as a compiler defining a function to be executed later. Keep in mind that if gleek is not defined until after zowie, it can't be bound. Also, if gleek is redefined after zowie is bound, zowie will use the old definition of gleek, not the new.

[words] describes an array where words can be any words. For example, [ 12 /Foo answer {9 div} ] is a 4 element array containing the number 12, a reference to a new word "Foo", the value 42 (assuming a previous /answer 42 def), and a reference to a function which has no name but that divides things by nine.

if else and then as well as for and other standard keywords are supported more or less as per Forth.

Language compairson

Some other language PostScript
javascript: var a = 21 /a 21 def
var answer = a * 2 /answer a 2 mul def
function square(x) { return  x*x; } /square { dup mul } def
for (var i=0; i<10; i++) { s += square(i); } 0 1 10 { /s i square s add def } for

Graphics

Coordinates in the User Space supplied as input in the form of a PostScript program in points (72/th of an inch) are translated to the Device Space used by the hardware through the Current Transformation Matrix which is a 3 by 3 matrix. One can degree rotate or x y scale or x y translate the User Space and all subsequent drawing commands will be affected.

The Graphics State describes the various settings that control how things are rendered. It can be saved (gsave) or restored (grestore) from the stack. This includes the Clipping Path which bounds the drawable area and the Current Path, which is the Path currently being described. A newpath is made up, bit by bit from from a starting point (x y moveto) followed by line segments, of a specified width (points setlinewidth), to other points (x y lineto) or by width and height (w h rlineto) and Bézier curves (x1 y1 x2 y2 x3 y3 curveto) along which lines can be drawn (stroke) or which, after closepath, can be filled (fill) with anything from white=1 to black=0 (fraction setgray), but Paths can also be used to do other things such as clip.

Raster Graphics are also possbile in PostScript but are avoided when possible to allow the result to be scaled without loss of resolution. The basic command is width height depth matrix data image where:

Text

Fonts are actually dictionaries which define the path to draw each letter. To get the font dictionary you need to /fontname findfont then pointsize scalefont and finally setfont to make it the current default font. Since the font is really just a path, you need to start a newpath then x y moveto and finally (load your string) and show it, which finds each letter from the string inside the font dictionary and fills it. Interesting effects can be done useing charpath in place of show to translate the string into a path, but NOT fill it. The resulting path can then be used to clip, stroke, fill, etc...

Pages

All pages end with showpage


%!
% example program

0 setgray                               % 100% black
1 setlinewidth                          % One point thick lines
72 72 moveto 72 144 lineto stroke       % Draw a one inch line

%over duplicates the entry just under the top of stack.
% a b => a b a
/over { 1 index } bind def	

/box {		%stack contains width height x y
  newpath
  moveto		% take the starting point from the stack
  0 over rlineto	% Left. Over gets the width from below the zero and height
  over 0 rlineto	% Top. Over gets the heigh from below width on the stack
  0 over neg rlineto	% Right. over gets width and neg makes it negative.
  closepath		% Bottom.
} bind def

gsave			% Save a copy of the current settings
  36 dup 85 72 box	% Build a half inch square path over a bit
  0.35 setgray		%   make it a little darker...
  fill			%   and fill it.
grestore		% Go back to the original settings

/inch { 72 mul } def	% Teach PS that an inch is 72 points.

3 setlinewidth		% Make the box lines wider
0.3 inch dup 		% set up a .3" square..
2 inch 1 inch box 	%  at 2",1" box ..
stroke			%    outline (why is the line black?)

/Times-Roman findfont   % Get the basic font.
10 scalefont            % Scale the font to 10 points.
setfont                 % Make it the current font.


newpath			% Start a new path
2.5 inch 1 inch moveto	% Lower left corner at (180, 72)
(Hello, world!) show	% Typeset "Hello, world!"

gsave
 72 72 translate	% Shift everything over an inch
 72 72 scale		% And make it an inch square

 8			% 8 columns of pixels
 8			% 8 rows of pixels
 1			% 1 bit per pixel
 [ 8 0 0 8 0 0] 	% unit square is (0,0) - (8,8)
 {<89ABCDEFFEDCBA98>}	% need 8 bytes of hex data here...
 image			%actually draw the thing
 gsrestore		%put back the environment

showpage



Note: For most of this, credit goes to Don Lancaster

To do a persistent download, you just start your PostScript textfile with serverdict begin 0 exitserver The printer should respond with %%[ exitserver: permanent state may be changed ]%%  More often than not, there will be no page ejected on a persistent download, so an end-of-file, a quit, a reset, or a timeout will be needed.

A carriage return or linefeed that is immediately preceeded by a reverse slash will get ignored by PostScript.

/oldstring workstring def does NOT create a new string. All you have done is saved a POINTER that points to the OLD copy of workstring. And if you change workstring with a PUT or a PUTINTERVAL, sure enough, oldstring also gets changed, because oldstring is NOT a string, it is just a pointer right on back to workstring. There is really JUST ONE string involved. When you want to save the EXACT contents of some string, you must create NOT A POINTER, but a BRAND NEW STRING that will hold the contents for you. Like so.... workstring dup length string cvs /oldstring exch def This immediately creates a new string and pokes the current values into oldstring. There is no longer any pointer involved, and workstring can go ahead and change all it wants to without changing old string.

The missing case command in PostScript is easily faked [{proc0} {proc1} … {procn}] exch get exec % options 0-n per stack integer A zero on the stack does {proc0} and so on. Extending this to a 256 element array lets you redefine any ASCII character for any action.

A little known power trick lets you place your entire PostScript program inside a string and then execute the string. As in (mygreat program code) cvx exec. Uses include preventing stack overflows on complex procs, or bypassing the 6K input buffer to solve comm hassles. The string can be 65K characters long.

Here’s a simple and clean way to merge two PostScript strings /mergestr {2 copy length exch length add string dup dup 4 3 roll 4 index length exch putinterval 3 1 roll exch 0 exch putinterval} def % merges top two stack strings into single stack string. To use, just do a (stringA) (stringB) mergestr to get (stringAstringB). The proc works by creating a new string of the proper length and then appropriately back-to-back copying the original strings.

This PostScript answer to CHR$(x) changes any 0-255 integer into its equivalent single ASCII character string... /chr$ {(X) dup 0 4 -1 roll put} def For instance, a decimal 76 on the stack changes into a string (L).

Convert an integer into a character string for printing with ( ) cvs show (after pushing the number to the stack, of course).

For complex shapes with many curves that must align perfectly at each cusp, see Don's curvtrace routine

Postscript print emulators:

Video Tutorials

A nice video that introduces the basic idea of Postscript:

And MaikBulme2000 made a series of Postscript programming tutorials: 1. Stack 2. Arithmetic 3. Graphics

See also: