ńņš. 18 |

(cf. Chapter 8); this time the idea is to play with transforms:

You type And the result is

identity (0,0,1,0,0,1)

identity shifted (a,b) (a,b,1,0,0,1)

identity scaled s (0,0,s,0,0,s)

identity xscaled s (0,0,s,0,0,1)

identity yscaled s (0,0,1,0,0,s)

identity slanted s (0,0,1,s,0,1)

identity rotated 90 (0,0,0,-1,1,0)

identity rotated 30 (0,0,0.86603,-0.5,0.5,0.86603)

identity rotatedaround ((2,3),90) (5,1,0,-1,1,0)

(x,y) rotatedaround ((2,3),90) (-y+5,x+1)

(x,y) reflectedabout ((0,0),(0,1)) (-x,y)

(x,y) reflectedabout ((0,0),(1,1)) (y,x)

(x,y) reflectedabout ((5,0),(0,10)) (-0.8y-0.6x+8,0.6y-0.8x+4)

EXERCISE 15.1

Guess the result of ā˜(x,y) reflectedabout ((0,0),(1,0))ā™.

EXERCISE 15.2

What transform takes (x, y) into (ā’x, ā’y)?

EXERCISE 15.3

(ā’(x, y)) transformed t = ā’((x, y) transformed t).

True or false:

Chapter 15: Transformations 143

In order to have some transform variables to work with, itā™s necessary to ā˜hideā™ hide

inverse

some declarations and commands before giving the next exprs:

known

unknown

You type And the result is left-handed dangerous bend

hide(transform t[]) t1 (xpart t1,ypart t1,xxpart...)

hide(t1=identity zscaled(1,2)) t1 (0,0,1,-2,2,1)

hide(t2=t1 shifted (1,2)) t2 (1,2,1,-2,2,1)

t2 xscaled s (s,2,s,-2s,2,1)

unknown t2 false

transform t2 true

t1=t2 false

t1<t2 true

inverse t2 (-1,0,0.2,0.4,-0.4,0.2)

inverse t2 transformed t2 (0,0,0.99998,0,0,0.99998)

hide(t3 transformed t2=identity) t3 (-1,0,0.2,0.4,-0.4,0.2)

The inverse function ļ¬nds the transform that undoes the work of another; the equation

that deļ¬nes t3 above shows how to calculate an inverse indirectly, without using inverse .

Like numeric expressions and pair expressions, transform expressions can be

either āknownā or āunknownā at any given point in a program. (If any

component of a transform is unknown, the whole transform is regarded as unknown.)

You are always allowed to use the constructions

known transformed known

unknown transformed known

known transformed unknown

but will balk at ā˜ unknown transformed unknown ā™. This is not the most

lenient rule that could have been implemented, but it does have the virtue of being

easily remembered.

EXERCISE 15.4

If z1 and z2 are unknown pairs, you canā™t say ā˜z1 shifted z2 ā™, because ā˜shifted z2 ā™

is an unknown transform. What can you legally say instead?

EXERCISE 15.5

Suppose dbend is a picture variable that contains a normal dangerous bend

sign, as in the āreverse-videoā example of Chapter 13. Explain how to transform it

into the left-handed dangerous bend that heads this paragraph.

The next three lines illustrate the fact that you can specify a transform com-

pletely by specifying the images of three points:

You type And the result is

hide((0,0)transformed t4=(1,2)) t4 (1,2,xxpart t4,xypart t4,...)

hide((1,0)transformed t4=(4,5)) t4 (1,2,3,xypart t4,3,yypart t4)

hide((1,4)transformed t4=(0,0)) t4 (1,2,3,-1,3,-1.25)

The points at which the transform is given shouldnā™t all lie on a straight line.

144 Chapter 15: Transformations

Now letā™s use transformation to make a little ornament, based on a ā˜ ā™ shape ornament

rotatedaround

replicated four times:

addto

picture

(Figure 15a will be inserted here; too bad you canā™t see it now.)

The following program merits careful study:

beginchar ("4", 11pt #, 11pt #, 0);

1

pickup pencircle scaled 3/4pt yscaled 1/3 rotated 30;

2

transform t;

3

t = identity rotatedaround((.5w, .5h), ā’90);

4

x2 = .35w; x3 = .6w;

5

y2 = .1h; top y3 = .4h;

6

path p; p = z2 {right } . . . {up }z3 ;

7

top z1 = point .5 of p transformed t;

8

draw z1 . . . p;

9

addto currentpicture also currentpicture transformed t;

10

addto currentpicture also currentpicture transformed (t transformed t);

11

labels(1, 2, 3); endchar;

12

Lines 3 and 4 compute the transform that moves each ā˜ ā™ to its clockwise neighbor.

Lines 5ā“7 compute the right half of the ā˜ ā™. Line 8 is the most interesting: It puts

point z1 on the rotated path. Line 9 draws the ā˜ ā™, line 10 changes it into two, and

line 11 changes two into four. The parentheses on line 11 could have been omitted, but

it is much faster to transform a transform than to transform a picture.

will transform a picture expression only when txx , txy , tyx , and tyy

are integers and either txy = tyx = 0 or txx = tyy = 0; furthermore, the values

of tx and ty are rounded to the nearest integers. Otherwise the transformation would

not take pixel boundaries into pixel boundaries.

EXERCISE 15.6

Explain how to rotate the ornament by 45ā—¦ .

Chapter 15: Transformations 145

Plain maintains a special variable called currenttransform , currenttransform

ļ¬ll

behind the scenes. Every ļ¬ll and draw command is aļ¬ected by this variable; draw

for example, the statement ā˜ļ¬ll pā™ actually ļ¬lls the interior of the path nonsquare pixels

aspect ratio

p transformed currenttransform pickup

DICKENS

TWAIN

instead of p itself. We havenā™t mentioned this before, because currenttransform

is usually equal to identity ; but nonstandard settings of currenttransform can be

used for special eļ¬ects that are occasionally desired. For example, itā™s possible

to change ā˜ ā™ to ā˜ ā™ by simply saying

currenttransform := identity slanted 1/4

and executing the programs of logo.mf that are described in Chapter 11; no

other changes to those programs are necessary.

Itā™s worth noting that the pen nib used to draw ā˜ ā™ was not

slanted when currenttransform was changed; only the ātracksā of the pen, the

paths in draw commands, were modiļ¬ed. Thus the slanted image was not simply

obtained by slanting the unslanted image.

When fonts are being made for devices with nonsquare pixels, plain -

will set currenttransform to ā˜identity yscaled aspect ratio ā™, and pickup

will similarly yscale the pen nibs that are used for drawing. In this case the slanted

ā˜ ā™ letters should be drawn with

currenttransform := identity slanted 1/4 yscaled aspect ratio .

EXERCISE 15.7

Our program for ā˜ ā™ doesnā™t work when pixels arenā™t square. Fix it so that

it handles a general aspect ratio .

Change begets change. Nothing propagates so fast.

ā” CHARLES DICKENS, Martin Chuzzlewit (1843)

There are some that never know how to change.

ā” MARK TWAIN, Joan of Arc (1896)

(page 146)

16

Calligraphic

Eļ¬ects

Chapter 16: Calligraphic Eļ¬ects 147

Pens were introduced in Chapter 4, and we ought to make a systematic study of Pens

pen

what can do with them before we spill any more ink. The purpose pickup

of this chapter will be to explore the uses of āļ¬xedā pen nibsā”i.e., variables pen expression

currentpen

and expressions of type penā”rather than to consider the creation of shapes by top

means of outlines or penstrokes. bot

lft

When you say ā˜pickup pen expression ā™, the macros of plain - rt

do several things for you: They create a representation of the speciļ¬ed draw

drawdot

pen nib, and assign it to a pen variable called currentpen ; then they store away ļ¬lldraw

information about the top, bottom, left, and right extents of that pen, for use savepen

clear pen memory

in top , bot , lft , and rt operations. A draw or drawdot or ļ¬lldraw command penrazor

will make use of currentpen to modify the current picture. pensquare

convex polygon

You can also say ā˜pickup numeric expression ā™; in this case the nu- makepen

meric expression designates the code number of a previously picked-up pen turning number

that was saved by ā˜savepenā™. For example, the logo.mf ļ¬le in Chapter 11

begins by picking up the pen thatā™s used to draw ā˜ ā™, then it says

ā˜logo pen := savepenā™. Every character program later in that ļ¬le begins with

the command ā˜pickup logo pen ā™, which is a fast operation because it doesnā™t

require the generation of a new pen representation inside the computer.

Caution: Every time you use savepen, it produces a new integer value and

stashes away another pen for later use. If you keep doing this, ā™s

memory will become cluttered with the representations of pens that you may never

need again. The command ā˜clear pen memoryā™ discards all previously saved pens

and lets start afresh.

But what is a pen expression ? Good question. So far in this book, almost

everything that weā™ve picked up was a pencircle followed by some sequence of

transformations; for example, the logo pen of Chapter 11 was ā˜pencircle xscaled px

yscaled py ā™. Chapter 13 also made brief mention of another kind of pen, when it said

pickup penrazor scaled 10;

this command picks up an inļ¬nitely thin pen that runs from point (ā’5, 0) to point

(5, 0) with respect to its center. Later in this chapter we shall make use of pens like

pensquare xscaled 30 yscaled 3 rotated 30;

this pen has a rectangular boundary measuring 30 pixels Ć— 3 pixels, inclined at an

angle of 30ā—¦ to the baseline.

You can deļ¬ne pens of any convex polygonal shape by saying ā˜makepen pā™,

where p is a cyclic path. It turns out that looks only at the key

points of p, not the control points, so we may as well assume that p has the form

z0 - - z1 - - etc. - - cycle. This path must have the property that it turns left at every

key point (i.e., zk+1 must lie to the left of the line from zkā’1 to zk , for all k), unless the

cycle contains fewer than three key points; furthermore the path must have a turning

number of 1 (i.e., it must not make more than one counterclockwise loop). Plain -

ā™s penrazor stands for ā˜makepen ((ā’.5, 0) - - (.5, 0) - - cycle)ā™, and pensquare

is an abbreviation for ā˜makepen (unitsquare shifted ā’(.5, .5))ā™. But pencircle is not

148 Chapter 16: Calligraphic Eļ¬ects

deļ¬ned via makepen; it is a primitive operation of . It represents a true circle

circle of diameter 1, passing through the points (Ā±.5, 0) and (0, Ā±.5). pen primary

(

)

The complete syntax for pen expressions is rather short, because you canā™t nullpen

really do all that much with pens. But it also contains a surprise: future pen primary

pencircle

pen primary ā’ā’ pen variable makepen

pen secondary

| ( pen expression ) future pen secondary

| nullpen pen tertiary

pen expression

future pen primary ā’ā’ pencircle nullpen

| makepen path primary ļ¬lldraw

pen secondary ā’ā’ pen primary ļ¬ll

beginchar

future pen secondary ā’ā’ future pen primary future pen

| future pen secondary transformer diamond-shaped nib

| pen secondary transformer

pen tertiary ā’ā’ pen secondary

| future pen secondary

pen expression ā’ā’ pen tertiary

The constant ā˜nullpenā™ is just the single point (0, 0), which is invisibleā”unless you

use it in ļ¬lldraw, which then reduces to ļ¬ll. (A beginchar command initializes

currentpen to nullpen, in order to reduce potentially dangerous dependencies between

the programs for diļ¬erent characters.) The surprise in these rules is the notion of a

āfuture pen,ā which stands for a path or an ellipse that has not yet been converted into

ā™s internal representation of a true pen. The conversion process is rather

complicated, so procrastinates until being sure that no more transforma-

tions are going to be made. A true pen is formed at the tertiary level, when future

pens are no longer permitted in the syntax.

The distinction between pens and future pens would make no diļ¬erence to a

user, except for another surprising fact: All of ā™s pens are convex

polygons, even the pens that are made from pencircle and its variants! Thus, for

example, the pen you get from an untransformed pencircle is identical to the pen you

get by specifying the diamond-shaped nib

makepen ((.5, 0) - - (0, .5) - - (ā’.5, 0) - - (0, ā’.5) - - cycle).

And the pens you get from ā˜pencircle scaled 20ā™ and ā˜pencircle xscaled 30 yscaled 20ā™

are polygons with 32 and 40 sides, respectively:

(Figure 16a&b will be inserted here; too bad you canā™t see it now.)

Chapter 16: Calligraphic Eļ¬ects 149

The vertices of the polygons, shown as heavy dots in this illustration, all have āhalf- digitization

Hobby

integerā coordinates; i.e., each coordinate is either an integer or an integer plus 1/2.

Every polygon that comes from a pencircle is symmetric under 180ā—¦ rotation; further-

more, there will be reļ¬‚ective left/right and top/bottom symmetry if the future pen is

a circle, or if itā™s an ellipse that has not been rotated.

This conversion to polygons explains why future pens must, in general, be

distinguished from ordinary ones. For example, the extra parentheses in

ā˜(pencircle xscaled 30) yscaled 20ā™ will yield a result quite diļ¬erent from the elliptical

polygon just illustrated. The parentheses force conversion of ā˜pencircle xscaled 30ā™

from future pen to pen, and this polygon turns out to be

(12.5, ā’0.5) - - (15, 0) - - (12.5, 0.5)

- - (ā’12.5, 0.5) - - (ā’15, 0) - - (ā’12.5, ā’0.5) - - cycle,

an approximation to a 30 Ć— 1 ellipse. Then yscaling by 20 yields

(Figure 16c will be inserted here; too bad you canā™t see it now.)

Why does work with polygonal approximations to circles, instead

of true circles? Thatā™s another good question. The main reason is that suitably

chosen polygons give better results than the real thing, when digitization is taken into

account. For example, suppose we want to draw a straight line of slope 1/2 thatā™s

exactly one pixel thick, from (0, y) to (200, y + 100). The image of a perfectly circular

along this line has outlines that run from (0, y Ā± Ī±) to

pen of diameter 1 that travels ā

(200, y + 100 Ā± Ī±), where Ī± = 5/4 ā 0.559. If we digitize these outlines and ļ¬ll the

region between them, we ļ¬nd that for some values of y (e.g., y = 0.1) the result is

...

a repeating pixel pattern like ā˜ . . . ā™; but for other values of y (e.g., y = 0.3)

...

the repeating pattern of pixels is 50 percent darker: ā˜ . . . ā™. Similarly, some

diagonal lines of slope 1 digitize to be twice as dark as others, when a truly circular

pen is considered. But the diamond-shaped nib that uses for a pencircle

of diameter 1 does not have this defect; all straight lines of the same slope will digitize

to lines of uniform darkness. Moreover, curved lines drawn with the diamond nib

always yield one pixel per column when they move more-or-less horizontally (with slopes

between +1 and ā’1), and they always yield one pixel per row when they move vertically.

By contrast, the outlines of curves drawn with circular pens produce occasional āblots.ā

Circles and ellipses of all diameters can proļ¬tably be replaced by polygons whose sub-

pixel corrections to the ideal shape will produce better digitizations; does

this in accordance with the interesting theory developed by John D. Hobby in his Ph.D.

dissertation (Stanford University, 1985).

150 Chapter 16: Calligraphic Eļ¬ects

Itā™s much easier to compute the outlines of a polygonal pen that follows a penoļ¬set

makepath

given curve than to ļ¬gure out the corresponding outlines of a truly circular

ļ¬llin

pen; thus polygons win over circles with respect to both quality and speed. When a penrazor

curve is traveling in a direction between the edge vectors zk+1 ā’ zk and zk ā’ zkā’1 of a endpoints

drawdot

polygonal pen, the curveā™s outline will be oļ¬set from its center by zk . If you want ļ¬ne draw

control over this curve-drawing process, provides the primitive operation cutoļ¬

currentpen

ā˜penoļ¬set w of pā™, where w is a vector and p is a pen. If w = (0, 0), the result is (0, 0);

if the direction of w lies strictly between zk+1 ā’ zk and zk ā’ zkā’1 , the result is zk ; and

if w has the same direction as zk+1 ā’ zk for some k, the result is either zk or zk+1 ,

whichever ļ¬nds most convenient to compute.

EXERCISE 16.1

Explain how to use penoļ¬set to ļ¬nd the point or points at the ātopā of a pen

(i.e., the point or points with largest y coordinate).

The primitive operation ā˜makepath pā™, where p is a (polygonal) pen whose

vertices are z0 , z1 , . . . , znā’1 , produces the path ā˜z0 . . controls z0 and z1 . . z1 . .

etc. . . znā’1 . . controls znā’1 and z0 . . cycleā™, which is one of the paths that might have

generated p. This gives access to all the oļ¬sets of a pen.

When a pencircle is transformed by any of the operations in Chapter 15, it

changes into an ellipse of some sort, since all of ā™s transformations

preserve ellipse-hood. The diameter of the ellipse in each direction Īø is decreased by

2 min(| sin Īø|, | cos Īø|) times the current value of ļ¬llin , before converting to a polygon;

this helps to compensate for the variation in thickness of diagonal strokes with respect

uses ļ¬llin

to horizontal or vertical strokes, on certain output devices. (

only when creating polygons from ellipses, but users can of course refer to ļ¬llin within

their own routines for drawing strokes.) The ļ¬nal polygon will never be perfectly ļ¬‚at

like penrazor, even if you say ā˜xscaled 0ā™ and/or ā˜yscaled 0ā™; its center will always be

surrounded at least by the basic diamond nib that corresponds to a circle of diameter 1.

EXERCISE 16.2

Run on the expr ļ¬le of Chapter 8 and look at what is typed

when you ask for ā˜pencircleā™ and ā˜pencircle scaled 1.1ā™. (The ļ¬rst will exhibit the

diamond nib, while the second will show a polygon thatā™s equivalent to pensquare.)

Continue experimenting until you ļ¬nd the āthresholdā diameter where

decides to switch between these two polygons.

ā™s polygonal pens work well for drawing lines and curves, but this

pleasant fact has an unpleasant corollary: They do not always digitize well

at the endpoints, where curves start and stop. The reason for this is explored further

in Chapter 24; polygon vertices that give nice uniform stroke widths might also be

āambiguousā points that cause diļ¬culties when we consider rounding to the raster.

Therefore a special drawdot routine is provided for drawing one-point paths. It is

sometimes advantageous to apply drawdot to the ļ¬rst and last points of a path p,

after having said ā˜draw pā™; this can fatten up the endpoints slightly, making them look

more consistent with each other.

Plain also provides two routines that can be used to clean up

endpoints in a diļ¬erent way: The command ā˜cutoļ¬ (z, Īø)ā™ removes half of the

currentpen image at point z, namely all points of the pen that lie in directions between

Chapter 16: Calligraphic Eļ¬ects 151

(Īø ā’ 90)ā—¦ and (Īø + 90)ā—¦ from the center point. And the command ā˜cutdraw pā™ is an cutdraw

T

abbreviation for the following three commands:

addto

cull

draw p; cutoļ¬ (point 0 of p, 180 + angle direction 0 of p); picture

cutoļ¬ (point inļ¬nity of p, angle direction inļ¬nity of p). doublepath

pen lft

lft

The eļ¬ect is to draw a curve whose ends are clipped perpendicular to the starting and

rt

ending directions. For example, the command top

bot

cutdraw z4 . . controls z1 and z2 . . z6

produces the following curve, which invites comparison with the corresponding uncut

ńņš. 18 |