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 and modules like asymptote and TikZ. But there are cases (especially if you want a small file) where directly drawing in PostScript is still the best way to go.

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.


While metapost can draw beautiful pictures, it struggles with including bitmaps. And in some aspects, asymptote just feels 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.

( 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.


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
            % 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
            % vertical lines
            dx dx w {
                0 moveto
                0 h rlineto
            } for
            % horizontal lines
            dy dy h {
                0 exch moveto
                w 0 rlineto
            } for
} 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.


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.

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

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
        {false setstrokeadjust false setoverprint} if
    } 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


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.

% For latex, because we want to make EPS eventually
$f(x) = a\cdot x^2 + b\cdot x + c$

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.

For comments, please send me an e-mail.

Related articles

←  TeXLive 2018 update Creating a nomogram with Python and Postscript  →