Index of /dev/null

PostScript: the beauty of thinking backwards

Lately, while snooping around within old coding stuff, I rediscovered an ancient treasure: the PostScript programming language. It's older than dirt and situated deep down under the bottom of Adobe's PDF technology. Intended to be an efficient and highly flexible printer instruction language, it's actually a Turing-complete programming language, based on a very clear and suprisingly simple stack-oriented concept, outputting geometric results to suitable devices.

Everything is on a stack here. That is, operators reduce items on the operand stack, where operands are objects of various type. Thus, calculations are denoted in reverse polish notation:

3 1 sub 2 div                             % (3 - 1) / 2

or, equivalently, and even more polish ;)

2 3 1 sub exch div                        % (3 - 1) / 2

where exch exchanges the two topmost operands. PostScript procedures are executable arrays, i.e. objects consisting of a series of operators and operands, enclosed in braces:

{ 2 div } 3 1 sub exch exec               % (3 - 1) / 2

Executable objects may be executed at any time using the exec operator. Hence, functional concepts such as lambda expressions and lazy evaluation immediately suggest themselves. A complete script using certain font and graphic operators might look as follows:

%!PS-Adobe-3.0 EPSF-3.0
%%BoundingBox: 0 0 170 50

% load standard font
24 /TimesBold findfont exch scalefont setfont
.25 setlinewidth
1 setgray                                 % white

gsave                                     % push graphic state
{                                         % push lambda expression
  gsave                                   % push graphic state
  (transformations) dup                   % duplicate string
  0 0 moveto show                         % print filled
  0 setgray                               % set black
  0 0 moveto true charpath stroke         % print outlined
  grestore                                % pop graphic state
} 
.6 .05 1.1 {                              % loop condition
  setgray                                 % using loop iterator
  .8 dup 13 dup translate scale           % matrix transformation
  -4 rotate
  dup exec                                % execute expression
} for                                     % loop operator
grestore                                  % pop graphic state
exec                                      % finally pop and execute

0 setgray                                 % black
[-1 0 0 1 150 30] concat                  % matrix transformation
(affine) 0 0 moveto show                  % print

showpage
%%EOF
affine transformations

Rendering is globally controlled by a transformation matrix, defining an affine transformation (i.e. a linear mapping plus translation) that can be modified either by the translate, scale, and rotate operators or by explicit left-sided matrix multiplication using the concat operator.

A named procedure in the usual manner is obtained as an executable literal that can be stored within the current dictionary. That is, the def operator is used to manage the symbol table:

/div2 { 2 div } def
3 1 sub div2                              % (3 - 1) / 2

Now, as an apprentice piece of recursive geometry, let's see how Koch's famous Snowflake works:

% t s koch => -
/koch {
  dup depth le {     % s < depth
    1 add exch % s => s+1
    3 div exch % t => t/3

    % prepare stack:
    % t/3 s+1 60 t/3 s+1 240 t/3 s+1 60 t/3 s+1
    60 3 copy 180 add 3 copy 180 sub 3 copy pop
    koch rotate koch rotate koch rotate koch
  } 
  { pop 0 rlineto }         % draw
  ifelse
} def
kochaffine transformations

The koch function reduces a step and a length parameter from the stack, while depth is defined in the global dictionary. The decision of whether dive into recursion incrementing the step value or stop and draw a line segment is made by help of the ifelse operator. So,

/depth 5 def
newpath 0 0 moveto 100 1 koch stroke

displays the curve of length 100 with a recursion depth of 5.

For reference, see the PostScript Language Reference Manual, the Blue Book, and also Thinking In PostScript by Glenn Reid.

1 comment

post a comment

Leave a comment
You may use HTML tags for style