ńņš. 5 |

diameter 1; it runs from point (0.5, 0) to point (0, 0.5). The program

beginchar ("a", 5pt #, 5pt #, 0);

pickup pencircle scaled (.4pt + blacker );

draw quartercircle scaled 10pt ; endchar;

therefore produces the character ā˜ ā™ in position ā˜aā™ of a font.

EXERCISE 14.1

Write a program that puts a ļ¬lled quarter-circle ā˜ ā™ into font position ā˜bā™.

EXERCISE 14.2

Why are the ā˜ ā™ and ā˜ ā™ characters of these examples only 5 pt wide and 5 pt

high, although they are made with the path ā˜quartercircle scaled 10pt ā™ ?

EXERCISE 14.3

Use a rotated quarter-circle to produce ā˜ ā™ in font position ā˜cā™.

EXERCISE 14.4

Use quartercircle to produce ā˜ ā™ in font position ā˜dā™.

Plain also provides a path called halfcircle that gives you

ā˜ ā™; this path is actually made from two quarter-circles, by deļ¬ning

halfcircle = quartercircle & quartercircle rotated 90.

And of course thereā™s also fullcircle , a complete circle of unit diameter:

fullcircle = halfcircle & halfcircle rotated 180 & cycle.

You can draw a circle of diameter D centered at (x, y) by saying

draw fullcircle scaled D shifted (x, y);

similarly, ā˜draw fullcircle xscaled A yscaled Bā™ yields an ellipse with axes A and B.

Besides circles and parts of circles, thereā™s also a standard square path

called unitsquare ; this is a cycle that runs from (0, 0) to (1, 0) to (1, 1) to (0, 1)

and back to (0, 0). For example, the command ā˜ļ¬ll unitsquare ā™ adds 1 to a single

pixel value, as discussed in the previous chapter.

124 Chapter 14: Paths

EXERCISE 14.5

Use fullcircle and unitsquare to produce the characters ā˜ ā™ and ā˜ ā™ in font

positions ā˜eā™ and ā˜fā™, respectively. These characters should be 10 pt wide and

10 pt tall, and their centers should be 2.5 pt above the baseline.

path branch [ ], trunk ;

branch 1 = ļ¬‚ex ((0, 660), (ā’9, 633), (ā’22, 610))

& ļ¬‚ex ((ā’22, 610), (ā’3, 622), (17, 617))

& ļ¬‚ex ((17, 617), (7, 637), (0, 660)) & cycle;

branch 2 = ļ¬‚ex ((30, 570), (10, 590), (ā’1, 616))

& ļ¬‚ex ((ā’1, 616), (ā’11, 592), (ā’29, 576), (ā’32, 562))

& ļ¬‚ex ((ā’32, 562), (ā’10, 577), (30, 570)) & cycle;

branch 3 = ļ¬‚ex ((ā’1, 570), (ā’17, 550), (ā’40, 535))

& ļ¬‚ex ((ā’40, 535), (ā’45, 510), (ā’60, 477))

& ļ¬‚ex ((ā’60, 477), (ā’20, 510), (40, 512))

& ļ¬‚ex ((40, 512), (31, 532), (8, 550), (ā’1, 570)) & cycle;

branch 4 = ļ¬‚ex ((0, 509), (ā’14, 492), (ā’32, 481))

& ļ¬‚ex ((ā’32, 481), (ā’42, 455), (ā’62, 430))

& ļ¬‚ex ((ā’62, 430), (ā’20, 450), (42, 448))

& ļ¬‚ex ((42, 448), (38, 465), (4, 493), (0, 509)) & cycle;

branch 5 = ļ¬‚ex ((ā’22, 470), (ā’23, 435), (ā’44, 410))

& ļ¬‚ex ((ā’44, 410), (ā’10, 421), (35, 420))

& ļ¬‚ex ((35, 420), (15, 455), (ā’22, 470)) & cycle;

branch 6 = ļ¬‚ex ((18, 375), (9, 396), (5, 420))

& ļ¬‚ex ((5, 420), (ā’5, 410), (ā’50, 375), (ā’50, 350))

& ļ¬‚ex ((ā’50, 350), (ā’25, 375), (18, 375)) & cycle;

branch 7 = ļ¬‚ex ((0, 400), (ā’13, 373), (ā’30, 350))

& ļ¬‚ex ((ā’30, 350), (0, 358), (30, 350))

& ļ¬‚ex ((30, 350), (13, 373), (0, 400)) & cycle;

branch 8 = ļ¬‚ex ((50, 275), (45, 310), (3, 360))

& ļ¬‚ex ((3, 360), (ā’20, 330), (ā’70, 300), (ā’100, 266))

& ļ¬‚ex ((ā’100, 266), (ā’75, 278), (ā’60, 266))

& ļ¬‚ex ((ā’60, 266), (0, 310), (50, 275)) & cycle;

branch 9 = ļ¬‚ex ((10, 333), (ā’15, 290), (ā’43, 256))

& ļ¬‚ex ((ā’43, 256), (8, 262), (58, 245))

& ļ¬‚ex ((58, 245), (34, 275), (10, 333)) & cycle;

branch 10 = ļ¬‚ex ((8, 262), (ā’21, 249), (ā’55, 240))

(Figure 14a will be inserted here;

too bad you canā™t see it now.)

& ļ¬‚ex ((ā’55, 240), (ā’51, 232), (ā’53, 220))

& ļ¬‚ex ((ā’53, 220), (ā’28, 229), (27, 235))

& ļ¬‚ex ((27, 235), (16, 246), (8, 262)) & cycle;

branch 11 = ļ¬‚ex ((0, 250), (ā’25, 220), (ā’70, 195))

& ļ¬‚ex ((ā’70, 195), (ā’78, 180), (ā’90, 170))

& ļ¬‚ex ((ā’90, 170), (ā’5, 188), (74, 183))

& ļ¬‚ex ((74, 183), (34, 214), (0, 250)) & cycle;

branch 12 = ļ¬‚ex ((8, 215), (ā’35, 175), (ā’72, 155))

& ļ¬‚ex ((ā’72, 155), (ā’75, 130), (ā’92, 110), (ā’95, 88))

& ļ¬‚ex ((ā’95, 88), (ā’65, 117), (ā’54, 104))

& ļ¬‚ex ((ā’54, 104), (10, 151), (35, 142))

. . ļ¬‚ex ((42, 130), (60, 123), (76, 124))

& ļ¬‚ex ((76, 124), (62, 146), (26, 180), (8, 215)) & cycle;

trunk = (0, 660) - - - (ā’12, 70) . . {curl 5}(ā’28, ā’8)

& ļ¬‚ex ((ā’28, ā’8), (ā’16, ā’4), (ā’10, ā’11))

& ļ¬‚ex ((ā’10, ā’11), (0, ā’5), (14, ā’10))

& ļ¬‚ex ((14, ā’10), (20, ā’6), (29, ā’11))

& (29, ā’11){curl 4} . . (10, 100) - - - cycle;

Chapter 14: Paths 125

Sometimes itā™s necessary to draw rather complicated curves, and plain ļ¬‚ex

El Palo Alto

provides a ā˜ļ¬‚ex ā™ operation that can simplify this task. The construc- Stanford University

tion ā˜ļ¬‚ex (z1 , z2 , z3 )ā™ stands for the path ā˜z1 . . z2 {z3 ā’ z1 } . . z3 ā™, and similarly

ā˜ļ¬‚ex (z1 , z2 , z3 , z4 )ā™ stands for ā˜z1 . . z2 {z4 ā’ z1 } . . z3 {z4 ā’ z1 } . . z4 ā™; in general

ļ¬‚ex (z1 , z2 , . . . , znā’1 , zn )

is an abbreviation for the path

z1 . . z2 {zn ā’ z1 } . . Ā· Ā· Ā· . . znā’1 {zn ā’ z1 } . . zn .

The idea is to specify two endpoints, z1 and zn , together with one or more

intermediate points where the path is traveling in the same direction as the

straight line from z1 to zn ; these intermediate points are easy to see on a typical

curve, so they are natural candidates for key points.

For example, the command

ļ¬ll ļ¬‚ex (z1 , z2 , z3 ) & ļ¬‚ex (z3 , z4 , z5 )

& ļ¬‚ex (z5 , z6 , z7 ) & ļ¬‚ex (z7 , z8 , z9 , z1 ) & cycle

will ļ¬ll the shape

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

after the points z1 , . . . , z9 have been suitably deļ¬ned. This shape occurs as

the fourth branch from the top of āEl Palo Alto,ā a tree that is often used to

symbolize Stanford University. The thirteen paths on the opposite page were

deļ¬ned by simply sketching the tree on a piece of graph paper, then reading oļ¬

approximate values of key points āby eyeā while typing the code into a computer.

(A good radio or television program helps to stave oļ¬ boredom when youā™re

typing a bunch of data like this.) The entire ļ¬gure involves a total of 47 ļ¬‚exes,

most of which are pretty mundane; but branch 12 does contain an interesting

subpath of the form

ļ¬‚ex (z1 , z2 , z3 ) . . ļ¬‚ex (z4 , z5 , z6 ),

which is an abbreviation for

z1 . . z2 {z3 ā’ z1 } . . z3 . . z4 . . z5 {z6 ā’ z4 } . . z6 .

Since z3 = z4 in this example, a smooth curve runs through all six points,

although two diļ¬erent ļ¬‚exes are involved.

126 Chapter 14: Paths

Once the paths have been deļ¬ned, itā™s easy to use them unļ¬ll

superellipse

to make symbols like the white-on-black medallion shown here: ellipse

superness

beginchar ("T", .5in #, 1.25in #, 0); LamĀ“ e

Deļ¬ne the thirteen paths on the preceding pages ; Hein

(Figure

14aa will Gardner

ļ¬ll superellipse ((w, .5h), (.5w, h), (0, .5h), (.5w, 0), .8); be inserted

fullcircle

here; too

branch 0 = trunk ; bad you

canā™t see it

for n = 0 upto 12: now.)

unļ¬ll branch [n] shifted (150, 50) scaled (w/300);

endfor endchar;

The oval shape that encloses this tree is a superellipse , which is another special

kind of path provided by plain . To get a general shape of this kind,

you can write

superellipse (right point , top point , left point , bottom point , superness )

where ā˜superness ā™ controls the amount by which the curve diļ¬ers from a true

ellipse. For example, here are four superellipses, drawn with varying amounts of

superness, using a pencircle xscaled 0.7pt yscaled 0.2pt rotated 30:

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

The superness should be between 0.5 (when you get a diamond) and 1.0 (when

you get a square); values in the vicinity of 0.75 are usually preferred. The zero

symbol ā˜0ā™ in this bookā™s typewriter font was drawn as a superellipse of superness

2ā’.5 ā .707, which corresponds to a normal ellipse; the uppercase letter ā˜Oā™ was

drawn with superness 2ā’.25 ā .841, to help distinguish it from the zero. The

ambiguous symbol ā˜Ā¼ā™ (which is not in the font, but can of course

draw it) lies between these two extremes; its superness is 0.77.

A mathematical superellipse satisļ¬es the equation |x/a|Ī² + |y/b|Ī² = 1, for

some exponent Ī². It has extreme points (Ā±a, 0) and (0, Ā±b), as well as the

ācornerā points (Ā±Ļa, Ā±Ļb), where Ļ = 2ā’1/Ī² is the superness. The tangent to the

curve at (Ļa, Ļb) runs in the direction (ā’a, b), hence it is parallel to a line from (a, 0)

to (0, b). Gabriel LamĀ“ invented the superellipse in 1818, and Piet Hein popularized the

e

special case Ī² = 2.5 [see Martin Gardner, Mathematical Carnival (New York: Knopf,

1975), 240ā“254]; this special case corresponds to a superness of 2ā’.4 ā .7578582832552.

ā™s superellipse routine does not produce a perfect superellipse, nor

Plain

does fullcircle yield a true circle, but the results are close enough for practical purposes.

EXERCISE 14.6

Try superellipse with superness values less than 0.5 or greater than 1.0; explain

why you get weird shapes in such cases.

Chapter 14: Paths 127

Letā™s look now at the symbols that are used between key points, when ..

...

we specify a path. There are ļ¬ve such tokens in plain : ā“

ā”

.. free curve; ampersand

ļ¬‚ex

... bounded curve; autorounding

-- straight line;

--- ātenseā line;

& splice.

In general, when you write ā˜z0 . . z1 . . etc. . . znā’1 . . zn ā™, will

compute the path of length n that represents its idea of the āmost pleasing

curveā through the given points z0 through zn . The symbol ā˜. . .ā™ is essentially

the same as ā˜. .ā™ , except that it conļ¬nes the path to a bounding triangle whenever

possible, as explained in Chapter 3. A straight line segment ā˜zkā’1 - - zk ā™ usually

causes the path to change course abruptly at zkā’1 and zk . By contrast, a segment

speciļ¬ed by ā˜zkā’1 - - - zk ā™ will be a straight line that blends smoothly with the

neighboring curves; i.e., the path will enter zkā’1 and leave zk in the direction of

zk ā’ zkā’1 . (The trunk of El Palo Alto makes use of this option, and we have

also used it to draw the signboard of the dangerous bend symbol at the end of

Chapter 12.) Finally, the ā˜&ā™ operation joins two independent paths together

at a common point, just as ā˜&ā™ concatenates two strings together.

Here, for example, is a somewhat silly path that illustrates all ļ¬ve basic

types of joinery:

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

z0 = (0, 100); z1 = (50, 0); z2 = (180, 0);

for n = 3 upto 9: z[n] = z[n ā’ 3] + (200, 0); endfor

draw z0 . . z1 - - - z2 . . . {up }z3

& z3 . . z4 - - z5 . . . {up }z6

& z6 . . . z7 - - - z8 . . {up }z9 .

The ā˜. . .ā™ operation is usually used only when one or both of the adjacent

directions have been speciļ¬ed (like ā˜{up }ā™ in this example). Plain -

ā™s ļ¬‚ex construction actually uses ā˜. . .ā™ , not ā˜. .ā™ as stated earlier, because this

avoids inļ¬‚ection points in certain situations.

A path like ā˜z0 - - - z1 - - - z2 ā™ is almost indistinguishable from the broken

line ā˜z0 - - z1 - - z2 ā™, except that if you enlarge the former path you will see

that its lines arenā™t perfectly straight; they bend just a little, so that the curve is

āsmoothā at z1 although thereā™s a rather sharp turn there. (This means that the

autorounding operations discussed in Chapter 24 will apply.) For example, the path

128 Chapter 14: Paths

(0, 3) - - - (0, 0) - - - (3, 0) is equivalent to unitsquare

tensepath

curl

(0, 3) . . controls (ā’0.0002, 2.9998) and (ā’0.0002, 0.0002)

. . (0, 0) . . controls (0.0002, ā’0.0002) and (2.9998, ā’0.0002) . . (3, 0)

while (0, 3) - - (0, 0) - - (3, 0) consists of two perfectly straight segments:

(0, 3) . . controls (0, 2) and (0, 1)

. . (0, 0) . . controls (1, 0) and (2, 0) . . (3, 0).

EXERCISE 14.7

ā™s unitsquare path is deļ¬ned to be ā˜(0, 0) - - (1, 0) - - (1, 1) - -

Plain

(0, 1) - - cycleā™. Explain how the same path could have been deļ¬ned using only ā˜. .ā™

and ā˜&ā™, not ā˜- -ā™ or explicit directions.

Sometimes itā™s desirable to take a path and change all its connecting links

to ā˜- - -ā™, regardless of what they were originally; the key points are left un-

has a tensepath operation that does this. For example,

changed. Plain

tensepath unitsquare = (0, 0) - - - (1, 0) - - - (1, 1) - - - (0, 1) - - - cycle.

When is deciding what curves should be drawn in place of

ā˜. .ā™ or ā˜. . .ā™, it has to give special consideration to the beginning and ending points,

so that the path will start and ļ¬nish as gracefully as possible. The solution that

usually works out best is to make the ļ¬rst and last path segments very nearly

the same as arcs of circles; an unadorned path of length 2 like ā˜z0 . . z1 . . z2 ā™

will therefore turn out to be a good approximation to the unique circular arc

that passes through (z0 , z1 , z2 ), except in extreme cases. You can change this

default behavior at the endpoints either by specifying an explicit direction or by

specifying an amount of ācurl.ā If you call for curliness less than 1, the path will

decrease its curvature in the vicinity of the endpoint (i.e., it will begin to turn

less sharply); if you specify curliness greater than 1, the curvature will increase.

(See the deļ¬nition of El Palo Altoā™s trunk , earlier in this chapter.)

Here, for example, are some pairs of parentheses that were drawn using

various amounts of curl. In each case the shape was drawn by a statement of the

form ā˜penstroke z0e {curl c} . . z1e . . {curl c}z2e ā™; diļ¬erent values of c produce

diļ¬erent-looking parentheses:

Ā½Ā¾ Āæ

curl value 0 1 2 4 inļ¬nity

yields

(The parentheses of Computer Modern typefaces are deļ¬ned by the somewhat

more general scheme described in Chapter 12; explicit directions are speciļ¬ed at

the endpoints, instead of curls, because this produces better results in unusual

cases when the characters are extremely tall or extremely wide.)

The amount of curl should not be negative. When the curl is very large,

doesnā™t actually make an extremely sharp turn at the endpoint;

instead, it changes the rest of the path so that there is comparatively little curvature

at the neighboring point.

Chapter 14: Paths 129

Chapter 3 points out that we can change ā™s default curves by tension

path primary

specifying nonstandard ātensionā between points, or even by specifying ex-

(

plicit control points to be used in the four-point method. Let us now study the full )

syntax of path expressions, so that we can come to a complete understanding of the reverse

subpath

paths that is able to make. Here are the general rules: of

path secondary

path primary ā’ā’ pair primary | path variable path tertiary

| ( path expression ) path expression

| reverse path primary cycle

path subexpression

| subpath pair expression of path primary path join

path secondary ā’ā’ pair secondary | path primary direction speciļ¬er

| path secondary transformer ā“

curl

path tertiary ā’ā’ pair tertiary | path secondary Ė

path expression ā’ā’ pair expression | path tertiary ā“

Ė

| path subexpression direction speciļ¬er ā“

| path subexpression path join cycle ,

path subexpression ā’ā’ path expression Ė

basic path join

| path subexpression path join path tertiary &

path join ā’ā’ direction speciļ¬er basic path join direction speciļ¬er ..

direction speciļ¬er ā’ā’ empty ..

..

| { curl numeric expression } ..

| { pair expression } ..

tension

| { numeric expression , numeric expression } tension

basic path join ā’ā’ & | .. | .. tension .. | .. controls .. tension

tension ā’ā’ tension tension amount and

tension amount

| tension tension amount and tension amount atleast

tension amount ā’ā’ numeric primary controls

| atleast numeric primary controls

controls

controls ā’ā’ controls pair primary and

| controls pair primary and pair primary up

The operations ā˜. . .ā™ and ā˜- -ā™ and ā˜- - -ā™ are conspicuously absent from this syntax; that

is because Appendix B deļ¬nes them as macros:

... is an abbreviation for ā˜. . tension atleast 1 . .ā™ ;

is an abbreviation for ā˜{curl 1} . . {curl 1}ā™ ;

--

is an abbreviation for ā˜. . tension inļ¬nity . .ā™ .

---

These syntax rules specify a wide variety of possibilities, even though they

donā™t mention ā˜- -ā™ and such things explicitly, so we shall now spend a little

while looking carefully at their implications. A path expression essentially has the form

Ā·Ā·Ā·

p0 j1 p1 j2 jn pn

where each pk is a tertiary expression of type pair or path, and where each jk is a āpath

join.ā A path join begins and ends with a ādirection speciļ¬er,ā and has a ābasic path

joinā in the middle. A direction speciļ¬er can be empty, or it can be ā˜{curl c}ā™ for some

c ā„ 0, or it can be a direction vector enclosed in braces. For example, ā˜{up }ā™ speciļ¬es

deļ¬nes up to be the pair (0, 1). This

an upward direction, because plain

same direction could be speciļ¬ed by ā˜{(0, 1)}ā™ or ā˜{(0, 10)}ā™, or without parentheses as

ā˜{0, 1}ā™. If a speciļ¬ed direction vector turns out to be (0, 0), behaves as

130 Chapter 14: Paths

if no direction had been speciļ¬ed; i.e., ā˜{0, 0}ā™ is equivalent to ā˜ empty ā™. An empty tension

Hobby

direction speciļ¬er is implicitly ļ¬lled in by rules that we shall discuss later.

A basic path join has three essential forms: (1) ā˜&ā™ simply concatenates

two paths, which must share a common endpoint. (2) ā˜. . tension Ī± and Ī² . .ā™

means that a curve should be deļ¬ned, having respective ātensionsā Ī± and Ī². Both Ī±

and Ī² must be equal to 3/4 or more; we shall discuss tension later in this chapter.

(3) ā˜. . controls u and v . .ā™ deļ¬nes a curve with intermediate control points u and v.

Special abbreviations are also allowed, so that the long forms of basic path

joins can usually be avoided: ā˜. .ā™ by itself stands for ā˜. . tension 1 and 1 . .ā™ ,

while ā˜. . tension Ī± . .ā™ stands for ā˜. . tension Ī± and Ī± . .ā™ , and ā˜. . controls u . .ā™ stands for

ā˜. . controls u and u . .ā™ .

Our examples so far have always constructed paths from points; but the syn-

tax shows that itā™s also possible to write, e.g., ā˜p0 . . p1 . . p2 ā™ when the pā™s

themselves are paths. What does this mean? Well, every such path will already

have been changed into a sequence of curves with explicit control points; -

expands such paths into the corresponding sequence of points and basic path

joins of type (3). For example, ā˜((0, 0) . . (3, 0)) . . (3, 3)ā™ is essentially the same as

ā˜(0, 0) . . controls (1, 0) and (2, 0) . . (3, 0) . . (3, 3)ā™, because ā˜(0, 0) . . (3, 0)ā™ is the path

ā˜(0, 0) . . controls (1, 0) and (2, 0) . . (3, 0)ā™. If a cycle is expanded into a subpath in this

way, its cyclic nature will be lost; its last point will simply be a copy of its ļ¬rst point.

Now letā™s consider the rules by which empty direction speciļ¬ers can inherit

speciļ¬cations from their environment. An empty direction speciļ¬er at the

beginning or end of a path, or just next to the ā˜&ā™ operator, is eļ¬ectively replaced by

ā˜{curl 1}ā™. This rule should be interpreted properly with respect to cyclic paths, which

have no beginning or end; for example, ā˜z0 . . z1 & z1 . . z2 . . cycleā™ is equivalent to

ā˜z0 . . z1 {curl 1}&{curl 1}z1 . . z2 . . cycleā™.

If thereā™s a nonempty direction speciļ¬er after a point but not before it, the

nonempty one is copied into both places. Thus, for example, ā˜. . z{w}ā™ is

treated as if it were ā˜. . {w}z{w}ā™. If thereā™s a nonempty direction speciļ¬er before a

point but not after it, the nonempty one is, similarly, copied into both places, except

if it follows a basic path join that gives explicit control points. The direction speciļ¬er

that immediately follows ā˜. . controls u and v . .ā™ is always ignored.

An empty direction speciļ¬er next to an explicit control point inherits the direc-

tion of the adjacent path segment. More precisely, ā˜. . z . . controls u and v . .ā™

is treated as if it were ā˜. . {u ā’ z}z . . controls u and v . .ā™ if u = z, or as if it were

ā˜. . {curl 1}z . . controls u and v . .ā™ if u = z. Similarly, ā˜. . controls u and v . . z . .ā™ is

treated as if z were followed by {z ā’ v} if z = v, by {curl 1} otherwise.

After the previous three rules have been applied, we might still be left with

cases in which there are points surrounded on both sides by empty direction

speciļ¬ers. must choose appropriate directions at such points, and it does

so by applying the following algorithm due to John Hobby [Discrete and Computational

Geometry 1 (1986), 123ā“140]: Given a sequence

z0 {d0 } . . tension Ī±0 and Ī²1 . . z1 . . tension Ī±1 and Ī²2 . . z2

etc. znā’1 . . tension Ī±nā’1 and Ī²n . . {dn }zn

Chapter 14: Paths 131

for which interior directions need to be determined, we will regard the zā™s as if they mock curvature

were complex numbers. Let lk = |zk ā’ zkā’1 | be the distance from zkā’1 to zk , and let

Ļk = arg((zk+1 ā’ zk )/(zk ā’ zkā’1 )) be the turning angle at zk . We wish to ļ¬nd direction

vectors w0 , w1 , . . . , wn so that the given sequence can eļ¬ectively be replaced by

z0 {w0 } . . tension Ī±0 and Ī²1 . . {w1 }z1 {w1 } . . tension Ī±1 and Ī²2 . . {w2 }z2

etc. znā’1 {wnā’1 } . . tension Ī±nā’1 and Ī²n . . {wn }zn .

Since only the directions of the wā™s are signiļ¬cant, not the magnitudes, it suļ¬ces to

determine the angles Īøk = arg(wk /(zk+1 ā’ zk )). For convenience, we also let Ļk =

arg((zk ā’ zkā’1 )/wk ), so that

Īøk + Ļk + Ļk = 0. (ā—)

Hobbyā™s paper introduces the notion of āmock curvatureā according to which the fol-

lowing equations should hold at interior points:

2 ā’1 ā’1 2 ā’1 ā’1

Ī²k lk (Ī±kā’1 (Īøkā’1 + Ļk ) ā’ 3Ļk ) = Ī±k lk+1 (Ī²k+1 (Īøk + Ļk+1 ) ā’ 3Īøk ). (ā—ā—)

We also need to consider boundary conditions. If d0 is an explicit direction vector w0 ,

we know Īø0 ; otherwise d0 is ā˜curl Ī³0 ā™ and we set up the equation

ā’1 ā’1

2 2

Ī±0 (Ī²1 (Īø0 + Ļ1 ) ā’ 3Īø0 ) = Ī³0 Ī²1 (Ī±0 (Īø0 + Ļ1 ) ā’ 3Ļ1 ). (ā—ā—ā—)

If dn is an explicit vector wn , we know Ļn ; otherwise dn is ā˜curl Ī³n ā™ and we set

ā’1

2 2 ā’1

Ī²n (Ī±nā’1 (Īønā’1 + Ļn ) ā’ 3Ļn ) = Ī³n Ī±nā’1 (Ī²n (Īønā’1 + Ļn ) ā’ 3Īønā’1 ). (ā—ā—ā— )

It can be shown that the conditions Ī±k ā„ 3/4, Ī²k ā„ 3/4, Ī³k ā„ 0 imply that there is a

unique solution to the system of equations consisting of (ā—) and (ā—ā—) for 0 < k < n plus

the two boundary equations; hence the desired quantities Īø0 , . . . , Īønā’1 and Ļ1 , . . . , Ļn

are uniquely determined. (The only exception is the degenerate case n = Ī³0 Ī³1 = 1.)

A similar scheme works for cycles, when there is no ā˜{d0 }ā™ or ā˜{dn }ā™. In this

case equations (ā—) and (ā—ā—) hold for all k.

EXERCISE 14.8

Write out the equations that determine the directions chosen for the general

cycle ā˜z0 . . tension Ī±0 and Ī²1 . . z1 . . tension Ī±1 and Ī²2 . . z2 . . tension Ī±2 and Ī²3 . . cycleā™

of length 3. (You neednā™t try to solve the equations.)

Whew ā” these rules have determined the directions at all points. To com-

plete the job of path speciļ¬cation, we need merely explain how to change a

segment like ā˜z0 {w0 } . . tension Ī± and Ī² . . {w1 }z1 ā™ into a segment of the form ā˜z0 . .

controls u and v . . z1 ā™ ; i.e., we ļ¬nally want to know ā™s magic recipe for

choosing the control points u and v. If Īø = arg(w0 /(z1 ā’z0 )) and Ļ = arg((z1 ā’z0 )/w1 ),

the control points are

v = z1 ā’ eā’iĻ (z1 ā’ z0 )f (Ļ, Īø)/Ī²,

u = z0 + eiĪø (z1 ā’ z0 )f (Īø, Ļ)/Ī±,

where f (Īø, Ļ) is another formula due to John Hobby:

ā 1 1

2 + 2 (sin Īø ā’ 16 sin Ļ)(sin Ļ ā’ 16 sin Īø)(cos Īø ā’ cos Ļ)

ā ā

f (Īø, Ļ) = .

3 (1 + 1 ( 5 ā’ 1) cos Īø + 1 (3 ā’ 5 ) cos Ļ)

2 2

132 Chapter 14: Paths

Thereā™s yet one more complication. If the tensions Ī± and/or Ī² have been atleast

bounding triangle

preceded by the keyword ā˜atleastā™, the values of Ī± and/or Ī² are increased, if

tension

necessary, to the minimum values such that u and v do not lie outside the ābounding unitsquare

triangle,ā which is discussed near the end of Chapter 3. reverse

What do these complex rules imply, for users who arenā™t āintoā

mathematics? The most important fact is that the rules for paths are invariant

under shifting, scaling, and rotation. In other words, if the key points zk of a path are

all shifted, scaled, and/or rotated in the same way, the resulting path will be the same as

you would get by shifting, scaling, and/or rotating the path deļ¬ned by the unmodiļ¬ed

zk ā™s (except of course for possible rounding errors). However, this invariance property

does not hold if the points or paths are xscaled and yscaled by separate amounts.

Another consequence of the rules is that tension speciļ¬cations have a fairly

straightforward interpretation in terms of control points, when the adjacent

directions have been given: The formulas for u and v simply involve division by Ī± and Ī².

This means, for example, that a tension of 2 brings the control points halfway in towards

the neighboring key points, and a tension of inļ¬nity makes the points very close indeed;

contrariwise, tensions less than 1 move the control points out.

Tension and curl speciļ¬cations also inļ¬‚uence ā™s choices of direc-

tions at the key points. That is why, for example, the construction ā˜zkā’1 - - - zk ā™

(which means ā˜zkā’1 . . tension inļ¬nity . . zk ā™ ) aļ¬ects the direction of a larger path as it

enters zkā’1 and leaves zk .

The rules imply that a change in the position of point zn causes a change

in the curve near point z0 , when has to choose directions at all

points between z0 and zn . However, this eļ¬ect is generally negligible except in the

vicinity of the changed point. You can verify this by looking, for example, at the

control points that chooses for the path ā˜(0, 0) . . (1, 0) . . (2, 0) . . (3, 0) . .

(4, 0) . . . {up }(5, y)ā™, as y varies.

EXERCISE 14.9

Run on the ā˜exprā™ ļ¬le of Chapter 8, and ask to see the path

expression ā˜unitsquare shifted (0, 1) . . unitsquare shifted (1, 0)ā™. Account for the

results that you get.

EXERCISE 14.10

ā™s abbreviation for ā˜{curl 1} . . {curl 1}ā™.

Weā™ve said that ā˜- -ā™ is plain

Would there be any essential diļ¬erence if ā˜- -ā™ were deļ¬ned to mean ā˜{curl 2} . . {curl 2}ā™ ?

EXERCISE 14.11

Look closely at the syntax of path expression and explain what

does with the speciļ¬cation ā˜(0, 0) . . (3, 3) . . cycle{curl 1}ā™.

Now letā™s come back to simpler topics relating to paths. Once a path has

been speciļ¬ed, there are lots of things you can do with it, besides drawing and

ļ¬lling and suchlike. For example, if p is a path, you can reverse its direction by saying

ā˜reverse pā™; the reverse of ā˜z0 . . controls u and v . . z1 ā™ is ā˜z1 . . controls v and u . . z0 ā™.

EXERCISE 14.12

True or false: length reverse p = length p, for all paths p.

Chapter 14: Paths 133

Itā™s convenient to associate ātimeā with paths, by imagining that we move time

subpaths

along a path of length n as time passes from 0 to n. (Chapter 8 has already

mediation

illustrated this notion, with respect to an almost-but-not-quite-circular path called p2; BernshteĖ˜

Ä±n

itā™s a good idea to review the discussion of paths and subpaths in Chapter 8 now before

you read further.) Given a path

p = z0 . . controls u0 and v1 . . z1 etc. znā’1 . . controls unā’1 and vn . . zn

determines ā˜point t of pā™ as follows: If t ā¤ 0, the result

and a number t,

is z0 ; if t ā„ n, the result is zn ; otherwise if k ā¤ t < k + 1, it is (t ā’ k)[zk , uk , vk+1 , zk+1 ],

where we generalize the ā˜t[Ī±, Ī²]ā™ notation so that t[Ī±, Ī², Ī³] means t[t[Ī±, Ī²], t[Ī², Ī³]] and

t[Ī±, Ī², Ī³, Ī“] means t[t[Ī±, Ī², Ī³], t[Ī², Ī³, Ī“]]. (This is a BernshteĖ˜ polynomial in t, cf. Chap-

Ä±n

ter 3.) Given a cyclic path

c = z0 . . controls u0 and v1 . . z1 etc. znā’1 . . controls unā’1 and vn . . cycle

and a number t, determines ā˜point t of cā™ in essentially the same way,

except that t is ļ¬rst reduced modulo n so as to lie in the range 0 ā¤ t < n.

EXERCISE 14.13

True or false: point t of (z0 - - z1 ) = t[z0 , z1 ].

Given a path p and two time values t1 ā¤ t2 , ā˜subpath (t1 , t2 ) of pā™ contains

all the values ā˜point t of pā™ as t varies from t1 to t2 . Thereā™s no problem

understanding how to deļ¬ne this subpath when t1 and t2 are integers; for example,

subpath (2, 4) of p = z2 . . controls u2 and v3 . . z3 . . controls u3 and v4 . . z4

in the notation above, if we assume that n ā„ 4. The fractional case is handled by

āstretching timeā in one segment of the curve; for example, if 0 < t < 1 we have

subpath (0, t) of p = z0 . . controls t[z0 , u0 ] and t[z0 , u0 , v1 ] . . t[z0 , u0 , v1 , z1 ];

subpath (t, 1) of p = t[z0 , u0 , v1 , z1 ] . . controls t[u0 , v1 , z1 ] and t[v1 , z1 ] . . z1 .

These two subpaths together account for all points of ā˜z0 . . controls u0 and v1 . . z1 ā™. To

get subpath (t1 , t2 ) of p when 0 < t1 < t2 < 1, applies this construction

twice, by computing subpath (t1 /t2 , 1) of subpath (0, t2 ) of p.

The operation ā˜subpath (t1 , t2 ) of pā™ is deļ¬ned for all combinations of times

(t1 , t2 ) and paths p by the following rules: Let n = length p. (1) If t1 > t2 ,

subpath (t1 , t2 ) of p = reverse subpath (t2 , t1 ) of p. Henceforth we shall assume that

t1 ā¤ t2 . (2) If t1 = t2 , subpath (t1 , t2 ) of p = point t1 of p, a path of length zero.

Henceforth we shall assume that t1 < t2 . (3) If t1 < 0 and p is a cycle, subpath (t1 , t2 )

of p = subpath (t1 + n, t2 + n) of p. If t1 < 0 and p is not a cycle, subpath (t1 , t2 ) of p =

subpath (0, max(0, t2 )) of p. Henceforth we shall assume that t1 ā„ 0. (4) If t1 ā„ n and

p is a cycle, subpath (t1 , t2 ) of p = subpath (t1 ā’ n, t2 ā’ n) of p. If t1 < n < t2 and p is a

cycle, subpath (t1 , t2 ) of p = subpath (t1 , t2 ) of (p & p & cycle). If t2 > n and p is not a

cycle, subpath (t1 , t2 ) of p = subpath (min(t1 , n), n) of p. Henceforth we shall assume

that 0 ā¤ t1 < t2 ā¤ n. (5) If t1 ā„ 1, subpath (t1 , t2 ) of p = subpath (t1 ā’ 1, t2 ā’ 1) of

subpath (1, n) of p, where subpath (1, n) of p is obtained by removing the ļ¬rst segment

of p. Henceforth we shall assume that 0 ā¤ t1 < 1. (6) If t2 > 1, subpath (t1 , t2 )

of p = subpath (t1 , 1) of p & subpath (1, t2 ) of p. Henceforth we shall assume that

0 ā¤ t1 < t2 ā¤ 1. (7) The remaining cases were deļ¬ned in the preceding paragraph.

134 Chapter 14: Paths

EXERCISE 14.14 postcontrol

precontrol

What is the length of ā˜subpath (2.718, 3.142) of pā™ ?

interpath

interpolate between paths

Besides ā˜point t of pā™, allows you to speak of ā˜postcontrol t of pā™

Knuth, D E

and ā˜precontrol t of pā™; this gives access to the control points of a path. Let Knuth, J C

valentine

p = z0 . . controls u0 and v1 . . z1 etc. znā’1 . . controls unā’1 and vn . . zn . heart

If t < n, postcontrol t of p is the ļ¬rst control point in subpath (t, n) of p; if t ā„ n,

postcontrol t of p is zn . If t > 0, precontrol t of p is the last control point in subpath (0, t)

of p; if t ā¤ 0, precontrol t of p is z0 . In particular, if t is an integer, postcontrol t of p

is ut for 0 ā¤ t < n, and precontrol t of p is vt for 0 < t ā¤ n.

The ability to extract key points and control points makes it possible to deļ¬ne

ā™s interpath function, which

interesting operations such as plain

allows you to interpolate between paths. For example, ā˜interpath (1/3, p, q)ā™ will produce

a path of length n whose points are 1/3[point t of p, point t of q] for 0 ā¤ t ā¤ n, given

any paths p and q of length n. It can be deļ¬ned by a fairly simple program:

vardef interpath (expr a, p, q) =

for t = 0 upto length p ā’ 1: a[point t of p, point t of q]

. . controls a[postcontrol t of p, postcontrol t of q]

and a[precontrol t + 1 of p, precontrol t + 1 of q] . . endfor

if cycle p: cycle % assume that p, q are both cycles or both noncycles

else: a[point inļ¬nity of p, point inļ¬nity of q] ļ¬ enddef ;

On February 14, 1979, the author bought a box of chocolates and placed the

box on a piece of graph paper (after suitably disposing of the contents). The

experimental data gathered in this way led to a ādeļ¬nitiveā heart shape:

heart = (100, 162) . . (140, 178){right } . . (195, 125){down }

. . (100, 0){curl 0} . . {up }(5, 125) . . {right }(60, 178) . . (100, 162);

It is interesting to interpolate between heart and other paths, by using a program like

for n = 0 upto 10: draw interpath (n/10, p, heart ); endfor.

For example, the left illustration below was obtained by taking

p = (100, 0) - - (300, 0) - - (200, 0) - - (100, 0) - - (0, 0) - - (ā’100, 0) - - (100, 0);

notice that interpath doesnā™t necessarily preserve smoothness at the key points. The

right illustration was obtained by duplicating point (100, 0) in heart (thereby making

it a path of length 7) and taking

p = (100, 200) - - (200, 200) - - (200, 100)

- - (200, 0) - - (0, 0) - - (0, 100) - - (0, 200) - - (100, 200).

(Figure 14bb&cc will be inserted here; too bad you canā™t see it now.)

Chapter 14: Paths 135

Plain allows you to say ā˜direction t of pā™ in order to determine the direction

directiontime

direction in which path p is moving at time t. This is simply an abbreviation

dir

for ā˜(postcontrol t of p) ā’ (precontrol t of p)ā™. Sometimes a path veers abruptly and has angle

no unique direction; in this case the direction function gives a result somewhere between epsilon

directionpoint

the two possible extremes. For example, the heart path above turns a corner at time 3; fullcircle

ā˜direction 3 of heart ā™ turns out to be (ā’93.29172, 0), but ā˜direction 3 ā’ epsilon of heart ā™

is (ā’46.64589, ā’31.63852) and ā˜direction 3 + epsilon of heart ā™ is (ā’46.64589, 31.63852).

Conversely, can tell you when a path heads in a given direction.

You just ask for ā˜directiontime w of pā™, where w is a direction vector and p is

a path. This operation is best understood by looking at examples, so letā™s resume our

dialog with the computer by applying to the ā˜exprā™ ļ¬le as in Chapter 8.

When ļ¬rst says ā˜gimmeā™, our opening strategy this time will be to type

hide(p3 = (0,0){right}..{up}(1,1)) p3

so that we have a new path to play with. Now the fun begins:

You type And the result is

directiontime right of p3 0

directiontime up of p3 1

directiontime down of p3 -1

directiontime (1,1) of p3 0.5

directiontime left of reverse p3 1

direction directiontime (1,2) of p3 of p3 (0.23126,0.46251)

directiontime right of subpath(epsilon,1) of p3 0

directiontime right of subpath(2epsilon,1)of p3 -1

directiontime (1,1) of subpath(epsilon,1) of p3 0.49998

direction epsilon of p3 (0.55226,0)

direction 2epsilon of p3 (0.55229,0.00003)

directiontime dir 30 of p3 0.32925

angle direction 0.32925 of p3 29.99849

angle direction 0.32925+epsilon of p3 30.00081

directionpoint up of p3 (1,1)

Note that directiontime yields ā’1 if the speciļ¬ed direction doesnā™t occur. At time

epsilon , path p3 is still traveling right, but at time 2epsilon it has begun to turn

upward. The ā˜directionpointā™ operation is analogous to directiontime, but it gives the

point on the path rather than the time of arrival.

You type And the result is

directiontime up of fullcircle 0

directiontime left of fullcircle 2

directiontime right of fullcircle 6

directiontime (-1,1) of fullcircle 1

directiontime (epsilon,infinity) of fullcircle 8

136 Chapter 14: Paths

directiontime right of unitsquare 0 unitsquare

cusp

directiontime up of unitsquare 1 strange

turning numbers

directiontime (1,1) of unitsquare 1

tension

directiontime (-1,1) of unitsquare 2 posttension

pretension

If a path travels in a given direction more than once, directiontime reports only the ļ¬rst intersection

halfcircle

time. The unitsquare path has sharp turns at the corners; directiontime considers that

all directions between the incoming and outgoing ones are instantaneously present.

Itā™s possible to construct pathological paths in which unusual things happen.

For example, the path p = (0, 0) . . controls (1, 1) and (0, 1) . . (1, 0) has a

ācuspā at time 0.5, when it comes to a dead stop and turns around. (If you ask

for ā˜direction 0.5 of pā™, the answer is zero, while direction 0.5 ā’ of p is (0, 2 ) and

direction 0.5 + of p is (0, ā’2 ).) The directiontime operation assumes that all possible

directions actually occur when a path comes to a standstill, hence ā˜directiontime right

of pā™ will be 0.5 in this case even though it might be argued that p never turns to the

right. Paths with cusps are numerically unstable, and they might become āstrangeā

after transformations are applied, because rounding errors might change their turning

numbers. The path p in this example has control points that correspond to tensions of

only 0.28 with respect to the initial and ļ¬nal directions; since insists that

tensions be at least 0.75, this anomalous path could never have arisen if the control

points hadnā™t been given explicitly.

EXERCISE 14.15

Write macros called posttension and pretension that determine the eļ¬ective

tensions of a pathā™s control points at integer times t. For example, ā˜pretension 1

of (z0 . . tension Ī± and Ī² . . z1 )ā™ should be Ī² (approximately). Test your macro by

computing posttension 0 of ((0, 0){right } . . . {up }(1, 10)).

We have now discussed almost all of the things that can do

with paths; but thereā™s one more important operation to consider, namely

intersection. Given two paths p and q, you can write

p intersectiontimes q

and the result will be a pair of times (t, u) such that point t of p ā point u of q. For

example, using the expr routine,

You type And the result is

unitsquare intersectiontimes fullcircle (0.50002,0)

unitsquare intersectiontimes fullcircle rotated 90 (0.50002,6)

reverse unitsquare intersectiontimes fullcircle (0.50002,2)

fullcircle intersectiontimes unitsquare (0,0.50002)

halfcircle rotated 45 intersectiontimes unitsquare (1,3.5)

halfcircle rotated 89 intersectiontimes unitsquare (0.02196,3.5)

halfcircle rotated 90 intersectiontimes unitsquare (0,3.50002)

halfcircle rotated 91 intersectiontimes unitsquare (-1,-1)

halfcircle rotated 45 intersectiontimes fullcircle (0,1)

fullcircle intersectiontimes (-0.5,0) (4,0)

Chapter 14: Paths 137

unitsquare intersectionpoint fullcircle (0.5,0) intersectionpoint

tertiary level

reverse unitsquare intersectionpoint fullcircle (0,0.5) precedence

Quick

Notice that the result is (ā’1, ā’1) if the paths donā™t intersect. The last two examples il- shuļ¬„ed binary

Journal of Algorithms

lustrate the ā˜intersectionpointā™ operator, which yields the common point of intersection.

logo

Both intersectiontimes and intersectionpoint apply at the tertiary level of precedence, Knuth, J C

hence parentheses were not needed in these examples.

EXERCISE 14.16

J. H. Quick (a student) wanted to construct a path r that started on some

previously deļ¬ned path p and proceeded up to the point where it touched another

path q, after which r was supposed to continue on path q. So he wrote

path r; numeric t, u; (t, u) = p intersectiontimes q;

r = subpath (0, t) of p & subpath (u, inļ¬nity ) of q;

but it didnā™t work. Why not?

If the paths intersect more than once, has a somewhat peculiar

way of deciding what times (t, u) should be reported by ā˜p intersectiontimes qā™.

Suppose p has length m and q has length n. (Paths of length 0 are ļ¬rst changed into

motionless paths of length 1.) proceeds to examine subpath (k, k + 1) of p

versus subpath (l, l + 1) of q, for k = 0, . . . , m ā’ 1 and l = 0, . . . , n ā’ 1, with l varying

most rapidly. This reduces the general problem to the special case of paths of length 1,

and the times (t, u) for the ļ¬rst such intersection found are added to (k, l). But within

paths of length 1 the search for intersection times is somewhat diļ¬erent: Instead of

reporting the ālexicographically smallestā pair (t, u) that corresponds to an intersection,

ļ¬nds the (t, u) whose āshuļ¬„ed binaryā representation (.t1 u1 t2 u2 . . . )2 is

minimum, where (.t1 t2 . . . )2 and (.u1 u2 . . . )2 are the radix-2 representations of t and u.

EXERCISE 14.17

(A mathematical puzzle.) The path p = (0, 0) . . controls (2, 2) and (0, 1) . .

(1, 0) loops on itself, so there are times t < u such that point t of p ā point u of p.

Devise a simple way to compute (t, u) in a program, without using the

subpath operation.

Letā™s conclude this chapter by applying what weā™ve learned about paths to a

real-life example. The Journal of Algorithms has been published since 1980

by Academic Press, and its cover page carries the following logo, which was designed

by J. C. Knuth to blend with the style of type used elsewhere on that page:

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

A program to produce this logo will make it possible for the editors of the

journal to use it on letterheads in their correspondence. Here is one way to do the job,

138 Chapter 14: Paths

without needing to erase anything: superellipse

whatever

beginchar ("A", 29mm #, 25mm #, 0); thick # := 2mm #; thin # := 5/4mm #; rotatedaround

1

reļ¬‚ectedabout

deļ¬ne whole blacker pixels(thick , thin );

2

forsuļ¬xes

forsuļ¬xes $ = a, b, c: transform $;

3 penstroke

range

forsuļ¬xes e = l, r: path $e, $ e; numeric t$[ ]e; endfor endfor

4

thru

penpos1 (thick , 0); penpos2 (thick , 90); penpos3 (thick , 180); penpos4 (thick , 270);

5

penpos5 (thick , 0); penpos6 (thick , 90); penpos7 (thick , 180); penpos8 (thick , 270);

6

x2 = x4 = x6 = x8 = .5[x5 , x7 ] = .5w; x1r = w; x3r = 0; x5 ā’ x7 = y6 ā’ y8 ;

7

y1 = y3 = y5 = y7 = .5[y6 , y8 ] = .5h; y2r = h; y4r = 0; y6r = .75h;

8

forsuļ¬xes e = l, r: a.e = b e = c e = superellipse (z1e , z2e , z3e , z4e , .75);

9

a e = b.e = c.e = superellipse (z5e , z6e , z7e , z8e , .72); endfor

10

penposa1 (thin , 0); penposa5 (whatever , ā’90); penposa9 (thin , 180);

11

xa1l ā’ xa9l = 1/3(x5l ā’ x7l ); xa5 = .5w; ya1 = ya9 ; ya5r = 4/7h;

12

xa3l = xa1l ; xa3r = xa1r ; xa4r = 1/6[xa3r , x1l ]; x0 = .5w; y0 = .52h;

13

xa6l + xa4l = xa6r + xa4r = xa7l + xa3l = xa7r + xa3r = xa9 + xa1 = w;

14

ya3r = ya4r = ya6r = ya7r = .2[y2l , y0 ]; ya3l = ya4l = ya6l = ya7l = ya3r ā’ thin ;

15

za4l = za4r + (thin , 0) rotated(angle(za4r ā’ za5r ) + 90)

16

+ whatever ā— (za4r ā’ za5r ); za4l ā’ za5l = whatever ā— (za4r ā’ za5r );

17

z = a.r intersectionpoint (z0 - - (w, 0)); ya1 ā’ ya5 = length(z ā’ z0 );

18

b = identity shifted (0, y0 ā’ ya1 ) rotatedaround(z0 , 90 ā’ angle(z0 ā’ (w, 0)));

19

c = b reļ¬‚ectedabout (z2 , z4 );

20

for n = 1, 3, 4, 5, 6, 7, 9: forsuļ¬xes e = l, , r: forsuļ¬xes $ = b, c:

21

z$[n]e = za[n]e transformed $; endfor endfor endfor

22

forsuļ¬xes e = l, r: forsuļ¬xes $ = a, b, c:

23

z$2e = $r intersectionpoint (z$1e - - z$3e );

24

z$8e = $r intersectionpoint (z$9e - - z$7e );

25

t$1e = xpart($e intersectiontimes (z$1l - - z$3l ));

26

t$9e = xpart($e intersectiontimes (z$9l - - z$7l ));

27

t$4e = xpart($ e intersectiontimes (z$5r - - z$4l ));

28

t$6e = xpart($ e intersectiontimes (z$5r - - z$6l )); endfor endfor

29

penstroke subpath(ta9e , tb6e ) of a.e;

30

penstroke subpath(tb4e , tc4e ) of b e;

31

penstroke subpath(tc6e , ta1e + 8) of c e;

32

penstroke subpath(ta6e , tb9e ) of a e;

33

penstroke subpath(tb1e , tc1e ) of b.e;

34

penstroke subpath(tc9e , ta4e + 8) of c.e;

35

forsuļ¬xes $ = a, b, c: penlabels($1, $2, $3, $4, $5, $6, $7, $8, $9);

36

penstroke z$2e - - z$3e - - z$4e - - z$5e - - z$6e - - z$7e - - z$8e ; endfor

37

penlabels(range 0 thru 8); endchar;

38

Lines 5ā“10 of this program deļ¬ne the main superellipses of the ļ¬gure. The outer

superellipse is eventually drawn as three separate strokes in lines 30ā“32, and the inner

one is drawn as three strokes in lines 33ā“35. The rest of the ļ¬gure consists of three

arrows, whose point labels are prefaced by the respective labels a, b, c. Lines 11ā“18

deļ¬ne the ā˜aā™ arrow; then lines 19ā“22 transform these points into the ā˜bā™ and ā˜cā™ arrows,

anticipating some of the things we shall discuss in Chapter 15. Thirty-six intersections

between arrows and superellipses are computed in lines 23ā“29, and the arrows are ļ¬nally

drawn by the penstrokes speciļ¬ed in lines 36ā“37.

Chapter 14: Paths 139

El Palo Alto

FONT

Giotto

RUSKIN

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

The route is indicated by dots,

the daysā™ journeys are expressed by numbers,

and letters are used to locate notable places and sites.

. . . We arrived at the Arroyo de San Francisco,

beside which stream is the redwood tree I spoke of yesterday;

I measured its height with the Graphometer

and reckoned it to be ļ¬fty yards high, more or less.

ā” FRAY PEDRO FONT, Diary (1776)

The practical teaching of the masters of Art was summed by the O of Giotto.

ā” JOHN RUSKIN, The Cestus of Aglaia (1865)

(page 140)

15

Transformations

Chapter 15: Transformations 141

Points, paths, pens, and pictures can be shifted, scaled, rotated, and revamped transformations

shifted

in a variety of ways. Our aim in this chapter will be to learn all about the built- scaled

in metamorphoses of , because they can make programs simpler and xscaled

yscaled

more versatile. slanted

The basic transformations have already appeared in many examples, but rotated

zscaled

letā™s start by reviewing them here: rotatedaround

reļ¬‚ectedabout

(x, y) shifted (a, b) = (x + a, y + b); transformed

transform

(x, y) scaled s = (sx, sy);

equations

(x, y) xscaled s = (sx, y); identity

(x, y) yscaled s = (x, sy);

(x, y) slanted s = (x + sy, y);

= (x cos Īø ā’ y sin Īø, x sin Īø + y cos Īø);

(x, y) rotated Īø

= (xu ā’ yv, xv + yu).

(x, y) zscaled (u, v)

One of the nice things about is that you donā™t have to remem-

ber the sine-and-cosine formulas of trigonometry; you just have to know that

ā˜(x, y) rotated Īøā™ means ā˜the vector (x, y) rotated Īø degrees counterclockwise

around (0, 0)ā™, and the computer does all the necessary calculations by itself.

The operation of zscaling may look a bit strange, but it is simply a combination

of rotating by angle (u, v) and scaling by length (u, v).

Plain provides two more transformations that are commonly

needed: You can say ā˜(x, y) rotatedaround (z0 , Īø)ā™ if you want to rotate around

point z0 instead of point (0, 0). And you can say ā˜(x, y) reļ¬‚ectedabout (z1 , z2 )ā™

if you want to ļ¬nd the point directly opposite (x, y) on the other side of the

straight line that runs through z1 and z2 .

All of these operations are special manifestations of a single glorious

maneuver that can be written in the general form

(x, y) transformed t.

Here t is a variable (or primary expression) of type transform; it stands for any

desired sequence of shiftings, scalings, slantings, etc., all in one fell swoop.

You can give equations between transforms, just as you can give equa-

tions between other types of things in programs. Thus, for example,

you might say

transform t[ ]; t2 = t1 shifted (2, 2) rotated 30;

then an expression like ā˜(x, y) transformed t1 shifted (2, 2) rotated 30ā™ can be

abbreviated to ā˜(x, y) transformed t2 ā™, which is simpler and faster.

Thereā™s a special transform variable called identity with the amazing

property that

(x, y) transformed identity = (x, y)

for all x and y. You might think that identity is useless, since it does nothing, but

in fact itā™s a natural starting point for building other transforms. For example,

142 Chapter 15: Transformations

line 19 of the program at the end of the previous chapter says show

xpart

b = identity shifted (0, y0 ā’ ya1 ) rotatedaround(z0 , theta ); ypart

xypart

yxpart

this deļ¬nes the transform variable b to be a compound transformation that is yypart

xxpart

used on lines 21 and 22 to construct the lower left arrow as a shifted and rotated

copy of the upper arrow, in the character being drawn.

A transform variable t represents six numbers (tx , ty , txx , txy , tyx , tyy ), in

much the same way as a pair variable represents two numbers (x, y). The

general transformation ā˜(x, y) transformed tā™ is simply an abbreviation for

(tx + x txx + y txy , ty + x tyx + y tyy );

thus, for example, ā˜txy ā™ appears in the xpart of the transform as the coeļ¬cient of y. If

you say ā˜show tā™ when t is a completely unknown transform, the computer will type

>> (xpart t,ypart t,xxpart t,xypart t,yxpart t,yypart t)

just as it would type ā˜>> (xpart u,ypart u)ā™ for a completely unknown variable u

of type pair. You can access individual components of a transform by referring to

ā˜xpart tā™, ā˜ypart tā™, ā˜xxpart tā™, etc.

Once again, we can learn best by computer experiments with the expr ļ¬le

(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

version at the end of Chapter 3:

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

Hereā™s another example of cutoļ¬ , in which

the endpoints of ā™s ā˜Tā™ have been

ā—¦

cropped at 10 angles to the perpendicular of the

stroke direction:

pickup logo_pen; (Figure 16e will be inserted here;

too bad you canā™t see it now.)

top lft z1=(0,h); top rt z2=(w,h);

top z3=(.5w,h); z4=(.5w,0);

draw z1--z2;

cutoff(z1,170); cutoff(z2,-10);

draw z3--z4; cutoff(z4,-80).

The cutoļ¬ macro of Appendix B deals with several things that weā™ve been

studying recently, so it will be instructive to look at it now (slightly simpliļ¬ed):

def cutoļ¬ (expr z, theta ) =

cut pic := nullpicture;

addto cut pic doublepath z withpen currentpen ;

addto cut pic contour ((0, ā’1) - - (1, ā’1) - - (1, 1) - - (0, 1) - - cycle)

scaled 1.42(1 + max(ā’pen lft , pen rt , pen top , ā’pen bot ))

rotated theta shifted z ;

cull cut pic keeping (2, 2) withweight ā’1;

addto currentpicture also cut pic enddef .

The main work is done in a separate picture variable called cut pic , so that neighboring

strokes wonā™t be aļ¬ected. First cut pic is set to the full digitized pen image (by making

a doublepath from a single point). Then a rectangle that includes the cutoļ¬ region

is added in; pen lft , pen rt , pen top , and pen bot are the quantities used to compute

the functions lft , rt , top , and bot , so they bound the size of the pen. The culling

operation produces the intersection of pen and rectangle, which is ļ¬nally subtracted

from currentpicture .

152 Chapter 16: Calligraphic Eļ¬ects

We shall conclude this chapter by studying two examples of how ā™s tilde

pencircle

pen-and-curve-drawing facilities can combine in interesting ways. First, letā™s

pensquare

examine two ātildeā characters BernshteĖ˜ polynomial

Ä±n

serif

ļ¬lldraw

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

dishing

ļ¬‚ex

epsilon

which were both created by a single command of the form strange paths

controls

draw z1 . . controls z2 and z3 . . z4 .

The left example was done with a pencircle xscaled .8pt yscaled .2pt rotated 50, and

the right example was exactly the same but with pensquare. The control points z2

and z3 that made this work were deļ¬ned by

y2 ā’ y1 = y4 ā’ y3 = 3(y4 ā’ y1 );

z2 ā’ z1 = z4 ā’ z3 = whatever ā— dir 50.

The second pair of equations is an old calligrapherā™s trick, namely to start and ļ¬nish

a stroke in the direction of the pen youā™re holding. The ļ¬rst pair of equations is a

mathematicianā™s trick, based on the fact that the BernshteĖ˜ polynomial t[0, 3, ā’2, 1]

Ä±n

goes from 0 to 1 to 0 to 1 as t goes from 0 to .25 to .75 to 1.

Next, letā™s try to draw a fancy serif with the same two pens, holding them at

a 20ā—¦ angle instead of a 50ā—¦ angle. Here are two examples

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

that can be created by ā˜ļ¬lldrawā™ commands:

ļ¬lldraw z1 . . controls z2 . . z3

- - (ļ¬‚ex (z3 , .5[z3 , z4 ] + dishing , z4 )) shifted (0, ā’epsilon )

- - z4 . . controls z5 . . z6 - - cycle.

The dishing parameter causes a slight rise between z3 and z4 ; the ļ¬‚ex has been lowered

by epsilon in order to avoid the danger of āstrange paths,ā which might otherwise be

caused by tiny loops at z3 or z4 . But the most interesting thing about this example

is the use of double control points, z2 and z5 , in two of the path segments. (Recall

that ā˜controls z2 ā™ means the same thing as ā˜controls z2 and z2 ā™.) These points were

determined by the equations

x2 = x1 ; z2 = z3 + whatever ā— dir 20;

x5 = x6 ; z5 = z4 + whatever ā— dir ā’20;

thus, they make the strokes vertical at z1 and z6 , parallel to the pen angle at z3 , and

parallel to the complementary angle at z4 .

Chapter 16: Calligraphic Eļ¬ects 153

EVETTS

REYNOLDS

The pen, probably more than any other tool,

has had the strongest inļ¬‚uence upon lettering

in respect of serif design . . .

It is probable that the letters [of the Trajan column]

were painted before they were incised,

and though their main structure is attributed to the pen

and their ultimate design to the technique of the chisel,

they undoubtedly owe much of their freedom

to the inļ¬‚uence of the brush.

ā” L. C. EVETTS, Roman Lettering (1938)

Remember that it takes time, patience, critical practice

and knowledge to learn any art or craft.

No āart experienceā is going to result from any busy work

for a few hours experimenting with the edged pen.

. . . Take as much time as you require,

and do not become impatient.

ńņš. 5 |