# Roland's homepage

My random knot in the Web

# Drawing with PostScript

PostScript (in the form of ghostscript) was for me the first way to generate vector graphics outside of a CAD program. I have several hundreds of figures written in PostScript for inclusion in e.g. LaTeX document.

Later I’ve started using other programs like metapost and asymptote. But in a sense, I’ve always been dissatisfied with them.

When the book Mathematical Illustrations was mentioned on hacker news, this re-kindled my interest in PostScript. And I learned some valuable lessons from it.

## Introduction

While metapost can draw beautiful pictures, it struggles with including bitmaps. And in some aspects, asymptote just feels plain weird to me because of its function overloading model. So I find myself coming back to PostScript.

Interactive drawing programs like for example Inkscape typically allow you to draw faster. But a programming language like PostScript give you precision. And since I mainly make technical illustrations which are generally drawn to scale, I need that. It is also easy to make PostScript code for a figure into a parametric functions for re-use. And while 3D CAD packaged like FreeCAD are generally parametric, 2D CAD programs like LibreCAD generally aren’t.

## Lessons learned

Over the years, I’ve learned some tricks, which I would like to share here. You will find the relevant PostScript fragments in my ps-lib repo.

### Using “include” files

This is one of the valuable lessons learned from Mathematical Illustrations. With the runlibfile operator, you can include postscript files in other postscript file. Keep in mind that such “include” files should not have an EPS header! I tend to use the .inc extension for such files. In a PostScript file they are used like this.

(grid.inc) runlibfile


If these “include” files are not in the current working directory, use the -I option for ghostscript to specify it. This is why we need to use runlibfile instead of run. The latter does not look for files in library paths!

Another possibility is to put all your include files in a dedicated directory, e.g. ~/lib/ps. Then create the environment variable GS_LIB pointing to that.

### Units

I like to use dimensions in metric units instead of points. So I’ve defined a couple of commands to do that.

/mm {72 mul 25.4 div} bind def
/cm {72 mul 2.54 div} bind def


With these commands, I can use dimensions like 2 mm which feels very natural. I prefer this over setting up a global scaling factor since it is explicit.

### Use a grid while drawing

The first thing I tend to draw on a blank page is a 10 mm by 10 mm grid, to help me with layout. When the image is finished I comment out the code that makes the grid.

I’ve put this code in a separate file that I include in my illustrations.

% Usage: “dx dy grid”
/grid {
5 dict begin
/dy exch def
/dx exch def
gsave
% Get the page sizes, keeping the orientation in mind.
/orient currentpagedevice /Orientation get def
orient 3 eq { % landscape
/w currentpagedevice /PageSize get 1 get def
/h currentpagedevice /PageSize get 0 get def
} if
orient 0 eq { % portrait
/w currentpagedevice /PageSize get 0 get def
/h currentpagedevice /PageSize get 1 get def
} if
% Set line width and color
.5 setlinewidth
.7 1 1 setrgbcolor
% draw
newpath
% vertical lines
dx dx w {
0 moveto
0 h rlineto
} for
% horizontal lines
dy dy h {
0 exch moveto
w 0 rlineto
} for
stroke
grestore
end
} bind def


At the beginning of my postscript files, I tend to do this.

(units) runlibfile
(grid) runlibfile

10 mm 10 mm grid


### Including bitmaps

Using e.g. mogrify (from the ImageMagick suite) we can convert bitmaps to EPS figures. These can then be included in our own documents.

Examples:

mogrify -format eps -density 300 -units PixelsPerInch <input>.jpg


The density argument determines how large images will appear. In the example, a 300×300 pixel image will be 1 inch square. Note that you can also use PixelsPerCentimeter units.

The larger the density number, the smaller the image will become. You might need to adjust it based on the size of the image.

These files can have side effects when we are not careful. So using a trick I learned from appendix 7 of Mathematical Illustrations, I surround them with calls to a couple of included functions that save our document state before the import and restore it afterwards, like this.

begin_import
4 cm 8.5 cm translate
1.9 dup scale
(image.eps) run
end_import


If the image to be included is in the same directory as the file, using run is sufficient. The translate and scale commands are used to move the included picture to the right place.

These commands are defined as follows.

/begin_import {
% Save the current state
save /savedstate exch def
% Save the sizes of the two stacks
count /opstacksize exch def
/dictstacksize countdictstack def
% Turn showpage off
/showpage {} def
% Set default graphics state
0 setgray 0 setlinecap
1 setlinewidth 0 setlinejoin
10 setmiterlimit [] 0 setdash newpath
/languagelevel where
{pop languagelevel 1 ne
} if
} bind def

/end_import {
count opstacksize sub
dup 0 gt { {pop} repeat } {pop} ifelse
countdictstack dictstacksize sub
dup 0 gt { {end} repeat } {pop} ifelse
savedstate restore
} bind def


### Text

The PostScript show command is fine for simple text. But if you want text that e.g. uses unicode or other symbols it becomes a lot more difficult.

So for complicated text, I tend to typeset it using the standalone documentclass in LaTeX. The following code could be used to display an equation in a nice font.

\documentclass{standalone}
% For latex, because we want to make EPS eventually
\usepackage[T1]{fontenc}
\usepackage{Alegreya}
\usepackage[italic]{mathastext}
\begin{document}
$f(x) = a\cdot x^2 + b\cdot x + c$
\end{document}


Using latex and dvips we can convert this to encapsulated PostScript, which can easily be included.

### Converting to PDF

This is best done with plain ghostscript.

gs -sDEVICE=pdfwrite -dNOPAUSE -dBATCH -sOutputFile=<output>.pdf \
-c .setpdfwrite -f <input>.ps


Where <input> and <output> are placeholder for real filenames. If you are using include files, add the path of those with an -I flag, e.g. -I \$HOME/lib/ps, or set up the environment variable GS_LIB.