By Jochen Voss, last updated 2012-02-18
Figure 1. Output of the script from example 1, below.
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.
first public release
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
).
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).
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.
lpr
).graphicx
package 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
pdflatex
instead, 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.
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.
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.
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.
A4,
A3or
letterto 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.
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:
In addition, there are two attributes which you can read to get the size of the drawing area:
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()
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.
stroke
command.
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
.
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:
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.
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:
There are special commands to draw rectangles and circles.
stroke
command.
stroke
command.
stroke
or fill
command after this to
actually draw the circle.
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.
stroke
to 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.
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:
PostScript provides a set of 13 standard fonts which can be scaled to arbitrary sizes. These are used with the following commands:
Times-Roman
,
Times-Italic
, Times-Bold
,
Times-BoldItalic
, Helvetica
,
Helvetica-Oblique
, Helvetica-Bold
,
Helvetica-BoldOblique
, Courier
,
Courier-Oblique
, Courier-Bold
,
Courier-BoldOblique
or Symbol
. See
figure 6 for the shape of the different fonts.
PostScript Language Tutorial and Cookbookor google for
ISOLatin1Encoding).
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.
The following two PostScript commands allow to shift and rotate the picture. These functions can, for example, be used to create rotated text.
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
.
#! /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.
PostScript Language Tutorial and Cookbook(as a ZIP file, containing the book and the example code), the PostScript Language Reference, and the
PostScript Language Program Designguide (again as a ZIP file).
Copyright © 2012 Jochen Voss. All content on this website (including text, pictures, and any other original works), unless otherwise noted, is licensed under the CC BY-SA 4.0 license.