PostScript

How to talk to your printer

You are looking at the guide accompanying a workshop about PostScript.

PostScript is a page description language. It allows developers to describe precisely how marks should appear on a page.

We believe in learning by doing, so you can expect to be asked to open your favorite editor or integrated development environment and gain first-hand practical experience with PostScript, by following along this guide. Exercises along the way help cement your new found knowledge making you into a PostScript whisperer.

What to expect

This book is divided into the following parts

  • Introduction
  • Tools
  • Projects
  • Appendices

Introduction

In the introduction part we will set the scene and talk about the what PostScript is, how it evolved and how it can be used. It will provide some context which will help to make sense of some of the language design decisions.

Tools

Tools are provided to have a better understanding of what makes PostScript tick.

The chapters are divided along interesting features that can and are used during the projects.

Projects

Projects are Bob Ross inspired expositions of the capabilities of PostScript. You should be able to dive right into a project, and have a holistic understanding of the tools. When you want to dive deeper into a certain subject, you can fall back onto the tools part. Remember, there are no mistakes, only happy accidents.

The projects are by no means the final destination. They are meant as a inspiration to fuel your own explorations into PostScript. It is literally a blank canvas that invites you to express your creativity.

Appendices

The appendices are there to provide context that are not naturally placed within the guide. They can be used as a resource for future studies and sometimes inspiration.

Origin

PostScript has a rich history that we delve into here for a small bit.

Developed by Adobe Systems PostScript culminated in 1984 as a laser printer driver. Now almost entirely replaced with its successor Portable Document Format (PDF), it is a

page description language in the electronic publishing and desktop publishing realm. It is a dynamically typed, concatenative programming language.

PostScript allows you to accurately describe how a page should look like. It is a language that usually is generated by a program instead of written by hand. Which is a shame because it is a beautiful language full of interesting aspects.

Characteristics

PostScript is described as a

dynamically typed, concatenative programming language

What does this mean?

Dynamically Typed

A type is a property associated with a certain programming constructs that can be used to determine what operations are legal.

Dynamically typed means that the type, i.e. the property that determines legal operations, is only known at runtime.

Concatenative

A concatenative language is part of programming paradigm that favors composition of functions over function application. This might be as illuminative as a broken flashlight.

What is meant by that can be illustrated by a code example in a pseudo language. Function application, i.e. applying functions to their arguments, typically looks like

x = f(w)
y = g(x)
z = h(z)

Here function f is applied to argument w, that result is applied to function g, etc.

In a concatenative language the same example would typically look like

w f g h

It's probably best to try your hand on an actual concatenative language to get a feel for the trade-offs between the different paradigms. Luckily PostScript provides just such a language.

Hello, World

"Hello, World!" for PostScript; a single line

The practice of a "Hello, World!" program, i.e. a simple program to demonstrate that your tool chain works as intended, has many forms. For PostScript this would be outputting a single line. In this chapter we are going to achieve just that.

Start your engines

Getting feedback is important. The sooner, the better. That is why we are going to use Ghostscript.

Ghostscript is

an interpreter for the PostScript® language and PDF files. Ghostscript consists of a PostScript interpreter layer and a graphics library.

You can find some notes about installing Ghostscript in the appendix.

Go ahead and start a Ghostscript session.

Note: Ghostscript on macOS does not have a rendered output. Instead of using the REPL you can write the instructions to a file and use ps2pdf to generate a PDF or use the PostScript interpreter on https://postscript-render.fly.dev/ for this workshop.

Read-Eval-Print-Loop

When you start Ghostscript the following things happen:

  1. A window is opened that shows a blank page. The page will show the output of the PostScript program that we enter.
  2. A Read-Eval-Print-Loop (REPL) is started. Via this REPL we can communicate with the PostScript interpreter.

The REPL first prints a header, something akin

Copyright (C) 2019 Artifex Software, Inc.  All rights reserved.
This software is supplied under the GNU AGPLv3 and comes with NO WARRANTY:
see the file COPYING for details.

And offers a prompt

GS>

Let's get moving

We will interact with the page by executing commands in the REPL. The first command will be moveto.

Enter the following line into the REPL.

0 0 moveto

Even when you look closely, the page will not be changed. The only thing that happened is that the current path is extended with a move to the origin. What this means will become clear in later chapters.

Draw the line

The next step in this walkthrough is drawing the line. This can be achieved with the lineto command

100 100 lineto

Again, nothing seems to have changed on the page. The PostScript interpreter only extended the current path with a line.

Stroke the current path

Only when we issue the stroke command, will the PostScript interpreter update the page.

stroke

Celebrate 🎉

With a line drawn on the page, you are well on your way to becoming a master of PostScript!

Final Thoughts

showpage

If you want to experiment some more it could be nice to start with a clean slate. The showpage command can be used for this. When you issue it

showpage

The REPL will respond with

>>showpage, press <return> to continue<<

When you continue the page is updated and you start in a fresh state. For an actual printer, the showpage command would instruct the printer to print all the marks on actual paper!

newpath

We have seen that the PostScript interpreter maintains a current path, and moveto and lineto commands modify it and stroke renders it on the page.

If you want to make sure that you start with a clean slate you can issue the newpath command. This will clear the current path and allows you to start anew.

Summary

We have created our first PostScript program by entering the following commands in the REPL.

newpath
0 0 moveto
100 100 lineto
stroke
showpage

It uses the following commands:

  • newpath: clears the current path.
  • moveto: extends the current path, and sets the current point.
  • lineto: extends the current path with a line from the current point to the point as specified by the arguments.
  • stroke: draws the current path.
  • showpage: Evicts the page and starts a new one.

Exercises

  1. What does the following program draw?
newpath
0 100 moveto
100 0 lineto
0 0 moveto
100 100 lineto
stroke
showpage
  1. With the commands you learned draw a simple house that looks like the house below "Hello, World!" for PostScript; a single line

Stack

I would like to revisit the "Hello, World!"-program to highlight an important point. For completeness, here is the source of the "Hello, World!"-program that draws a line on the page.

0 0 moveto
100 100 lineto
stroke

A different program that produces the exact same output is shown next

100 100 0 0
moveto
lineto
stroke

How can this be?

Operators

In the last chapter we have called moveto a command. The name that PostScript uses is operator.

An operator is a functionality that PostScript provides. We have seen examples like newpath, moveto, lineto and stroke. Some operators need data to operate correctly. PostScript calls the necessary data operands. You can think of them as arguments to the operator.

Operand Stack

Operands to operators are taken from the operand stack. A stack is

an abstract data type that serves as a collection of elements, with two main principal operations:

  • Push, which adds an element to the collection, and
  • Pop, which removes the most recently added element that was not yet removed.

Below we can see a stack with two items, 100 and 90.

A stack containing 100 and 90

The item 100 is said to be on the bottom of the stack, the item 90 is said to be on the top of the stack. When one pushes the item 10 on the stack, the stack grows upward.

A stack containing 100, 90 and 10

Value

When the PostScript interpreter encounters a value, like 100, 90 or 10, it will push it on the operand stack. So the first part of the alternative "Hello, World!"-program

100 100 0 0

Will push the values 100, 100, 0 and 0 on the operand stack. Resulting in a stack that looks like this

The stack that results in interpreting the first line of the alternative "Hello, World!"-program

Operator

When the PostScript interpreter encounters an operator it will execute it. If an operator needs operands to operate, it will take does from the operand stack.

For example, the moveto operator expects two operands. It will pop does from the operand stack.

100 100 0 0
moveto

So the above program will result in an operand stack similar too

The stack that results in interpreting the first two lines of the alternative "Hello, World!"-program

It leaves two operands for the lineto operator to consume.

Stack underflow

When the PostScript interpreter encounters an operator, but the necessary operands aren't on the stack, a stack underflow error is issued.

For example, when ones starts a Ghostscript session and immediately performs moveto

GS> moveto

one is greeted with an error:

Error: /stackunderflow in --moveto--
Operand stack:

Execution stack:
   %interp_exit   .runexec2   --nostringval--   --nostringval--   --nostringval--   2   %stopped_push   --nostringval--   --nostringval--   %loop_continue   --nostringval--   --nostringval--   false   1   %stopped_push   .runexec2   --nostringval--   --nostringval--   --nostringval--   2   %stopped_push   --nostringval--
Dictionary stack:
   --dict:727/1123(ro)(G)--   --dict:0/20(G)--   --dict:75/200(L)--
Current allocation mode is local
Last OS error: Resource temporarily unavailable
Current file position is 7

While developing PostScript programs one often encounters these kind of errors.

Summary

PostScripts maintains various stacks. One of them is the operand stack. The operand stack is manipulated by pushing values onto them. Operators take values from the stack by popping operands from the stack. When an operator tries to pop operands from the stack which does not have enough items a stack underflow error occurs.

Exercises

  1. For each of the following program fragments, draw the resulting operand stack.
100 100
100 100
0 0 
100 0
100 100
0 0 
100 0
0 100
moveto
100 100
0 0 
100 0
0 100
moveto
lineto
100 100
0 0 
100 0
0 100
moveto
lineto
moveto
lineto

What does the last program look like when printed?

  1. Write a program that results in an operand stack like this:

The stack containing 1, 2, 3, 4

  1. Is it always possible to write programs that first pushes all the operands on the stack before executing operators?

Stack Manipulation

Because the stack is so important, PostScript offers a slew of operators to manipulate the operand stack. In this chapter we are going to explore some of them.

We could show a picture of the stack, but that takes up a lot of space. Instead we will rely on a representation of the operand stack that fits on a line.

\[ a_{n-1}\ a_{n-2}\ \dots\ a_{1}\ a_{0} \]

The above line represents a stack that has \(n\) items. The bottom item is \(a_{n-1}\), the item above that one is \(a_{n-2}\), etc. The top item is \(a_{0}\).

Operators

push

Pushes onto the operand stack are often implicit. For example, it is enough just to list the value for it to be pushed on the operand stack.

37

The program listing above will push the value 37 onto the operand stack.

pop

The opposite of pushing is popping. This can be achieved by the pop operator. This will remove the top most item from the operand stack.

37 51 pop

This leaves the operand stack containing the value 37.

dup

To duplicate the top element on the operand stack use the dup operator.

37 dup

The above program will result in an operand stack containing two values: 37 37.

exch

If there are more than two items on the operand stack, exch allows you to exchange the top two items. If the operand stack contains 1 2 3, the operator exch will exchange the 2 and the 3, resulting in a stack containing 1 3 2. The program below demonstrates that behavior.

1 2 3
exch

copy

The copy operator is like dup on steroids. The dup operator copies the top most item on the operand stack. The copy operator, when provided with a number n to copy, copies the topmost n items.

Look at the following program.

1 2 3 4
3 copy

The first line pushes the values 1, 2, 3 and 4 on the operand stack. The next line, specifically 3 copy, first puts 3 on the operand stack and then executes the copy operator. This pops the top most number from the stack, in this case that would be 3, and copies that many elements onto the stack. This results in a operand stack

1 2 3 4 2 3 4

roll

Just like how copy is a generalization of dup, roll is a generalization of exch.

Assume we have the following stack

\[ \ldots\ a_{n-1}\ a_{n-2}\ \ldots\ a_{0}
\]

The roll operator allows to select the top most n items from the stack and "roll" them through j positions. So executing n j roll with the above stack results in

\[ \ldots\ a_{j-1}\ \ldots\ a_{1}\ a_{0}\ a_{n-1}\ a_{n-2}\ \ldots\ a_{j}
\]

With a concrete example, the following program

1 2 3 4
3 2 roll

results in the operand stack being

1 3 4 2

index

The index operator allows you to copy a single element in the operand stack to the top. With an operand stack as below

\[ \ldots\ a_{n}\ a_{n-1}\ \ldots\ a_{j}\ \ldots\ a_{0}
\]

the snippet j index results in a stack like

\[ \ldots\ a_{n}\ a_{n-1}\ \ldots\ a_{j}\ \ldots\ a_{0}\ a_{j} \]

Or with a concrete example

1 2 3 4
2 index

results in the operand stack

1 2 3 4 2

count

To count the number of items on the operand stack use the count operator.

clear

If you want to remove all the items execute the clear operator

Philosophy

Manipulating the operand stack is an important skill to learn. So much so that idiomatic PostScript advocates to think hard about the order of arguments and exclusively use operand stack manipulations.

This advice is written for application that generate PostScript in mind. When using PostScript creatively there are other mechanisms to organize your program. Ways that are geared towards readability and maintainability.

That does not mean that stack manipulation isn't worthwhile. It allows great flexibility.

Exercises

  1. What happens when there is only one item on the operand stack when exch is executed?
  2. How can you use copy to simulate dup?
  3. How can you use roll to simulate exch?
  4. How can you use index to simulate dup?
  5. What happens when you use negative numbers as the second operand for the roll operator? The second operand is the one on the top on the stack when roll is executed.
  6. The stack contains 1 2 3 4. Create a program to reverse the operand stack that contains four items.

Variables

Up until now all the operands that we used are put on the stack from literal values. When we are working on larger projects that quickly becomes a hassle. In this chapter we will learn how to define variables that can be reused.

Key

PostScript has a notion key. They are akin to symbols in Ruby. Keys provide a way to associate a value to a certain name.

You create a key by prefix a sequence of certain characters with a slash: "/". So the following code

/booster

pushes a key on the operand stack that is identified by the name booster.

Definition

To associate a value with a key PostScript uses the def operator. For example the following code

/size 100 def

associates the value 100 with the key /size.

Lookup

When you want to use the value that is associated with a certain key, you can look up the value by using the name of the key.

For example, the result of the following code

/size 100 def
size size

is that the value associated with the key size is pushed on the stack twice. In this particular case the top two items of the operand stack would be 100 and 100.

Benefits

The same benefits of using variables apply to PostScript programs as well as in other programming languages. Those include:

  • Readability: A program that uses well chosen names can help in understanding code.
  • Reuse: Instead of copying the literal value, variables allow you to reuse that value.
  • Adoptability: When a value needs to change there is a single source of truth for it. This makes it easier to experiment with different values.
  • Maintainability: Variables, when used to your benefit, serve as a form of documentation. They allow to show the intent which helps in the maintainability of code.

Tip

An idiom that you often encounter in PostScript programs involving the def operand is the following

100 /size exch def

If you take a minute to think about the above code sample, you might come to the conclusion that it associates the value 100 with the key /size.

The reasoning could go like this: The literal value 100 is pushed on the stack first, then the key /size is pushed on top of that. The exch operator changes the order of the top two items on the stack. So now, /size is at the bottom and 100 is on top. Just like if we would have executed the following code

/size 100

The def operator takes the two items on the stack and associates the key with the value. In this case size is associated with 100.

One place that you will use this idiom is when you have a long and involved calculation to produce a value and want repeated access to the value. Instead of first pushing a key onto the stack, performing the calculation and then making an association, effectively like this

/size
.
.
.
lines and lines of code to calculate a value
.
.
.
def

You could rearrange that into

.
.
.
lines and lines of code to calculate a value
.
.
.
/size exch def

This way the key is closer to the definition, which could aid in understanding.

Exercises

  1. You made a PostScript program that draws a house in the "Hello, World!"-chapter. Introduce some variables into your program that allows you to manipulate the shape of the house by changing the values.
  2. The dimensions for an A4 sheet of paper are 210 millimeters by 297 millimeters. Create a program that binds variables to these numbers.

Arithmetic

We have learned how to use variables in PostScript. Sometimes we want a group of variables related to each other.

For example, if you are drawing a house with a door, where the door should be directly in the center of the house. In this situation the location of the door should be calculated from the width of the house and the width of the door.

That is why we are looking at arithmetic operators.

Operators

Like regular operators arithmetic operators take arguments from the stack and places their results back on the stack.

There are corresponding operators for addition, subtraction, multiplication and division. The operators usually have a shortened name

OperationOperator name
additionadd
subtractionsub
multiplicationmul
divisiondiv

It would become pretty boring to list all the known arithmetic operators. Instead I would like to point you to the reference manual for an complete overview. You can use that to lookup operators that you think would exist.

Exercises

  1. In your drawing of a house, add a centered door.
  2. Assume that there are two numbers on the stack. What does the following program calculate
dup mul exch
dup mul add
sqrt

Procedures

Multiple houses drawn by a procedure

In the exercises before you found the opportunity to draw a house. An example of a house can be seen below

A simple house with a door

The image that opens this chapter is based on the above house. The naive way to create the opening image is to repeat the code and change the relevant parameters.

Although feasible, this is repetitive, error-prone and not much fun. We would rather create a procedure that does the work for us.

Deferred Execution

There is an interesting aspect to creating a procedures in PostScript or, for that matter, any interpreted language.

Take for example the add operator. The operator add takes two operands from the stack, adds them together and puts the result back onto the stack. But if we are creating a procedure, that should not happen when we are creating the procedure. It should happen when we execute the procedure. It should defer the execution until the procedure is called.

To signal to the PostScript interpreter to defer the execution of operators you use the opening brace: {.

This is akin to C-style languages, but it does play a different role here.

With a little imagination one could guess that to finish defining a procedure one uses the closing brace: }.

Venerable "Hello, World!"-example

Let's hark back to the PostScript variant of the "Hello, World!"-program. There we created a line. If we wanted to create a procedure for that we could do that in the following way.

I have opened a REPL in order to better explore how PostScript handles procedures. When I go and type in the following code into the REPL

GS>{
    0 0 moveto
    100 100 lineto
}

the PostScript interpreter responds with

GS<1>

Indicating that there is something on the operand stack. Using pstack to show the contents of the operand stack, PostScript echos back the definition of the procedure:

GS<1>pstack
{0 0 moveto 100 100 lineto}

With a procedure at the top of the stack, we can execute it with the exec operator. This is a novelty operator that we probably will use sparingly, but it gets the job done.

GS<1>exec

Remember, the only thing that will happen is that the current path is extended. Not until we stroke the path will anything become visible.

GS>stroke

The entire transcript of the REPL run is found below.

GPL Ghostscript 9.50 (2019-10-15)
Copyright (C) 2019 Artifex Software, Inc.  All rights reserved.
This software is supplied under the GNU AGPLv3 and comes with NO WARRANTY:
see the file COPYING for details.
GS>{
0 0 moveto
100 100 lineto
}
GS<1>pstack
{0 0 moveto 100 100 lineto}
GS<1>exec
GS>stroke
GS>

Binding Procedures

Defining procedures and immediately executing them with the exec operator kind of defies the purpose. Instead we can bind the procedure to a name so we can reuse it later on.

We can bind the above procedure to a key /segment with the following code.

/segment {
    0 0 moveto
    100 100 lineto
} def

Later on we can call it by looking up the name:

segment

If you would execute that in a REPL, you would notice that it does not leave anything on the stack. Instead, when PostScript notices that a bound value is executable it goes ahead and executes it!

Variables

By now you have seen numerous usages of the stack in PostScript. With a leap of imagination you could come up with a mechanism how to pass arguments to procedures: place them on the stack. See the exercises for a suggestion how that could work.

Exercises

  1. The segment procedure can draw only one segment; the one from 0 0 to 100 100. Change the definition of segment to accept arguments via the stack. The following program fragment should draw three lines
/segment {
    % your definition here
} def

0 0 50 100 segment
0 0 100 100 segment
0 0 150 100 segment
stroke
  1. In your implementation of 0 0 100 100 segment is the line drawn from 0 0 to 100 100, or the other way around? For a single segment it does not make a huge difference. But when we are creating longer paths it might. Write an implementation of segment that draws the line in the opposite direction.
  2. If you haven't already, try to bind the arguments for the segment procedure to names and use them.
  3. Take a look at your house and write a procedure that can reproduce the house.

Dictionaries

In the previous chapter on procedures you were asked to use variables in the exercises. If one isn't careful, one could encounter the problem of aliasing.

Take a careful look at the following code and try to predict what the resulting image looks like:

/height 595 def
/width 419 def

/box { % x y width height
    /height exch def
    /width exch def
    /y exch def
    /x exch def
    x y moveto
    x width add y lineto
    x width add y height add lineto
    x y height add lineto
    x y lineto
} def

newpath
0 height 2 div 72 height 3 div box
0 height 2 div 72 height 3 div box
0 height 2 div 72 height 3 div box
stroke

showpage

You will recognize a few sections in the above code

Definitions

/height 595 def
/width 419 def

Here we bind the keys height and width to the values 595 and 419. These are the dimension of A5 paper in PostScript points, the default scale that is used.

Procedures

/box { % x y width height
    /height exch def
    /width exch def
    /y exch def
    /x exch def
    x y moveto
    x width add y lineto
    x width add y height add lineto
    x y height add lineto
    x y lineto
} def

The code goes on to bind a procedure to the key box.

The procedure uses the tip from the variable chapter to bind the arguments to names.

Note that if we write the arguments in order

x y width height

height ends up on top of the stack. That is why you have to bind them in the reverse order.

It goes on to draw a box by moving to the lower left corner, and drawing lines to each corner in a counter-clockwise fashion.

    x y moveto
    x width add y lineto
    x width add y height add lineto
    x y height add lineto
    x y lineto

Drawing

The next section of code uses the defined box procedure to draw, and stroke, three boxes.

newpath
0 height 2 div 72 height 3 div box
0 height 2 div 72 height 3 div box
0 height 2 div 72 height 3 div box
stroke

The same arguments are passed each time. So one would expect to see only one box.

Three boxes where one would expect only one

Aliasing

As hinted in the opening paragraph the unexpected boxes originate from aliasing. If you look carefully you will notice that the "global" width and height variables are reassigned each time the box procedure is called.

One could solve the problem if one could scope the variables to the procedure only. This is where dictionaries come in.

Use of dictionary

A single box, drawn three times

The above image is the one we expected. It is drawn by the following program.

/height 595 def
/width 419 def

/box { % x y width height
    4 dict begin
    /height exch def
    /width exch def
    /y exch def
    /x exch def
    x y moveto
    x width add y lineto
    x width add y height add lineto
    x y height add lineto
    x y lineto
    end
} def

newpath
0 height 2 div 72 height 3 div box
0 height 2 div 72 height 3 div box
0 height 2 div 72 height 3 div box
stroke

showpage

The only difference with the program mentioned in the beginning of this chapter is the pair

    4 dict begin

and

    end

4 dict creates a dictionary that can contain 4 items and puts it on the operand stack, begin takes the dictionary from the stack and makes it the current dictionary, so that it will be used to bind variables in and look up names from.

end restores the previous dictionary as the current dictionary.

Dictionary Stack

The operand stack is of central importance in any PostScript program. But it is not the only stack that PostScript knows about.

There is also a dictionary stack. Just like the operand stack the dictionary stack is a stack. We saw that you can use begin and end to push dictionaries onto and pop them from the stack.

The dictionary stack plays an integral role in PostScript programs. Specifically how it looks up values bound to names. When the PostScript interpreter encounters a name like bergen it will search for a binding in the current, or top-most, dictionary.

If it finds the key in the dictionary it returns the value bound to it.

If it can not find the name in the dictionary it begins searching in lower dictionaries on the dictionary stack.

Interestingly enough, PostScript programs start with a number of dictionaries on the stack. The most important one is the system dict. It contains all definitions of the PostScript operators!

Literal Dictionaries

Creating a dictionary with the dict operator and pushing it on the dictionary stack with begin makes it the current dictionary. It will be used to bind names to values with the def operator.

There is a literal notation for dictionaries. For example, the following snippet

3 dict begin
    /foo 1 def
    /bar 2 def
    /baz 3 def
...

is equivalent to

<</foo 1 /bar 2 /baz 3>> begin
...

Exercises

  1. What values are left on the stack after the following program executes.
<</a 1 /b 2>> begin
    /a 3 def
    a b add
    /b exch def
    a b
end
  1. In the following snippet, what value is the name a bound to, according to the PostScript interpreter.
3 dict begin
    /a 1 def
    /b 2 def
    /c <</a 3 /b 4>> def
    c begin
...

Drawing primitives

A doodle made possible by the drawing primitives of PostScript

It is about time we talk about drawing with PostScript!

The above doodle is created with some of the drawing primitives that PostScript provides. To wet our appetite we will list the entire program below

newpath

0 0 moveto
100 100 lineto
100 150 50 -90 180 arc 
50 50 0 100 0 0 curveto
closepath
stroke

showpage

Current Path

The central concept in PostScript programs is the path. The drawing primitives construct paths. You might remember from the "Hello, World!"-program that your first construct a path and then stroke it. Later we will learn that paths can also be filled.

The path that is being constructed is called the current path. The drawing operators change the current path in certain ways.

newpath

The newpath operator clears the current path and starts an empty path, ready to be altered to your hearts content.

moveto

The moveto operator changes the current point. The current point is an important building block for drawing primitives. Usually drawing primitives change the current point to be the current point to the last control point.

The moveto operator requires two operands to be present on the stack. Those are the x-coordinate and the y-coordinate of the current point to be.

No marks will be left on the paper with a moveto operator.

lineto

If you do want to leave a straight line segment as a mark on the paper, use the lineto operator.

The lineto operator requires four operands. The x- and y-coordinates of the start point and the x- and y-coordinates of the end point. The current point will be set to the end point.

arc

The arc operator and variants create a circular arc. It needs 5 operands. The x- and y-coordinate of the center of the arc, the radius of the arc and the two angles in degrees.

arc creates a counter-clockwise arc from the first angle to the second angle. The arcn variant produces a clockwise arc from the first angle to the second angle.

Furthermore, a line segment is added from the current point to the first point, the point at the first angle, of the circular arc.

The following snippets show the various options

0 0 moveto
100 100 50 0 270 arc
0 0 moveto
100 100 50 270 0 arc
0 0 moveto
100 100 50 0 270 arcn
0 0 moveto
100 100 50 270 0 arcn

Their result collated in a single image:

Some of the variants of arc

curveto

The curveto operator extends the current path with a cubic bezier curve.

Bezier curves are fascinating and well-behaved drawing tool. We will not say a lot about it now, but feel free to pick the brain of the workshop leaders.

closepath

The close path operator will draw a line segment from the current point to the point designated by the last moveto operator.

There is a subtle difference, that will be noticeable under close scrutiny, between drawing the segment yourself and using closepath.

The difference between "closing" a path by hand and using closepath

Relative drawing primitive

The above drawing primitives all accept operands. When the operands are coordinates they are global. For example if you want to draw a square with the lower left corner at 10 10 and with side length of 30 one could use the following program

10 10 moveto
40 10 lineto
40 40 lineto
10 40 lineto
closepath

The problem is that we need to calculate the endpoints. We can let PostScript do the heavy lifting, but there is an alternative. Take a look at the following snippet

10 10 moveto
30 0 rlineto
0 30 rlineto
30 neg 0 rlineto
closepath

The big difference is the use of rlineto. This will draw a line segment with a relative to the current point.

Almost all the mentioned drawing primitives have such a relative cousin.

Reference

There are more drawing operators, all with a specific need. Listing them all isn't very exciting. Instead I will point you to the reference.

Exercises

  1. Match the arc snippets to the drawing.
  2. There is no relative arc operator. Provide one yourself. Hint: take a look at the currentpoint operator.

Graphics State

We have learned that PostScript differentiates between constructing a path and painting that path.

In this chapter we are mentioning a few internal graphic states that PostScript keeps track of that effect the resulting marks on the paper.

Almost all state variables have a pair of operators associated with them. One that set the value, the other to get the value.

setgray / currentgray

To influence the grayness with which paths are stroked use the setgray operator. It takes a single operand, a value between 0 and 1. Black corresponds to 0, white corresponds to 1.

Below you see the same line stroked with different levels of gray.

Different levels of gray

setlinewidth / currentlinewidth

To influence the thickness with which paths are stroked use the setlinewidth operator. Just like the setgray operator, setlinewidth takes a single operand, a measure how thick the stroked line should be.

Below you see the same line stroked with different thickness levels.

Different thicknesses of lines

setlinecap / currentlinecap

To influence how the ends of lines are capped when stroked use the setlinecap operator. It takes a single operand. Its value determines the cap.

operandcap
0butt
1round
2square

Different line caps

setlinejoin / currentlinejoin

To influence how lines are joined when stroked use the setlinejoin operator. Just like the setlinecap operator, setlinejoin takes a single operand. Its value determines how to join lines.

operandjoin
0miter
1round
2bevel

The miter join has some other parameters associated with it. We will let the reference tell you about them.

Different line joins

gsave / grestore

It will probably not surprise you that the graphics state is maintained in a stack as well, the graphics stack.

If you want to revert to a graphics stack after you have stroked a few paths, you can use gsave to push onto the graphics stack, and grestore to return to it.

Exercises

  1. Draw your house in a different shade of gray.

Coordinates

The painting primitives accept operands that describe where marks should be made. These descriptions are relative to a coordinate system.

This coordinate system starts out with the center in the lower left corner of the page. With its x-axis horizontal pointing to the right and its y-axis vertical pointing up. Its units are expressed in the arcane PostScript points.

The coordinate system can be modified to suit your needs. This chapter describes a few possibilities. We will be using the asymmetric letter P to visualize the transformations.

The asymmetric letter P

translate

The asymmetric letter P, translated

The translate operator translates the coordinate system. It accepts two operands, that determine the amount to translate the origin in both the x-axis and y-axis.

rotate

The asymmetric letter P, rotated

The rotate operator rotates the coordinate system. It accepts a single operand, an angle in degrees, that determines the amount to rotate the coordinate system.

Positive angles rotate counter clockwise.

scale

The asymmetric letter P, scaled

The scale operator scales the coordinate system. It accepts two operands, that determine the factor to scale the x-axis and y-axis.

Non-uniform scaling, i.e. scaling with different factors for the x-axis and y-axis, produce a line width that depends on the direction of the line.

Combination

By combining translate, rotate and scale in various ways, one can achieve all transformations that are allowed and possible by PostScript.

PostScript allows for direct manipulation of the current coordinate system. If you want to know more about that see the reference.

Control Flow

PostScript is an interpreted language. It reads operators from a program and executes them. Some of these operators allow the developer to control the flow of execution.

All control flow operators have a similar structure. They take as operands one or more procedures and a description when to execute it.

We will go through a few control flow operators, but don't hesitate to look for more in the reference.

if

The if operator takes as its two operands a boolean, which can be obtained with various operands like eq, and, not etc, and a procedure.

It will execute the procedure only if the boolean is true.

ifelse

With a little imagination one could guess what the ifelse operator does. It accepts three operands, a boolean and two procedures. Depending on the truth value of the boolean, either the first procedure is executed or else the second procedure is executed.

repeat

If you want to repeat a procedure a number of times, repeat is the operator you are looking for. Its operands, a number and a procedure, control the execution.

The number determines how many times the procedure is executed.

for

This is your standard for loop. It accepts a whooping four operands. E.g.:

0 1 10 { pop } for

The first three operands are numbers: initial, increment and limit. The fourth operand is the procedure to execute.

The procedure runs with values from initial by steps of increment to limit, inclusive. Each time around the loop, the index is pushed onto the stack for the procedure to consume.

Exercises

  1. Imagine that the language designers forgot to provided ifelse, but they provided if. Implement ifelse in terms of if and the boolean operators.

Grid

A grid visualizing the current coordinate system

The first project we are going to work on is a utility that allows us to visualize the current coordinate system.

Brownian Motion

A random word with 6 principal directions

Brownian motion is

the random motion of particles suspended in a medium.

Pythagorean Tree

A Pythagorean Tree with an angle of 44

Pythagoras is attributed to a theorem that relates the area of the squares erected on the sides of a right-angled triangle.

\[ a^{2} + b^{2} = c^2 \]

Koch Curve

An approximation of the Koch Curve

The Koch curve is a famous fractal.

Hex Grid

Hex paper as used with GURPS, a role-playing system

Hex grids are sometimes used for role-playing purposes.

Sun flower

A sunflower, chosing a most irrational angle successive seeds

The florets of the sunflower naturally arrange themselves along spirals.

Chaos Game

The Sierprinksi gasket as rendered with the Chaos game

The chaos game is a way to render certain fractals, like the Sierprinski gasket above.

L Tiling

fifth stage of the L-tiling, based on the self-similar subdivision of the L tetromino

You can tile the plane with L-tetrominoes.

Ghostscript

In order to save trees, we are not going to send our experimentations to an actual printer. Instead we are going to use a PostScript interpreter.

Specifically, we are going to use Ghostscript.

The Ghostscript Logo

Ghostscript is a

an interpreter for the PostScript® language and PDF files. Ghostscript consists of a PostScript interpreter layer and a graphics library.

Installation

Obtaining Ghostscript depends on your platform.

Windows

The installation page has binaries for Windows.

Mac

There is a homebrew recipe that can be used to install Ghostscript.

homebrew install ghostscript

Linux

It is likely that Ghostscript is already installed, or that you can install it via a package manager. Otherwise the installation page has binaries for Linux as well.

Verification

Once installed you can verify that Ghostscript works as intended by starting the program. There are various aliases notably ghostscript or the shorter gs should open a blank window and show the following prompt in the terminal

GPL Ghostscript 9.50 (2019-10-15)
Copyright (C) 2019 Artifex Software, Inc.  All rights reserved.
This software is supplied under the GNU AGPLv3 and comes with NO WARRANTY:
see the file COPYING for details.
GS>

This can be used as a Read-Eval-Print-Loop (REPL). See the Hello, World on how to use that.

Alternative

Ghostscript is a low-level tool that allows you to inspect PostScript programs and how they are interpreted. But often you will want to open a high level viewer.

Again, installing a PostScript viewer is platform dependent. Below we will provide some suggestions per platform

Windows

psviewer seems a capable PostScript viewer on Windows

Mac

The default Preview.app has the capability to show PostScript files

Linux

The utility gv, which stands for ghostview and can be installed via a package manager, is a clunky, but good viewer. It has the option to watch the file you are developing for changes and update the display when changes occur.

In case of emergency

If all the above fails, we have created a utility that will render your PostScript on the web:

https://postscript-render.fly.dev

Reference Manual

This workshop is an introduction to the PostScript language. Although it tries to provide a thorough exposition of PostScript, the image model and its operators, it was never intended to be complete.

When you find the guide lacking the PostScript Language Reference is the definitive source for all things PostScript. We provided a PDF version of the reference with the material. You can find it in the file PLRM.pdf or online:

https://tmp.fnordig.de/booster2022/PLRM.pdf

Paper Sizes

There are various standards that specify paper sizes. A popular one is the A-series, which we will explore in some detail in this chapter.

A-series

The A-series of paper sizes, that range from A0 to A10, has interesting properties that allow one to deduce a lot about the actual dimensions of each type.

The first property is that A0, the largest in the series, has an area of exactly 1 square meter. Each next type in the series is half as big in area as its predecessor.

The second property is that each type has a similar shape. This means that if one could shrink or grow a certain type with an appropriate amount, you would be able to produce all the other types.

Stacked paper-types from the A-series of paper sizes

Dimensions

If you are into those things, you can put pen to paper and figure out the exact dimensions for each type of paper in the A-series. We do just that below the summary of the results.

The next table summarizes the calculations below and lists width and height in millimeters for the types in of paper in the A-series when in portrait mode.

TypeWidth (mm)Height (mm)
A08411189
A1594'841
A2420594'
A3297420
A4210297
A5148'210
A6105148'
A774105
A852'74
A93752'
A102637

The numbers with an accent are taken from the standard and deviate from the calculated value due to different rounding scheme.

Calculation

Diagram to aid in the calculation of paper sizes in the A-series

Take a look at the above diagram. We will use it to figure out the dimensions of the various types of paper in the A-series.

We know that all the papers have similar size. So to get started we are going to figure out how much longer the long side is compared to the short side.

Scaling Factor

We set the width to 1 unit and for our convience the height to \(2x\) units. Since the two sheets of paper one type down make up one of the starting type, and since the shapes are similar, meaning they have the same ratio we find out that

\[ \frac{2x}{1} = \frac{1}{x} \]

By multiplying with \(x\) on both sides we find that

\[ 2x^{2} = 1 \]

Or

\[ x^{2} = \frac{1}{2}
\]

This equation has two solutions, one negative and one positive. Since we are looking for a length, only the positive one makes sense.

\[ x = \sqrt{\frac{1}{2}} = \frac{\sqrt{1}}{\sqrt{2}} = \frac{1}{\sqrt{2}}\cdot 1 = \frac{1}{\sqrt{2}}\frac{\sqrt{2}}{\sqrt{2}} = \frac{1}{\sqrt{2}\cdot\sqrt{2}}\frac{\sqrt{2}}{1} = \frac{1}{2}\sqrt{2} \]

With the scaling factor being twice \(x\) the scaling factor becomes \(\sqrt{2}\).

Dimension of A0

We now know the scaling factor. The next step is finding out the dimension of a sheet of A0 paper. The defining property, besides the ratio between the sides, is that A0 has an area of 1 square meter. Let's figure out the dimensions from that.

Let \(s\) be the short side of an A0 sheet of paper. The long side would then be \(s\sqrt{2}\). Its area becomes

\[ s^{2}\cdot\sqrt{2} = 1
\]

or

\[ s^{2} = \frac{1}{\sqrt{2}} \]

We are still looking for a size, again we are interested in the positive solution of this equation.

\[ s = \frac{1}{\sqrt[4]{2}} \approx 0.8408964152537145430311254762332148950400342623567845108132260859\ldots \]

The dimensions of the A0 sheet of paper then becomes in millimeters 841 by 1189.

Points, Inches and Millimeters

For historic reasons the default unit of length is the PostScript point, or points for short.

There are 72 points in an inch. There are also 25.4 millimeters in an inch.

So if you want your default measurement to be in millimeters you can use a scaling factor of \(\frac{72}{25.4}\).

Or in PostScript code:

72 25.4 div

Cheatsheet

A short overview of common operators and constructs, useful for examples & projects in this guide. See the Reference Manual for the full list.

Stack Manipulation Operators

Stack beforeOperatorStack afterDescription
$$any$$1$$any\ 1$$Push element 1 onto the stack, value can be any literal or name
$$any$$pop$$-$$Discard top element
$$a_{1}\ a_{2}$$exch$$a_{2}\ a_{1}$$Exchange top two elements
$$a_{1}\ a_{2}$$dup$$a_{2}\ a_{1}$$Duplicate top element
$$a_{1}\ \dots\ a_{n}\ n$$copy$$a_{1}\ \dots\ a_{n}\ a_{1}\ \dots\ a_{n}\ $$Duplicate top \( n \) elements
$$a_{n}\ \dots\ a_{0}\ n$$index$$a_{n}\ \dots\ a_{0}\ a_{n}$$Duplicate arbitrary element
$$a_{n-1}\ \dots\ a_{0}\ n\ j$$roll$$a_{(j-1)\ mod\ n}\ \dots\ a_{0}\ a_{n-1}\ \dots a_{j\ mod\ n}$$Roll \( n \) elements up \( j \) times
$$a_{n}\ \dots\ a_{0}$$clear$$-$$Discard all elements
$$a_{n}\ \dots\ a_{0}$$count$$a_{n}\ \dots\ a_{0}\ n$$Count elements on stack

Arithmetic and Math Operators

Stack beforeOperatorStack afterDescription
$$n_{1}\ n_{2}$$add$$sum$$\( n_{1} + n_{2} \)
$$n_{1}\ n_{2}$$sub$$diff$$\( n_{1} - n_{2} \)
$$n_{1}\ n_{2}$$mul$$product$$\( n_{1} \times n_{2} \)
$$n_{1}\ n_{2}$$div$$quotient$$\( n_{1} \div n_{2} \)
$$n$$neg$$-n$$Negative of \( n \)
$$n$$sqrt$$\sqrt{n}$$Square root of \( n \)
$$a$$cos$$\cos{a}$$Cosine of \( a \) degrees
$$a$$sin$$\sin{a}$$Sine of \( a \) degrees
$$-$$rand$$n$$Pseudo-random integer

Array Operators

Stack beforeOperatorStack afterDescription
$$-$$[$$mark$$Start array construction
$$mark\ obj_{0}\ \dots\ obj_{n-1}$$]$$array$$End array construction
$$array$$length$$int$$Number of elements in \( array \)
$$array\ proc$$forall$$-$$Execute \( proc \) for each element of \( array \)

Dictionary Operators

Stack beforeOperatorStack afterDescription
$$n$$dict$$dict$$Create dictionary with capacity \( n \)
$$-$$<<$$mark$$Start dictionary construction
$$mark\ key_{1}\ value_{1}\ \dots\ key_{n}\ value_{n}$$>>$$dict$$End dictionary construction
$$dict$$length$$int$$Number of entries in \( dict \)
$$dict$$begin$$-$$Push \( dict \) on dictionary stack
$$-$$end$$-$$Pop current dictionary off dictionary stack
$$/key\ value$$def$$-$$Associate \( key \) with \( value \) in current dictionary

Relational and Boolean Operators

Stack beforeOperatorStack afterDescription
$$n_{1}\ n_{2}$$eq$$bool$$Test equal
$$n_{1}\ n_{2}$$ne$$bool$$Test not equal
$$n_{1}\ n_{2}$$ge$$bool$$Test greater than or equal
$$n_{1}\ n_{2}$$gt$$bool$$Test greater than
$$n_{1}\ n_{2}$$le$$bool$$Test less than or equal
$$n_{1}\ n_{2}$$gt$$bool$$Test less than
$$-$$true$$\text{true}$$Literal value \( \text{true} \)
$$-$$false$$\text{false}$$Literal value \( \text{false} \)

Control Operators

Stack beforeOperatorStack afterDescription
$$any$$exec$$-$$Execute arbitrary object
$$bool\ proc$$$$if$$$$-$$Execute \( proc \) if \( bool \) is true
$$bool\ proc_{1}\ proc_{2}$$$$ifelse$$$$-$$Execute \( proc_{1} \) if \( bool \) is true, \( proc_{2} \) if false
$$init\ inc\ limit\ proc$$$$for$$$$-$$Execute \( proc \) with values from \( init \) by steps of \( inc \) to \( limit \)
$$n\ proc$$$$repeat$$$$-$$Execute \( proc \) \( n \) times
$$proc$$$$loop$$$$-$$Execute \( proc \) an indefinite number of times
$$-$$$$exit$$$$-$$Exit innermost active loop

Graphics State Operators

Stack beforeOperatorStack afterDescription
$$-$$gsave$$-$$Push graphics state
$$-$$grestore$$-$$Pop graphics state
$$num$$setlinewidth$$-$$Set line width
$$-$$currentlinewidth$$int$$Current line width
$$int$$setlinecap$$-$$Set shape of line ends for stroke (0 = butt, 1 = round, 2 = square)
$$-$$currentlinecap$$int$$Current line cap
$$int$$setlinejoin$$-$$Set shape of corners for stroke (0 = miter, 1 = round, 2 = bevel)
$$-$$currentlinejoin$$int$$Current line join
$$num$$setgray$$-$$Set color to specified gray value (0 = black, 1 = white)

Coordinate System and Matrix Operators

Stack beforeOperatorStack afterDescription
$$t_{x}\ t_{y}$$translate$$-$$Translate user space by \( (t_{x}, t_{y}) \)
$$s_{x}\ s_{y}$$scale$$-$$Translate user space by \( (s_{x}, s_{y}) \)
$$a$$rotate$$-$$Rotate user space by \( a \) degrees

Path Construction Operators

Stack beforeOperatorStack afterDescription
$$-$$newpath$$-$$Initialize current path to be empty
$$-$$currentpoint$$x y$$Return current point coordinates
$$x\ y$$moveto$$-$$Set current point to \( (x, y) \)
$$dx\ dy$$rmoveto$$-$$Perform relative moveto
$$x\ y$$lineto$$-$$Append straight line to \( (x, y) \)
$$dx\ dy$$rmoveto$$-$$Perform relative lineto
$$x\ y\ r\ a_{1}\ a_{2}$$arc$$-$$Append counterclockwise arc, with \( (x, y) \) as the center for a radius of \( r \) from angle \( a_{1} \) to angle \( a_{2} \)
$$x\ y\ r\ a_{1}\ a_{2}$$arcn$$-$$Append clockwise arc
$$x_{1}\ y_{1}\ x_{2}\ y_{2}\ x_{3}\ y_{3}$$curveto$$-$$Append Bézier cubic section
$$-$$closepath$$-$$Connect subpath back to its starting point

Painting Operators

Stack beforeOperatorStack afterDescription
$$-$$erasepage$$-$$Paint current page white
$$-$$stroke$$-$$Draw line along current path
$$-$$fill$$-$$Fill current path with current color

Utility Operators

Stack beforeOperatorStack afterDescription
$$any_{1}\ \dots\ any_{n}$$stack$$any_{1}\ \dots\ any_{n}$$Print stack nondestructively
$$-$$showpage$$-$$Transmit and reset current page

Common constructs

Comment

% Everything here is now a comment

Default A5 paper size

See also: Paper Sizes.

/pagewidth 148 def
/pageheight 210 def

Scale the coordinate system to millimeters

See also: Points, Inches and Millimeters.

72 25.4 div dup scale

Center the coordination system on the page

pagewidth 2 div pageheight 2 div translate

Dictionary literals

Creates a dictionary of key-value pairs from the given list, puts the dictionary on the dictionary stack, and later pops it off the stack again.

<</foo 1 /bar 2 /baz 3>> begin
    ...
end

Defining a procedure

Creates a new procedure named proc:

/proc {
    ...
} def

Giving names to procedure operands

Creates a new procedure named proc, which expects 3 operands on the stack: x y z.

/proc { % x y z
    [/x /y /z] dup length dict begin { exch def } forall
        ...
    end
} def

Starter kit

If you want to try your hand on an interesting problem, without writing your own project, then this starter kit is for you.

% Relative arc
%
% Draws an arc where the center is relative to the current point. Other arguments are like the arc operand.
% arguments
% dx: the amount to shift the center of the arc from the current point in the x-direction
% dy: similar to dx, but shift in the y-direction
% r: radius of the arc
% a: start angle
% b: end angle
% see the arc operator for more information.
/rarc { % dx dy r a b 
    [/b /a /r /dy /dx] dup length dict begin { exch def } forall
        currentpoint % x y 
        dy           % x y dy
        add          % x (y+dy)
        exch         % (y+dy) x
        dx add exch  % (x+dx) (y+dy)
        r a b        % (x+dx) (y+dy) r a b
        arc
    end
} def

% scale to coordinate system to use millimeters
72 25.4 div dup scale

% The dimensions of A5 paper
/page-width 148 def
/page-height 210 def

% translate the coordinate system to the center of the page
page-width 2 div page-height 2 div translate


newpath

% Create a procedure to automate the following code
% Think about what arguments the procudure should accept
% and how they relate to the resulting spiral
0 0 moveto
0 0 10 180 0 rarc
20 neg 0 20 0 180 rarc
30 0 30 180 0 rarc
40 neg 0 40 0 180 rarc

stroke

showpage

Create the above file and view it with a PostScript viewer. There is a spiral drawn.

Unfortunatly the spiral is fixed. The exercise is to create a procudure that draws spirals with these and other characteristics.

Sources

The sources for this guide, its examples and references can be found at the project repository.

It also holds a downloadable release; a package of all aforementioned items.