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.


While metapost can draw beautiful graphs, 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.

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.

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.

←  TeXLive 2018 update