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.
Introduction
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.
(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
{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
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
.
For comments, please send me an e-mail.
Related articles
- Generating barcodes with “BWIPP”
- Creating a nomogram with Python and Postscript
- Extracting glyphs from an OpenType file
- Converting PostScript and PDF images to SVG format
- Adding an overlay to each page of a pdf file