ONLamp.com    
 Published on ONLamp.com (http://www.onlamp.com/)
 See this if you're having trouble printing code examples


In Praise of Pic

by Philipp Janert
06/21/2007

In praise of what? Pic? Pic? Pic, the old diagram generation "little language" and half brother of roff (or troff or groff), from the days when Unix was young? Yes, indeed, that pic.

Why, in this day of convenient WYSIWYG tools, from Xfig over the GIMP to Visio, would anybody want to make drawings using a command-language, which needs to be compiled(!) to generate a viewable diagram? Well, as it turns out, there are in fact a number of excellent reasons:

My own reasons for learning about pic were a combination of several of these reasons. I wanted to generate images of several 3D bodies (cubes, cones, etc.). But I wanted to be free to change the viewing angles and orientations, without having to do a lot of manual rework. And preparing these illustrations was supposed to distract me only minimally from my main objective. So, pic served me well.

But there is yet another reason for studying pic. It is an excellent example how powerful a "little language" can be. In particular, I was impressed how pic achieves great ease of use through meaningful defaults, which often seem to anticipate the "common case" very well. It can do so, in part because its application area is intentionally quite limited: black-and-white drawings of regular shapes. Because the job it is meant to do is small, pic itself can be simple.

Introducing Pic

Pic is a little language for the specification of certain kinds of diagrams "frequently used in technical papers and textbooks" (quoted from "Making Pictures with GNU PIC" by Eric S. Raymond). It is not meant to be a general-purpose graphing language, although its scripting abilities allow application to areas not initially intended.

Here is the equivalent of "Hello, World!", using pic:

.PS
box "Hello"
.PE

and it is compiled into PostScript using the following command line (using the GNU version of all tools):

pic hello.pic | groff > hello.ps

Alternatively, you can call groff with the -p option, like so (see Figure 1):

groff -p hello.pic > hello.ps


Figure 1. Hello

A more interesting program is this:

.PS
box "Box"
arrow "Arrow" ""
circle "Circle"
line "Line" ""
ellipse "Ellipse"
arc
"Arc"
.PE

With the exception of the spline command, this picture demonstrates all graphing elements native to pic. We see two things; for one the syntax is trivially easy. The only features that aren't entirely obvious are the lines .PS and .PE. These are groff directives to start and end a picture. Also, the selection of graphics primitives is quite limited: no triangles, diamonds, or pie slices, etc. (see Figure 2).


Figure 2. All graphing elements native to pic

What struck me most about this syntax, though, was the kind of sensible defaults. The diagram "flows" in some direction, following the commands. New picture elements are joined to one another in an obvious way. (By default, the flow is left-to-right, but can be changed at any point with the up, down, left, and right directives.)

Decorations

Although the choice of graphics primitives is limited, there are many ways to change the appearance of the elements.

All elements can be individually sized like so (see Figure 3):

.PS
box                 # Default size: 0.75 wide by 0.5 high
move
box width 0.1       # Full name of size attribute
move
box wid 1.5 ht 0.25 # Abbreviated attribute names are possible
.PE


Figure 3. Box sizes

All sizes are given in inches of the final (printed) diagram (!). The overall scale can be changed by including the scale statement before any drawing command, e.g., scale=2.54 will change the unit to centimeters.

Objects can be dashed, dotted, and filled. GNU pic also allows you to generate boxes with rounded corners. All these decorations can be customized, usually through a numeric argument. (In the following example, note the double arrow as well.) See Figure 4.

.PS
box rad 0.15        # Box with rounded corners
line dotted
ellipse dashed 0.2
line <->            # Double arrow
circle fill 0.4

move to last circle .n + ( 1, 0 )  # Explained in section on Coordinates below
spline right 1 then down 0.5 left 1 then right 1

move to last spline .start         # Go back to start of spline, cf. text below
line dashed right 1 then down 0.5 left 1 then right 1
.PE


Figure 4. Decorations

Splines can be used to express some "dynamic" diagrams in an easy fashion. They are specified through their vertices, as seen when comparing the spline to the control line in the picture above.

Coordinates

Pic makes massive use of relative coordinates. Mostly, you tell pic to go some place by telling it how to get there from some place you already know. So, pic gives you a rather expressive set of commands to describe relative positions.

The simplest is the last (or 2nd last etc.) directive, illustrated in Figure 5:

.PS
circle
move 
box
move
circle

arrow from 2nd last circle to last box .nw
arrow from 2nd last circle to last box .sw

line from last box to last circle .w
.PE


Figure 5. Circle illustrating 2nd last directive

If nothing else is specified, a position is located at the center of a picture element. However, we can also express different points on a picture element, by using compass points, such as .n (north), .ne (north-east), .e (east), etc. For lines, arrows, and splines, we can use the .start, .center, and .end control points. (The dot preceding each location name can be remembered by thinking of graphics elements as structs and the control points as their members.)

To specify arbitrary locations using these control points, we can either use offsets, using the notation of vector addition, e.g.,:

last box.ne + ( 0.1, 0.1 )

Alternatively, we can "interpolate" between any two positions by specifying a fraction, using the rather verbose syntax:

f of the way between p1 and p2

or the much terser variation (see Figure 6):

f< p1, p2 >.
.PS
C1: circle
move 
B:  box
move
C2: circle

arrow from C1 to B .nw + ( -0.1, 0 )         # Vector addition
arrow from C1 to B .sw + ( -0.1, 0 )

arrow from B to 1/3 between B .e and C2 .w   # Interpolation
.PE


Figure 6. Relative coordinates

In the last example, we have used labels (starting with a capital letter and ending with a colon) to refer to picture elements. Labels are a great help to stay in control of more complex diagrams. Use them a lot!

While relative coordinates are very convenient, and allow easy, almost logo-like creation of diagrams, absolute coordinates can have their advantages, too. In particular, I found it helpful to define a set of "reference points" at the beginning of a complex diagram and to assign labels to them. Relative coordinates and offsets can then be made from those reference points. Figure 7 illustrates absolute coordinates.

.PS
A: (    0, sqrt(3/4))
B: (  0.5, 0)
C: ( -0.5, 0)

line from A then to B then to C then to A
circle rad 0.1 at A
circle rad 0.1 at B
circle rad 0.1 at C
.PE


Figure 7. Absolute coordinates

Note the use of the mathematical expression sqrt(3/4) in the last example. Pic allows you to include mathematical expressions (including trigonometric functions, exponentials and logarithms) and to evaluate them at runtime.

Macros, Scripting, and Shell Escapes

You can define macros in pic, using the define command. Through macros, we can extend the very small set of built-in graphics primitives. The following program demonstrates a friendly example.

.PS
define smiley {
   # Takes three arguments: $1: x-pos, $2: y-pos, $3: size (radius)

   r0 = $3       # Face
   r1 = 0.4*r0   # Radius of mouth and eye locations
   r2 = 0.04*r0  # Radius of eyes

C: circle rad r0 at ( $1, $2 )

   circle rad r2 filled at last circle + ( r1, r1 )      # Right eye
   circle rad r2 filled at 2nd last circle + ( -r1, r1 ) # Left eye

   pi = atan2( 0, -1 )
S: C + ( r1*cos(1.25*pi), r1*sin(1.25*pi) )
   line from S to S
   for phi=1.25*pi to 1.75*pi by 0.1 do {
     line to C + ( r1*cos(phi), r1*sin(phi) )            # Mouth
   }
}

pi2 = 2*atan2( 0, -1 )
for x=0.1 to 1.3 by 0.08 do {
  smiley( 1.5*x*cos(x*pi2), 1.1*x*sin(x*pi2), 0.23*x ) 
}
.PE

Note how variables can be passed to the macro and are available inside the macro in the form of the expressions $1, $2, etc. See Figure 8.


Figure 8. Smiley

The last example also demonstrated pic's built-in looping facility. (There is a conditional construct, as well.)

Macros, together with the control flow constructs, can be used to generate graphics elements, which are missing from the list of built-in primitives. For instance, the mouth of the smiley in the example above shows how arbitrary arcs (i.e., with arbitrary start and end angles) can be set up as macros.

Finally, pic can execute an arbitrary command or include an arbitrary file through the sh or copy commands, respectively. This can be very handy. For instance, in the example below the Perl program trsf.pl applies a set of coordinate transformations to the raw coordinates contained in the file coord.raw and generates an intermediate file coord.tmp of pic objects (e.g., containing lines like A: ( 0.2, 0.5 ), etc.). This intermediate file is included though the copy command, and the locations defined in it are then used to generate the actual drawing.

.PS
sh { perl trsf.pl < coords.raw > coords.tmp }
copy "coords.tmp"

spline from A then to B then to C then to D then to E
circle at F
.PE

This can be more convenient than reading commands from standard input, because in the example above, the Perl program does not actually have to generate complete pic commands: it merely transforms and formats raw coordinates, the remainder of the drawing commands is kept in a separate pic program file.

(Note that escaping to a sub-shell is considered unsafe, since in principle any command could be executed by sh{...} and pic needs to be put into unsafe mode using the -U command-line option.)

After Pic

No program is an island, and pic is no exception. We have already seen how to include output of other programs into pic. What can we do with the output generated by pic?

Pic itself is merely a pre-processor for troff, so we can include troff commands in a pic program. In general, lines beginning with a period are being passed through to troff. This is mostly useful to control the rendering of text included in a pic-diagram. Some of the most relevant troff commands are:

.ft X
Changes the font shape, where X can be one of: R (upright, Roman), B (bold), I (italic), BI (bold italic), or P (previous, to switch back to the previous font).
.ps N
Changes the font size. The argument N can either be a number, giving the absolute size of the desired font in printer points (e.g., .ps 14), or it can be a relative size specification (such as .ps +2 or .ps -2), which changes the font size relative to the previous size. Omitting the argument will reset the font size to the previous value.
.fam F
Changes the font family, e.g., the argument T selects Times Roman, H selects Helvetica, etc.

By default, troff generates plain PostScript, although it can also generate output suitable for processing by TeX (as well as some other formats). One useful program to use with pic (and troff) is ps2eps. This program converts plain PostScript into encapsulated PostScript, and crops the page to the minimal bounding box containing the image. An encapsulated PostScript file so obtained can be converted using the convert utility from the ImageMagick toolset to practically any common graphics format out there , for instance to PNG, for use in web pages.

Summary

This concludes our brief overview of pic. Despite its age, it is still amazingly useful when generating certain diagrams, in particular when the diagrams are to be scripted in some way.

Pic is a small language and easy to pick up. Although this article doesn't cover everything, it introduces almost all of the elements of the pic language. The sensible choice of defaults and the rich facilities to express relative coordinates make working with pic quite easy and rather enjoyable.

The original tutorial and reference on pic was authored by Brian Kernighan: "PIC - A Graphics Language for Typesetting" and provides a complete introduction in a mere 25 pages. Eric S. Raymond has written an updated manual, specifically for the GNU version of pic (and groff) "Making Pictures with GNU PIC," which, at 37 pages, is not much longer. Between those two papers, you will find answers to almost all questions about pic. Both can be found on the Net.

The ps2eps tool is available at http://www.tm.uka.de/~bless/ps2eps and is highly recommended over the equivalent ps2epsi utility, which is part of the Ghostscript distribution. Finally, the homepage for ImageMagick's set of image manipulation tools is http://www.imagemagick.org.

Philipp Janert is a software project consultant, server programmer, and architect.


Return to ONLamp.com.

Copyright © 2009 O'Reilly Media, Inc.