<< . .

. 36
( : 45)

. . >>

The general technique will be illustrated here in the case n = 3. Let us try to tolerance
¬nd numbers a, b, and c such that
’2a + 3b/c = c ’ 3;
ac + 2b = c3 ’ 20;
a3 + b3 = c2 .
When c is ¬xed, the ¬rst two equations are linear in a and b. We make an inequality
out of the remaining equation by changing ˜=™ to ˜<™, then we embed the system in a
boolean-valued function:
vardef f(expr c) = save a,b;
-2a + 3b/c = c - 3;
a*c + 2b = c*c*c - 20;
a*a*a + b*b*b < c*c enddef;
c = solve f(1,7);
-2a + 3b/c = c - 3;
a*c + 2b = c*c*c - 20;
show a, b, c.
If we set tolerance = epsilon (which is the minimum value that avoids in¬nite looping
in the solve routine), the values a = 1, b = 2, and c = 3 are shown (so it is obvious that
the example was rigged). If tolerance has its default value 0.1, we get a = 1.05061,
b = 2.1279, c = 3.01563; this would probably be close enough for practical purposes,
assuming that the numbers represent pixels. (Increasing the tolerance saves time
because it decreases the number of iterations within solve ; you have to balance time
versus necessary accuracy.)
The only tricky thing about this use of solve was the choice of the numbers
1 and 7 in ˜f (1, 7)™. In typical applications we™ll usually have obvious values of the
unknown where f will be true and false, but a bit of experimentation was necessary
for the problem considered here. In fact, it turns out that f (’3) is true and f (’1) is
false, in this particular system; setting c = solve f (’3, ’1) leads to another solution:
a = 7.51442, b = ’7.48274, c = ’2.3097. Furthermore, it™s interesting to observe that
this system has no solution with c between ’1 and +1, even though f (+1) is true and
f (’1) is false! When c ’ 0, the quantity a3 + b3 approaches ’∞ when c is negative,
+∞ when c is positive. An attempt to ˜solve f (1, ’1)™ will divide by zero and come
up with several arithmetic over¬‚ows.
Let™s consider now a real application instead of
a contrived example. We wish to ¬nd the vertices of a
parallelogram z1l , z1r , z0l , z0r , such that (Figure Da will be inserted
here; too bad you can™t see it
x1l = a; y1r = b; z0r = (c, d);
length(z1r ’ z1l ) = length(z0r ’ z0l ) = stem ,
and such that the lines z1r - - z1l and z1r - - z0r meet at a
given angle phi. We can consider the common angle θ of z1r ’ z1l and z0r ’ z0l to be
the “nonlinear” unknown, so the equations to be solved can be written
penpos1 (stem , θ); penpos0 (stem , θ);
x1l = a; y1r = b; z0r = (c, d);
angle(z1r ’ z0r ) = θ + φ.
294 Appendix D: Dirty Tricks

When θ has a given value, all but the last of these equations are linear; hence we can d
solve them by turning the crank in our general method:
vardef f(expr theta) = save x,y;
penpos1(stem,theta); penpos0(stem,theta); function
x1l=a; y1r=b; z0r=(c,d);
angle(z1r-z0r)<theta+phi enddef;
theta=solve f(90,0);
penpos1(stem,theta); penpos0(stem,theta);
x1l=a; y1r=b; z0r=(c,d);
show z1l,z1r,z0l,z0r,theta,angle(z1r-z0r).
For example, if a = 1, b = 28, c = 14, d = 19, stem = 5, and φ = 80, we get
(1, 23.703) (3.557, 28) (11.443, 14.703) (14, 19) 59.25 139.25
as answers when tolerance = epsilon , and
(1, 23.702) (3.554, 28) (11.446, 14.702) (14, 19) 59.28 139.25
when tolerance = 0.1. The function f prescribed by the general method can often be
simpli¬ed; for example, in this case we can remove redundancies and get just
vardef f(expr theta) = save x,y;
penpos1(stem,theta); x1l=a; y1r=b;
angle(z1r-(c,d))<theta+phi enddef.
The problem just solved can be called the “d problem,” because it arose in connection
with N. N. Billawala™s meta-design of a black-letter ˜ ™, and because it appears in
Appendix D.
5. Nonlinear interpolation. Suppose a designer has empirically determined good values
of some quantity f (x) for several values of x; for example, f (x) might be a stroke
weight or a serif length or an amount of overshoot, etc. These empirical values can be
generalized and incorporated into a meta-design if we are able to interpolate between
the original x™s, obtaining f (x) at intermediate points.
Suppose the data points are known for x = x1 < x2 < · · · < xn . We can
represent f (x) by its graph, which we can assume is well approximated by the -
path de¬ned by
F = (x1 , f (x1 )) . . (x2 , f (x2 )) . . etc. . . (xn , f (xn ))
if f (x) is a reasonable function. Therefore interpolation can be done by using path
intersection (!):
vardef interpolate expr F of x = save t; t =
if x < xpart point 0 of F: extrap_error 0
elseif x > xpart point infinity of F: extrap_error infinity
else: xpart(F intersectiontimes verticalline x) fi;
ypart point t of F enddef;
def extrap_error = hide(errhelp "The extreme value will be used.";
errmessage "˜interpolate™ has been asked to extrapolate";
errhelp "") enddef;
vardef verticalline primary x =
(x,-infinity)--(x,infinity) enddef;
Appendix D: Dirty Tricks 295

For example, if f (1) = 1, f (3) = 2, and f (15) = 4, this interpolation scheme gives overlays
˜interpolate (1, 1) . . (3, 2) . . (15, 4) of 7™ the value 3.37.
6. Drawing with overlays. Let™s leave numerical computations now and go back into shipit
the realm of pictures. Bruce Leban has suggested an extension of plain ™s ¬ll
˜clearit/showit/shipit™ commands by which ˜¬ll™ and ˜draw™ essentially operate on
imaginary sheets of clear plastic. A new command ˜keepit™ places a fresh sheet of totalpicture
plastic on top of whatever has already been drawn, thereby preserving the covered totalnull
image against subsequent erasures. shipout
We can implement keepit by introducing a new picture variable totalpicture , gf
and new boolean variables totalnull , currentnull , then de¬ning macros as follows:
log ¬le
def clearit = currentpicture:=totalpicture:=nullpicture;
currentnull:=totalnull:=true; enddef;
def keepit = cull currentpicture keeping (1,infinity);
addto totalpicture also currentpicture;
totalnull:=currentnull; currentnull:=true; enddef;
def addto_currentpicture =
currentnull:=false; addto currentpicture enddef;
def mergeit (text do) =
if totalnull: do currentpicture
elseif currentnull: do totalpicture
else: begingroup save v; picture v; v:=currentpicture;
cull v keeping (1,infinity); addto v also totalpicture;
do v endgroup fi enddef;
def shipit = mergeit(shipout) enddef;
def showit_ = mergeit(show_) enddef;
def show_ suffix v = display v inwindow currentwindow enddef;

The totalnull and currentnull bookkeeping isn™t strictly necessary, but it contributes
greatly to the e¬ciency of this scheme if the extra generality of keepit is not actually
being used. The ˜v™ computations in mergeit involve copying the accumulated picture
before displaying it or shipping it out; this takes time, and it almost doubles the amount
of memory needed, so we try to avoid it when possible.

7. Filing pictures. If you want to store a picture in a ¬le and read it in to some other
job, you face two problems: (1) ™s shipout command implicitly
culls the picture, so that only binary data is left. Pixel values > 0 are distinguished
from pixel values <= 0, but no other information about those values will survive.
(2) The result of shipout can be used in another job only if you have
an auxiliary program that converts from binary gf format to a source
program; can write gf ¬les, but it can™t read them.
These problems can be resolved by using ™s transcript or log ¬le
as the output medium, instead of using the gf ¬le. For example, let™s consider ¬rst the
use of tracingedges . Suppose we say

tracingedges := 1;
any sequence of ¬ll, draw, or ¬lldraw commands
message "Tracing edges completed."; tracingedges := 0;
296 Appendix D: Dirty Tricks

then the log ¬le will contain lines such as the following: turningcheck
Tracing edges at line 15: (weight 1)
(1,5)(1,2)(2,2)(2,1)(3,1)(3,0)(8,0)(8,1)(9,1)(9,2)(10,2)(10,8)(9,8) almost digitized
(9,9)(8,9)(8,10)(3,10)(3,9)(2,9)(2,8)(1,8)(1,5). culling
Tracing edges at line 15: (weight -1) show
Tracing edges at line 18: (weight -1)
(No new edges added.)
Tracing edges completed.
Let us write macros so that these lines are acceptable input to .
def Tracing=begingroup save :,[,],Tracing,edges,at,weight,w;
delimiters []; let Tracing = endfill; interim turningcheck := 0;
vardef at@#(expr wt) = save (,); w := wt;
let ( = lp; let ) = rp; fill[gobble begingroup enddef;
let edges = \; let weight = \; let : = \; enddef;
def lp = [ enddef;
def rp = ] -- enddef;
vardef No@# = origin enddef;
def endfill = cycle] withweight w endgroup; enddef;
def completed = endgroup; enddef;
The precise form of edge-traced output, with its limited vocabulary and its restricted
use of parentheses and commas, has been exploited here.
With slight changes to this code, you can get weird e¬ects. For example, if the
de¬nition of rp is changed to ˜]..tension 4..™, and if ˜scaled 5pt™ is inserted before
˜withweight™, the image will be an “almost digitized” character:

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

(The bumps at the left here are due to the repeated points ˜(1,5)™ and ˜(3,5)™ in the
original data. You can remove them by adding an extra pass, ¬rst tracing the edges
that are output by the unmodi¬ed Tracing macros.)
Although the e¬ects of ¬ll and draw can be captured by tracingedges , other
operations like culling are not traced. Let us therefore consider the more general picture
produces when tracingoutput is positive, or when you
representation that
ask it to show a picture (see Chapter 13). The macros on the next page will recreate
a picture from input of the form
row 1: 1+ -2- | 0+ 2-
row 0: | 0+ 2++ 5---
row -2: 0- -2+ |
Appendix D: Dirty Tricks 297

where the middle three lines have been copied verbatim from a transcript ¬le. (The xy swap
task would be easier if the token ˜-™ didn™t have to perform two di¬erent functions!)
let neg_ = -; let colon_ = :;
def beginpicture = penrazor
begingroup save row, |, :, ---, --, +, ++, +++, v, xx, yy, done; path
picture v; v := nullpicture; interim turningcheck := 0;
let --- = mmm_; let -- = mm_;
let + = p_; let ++ = pp_; let +++ = ppp_;
let row = pic_row; let | = relax; let : = pic_colon; : enddef;
def pic_row primary y = done; yy := y; enddef;
def pic_colon primary x =
if known x colon_ ; xx := x; pic_edge fi enddef;
def pic_edge =
let - = m_;
addto v contour unitsquare xscaled xx shifted(0,yy) enddef;
def mmm_ = withweight 3; let - = neg_; : enddef;
def mm_ = withweight 2; let - = neg_; : enddef;
def m_ = withweight 1; let - = neg_; : enddef;
def p_ = withweight neg_1; let - = neg_; : enddef;
def pp_ = withweight neg_2; let - = neg_; : enddef;
def ppp_ = withweight neg_3; let - = neg_; : enddef;
transform xy_swap; xy_swap = identity rotated 90 xscaled -1;
def endpicture = done;
v transformed xy_swap transformed xy_swap endgroup enddef;
The reader will ¬nd it instructive to study these macros closely. When ˜done™ appears,
it is an unknown primary, so pic_colon will not attempt to generate another edge.
Each new edge also inserts a cancelling edge at x = 0. The two applications of xy_swap
at the end will clear away all redundant edges. (Double swapping is a bit faster than
the operation ˜rotated-90 rotated 90™ that was used for this purpose in Chapter 13.)
8. Fattening a pen. Let™s move on to another aspect of by considering
an operation on pen polygons: Given a pen value p, the task is to construct a pen
˜taller p™ that is one pixel taller. For example, if p is the diamond nib ˜(0.5, 0) - -
(0, 0.5) - - (’0.5, 0) - - (0, ’0.5) - - cycle™, the taller nib will be
(0.5, 0.5) - - (0, 1) - - (’0.5, 0.5) - - (’0.5, ’0.5) - - (0, ’1) - - (0.5, ’0.5) - - cycle;
if p is a tilted penrazor ˜(’x, ’y) - - (x, y) - - cycle™, the taller nib will be
(’x, ’y ’ 0.5) - - (x, y ’ 0.5) - - (x, y + 0.5) - - (’x, ’y + 0.5) - - cycle,
assuming that x > 0. The macro itself turns out to be fairly simple, but it makes
instructive use of path and pen operations.
We want to split the pen into two parts, a “bottom” half and a “top” half; the
bottom half should be shifted down by .5 pixels, and the top half should be shifted up.
The dividing points between halves occur at the leftmost and rightmost vertices of the
pen. Hmmm; a potential problem arises if there are two or more leftmost or rightmost
points; for example, what if we try to make ˜taller taller p™ ? Fortunately
doesn™t mind if a pen polygon has three or more consecutive vertices that lie on a line,
hence we can safely choose any leftmost point and any rightmost point.
298 Appendix D: Dirty Tricks

The next question is, “How should we ¬nd leftmost and rightmost points?” makepath
We will, of course, use makepath to ¬nd the set of all vertices; so we could simply
traverse the path and ¬nd the minimum and maximum x coordinates. However, it will velocity
be faster (and more fun) to use either directiontime or peno¬set for this purpose. Let™s tensepath
try directiontime ¬rst: subpath
future pen
vardef taller primary p =
save r, n, t, T; path r; mediation
r = tensepath makepath p; n = length r;
t = round directiontime up of r;
T = round directiontime down of r;
if t>T: t := t-n; fi
makepen(subpath(T-n,t) of r shifted .5down
--subpath(t,T) of r shifted .5up -- cycle) enddef;
The result of makepath has control points equal to their adjacent vertices, so it could
not be used with directiontime. (If any key point is equal to its precontrol or
postcontrol, the “velocity” of the path is zero at that point; directiontime assumes
that all directions occur whenever the velocity drops to zero.) Therefore we have used
˜tensepath™. This almost works, once we realize that the values of t and T sometimes
need to be rounded to integers. But it fails for pens like penspeck that have points very
close together, since tensepath is no better than an unadulterated makepath in such
cases. Furthermore, even if we could de¬ne a nice path from p (for example by scaling
it up), we would run into problems of numerical instability, in cases like penrazor
where the pen polygon takes a 180—¦ turn. Razor-thin pens cannot be recognized easily,
because they might have more than two vertices; for example, rotations of future pens
such as ˜makepen(left . . origin . . right . . cycle)™ are problematical.
We can obtain a more robust result by using peno¬set, because this operation
makes use of the convexity of the polygon. The “fastest” solution looks like this:
vardef taller primary p =
save q, r, n, t, T; pen q; q = p;
path r; r = makepath q; n = length r;
t = round xpart(r intersectiontimes penoffset up of q);
T = round xpart(r intersectiontimes penoffset down of q);
if t>T: t := t-n; fi
makepen(subpath(T-n,t) of r shifted .5down
--subpath(t,T) of r shifted .5up -- cycle) enddef;
(The argument p is copied into q, in case it™s a future pen; this means that the conversion
of future pen to pen need be done only once instead of three times.)
9. Bernshte˜ polynomials. And now, for our last trick, let™s try to extend
±n ™s
syntax so that it will accept generalized mediation formulas of the form ˜t[u1 , . . . , un ]™
for all n ≥ 2. (This notation was introduced for n = 3 and 4 in Chapter 14, when we
were considering fractional subpaths.) If n > 2, the identity
t[ u1 , . . . , un ] = t[t[u1 , . . . , un’1 ], t[u2 , . . . , un ] ]
de¬nes t[u1 , . . . , un ] recursively, and it can be shown that the alternative de¬nition
t[ u1 , . . . , un ] = t[t[u1 , u2 ], . . . , t[un’1 , un ] ]
Appendix D: Dirty Tricks 299

gives the same result. (Indeed, we have brackets
for list
n’1 empty text arguments
(1 ’ t)n’k tk’1 uk ,
t[u1 , . . . , un ] = for
k’1 save
a Bernshte˜ polynomial of order n ’ 1.) text argument
Our problem is to change the meaning of ™s brackets so that ex- ANDERSON
pressions like ˜1/2[a, b, c, d]™ will evaluate to ˜.125a + .375b + .375c + .125d™ in accordance
with the formulas just given, but we don™t want to mess up the other primitive uses
of brackets in contexts like ˜x[n]™ and ˜path p[][]a™. We also want to be able to use
brackets inside of brackets.
The reader is challenged to try solving this problem before looking at the
weird solution that follows. Perhaps there is a simpler way?
def lbrack = hide(delimiters []) lookahead [ enddef;
let [[[ = [; let ]]] = ]; let [ = lbrack;
def lookahead(text t) =
hide(let [ = lbrack;
for u=t, hide(n_ := 0; let switch_ = first_): switch_ u; endfor)
if n_<3: [[[t]]] else: Bernshtein n_ fi enddef;
def first_ primary u =
if numeric u: numeric u_[[[]]]; store_ u
elseif pair u: pair u_[[[]]]; store_ u fi;
let switch_ = store_ enddef;
def store_ primary u = u_[[[incr n_]]] := u enddef;
primarydef t Bernshtein nn =
begingroup for n=nn downto 2:
for k=1 upto n-1: u_[[[k]]]:=t[[[u_[[[k]]],u_[[[k+1]]] ]]];
endfor endfor u_[[[1]]] endgroup enddef;
The most subtle thing about this code is the way it uses the ˜empty™ option of a for list
evaluates all the expressions
to dispense with empty text arguments. Since
of a for loop before reading the loop text, and since ˜n_™ and ˜u_™ are used here only
when no recursion is taking place, it is unnecessary to save their values even when
brackets are nested inside of brackets.
Of course this trick slows down tremendously, whenever brackets
appear, so it is just of academic interest. But it seems to work in all cases except with
respect to formulas that involve ˜]]™ (two consecutive brackets); the latter token, which
plain expands to ˜] ]™, is not expanded when lookahead reads its text ar-
gument, hence the user must remember to insert a space between consecutive brackets.

Their tricks an™ craft hae put me daft,
They™ve taen me in, an™ a™ that.
” ROBERT BURNS, The Jolly Beggar (1799)

Ebery house hab him dutty carner.
” ANDERSON and CUNDALL, Jamaica Proverbs and Sayings (1927)
(page 300)

Appendix E: Examples 301

We™ve seen lots of examples of individual letters or parts of letters; let™s con- logo
command line
centrate now on the problem of getting things all together. The next two pages parameter ¬le
contain the entire contents of an example ¬le ˜logo.mf™, which generates the slant
letters of the logo. The ¬le is short, because only seven letters are meta-ness
involved, and because those letters were intentionally done in a style that would
be easy for the system they name. But the ¬le is complete, and it illustrates
in simpli¬ed form all the essential aspects of larger fonts: Ad hoc dimensions
are converted to pixels; subroutines are de¬ned; programs for individual letters
appear; intercharacter and interword spacing conventions are nailed down. Fur-
thermore, the character programs are careful to draw letters that will be well
adapted to the raster, even if pixels on the output device are not square.
We™ve been studying the ˜ ™ letters o¬ and on since Chapter 4,
making our examples slightly more complex as more of the language has been
encountered. Finally we™re ready to pull out all the stops and look at the real,
professional-quality logo.mf, which incorporates all the best suggestions that
have appeared in the text and in answers to the exercises.
It™s easy to generate a font with logo.mf, by proceeding as explained
in Chapter 11. For example, the logo10 font that produces ˜ ™ in
10-point size can be created for a low-resolution printer by running
with the command line
\mode=lowres; input logo10
where the parameter ¬le logo10.mf appears in that chapter. Furthermore the
slanted version ˜ ™ can be created by inputting the parameter ¬le

<< . .

. 36
( : 45)

. . >>