///PSFile: Generate PostScript files with Python

PSFile: Generate PostScript files with Python

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

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

Creating a New PostScript 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 genereated file.
creator
An optional document creator designation. This string will be stored in the header of the genereated 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 or letter 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 genereated 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 genereated 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()


Figure 1. Output of the script from example 1, above.

Drawing Lines in PostScript

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

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:

[psfile example 2]

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:

[psfile example 3]

Geometric Shapes in PostScript

There are special commands to draw rectangles and circles.

x y w h rectstroke
Draw a rectangle with lower left corner (x,y) and upper right corner (x+w,y+h). This command already includes the final stroke command.
x y r 0 360 arc
Add a circle with centre (x,y) and radius r to the current path. You need to use the stroke command 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.

[psfile example 4]

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

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:

[psfile example 5]

Text in PostScript

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-BoldOblique or Symbol. 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 for ISOLatin1Encoding).

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:

[psfile example 6]

Figure 6. The PostScript standard fonts. This is the output of the script from example 6.

Coordinate Transformations in PostScript

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

[psfile example 7]

Figure 7. A circular histogram for the von Mises distribution with μ=π/4 and κ=1.

References

This tutorial is licensed under the Creative Commons License

2010-05-25T21:16:39+00:00 October 27th, 2009|Python|0 Comments

About the Author:

Leave A Comment