PSFile: Generate PostScript files with Python
By Jochen Voss, last updated .
Contents
- Introduction
- Download
- Documentation
- References
Figure 1. Output of the script from example 1, below.
Introduction
The PSFile Python module helps you to create PostScript files from Python scripts. PSFile can creating the required wrappers to set up the page geometry; this allows you to only provide the PostScript code to draw the figure or page you want to create.
Download
- psfile version 0.9, 2009-09-19
first public release
- archive: psfile-0.9.tar.gz (13KB)
signature: psfile-0.9.tar.gz.asc
sha1: 625c5ae7658541bf5bab3e21355d254a442d326b
md5: a740f73a20f731269f3117a247882985
- archive: psfile-0.9.tar.gz (13KB)
Installation instructions are in the file
README of the source code archive. The source code
archive also contains the example scripts from the documentation below (in
the sub-directory examples).
Documentation
This manual explains the use of PSFile. In addition it includes a short
PostScript primer, mainly by use of examples. For a more in-depth
introduction to PostScript, I recommend Adobe's PostScript Language
Tutorial and Cookbook
, which can be downloaded from the Adobe web page
(as a
ZIP file,
containing the book and PostScript example code).
Working with PostScript Files
Before we focus on how to create PostScript files using the PSFile module, here is a short summary about how PostScript files can be used.
- Many laser printers can print PostScript files directly. Also, on
Linux/Unix systems, things are normally set up in a way that you can print
PostScript files directly on any connected printer
(e.g. using
lpr). - There are many document viewers to display PostScript files on the screen of your computer, e.g. GhostScript, gv and Evince.
- PostScript files can be converted to PDF files using ps2pdf (part of the ghostscript package) and epstopdf (for Encapsulated PostScript files; distributed by the texlive distribution).
- Encapsulated PostScript files can be easily embedded into TeX
documents. You can, for example, use the
graphicxpackage as follows:\documentclass{article} \usepackage{graphicx} \begin{document} \includegraphics{fig1} \end{document}If you use LaTeX and dvips to process this input file, the output will include the figure from fig1.eps. If you use
pdflatexinstead, you need to convert the figure to PDF first (using epstopdf, see above); pdflatex will then include the figure from fig1.pdf into the output.
Creating a New File
The PSFile Python module allows to generate two different types of PostScript files: stand-alone files describing a complete one-page document and Encapsulated PostScript files describing a figure for inclusion into other documents.
PostScript Files describing a Figure
An Encapsulated PostScript file describing an individual figure can be created as follows.
#! /usr/bin/env python
from psfile import EPSFile
fd = EPSFile("example.eps", 300, 300)
# ... write PostScript commands to `fd` ...
fd.close()
The EPSFile constructor has three required arguments: the
name of the file to create (typically using the file extension
.eps
), followed by the width and height of the figure in units of
1/72th of an inch. The following optional keyword arguments are available.
- margin
- Set the width of the margin between the plotting region and the edge of the figure. Default is to use 3/72th of an inch (i.e. three PostScript points).
- margin_top, margin_right, margin_bottom, margin_left
- These parameters can be used to override the margin size for individual edges.
- title
- An optional figure title. This string will be stored in the header of the generated file.
- creator
- An optional document creator designation. This string will be stored in the header of the generated file and can be used to store the name of the Document composition software (i.e. of the program you wrote using PSFile).
PostScript Files describing a Full Page
A PostScript file describing a full page can be created as follows. Files created this way can be printed directly.
#! /usr/bin/env python
from psfile import PSFile
fd = PSFile("example.ps", paper="letter")
# ... write PostScript commands to `fd` ...
fd.close()
The only required argument to the PSFILE constructor is
the output file name (typically using the file extension .ps
). The
following optional keyword arguments are available.
- paper
- The page size to use. This can either be one of the strings
A4
,A3
orletter
to choose the corresponding pre-defined paper size (an appended*
indicates landscape mode), or a pair `(width,height)` of integers to specify a custom paper size in units of 1/72th of an inch. The default is to use A4 paper. - margin
- Set the size of the margins between the plotting region and the edge of the paper. Default is to use 1 inch.
- margin_top, margin_right, margin_bottom, margin_left
- These parameters can be used to override the margin size for individual edges.
- title
- An optional document title. This string will be stored in the header of the generated file and may, for example, be displayed in the title bar of a PostScript viewer.
- creator
- An optional document creator name. This string will be stored in the header of the generated file and can be used to store the name of the Document composition software.
Using the PostScript File Object
The psfile module constructs the required headers for the output file to set up page dimensions, margins, etc. The coordinate system is set up so that coordinates (0,0) correspond to the lower-left corner of the drawing area.
Instances of the PSFile and EPSFile classes,
described above, are file-like objects which can be used to write the body
of the PostScript file. The following methods are provided:
- close()
- Close the PostScript file. This method writes the PostScript code to the output file and then closes the PostScript file. A closed PostScript file cannot be written to any more.
- append(text)
- Append a block of text to the body of the PostScript file. This method sanitises white space in text (removes leading and trailing empty lines, expands tabs, removes indentation, and adds a trailing newline character as needed), and then appends the result to the body of the PostScript file.
- write(text)
- Append text to the body of the PostScript file without any change.
- define(name, body)
- Define a PostScript macro. This adds a PostScript macro to the header of the generated file, making name an abbreviation for body. The resulting macro can be used to prevent the generated PostScript from getting overly big.
In addition, there are two attributes which you can read to get the size of the drawing area:
- width
- The width of the drawing area (excluding margins) in units of 1/72th inch.
- heigth
- The height of the drawing area (excluding margins) in units of 1/72th inch.
Example 1 (colourful squares)
The following example script creates the image from figure 1 above.
#! /usr/bin/env python
from random import uniform
from psfile import EPSFile
fd = EPSFile("ex1.eps", 600, 100)
# dark gray background
fd.append("0.1 setgray")
fd.append("0 0 %d %d rectfill"%(fd.width, fd.height))
# a grid of dark orange lines
fd.append("1 .596 .118 setrgbcolor")
fd.append("1 setlinewidth")
for i in range(1,5):
y = 100*i/5.0
fd.append("5 %.1f moveto 595 %.1f lineto"%(y,y))
for i in range(1,30):
x = 100*i/5.0
fd.append("%.1f 5 moveto %.1f 95 lineto"%(x,x))
fd.append("stroke")
# randomly colored, filled squares
for i in range(0,30):
x = i*20+3
for j in range(0,5):
y = j*20+3
col = uniform(0,1)
if 31*uniform(0,1) > i+1:
fd.append("0 %.3f 0 setrgbcolor"%col)
else:
fd.append("%.3f 0 0 setrgbcolor"%col)
fd.append("%.1f %.1f 14 14 rectfill"%(x,y))
fd.close()
Drawing Lines
To draw a polygonal line in PostScript you first have to move the
PostScript point to the starting point of the line using the
moveto PostScript operator, and then to move the pen
along the segments of the polygon using the lineto operator.
Finally, you have to use the stroke command to actually draw
the line.
- x y moveto
- Move the current point to position (x,y). Don't change the current path.
- dx dy rmoveto
- Move the current point dx units to the right and dy units up. Don't change the current path.
- x y lineto
- Append a straight line segment, connecting the current point to the point (x,y), to the current path. Make the end of this line the new current point.
- dx dy rlineto
- Append a straight line from the current point to the point dx units to the right and dy units up to the current path. The end of this line segment is then the new current point.
- closepath
- Close the current path by appending a straight line from the current point to the starting point. This should be used for drawing closed curves, e.g. polygons.
- stroke
- Draw a line along the current path. The line uses the colour and line
width (see below) current at the time of the
strokecommand. Then clear the current path.
If you have to draw many line segments, it may be a good idea to use
commands like fd.define("l", "lineto") to
define one-letter abbreviations for moveto and
lineto.
Example 2 (basic lines)
The following code generates an Encapsulated PostScript file, containing two squares (one has a missing edge).
#! /usr/bin/env python
from psfile import EPSFile
fd = EPSFile("ex2.eps", 100, 100)
fd.append("""
% outer square
0 0 moveto
100 0 lineto
100 100 lineto
0 100 lineto
closepath
% inner square, open to the left
10 10 moveto
90 10 lineto
90 90 lineto
10 90 lineto
% draw the constructed path
stroke
""")
fd.close()
The output looks as follows:
Example 3 (line width)
You can use the setlinewidth command to set the width of a
line. This command can be given any time before the corresponding
stroke command.
- w setlinewidth
- The the current line width to w (in units of 1/72th of an inch). The default line width is 1.
The following example illustrates different choices of line widths.
#! /usr/bin/env python
from __future__ import division
from math import pow
from psfile import EPSFile
min_lw = .5
max_lw = 9
steps = 18
fd = EPSFile("ex3.eps", 350, 38,
margin_left=10+.5*min_lw, margin_right=10+.5*max_lw)
fd.append("/Times-Roman 10 selectfont")
for i in range(0, steps+1):
lw = min_lw * pow(max_lw/min_lw, i/steps)
x = fd.width*i/steps
fd.append("%.1f setlinewidth"%lw)
fd.append("%f 0 moveto 0 27 rlineto"%x)
fd.append("stroke")
fd.append("%f 30 moveto"%(x-6))
fd.append("(%.1f) show"%lw)
fd.close()
The output of this script looks as follows:
Geometric Shapes
There are special commands to draw rectangles and circles.
- x y w h rectstroke
- Draw the outline of a rectangle with lower left corner
(x,y) and upper right
corner
(x+w,y+h).
This command already includes the final
strokecommand. - x y w h rectfill
- Fill a rectangle with lower left corner
(x,y) and upper right
corner
(x+w,y+h),
using the current colour. This command already includes the final
strokecommand. - x y r 0 360 arc
- Add a circle with centre
(x,y) and
radius r to the current path. You need to
use a
strokeorfillcommand after this to actually draw the circle.
Colours
- x setgray
- Set the current colour to gray level x. The value x must be between 0 (black) and 1 (white).
- r g b setrgbcolor
- Set the current colour to the RGB colour (r,g,b).
Example 4 (colours)
The following code creates a grid of 28 randomly coloured rectangles and labels them with the corresponding RGB intensities.
#! /usr/bin/env python
from __future__ import division
from random import uniform
from psfile import EPSFile
cols = 7
rows = 4
gap = 3
fd = EPSFile("ex4.eps", 350, 80)
fd.append("/Times-Roman 8 selectfont")
dx = (fd.width+gap)/cols
dy = (fd.height+gap)/rows
w = dx - gap
h = dy - gap
for j in range(0, rows):
for i in range(0, cols):
r = g = b = 0
while r + g + b < 1:
r, g, b = [ uniform(0,1), uniform(0,1), uniform(0,1) ]
fd.append("%.1f %.1f %.1f setrgbcolor"%(r,g,b))
fd.append("%f %f %f %f rectfill"%(i*dx, j*dy, w, h))
fd.append("0 setgray")
fd.append("%f %f moveto"%(i*dx+4, j*dy+3))
fd.append("(%.1f, %.1f, %.1f) show"%(r,g,b))
fd.close()
The output of one run of the code above looks as follows. Since the choice of colours is random, you'll most likely get a different picture when you run the script yourself.
Filling
- x y w h rectfill
- Draw a filled rectangle with lower left corner (x,y) and upper right corner (x+w,y+h).
- fill
- Fill the current path. This operator can be used to fill the region
enclosed by the current path (instead of using
stroketo draw the outline). This operation clears the current path.
Remark. The operators described in this section can also be used to set the background colour of a figure: just start the figure by drawing a coloured, filled rectangle which covers all of the drawing area, and then draw everything else on top of this.
Since fill clears the current path, some care needs to be
taken when filling a region and drawing the outline of the same region in
a different colour. The solution is to save the current graphics state
(which includes the current path) with gsave before issuing
the fill command and then to restore the state using
grestore before stroking the outline of the region. This
technique is illustrated in the following example.
Example 5 (filled circles)
The following code draws seven filled circles on a yellow background.
#! /usr/bin/env python
from psfile import EPSFile
fd = EPSFile("ex5.eps", 350, 50)
# draw the yellow background
fd.append("1 1 0 setrgbcolor")
fd.append("0 0 %d %d rectfill"%(fd.width, fd.height))
# draw black circles filled with red
fd.append("0 setgray")
for x in range(25, 375, 50):
fd.append("""%f 25 20 0 360 arc
gsave
1 0 0 setrgbcolor
fill
grestore
stroke"""%x)
fd.close()
The output looks as follows:
Text
PostScript provides a set of 13 standard fonts which can be scaled to arbitrary sizes. These are used with the following commands:
- /font size selectfont
- Make the given font, scaled to the given size, the active font.
font should be one of
Times-Roman,Times-Italic,Times-Bold,Times-BoldItalic,Helvetica,Helvetica-Oblique,Helvetica-Bold,Helvetica-BoldOblique,Courier,Courier-Oblique,Courier-Bold,Courier-BoldObliqueorSymbol. See figure 6 for the shape of the different fonts. - (str) show
- Print the string str. The lower left corner
of the string will be placed at the current point. Without special
preparations, the string is restricted to the ASCII character set.
Unbalanced brackets and backslashes in the string need to be quoted with a
backslash. There are various methods to print accented characters, too
(see the
PostScript Language Tutorial and Cookbook
or google forISOLatin1Encoding
).
Example 6 (fonts)
The following code illustrates the 13 PostScript standard fonts.
#! /usr/bin/env python
from psfile import EPSFile
fonts = [
"Times-Roman", "Times-Italic", "Times-Bold", "Times-BoldItalic",
"Helvetica", "Helvetica-Oblique", "Helvetica-Bold",
"Helvetica-BoldOblique", "Courier", "Courier-Oblique",
"Courier-Bold", "Courier-BoldOblique", "Symbol"
]
str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
fd = EPSFile("ex6.eps", 420, len(fonts)*12)
for i, name in enumerate(reversed(fonts)):
fd.append("/Times-Roman 10 selectfont")
fd.append("0 %d moveto"%(12*i+3))
fd.append("(%s:) show"%name)
fd.append("/%s 10 selectfont"%name)
fd.append("100 %d moveto"%(12*i+3))
fd.append("(%s) show"%str)
fd.close()
The output looks as follows:
Figure 6. The PostScript standard fonts. This is the output of the script from example 6.
Coordinate Transformations
The following two PostScript commands allow to shift and rotate the picture. These functions can, for example, be used to create rotated text.
- x y translate
- Change the the current coordinate system so that the origin of the new coordinate system is where the point (x,y) was in the old system.
- phi rotate
- Change the the current coordinate system so that the new axes are rotated by phi degrees clockwise w.r.t.the axes of the old coordinate system.
You can use gsave to save the current coordinate system
before using translate or rotate. This allows to
restore the old coordinate system using grestore.
Example 7 (coordinate transforms)
#! /usr/bin/env python
from __future__ import division
from math import pi, sqrt
from random import vonmisesvariate
from psfile import EPSFile
# a sample from a von Mises distribution
sample = [ vonmisesvariate(pi/4, 1.0) for i in range(0,200) ]
# generate a histogram of the data
nhist = 4 * int(sqrt(len(sample))/4 + 0.5);
count = [ 0 ] * nhist
for x in sample:
y = x/(2*pi) % 1
count[int(y*nhist+.5)%nhist] += 1
# turn into a plot
radius = 72
fd = EPSFile("ex7.eps", 4*radius, 4*radius)
fd.append("/Times-Roman 10 selectfont")
fd.append("%f %f translate"%(2*radius, 2*radius))
fd.append("%f setlinewidth"%((2*pi*radius) / nhist - 1))
for k in count:
r = radius * (1 + .9*k/max(count))
fd.append("0 0 moveto %f 0 lineto"%r)
fd.append("%f -3 moveto (%d) show"%(r+3, k))
fd.append("%f rotate"%(360/nhist))
fd.append("stroke")
fd.append("""1 setlinewidth
0 0 %f 0 360 arc
gsave 1 setgray fill grestore stroke"""%radius)
fd.append("/Symbol 24 selectfont")
fd.append("-35 -10 moveto (k = 1.0) show")
fd.close()
Figure 7. A circular histogram for the von Mises distribution with μ=π/4 and κ=1.
References
- Adobe offers several excellent manuals and specifications on its
developers' page,
in particular the
PostScript Language Tutorial and Cookbook
(as a ZIP file, containing the book and the example code), the PostScript Language Reference, and thePostScript Language Program Design
guide (again as a ZIP file). - The PostScript FAQ.