ńņš. 24 |

be odd, so that there will be as many pixels to the left of the stem as there are to the

right. If the pen breadth is even, we want w to be even. Therefore we have a 50-50

chance of being unhappy with the value of w that is computed by beginchar.

EXERCISE 24.3

Prove that the value of w is satisfactory for ā˜ ā™ with respect to the logo pen

if and only if .5w is a good x value for vertical strokes.

If w is not a good value, we want to replace it by either w+1 or wā’1, whichever

is closer to the device-independent width from which w was rounded. For

example, if w was rounded to 22 from the ideal width 21.7, we want to change it to 21

rather than 23. Plain ā™s change width routine does this. Hence we have

the following program for ā˜ ā™, in place of the simpler version found in exercise 11.4:

beginlogochar("T", 13);

if .5w <> good.x .5w: change width; ļ¬

lft x1 = ā’eps ;

x2 = w ā’ x1 ;

x3 = x4 = .5w;

y1 = y2 = y3 ; top y1 = h; bot y4 = ā’o; (Figure 4b will be inserted here;

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

draw z1 - - z2 ; draw z3 - - z4 ;

labels(1, 2, 3, 4); endchar.

Chapter 4 said that ā˜ ā™ was the simplest of the seven

logo letters, but it has turned out to be the trickiest.

200 Chapter 24: Discreteness and Discretion

This program has one unexplained feature. Why was lft x1 set to ā’eps instead eps

overshoot

of zero? The answer requires an understanding of the pen polygons discussed

M

in Chapter 16. The edges of those polygons are highly likely to pass through ambiguous pencircle

points when the center of the pen has integer or half-integer coordinates. draw

sharp sign

shifts paths slightly to the right and up, in order to resolve ambiguities; therefore if hash mark

ambiguous points occur at the left and right edges of the ā˜ ā™, some pixels will be lost range

thru

at the left but gained at the right. The constant eps is 0.00049, which is small but

will surely notice it. Subtracting eps from x1 and

positive enough that

adding eps to x2 avoids ambiguous edge points and keeps the result symmetric.

Since the overshoot ā˜oā™ is always eps more than an integer, it is unnecessary

to do anything similar at point z4 ; the equation ā˜bot y4 = ā’oā™ is suļ¬cient.

Point z3 in the middle of the ā˜ ā™ is in a satisfactory position because bot y3 =

ygap ā’ o . If bot y3 were exactly an integer, the would often turn out to be

unsymmetric, because of ambiguous points on the boundary at z3 .

EXERCISE 24.4

True or false: If currentpen is pencircle xscaled px yscaled py , the command

ā˜draw (ā’epsilon , 0) . . (+epsilon , 0)ā™ will produce an image that has both left-right

and top-bottom symmetry. (Assume that autorounding =smoothing =0.)

EXERCISE 24.5

The polygon for ā˜pencircle scaled 3ā™ is an octagon whose vertices are at the

points (Ā±0.5, Ā±1.5) and (Ā±1.5, Ā±0.5). Prove that if you ā˜draw (x, y)ā™ with this pen,

the result never has both top-bottom and left-right symmetry.

Rounding can also help to position points at which we donā™t have horizontal

or vertical tangents. For example, consider the āsharp signā or āhash markā

character thatā™s drawn by the following program:

u# := 10 pt #; deļ¬ne pixels(u);

18

beginchar (0, 15u#, 250 pt #, 70 pt #);

36 36

pickup pencircle

scaled (.4pt + blacker );

lft x1 = round u ā’ eps ;

x3 = x1 ;

x2 = x4 = w ā’ x1 ;

y1 = y2 = good.y (.5[ā’d, h] + pt );

y3 = y4 = h ā’ d ā’ y1 ;

draw z1 - - z2 ; draw z3 - - z4 ;

lft x6 = round 3u;

x7 = w ā’ x6 ; (Figure 24e will be inserted here; too bad you

canā™t see it now.)

x8 = good.x .5w;

x5 ā’ x6 = x7 ā’ x8 ;

top y5 = top y7 = h + eps ;

bot y6 = bot y8 = ā’d ā’ eps ;

draw z5 - - z6 ; draw z7 - - z8 ;

labels(range 1 thru 8);

endchar.

Chapter 24: Discreteness and Discretion 201

If we digitize this character according to lowres mode at 200 pixels per inch, we get lowres

jaggies

the following results:

n

stems

The left-hand example was obtained by omitting the ā˜roundā™ and ā˜good.x ā™ instructions

in the equations for x6 and x8 . This meant that points z6 and z8 fell into diļ¬erent,

possibly unlucky, raster positions, so the two diagonal strokes digitized diļ¬erently even

though they came from essentially identical undigitized lines. The middle example

was produced by the given program without changes. And the right-hand example

was produced by drawing the diagonals in a more complicated way: The commands

ā˜draw z5 - - z6 ; draw z7 - - z8 ;ā™ were replaced by

y15 = y1 ; z15 = whatever [z5 , z6 ]; y36 = y3 ; z36 = whatever [z5 , z6 ];

y27 = y2 ; z27 = whatever [z7 , z8 ]; y48 = y4 ; z48 = whatever [z7 , z8 ];

draw z5 - - (good.x (x15 + .5), y1 ) - - (good.x (x15 ā’ .5), y1 )

- - (good.x (x36 + .5), y3 ) - - (good.x (x36 ā’ .5), y3 ) - - z6 ;

draw z7 - - (good.x (x27 + .5), y2 ) - - (good.x (x27 ā’ .5), y2 )

- - (good.x (x48 + .5), y4 ) - - (good.x (x48 ā’ .5), y4 ) - - z8 ;

The idea here was to control the goodness of the points where the diagonals intersect

the horizontal bar lines, and to hide one of the ājaggiesā inside each bar line. If we

do the same three experiments but triple the resolution, we get similar results but the

diļ¬erences are not quite so obvious:

When letters are drawn by ļ¬lling outlines, the left and right outlines are

digitized independently; therefore corresponding outlines should usually be

oļ¬set from each other by an integer amount whenever possible. For example, suppose

that the letter ā˜nā™ is being drawn with commands like

penpos2 (stem , 0); penpos4 (stem , 0)

to specify the stroke widths at the base of the two stems. We will therefore have

x2r ā’ x2l = x4r ā’ x4l = stem . If stem is not an integer, say stem = 2.7, we might have

x2l = 2.1, x2r = 4.8, x4l = 9.6, x4r = 12.3; then x2r ā’ x2l will digitize to 5 ā’ 2 = 3,

so the left stem will be three pixels wide, but the right stem will be only 12 ā’ 10 = 2

pixels wide. We could get around this problem by insisting that either x2l or x2r be an

202 Chapter 24: Discreteness and Discretion

integer, and that either x4l or x4r be an integer; then both stems would be three pixels deļ¬ne whole blacker pixels

round

wide. But other quantities calculated from stem (e.g., the breadth of diagonal strokes)

w

would then be based on a value of 2.7 instead of the stem width 3 that an observer of

the font actually perceives. Therefore it is best to make stem an integer. The proper

way to do this is generally to say

deļ¬ne whole blacker pixels(stem );

this command computes stem from stem # by the formula

stem := max(1, round(stem # ā— hppp + blacker )).

(Notice that this rounding operation is not allowed to reduce stem to zero at low

resolutions.)

Even when the stem width is an integer in the ā˜nā™ example, we probably want

to arrange things so that x2l , x2r , x4l , and x4r are integers, because this will

give the least distortion under digitization. Suppose, however, that itā™s most convenient

to deļ¬ne the pen position at the center of the stroke instead of at the edge; i.e., the

program would say just ā˜x2 = Ī±ā™ if rounding were not taken into account. How should

x2 be deļ¬ned, when we want x2l to be an integer? We could say

x2 = Ī±; x2l := round x2l ; x2r := round x2r ; x2 := .5[x2l , x2r ]

but thatā™s too complicated; moreover, it will fail if any other variables depend on x2 ,

x2l , or x2r , because such dependencies are forgotten when new values are assigned. In

the case of ļ¬xed pens we solved this problem by saying ā˜x2 = good.x Ī±ā™; but the good.x

function doesnā™t know about stem . One solution is to say

x2l = round(Ī± ā’ .5stem ),

or equivalently, ā˜x2r = round(Ī± + .5stem )ā™. This does the job all right, but it isnā™t com-

pletely satisfying. It requires knowledge of the breadth that was speciļ¬ed in the penpos2

command, and it works only when the penpos angle is 0. If the penpos command is

changed, the corresponding equation for rounding must be changed too. Thereā™s an-

other solution thatā™s more general and more attractive once you get used to it:

x2l = round(x2l ā’ (x2 ā’ Ī±)).

Why does this work? The argument to ā˜roundā™ must be a known value, but both

x2l and x2 are unknown. Fortunately, their diļ¬erence x2l ā’ x2 is known, because of

the penpos2 command. The rounding operation makes x2 ā Ī± because it makes x2l

approximately equal to the value of x2l minus the diļ¬erence between x2 and Ī±.

EXERCISE 24.6

The generality of this technique can be appreciated by considering the follow-

ing more diļ¬cult problem that the author faced while designing a ā˜wā™: Suppose you

want x1 ā’ x2 to be an integer and x3 ā x4 , and suppose that x2 , x3 ā’ x1 , and x4 + x1

are known; but x1 is unknown, hence x3 and x4 are also unknown. According to our

general idea, we want to specify an equation of the form ā˜x1 ā’ x2 = round(x1 ā’ x2 + f )ā™,

where x1 ā’ x2 + f is known and f is a formula that should be approximately zero. In

this case x3 ā’ x4 is approximately zero, and (x3 ā’ x1 ) ā’ (x4 + x1 ) is known; what value

of f should we choose?

Chapter 24: Discreteness and Discretion 203

In many fonts, such as the one you are now reading, curved lines swell out o

Computer Modern

so that the thick parts of ā˜oā™ are actually a bit broader than the stems of ā˜nā™.

cmr9

Therefore the Computer Modern font routines discussed in Appendix E have two pa- font setup

rameters, stem # and curve #, to govern the stroke thickness. For example, the font stem

curve

cmr9 used in the present paragraph has stem # = 2/3pt # and curve # = 7/9pt #. Both lowres ļ¬x

of these should be integers, hence the font setup macro in Appendix E dutifully says triangle

deļ¬ne whole blacker pixels(stem , curve ).

Although this looks good on paper, it can cause problems at certain low resolutions,

because the rounding operation might make stem and curve rather diļ¬erent from each

other even though stem # and curve # are fairly close. For example, the resolution might

be just at the value where cmr9ā™s stem turns out to be only 2 but curve is 3. Curves

shouldnā™t be that much darker than stems; they would look too splotchy. Therefore

plain has a ā˜lowres ļ¬xā™ subroutine, and Appendix E says

lowres ļ¬x(stem ,curve ) 1.2

after stem and curve have been deļ¬ned as above. In this particular case lowres ļ¬x will

reset curve := stem if it turns out that the ratio curve /stem is greater than 1.2 times

the ratio curve #/stem #. Since curve #/stem # = 7/6 in the case of cmr9, this means

that the ratio curve /stem after rounding is allowed to be at most 1.4; if curve = 3 and

stem = 2, the curve parameter will be lowered to 2. In general the command

lowres ļ¬x(d1 , d2 , . . . , dn ) r

will set dn := Ā· Ā· Ā· d2 := d1 if max(d1 , d2 , . . . , dn )/ min(d1 , d2 , . . . , dn ) is greater than

r Ā· max(d1 #, d2 #, . . . , dn #)/ min(d1 #, d2 #, . . . , dn #).

EXERCISE 24.7

Good digitization can also require attention to the

shapes of the digitized angles where straight lines meet.

The purpose of the present exercise is to illustrate the rel-

evant ideas by studying the ā˜ ā™ symbol, for which a pro- (Figure 4e will be inserted

gram appears in Chapter 4. If that program is used with- here; too bad you canā™t see

it now.)

out change to produce low-resolution triangles, the results

might turn out to be unsatisfactory because, for example,

the point of the triangle at the right might digitize into a

snubnosed or asymmetric shape. If y3 is an integer, the

triangle will be top-bottom symmetric, but the right-hand

tip will be two pixels tall and this will look too blunt. Therefore we should choose y3 to

be an integer plus 1/2. Given this value of y3 , what will be the shape of the rightmost

four columns of the digitized tip, as x3 varies?

EXERCISE 24.8

Continuing the previous exercise, assume that x1 is an integer. What value

of y1 will make the upper tip of the triangle look like ā˜ ā™ after digitization?

EXERCISE 24.9

Concluding the previous exercise, modify the program of Chapter 4 so that

the upper tip and the upper part of the right tip both digitize to the shape ā˜ ā™.

204 Chapter 24: Discreteness and Discretion

So far in this chapter weā™ve assumed that pixels are square. But sometimes we nonsquare

currenttransform

need to prepare output for devices with general rectangular pixels, and this

aspect ratio

adds an extra dimension of complexity to rounding. Plain sets things up top

so that currenttransform multiplies all y coordinates by aspect ratio , when paths are bot

vround

ļ¬lled or drawn, or when pens are picked up. Furthermore the top and bot functions overshoot

divide the amount of oļ¬set by aspect ratio . This means that programs can beginchar

h

still be written as if pixels were square; the normal ā˜angleā™ and ā˜directionā™ functions,

d

etc., can be used. But the good places for rounding horizontal tangents are not at good.y

integer values of y in general, they are actually at values that will become integers logo

deļ¬ne whole vertical pixels

after multiplication by the aspect ratio.

deļ¬ne horizontal corrected pixels

o

The vround function rounds its argument to the nearest y coordinate that

E

corresponds to a pixel boundary in the general case. Thus if aspect ratio = 1, F

good.top

vround simply rounds to the nearest integer, just like ā˜roundā™; but if, say, aspect ratio =

good.bot

4/3, then vround will round to the nearest multiple of 3/4. Plain uses good.lft

vround instead of ā˜roundā™ when it computes an overshoot correction, and also when good.rt

currenttransform

beginchar computes the values of h and d . The good.y function produces a good

currentpen

y value that takes aspect ratio properly into account. autorounding

pimples

EXERCISE 24.10

Without looking at Appendix B, try to guess how the vround and good.y

macros are deļ¬ned.

EXERCISE 24.11

What are the āambiguous pointsā when pixels are not square?

The logo as we have described it so far will round properly with

respect to arbitrary aspect ratios if we make only a few more reļ¬nements. The

value of ygap should be vrounded instead of rounded, so we initialize it by saying

deļ¬ne whole vertical pixels(ygap ).

Furthermore we should say

ho # := o #; deļ¬ne horizontal corrected pixels(ho );

and ho should replace o in the equations for x4 in the programs for ā˜ ā™ and ā˜ ā™.

Everything else should work satisfactorily as it stands.

Appendix B includes macros good.top , good.bot , good.lft , and good.rt that take

pairs as arguments. If you say, for example, ā˜z3 = good.top (Ī±, Ī²)ā™ it means

that z3 will be near (Ī±, Ī²) and that when z3 is modiļ¬ed by currenttransform the top

point of currentpen placed at the transformed point will be in a good raster position.

ā™s ā˜autorounding ā™ feature tries to adjust curves to the raster for

you, but it is a mixed blessing. Hereā™s how it works: If the internal quantity

autorounding is positive, the x coordinates of all paths that are ļ¬lled or drawn are

rounded to good raster positions wherever thereā™s a vertical tangent; and the y coordi-

nates are rounded to good raster positions wherever thereā™s a horizontal tangent. The

rest of the curve is distorted appropriately, as if the raster were stretching or shrinking

slightly. If autorounding > 1, you get even more changes: paths are perturbed slightly

at Ā±45ā—¦ tangent directions, so that second-order pimples and ļ¬‚at spots donā™t appear

there.

Chapter 24: Discreteness and Discretion 205

For example, if we return to the Ionian ā˜ ā™ with which we began this chapter, granularity

smoothing

letā™s suppose that curve sidebar was left unrounded. We saw that the result

was bad when autorounding was 0; when autorounding = 1 and 2 we get this:

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

The stroke has gotten a lot thinner at the sides, by comparison with the original design

(which, incidentally, can be seen in the illustrations below). Although autorounding

has produced a fairly recognizable O shape, the character of the original has been lost,

especially in the case autorounding = 2; indeed, the inner outline has been brought

towards the center, in the upper left and lower right sectors, and this has made the

digitized inner boundary perfectly symmetric!

Thereā™s an internal quantity called granularity , normally equal to 1, which

aļ¬ects autorounding by eļ¬ectively scaling up the raster size. If, for example,

granularity = 4, the autorounded x coordinates and y coordinates will become multi-

ples of 4 instead of simply integers. The illustrations above were produced by setting

granularity = 10 and mag = 10; this made the eļ¬ects of autorounding visible. The

granularity should always be an integer.

Besides autorounding , thereā™s a ā˜smoothingā™ feature that becomes active when

smoothing > 0. The basic idea is to try to make the edges of a curve fol-

low a regular progression instead of wobbling. A complete discussion of the smooth-

ing algorithm is beyond the scope of this manual, but an example should make the

general idea clear: Letā™s use the letters R and D to stand for single-pixel steps to

the right and down, respectively. If a digitized path goes ā˜RDDRDRDDD ā™, say, the

number of downward steps per rightward step is ļ¬rst decreasing, then increasing; the

smoothing process changes this to ā˜RDDRDDRDD ā™. If smoothing is applied to the

Ionian ā˜ ā™ shapes above, nothing happens; but if we go back to the original obtained

with autorounding = 0, we get a few changes:

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

Three pixels have been added by smoothing in the right-hand illustration; e.g., a pattern

RDRDDDDRDD has become RDDRDDDRDD .

206 Chapter 24: Discreteness and Discretion

If you do your own rounding, it turns out that autorounding and smoothing slant

Knuth

usually change very few pixels, if any; thus your safest strategy is probably to

Computer Modern

turn them oļ¬ in such cases. If you deļ¬ne your strokes by outlines, autorounding and italic

smoothing apply independently to the left and right edges, so they may hurt as often as tracingspecs

octant

they help; again, they should probably be turned oļ¬. But if you are drawing with ļ¬xed ENE

pens, autorounding generally works well and saves a lot of fuss. If the pens are circles ESE

SSE

or nearly circles, smoothing is also helpful; but if the pens are more ācalligraphic,ā

compass directions

they are supposed to produce nonsmooth edges occasionally, so you had better set

smoothing := 0.

If you āslantā a font by modifying currenttransform as described in Chap-

ter 15, positions of horizontal tangency will remain the same. But positions

of vertical tangency will change drastically, and they will probably not fall in known

parts of your design. This means, for example, that autorounding will be helpful in

a slanted pen-generated font like the ā˜ ā™ logo. However, the author found

that the outline-generated letters of Computer Modern italic came out better with

autorounding = 0, because autorounding tended to make some characters too dark

and others too light.

The eļ¬ect of autorounding can be studied numerically if you set tracingspecs

to a positive value; this displays ā™s internal calculations as it ļ¬nds

horizontal, vertical, and diagonal tangent points. ( prepares to digitize

paths by ļ¬rst subdividing each BĀ“zier segment into pieces that travel in only one

e

āoctantā direction.) For example, if autorounding = 0 and tracingspecs = 1, and if

curve sidebar is left unrounded, the ļ¬le io.log will contain the following information

about the outer curve of the ā˜ ā™:

Path at line 15, before subdivision into octants:

(1.53745,9.05345)..controls (1.53745,4.00511) and (5.75409,-0.00049)

..(10.85147,-0.00049)..controls (16.2217,-0.00049) and (20.46255,4.51297)

..(20.46255,9.94655)..controls (20.46255,14.99713) and (16.23842,19.00049)

..(11.13652,19.00049)..controls (5.77066,19.00049) and (1.53745,14.48491)

..cycle

Cycle spec at line 15, after subdivision:

(1.53745,9.05345) % beginning in octant ā˜SSEā™

..controls (1.53745,6.58786) and (2.54324,4.371)

..(4.16621,2.74803) % segment 0

% entering octant ā˜ESEā™

..controls (5.8663,1.04794) and (8.24362,-0.00049)

..(10.85147,-0.00049) % segment 0

% entering octant ā˜ENEā™

. . . and so on; there are lots more numbers! What does this all mean? Well, the

ļ¬rst segment of the curve, from (1.53745, 9.05345) to (10.85147, ā’0.00049), has been

subdivided into two parts at the place where the slope is ā’1. The ļ¬rst of these parts

travels basically ā˜South by South Eastā™ and the second travels ā˜East by South Eastā™. The

other three segments are subdivided in a similar way (not shown here). If you try the

same experiment but with autorounding = 1, some rather diļ¬erent numbers emerge:

Chapter 24: Discreteness and Discretion 207

Ā“

LE BE

Cycle spec at line 15, after subdivision and autorounding: CARTER

(2,9.05348) % beginning in octant ā˜SSEā™

..controls (2,6.50526) and (3.02194,4.22272)

..(4.6577,2.58696) % segment 0

ńņš. 24 |