3  Drawing TeX pictures with PicScheme

PicScheme, or the Scheme code in picscheme.tex, provides a set of picture-making primitives that is similar in spirit to LaTeX’s {picture} environment [4, ch. 7], except that the commands are in Scheme and can be combined using full Scheme.

You can specify picture objects such as paths and texts and position them in a composite picture object called a picture. In general, a picture specification looks as follows:


;Scheme code, enhanced with
;the picture-drawing primitives

The picture produced is a box, which means it will be typeset as a giant letter might be. We can use the usual TeX approaches to centering and displaying boxes, eg, \centerline.

The picture description makes use of a unit length, which is stored in the variable *unit‑length* and is by default 1 bp (big point), which is 1/72 of an inch. Regardless of the *unit‑length*, PicScheme internally manipulates lengths as multiples of big points, and thus the default initial value of of *unit‑length* is the number 1.

In order to allow the resetting of *unit‑length* easily, PicScheme provides the procedure dimen, which converts lengths in other units to scaled points. For example,

(dimen 1 'in)

returns the bp equivalent of 1 inch, which is 72. dimen’s second argument is thus a symbol naming the unit converted from, and this can be one of pt (point), pc (pica), in (inch), bp (big point), cm (centimeter), mm (millimeter), dd (didot point), cc (cicero), and sp (scaled point) [3, ch. 10], [2, ch. 11]. (The word dimen is merely standard TeX jargon for (non-springy) length — the only physical dimension that is relevant to typesetting.)

We can thus set *unit‑length* to be 42 mm by

(set! *unit-length* (* 42 (/ 25.4) 72))

where we make use of the equivalences 1 mm = 1/25.4 inch, 1 inch = 72 bp, or, we could say

(set! *unit-length* (dimen 42 'mm))

Use whichever is easier for you. Changing *unit‑length* is a convenient way to scale a picture without rewriting its innards.

An important length quantity is the thickness of (the lead of) the imaginary pencil used to draw a PicScheme picture. By default this is 0.5 bp. You may reset it using the pencil‑width procedure. Thus

(pencil-width 1/72)

sets the pencil width to 1/72 of whatever the prevailing *unit‑length* is. (Setting pencil width is probably the only occasion where it may be more convenient to specify a true length in bp rather than a multiple of the prevailing *unit‑length*. To set it to 0.5 bp, you can use (pencil‑width (/ (dimen .5 'bp) *unit‑length*)), which does the correct thing regardless of the value of *unit‑length*.)

The 0-argument procedures thin‑lines and thick‑lines can be used to toggle between the default pencil width and a value that is twice the default width. (thin‑lines) sets pencil thickness to the default .5 bp, while (thick‑lines) sets it to 1 bp.

A picture is introduced by a call to the picture procedure:

(picture th)

th is a thunk (zero-argument procedure) that will contain commands to populate the picture. For example,


(set! *unit‑length* (dimen 1 'cm))

  (lambda ()
   (label (point 0 0) "nothing special")))



The only “picture” here is a text object, created by the procedure label, but it nevertheless serves to illustrate the use of coordinates. The procedure point makes a point from its x- and y-coordinates. PicScheme represents fully determined points as complex numbers, with the x-coordinate as the real part and the y-coordinate as the imaginary part, so (point 2 3) can be more succinctly entered as 2+3i. In particular, (point 0 0) is simply 0. In the following, we will use the shorter complex-number notation when feasible.

The first argument to label is the point at which to place the second argument, which can be any valid TeX fragment. Eg1

  (lambda ()
    (label ‑3 "West")
    (label +3i "North")
    (label 3 "East")
    (label ‑3i "South")))



Note that the center of the text coincides with the point supplied. label can take a third argument that can place the text at a different relation to the chosen point.

In the following example, the label’s point is the center of the “cross hairs”, and we use the position indicators ulft and lrt to put the text "UPPER LEFT" on the upper left of the point, and the text "LOWER RIGHT" on its lower right. The various position possibilities are lft, ulft, top, urt, rt, lrt, bot, llft.

  (lambda ()
    (draw (path ‑2.5i '‑‑ +2.5i))
    (draw (path ‑2.5 '‑‑ 2.5))
    (label 0 "UPPER LEFT" 'ulft)
    (label 0 "LOWER RIGHT" 'lrt)))



The procedure path returns a path passing through a sequence of points. The form of the path between successive points is governed by the symbols ‑‑ and **, which produce straight-line and curved segments respectively. Additional arguments such as direction, controls (with and), and cycle can further alter the path. The procedure draw draws its argument path.

(set! *unit‑length* (dimen 1 'cm))

  (lambda ()
    (draw (path 0 '‑‑ 10 '‑‑ 10+10i '‑‑ +10i '‑‑ 'cycle))
    (let ((lft‑control‑point 3+12i)
          (rt‑control‑point 7+12i))
      (draw (path 2+2i '**
                  'controls lft‑control‑point
                  '** 7+2i))
      (draw (path 3+2i '**
                  'controls rt‑control‑point
                  '** 8+2i)))
    (label 9.8+0.2i
           "{\\it 020203120702, 030207120802}"



'controls z1 uses z1 as a control point (instead of having the path pass through it) for the enclosing points. For instance, a Bezier curve with the three points z1, zc, z2 connects z1 and z2 using zc as a control point, and can be represented as

(path z1 '** 'controls zc '** z2)

'controls z1 'and z2 uses both z1 and z2 as control points for the enclosing points. cycle, if it occurs at all, should be the last argument, and returns a closed path. direction governs the direction of the part of the path at the adjacent point (which could be directly to the left or to the right).

Here is a grid:

(set! *unit‑length* (dimen .5 'cm))

  (lambda ()
    ;draw vertical lines
    (let loop ((x 0))
      (when (<= x 20)
        ((if (= (modulo x 5) 0)
             thick‑lines thin‑lines))
        (draw (path (point x 0) '‑‑ (point x 10)))
        (loop (+ x 1))))
    ;draw horizontal lines
    (let loop ((y 0))
      (when (<= y 10)
        ((if (= (modulo y 5) 0)
             thick‑lines thin‑lines))
        (draw (path (point 0 y) '‑‑ (point 20 y)))
        (loop (+ y 1))))



draw‑arrow is similar to draw except it attaches an arrow to the end of the path. draw‑double‑arrow attaches arrows to both the beginning and end of the path.

The thunks quarter‑circle, half‑circle and full‑circle return the relevant portions of a circle of diameter unity and center at the origin. unit‑square returns a unit square with its lower left at the origin.

Being so pegged to the origin is not a problem, as one can transform paths using the procedures shift, rotate, rotate‑about, reflect‑about, slant, scale, x‑scale, y‑scale, and z‑scale [2, ch. 15].

(define rectangle
  (lambda (z1 z4)
    (let ((x1 (x‑part z1))
          (y1 (y‑part z1))
          (x4 (x‑part z4))
          (y4 (y‑part z4)))
      (path z1 '‑‑ (point x4 y1) '‑‑ z4
            '‑‑ (point x1 y4) '‑‑ 'cycle))))

  (lambda ()
    (label 0 "\\vbox{\\hbox{square}\\hbox{peg}}" 'llft)
    (label 4+3i "\\vbox{\\hbox{round}\\hbox{hole}}" 'urt)
    ;draw circle of dia = 8
    (draw (scale 8 (full‑circle)))
    ;for square to fit in circle,
    ;its side must be 8/(sqrt 2)
    (let* ((s (/ 8 (sqrt 2)))
           (s/2 (/ s 2)))
      (draw (rectangle (point ( s/2) ( s/2))
                       (point s/2 s/2))))))



The procedures x‑part and y‑part return the x- and y-coordinate respectively of their argument point.

In the following program for a spoked wheel, the path for the spoke is created only once, but we draw several differently rotated versions of it.

(set! *unit‑length* (dimen 1 'cm))

  (lambda ()
    ;draw two concentric circles for wheel rim
    (let ((c (full‑circle)))
      (draw (scale 4 c))
      (draw (scale 4.5 c)))
    (let ((one‑deg (let ((pi (acos ‑1)))
                     (/ pi 180))))
      ;one‑deg is 1 degree in radians
      (let ((spoke (path 0 '‑‑ 2)))
        ;draw spoke at 20‑deg increments
        (let loop ((i 0))
          (unless (>= i 360)
            (draw (rotate (* i one‑deg) spoke))
            (loop (+ i 20))))))))



1 Henceforth, the \centerline{...} wrapper around the Scheme code is understood.