The general technique will be illustrated here in the case n = 3. Let us try to tolerance

parallelogram

¬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

now.)

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

Billawala

solve them by turning the crank in our general method:

black-letter

meta-design

vardef f(expr theta) = save x,y;

interpolate

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

Leban

˜interpolate (1, 1) . . (3, 2) . . (15, 4) of 7™ the value 3.37.

clearit

showit

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

draw

˜clearit/showit/shipit™ commands by which ˜¬ll™ and ˜draw™ essentially operate on

keepit

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

currentnull

image against subsequent erasures. shipout

We can implement keepit by introducing a new picture variable totalpicture , gf

transcript

and new boolean variables totalnull , currentnull , then de¬ning macros as follows:

log ¬le

tracingedges

def clearit = currentpicture:=totalpicture:=nullpicture;

currentnull:=totalnull:=true; enddef;

def keepit = cull currentpicture keeping (1,infinity);

addto totalpicture also currentpicture;

currentpicture:=nullpicture;

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

save

delimiters

Tracing edges at line 15: (weight 1)

tension

(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

tracingoutput

Tracing edges at line 15: (weight -1) show

(3,5)(3,2)(4,2)(4,1)(7,1)(7,2)(8,2)(8,8)(7,8)(7,9)(4,9)(4,8)(3,8)(3,5).

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

beginpicture

row 1: 1+ -2- | 0+ 2-

row 0: | 0+ 2++ 5---

row -2: 0- -2+ |

endpicture

Appendix D: Dirty Tricks 297

where the middle three lines have been copied verbatim from a transcript ¬le. (The xy swap

pen

task would be easier if the token ˜-™ didn™t have to perform two di¬erent functions!)

polygons

taller

let neg_ = -; let colon_ = :;

diamond

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

directiontime

We will, of course, use makepath to ¬nd the set of all vertices; so we could simply

peno¬set

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

intersectiontimes

try directiontime ¬rst: subpath

future pen

vardef taller primary p =

Bernshte˜±n

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

n’1 empty text arguments

(1 ’ t)n’k tk’1 uk ,

t[u1 , . . . , un ] = for

k’1 save

k=1

]]

a Bernshte˜ polynomial of order n ’ 1.) text argument

±n BURNS

Our problem is to change the meaning of ™s brackets so that ex- ANDERSON

CUNDALL

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)

E

Examples

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

currenttransform

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