<< . .

. 24
( : 45)

. . >>

left and right. If the pen breadth is odd, we want the character width w to
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.

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
of zero? The answer requires an understanding of the pen polygons discussed
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
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 .

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.)

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);
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);
Chapter 24: Discreteness and Discretion 201

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

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
wide. But other quantities calculated from stem (e.g., the breadth of diagonal strokes)
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
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 ±.
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™.
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
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 #).

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?

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?

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
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
¬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
still be written as if pixels were square; the normal ˜angle™ and ˜direction™ functions,
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
The vround function rounds its argument to the nearest y coordinate that
corresponds to a pixel boundary in the general case. Thus if aspect ratio = 1, F
vround simply rounds to the nearest integer, just like ˜round™; but if, say, aspect ratio =
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
beginchar computes the values of h and d . The good.y function produces a good
y value that takes aspect ratio properly into account. autorounding
Without looking at Appendix B, try to guess how the vround and good.y
macros are de¬ned.
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
Chapter 24: Discreteness and Discretion 205

For example, if we return to the Ionian ˜ ™ with which we began this chapter, granularity
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
206 Chapter 24: Discreteness and Discretion

If you do your own rounding, it turns out that autorounding and smoothing slant
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
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
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
“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 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

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
( : 45)

. . >>