. 10
( 13)


(This range abbreviation will work in any forsu¬xes list; and in a ˜for™ list you can
even omit the word ˜range™. But you might ¬ll up ™s main memory if too
many values are involved.)
A straight line will be drawn on the proofsheet by proofrule. Although
makelabel takes the current transform into account, proofrule does not. There™s also
a corresponding routine ˜screenrule™ that puts a straight line in the current picture,
so that design guidelines will be visible on your screen:
def proofrule(expr w,z) =
special "rule"; numspecial xpart w; numspecial ypart w;
numspecial xpart z; numspecial ypart z enddef;
def screenrule(expr w,z) =
addto currentpicture doublepath w--z withpen rulepen enddef;
pen rulepen; rulepen = pensquare scaled 2;
Appendix B: Basic Operations 275

(The rulepen is two pixels wide, because screen rules are usually drawn exactly over rulepen
raster lines. A two-pixel-wide pen straddles the pixel edges so that you can “see” the
correct line position. If a two-pixel-wide line proves to be too dark, you can rede¬ne titlefont
rulepen to be simply pensquare; then will draw the thinnest possible labelfont
screen rule, but it will be a half-pixel too high and a half-pixel too far to the right.) slantfont
You can produce lots of proof rules with makegrid, which connects an arbi- proofo¬set
trary list of x coordinates with an arbitrary list of y coordinates:
def makegrid(text xlist,ylist) = charcode
xmin_ := min(xlist); xmax_ := max(xlist);
ymin_ := min(ylist); ymax_ := max(ylist); chardp
for x=xlist: proofrule((x,ymin_), (x,ymax_)); endfor
for y=ylist: proofrule((xmin_,y), (xmax_,y)); endfor d
italic correction
Finally we have a few macros that allow further communication with the
hardcopy proof-drawing routine of Appendix H. You can change the fonts, the thickness
of proof rules, and the position of the image on its page.

vardef titlefont suffix $ = special "titlefont "&str$ enddef;
vardef labelfont suffix $ = special "labelfont "&str$ enddef;
vardef grayfont suffix $ = special "grayfont "&str$ enddef;
vardef slantfont suffix $ = special "slantfont "&str$ enddef;
def proofoffset primary z = % shift proof output by z
special "offset"; numspecial xpart z; numspecial ypart z enddef;
vardef proofrulethickness expr x =
special "rulethickness"; numspecial x enddef;

7. Character and font administration. After this elaborate preparation, we™re ¬nally
ready to consider the beginchar . . . endchar framework for the individual characters of
a font. Each beginchar begins a group, which should end at the next endchar. Then
beginchar stores the given character code and device-independent box dimensions
™s internal variables charcode , charwd , charht , and chardp . Then it
computes the device-dependent box dimensions w , h , and d . Finally it clears the z
variables, the current picture, and the current pen.

def beginchar(expr c,w_sharp,h_sharp,d_sharp) =
charcode:=if known c: byte c else: 0 fi;
charwd:=w_sharp; charht:=h_sharp; chardp:=d_sharp;
w:=hround(charwd*hppp); h:=vround(charht*hppp); d:=vround(chardp*hppp);
charic:=0; clearxy; clearit; clearpen; scantokens extra_beginchar;

The italic correction is normally zero, unless the user gives an ˜italcorr™ command;
even then, the correction stays zero unless the given value is positive:

def italcorr expr x_sharp = if x_sharp>0: charic:=x_sharp fi enddef;
276 Appendix B: Basic Operations

When we want to change the pixel width w from even to odd or vice versa, the change width
change width macro does the right thing.
extra beginchar
extra endchar
def change_width = bounding box
w:=w if w>charwd*hppp:- else:+ fi 1 enddef; makebox
italic correction
(The user might also decide to change w in some other way.) The current value of w maketicks
font size etc
at the time of endchar will be the “o¬cial” pixel width of the character, chardx , that
is shipped to the gf output ¬le.
def endchar =
scantokens extra_endchar;
if proofing>0: makebox(proofrule); fi
chardx:=w; % desired width of the character in pixels
if displaying>0: makebox(screenrule); showit; fi
endgroup enddef;
Extensions to these routines can be provided by putting commands in the string vari-
ables extra beginchar and extra endchar .
string extra_beginchar, extra_endchar;
A “bounding box” that surrounds the character according to the speci¬cations
given in beginchar is produced by makebox, which takes into account the possibility
that pixels might not be square. An extra line is drawn to mark the width of the
character with its italic correction included, if this correction is nonzero.
def makebox(text r) =
for y=0,h.o_,-d.o_: r((0,y),(w,y)); endfor % horizontals
for x=0,w: r((x,-d.o_),(x,h.o_)); endfor % verticals
if charic<>0: r((w+charic*hppp,h.o_),(w+charic*hppp,.5h.o_)); fi
The maketicks routine is an alternative to makebox that draws less con-
spicuous lines. This makes it easier to visualize a character™s appearance near the edges
of its bounding box.
def maketicks(text r) =
for y=0,h.o_,-d.o_: r((0,y),(10,y)); r((w-10,y),(w,y)); endfor
for x=0,w: r((x,10-d.o_),(x,-d.o_)); r((x,h.o_-10),(x,h.o_)); endfor
if charic<>0: r((w+charic*hppp,h.o_-10),(w+charic*hppp,h.o_)); fi
Overall information about the font as a whole is generally supplied by the
following commands, which are explained in Appendix F.
def font_size expr x = designsize:=x enddef;
def font_slant expr x = fontdimen 1: x enddef;
def font_normal_space expr x = fontdimen 2: x enddef;
def font_normal_stretch expr x = fontdimen 3: x enddef;
def font_normal_shrink expr x = fontdimen 4: x enddef;
Appendix B: Basic Operations 277

def font_x_height expr x = fontdimen 5: x enddef; z
screen rows
def font_quad expr x = fontdimen 6: x enddef;
screen cols
def font_extra_space expr x = fontdimen 7: x enddef; openit
def font_identifier expr x = font_identifier_:=x enddef; clearxy
def font_coding_scheme expr x = font_coding_scheme_:=x enddef; clearit
string font_identifier_, font_coding_scheme_;
font_identifier_=font_coding_scheme_="UNSPECIFIED"; command line
8. The endgame. What have we left out? A few miscellaneous things still need to be
handled. First, we almost forgot to de¬ne the z convention for points:
vardef z@#=(x@#,y@#) enddef;
Then we need to do something rudimentary about ™s “windows.”
newinternal screen_rows, screen_cols, currentwindow;
screen_rows:=400; % these values should be corrected,
screen_cols:=500; % by reading in a separate file after plain.mf
def openit = openwindow currentwindow
from origin to (screen_rows,screen_cols) at (-50,300) enddef;
def showit = openit; let showit=showit_; showit enddef; % first time only
def showit_ = display currentpicture inwindow currentwindow enddef;
Plain has several other terse commands like ˜openit™ and ˜showit™:
def clearxy = save x,y enddef;
def clearit = currentpicture:=nullpicture enddef;
def shipit = shipout currentpicture enddef;
def cullit = cull currentpicture dropping (-infinity,0) enddef;

The next several macros are handy things to put on your command line when
you are starting a job (i.e., just before ˜input font ¬le name ™):
screenchars. Say this when you™re making a font but want the characters to
be displayed just before they are shipped out.
screenstrokes. Say this when you™re in proof mode and want to see each
stroke as it™s added to the current picture.
imagerules. Say this when you want to include the bounding box in the
current character, before you begin to draw it.
gfcorners. Say this when you expect to make proofsheets with large pixels,
from a low-resolution font.
nodisplays. Say this to save computer time when you don™t want proof mode
to display each character automatically.
notransforms. Say this to save computer time when you know that the current
transform is the identity.
def screenchars = % endchar should ˜showit™
extra_endchar:=extra_endchar&"showit;" enddef;
def screenstrokes = % every stroke should ˜showit™
def addto_currentpicture text t=
addto currentpicture t; showit enddef; enddef;
278 Appendix B: Basic Operations

def imagerules = % a box should be part of the character image imagerules
extra_beginchar:=extra_beginchar & "makebox(screenrule);" enddef;
def gfcorners = % ˜maketicks™ should send rules to the gf file
extra_setup:=extra_setup & "let makebox=maketicks;proofing:=1;" enddef; end
clear pen memory
def nodisplays = % endchar shouldn™t ˜showit™
mode setup
extra_setup:=extra_setup & "displaying:=0;" enddef; mode
def notransforms = % currenttransform should not be used
let t_ = \ enddef; screen rows
screen cols
We make ˜bye™ synonymous with ˜end™, just in case TEX users expect - ¬llin
programs to end like TEX documents do.
let bye = end; outer end,bye;
And ¬nally, we provide the default environment that a user gets when simple
experiments like those at the beginning of Chapter 5 are desired.
clear_pen_memory; % initialize the ˜savepen™ mechanism
mode_setup; % establish proof mode as the default
numeric mode,mag; % but leave mode and mag undefined
Whew! That™s the end of the plain.mf ¬le.
9. Adapting to local conditions. In order to make plain programs inter-
changeable between di¬erent computers, everybody should use the same plain.mf base.
But there are some things that clearly should be customized at each installation:
Additional modes should be de¬ned, so that fonts can be made for whatever
output devices are of interest.
The proper localfont mode should be established.
The correct numbers should be assigned to screen rows and screen cols .
Here™s an example of a supplementary ¬le ˜local.mf™ that would be appropriate for
a computer center with the hypothetical cheapo and luxo printers described in Chap-
ter 11. We assume that cheapo mode is to be identical to lowres mode, except that
the cheapo fonts should be generated with a negative value of ¬llin (because cheapo
tends to make diagonal lines lighter than normal, not heavier). The terminal screens
are assumed to be 768 pixels wide and 512 pixels high.
% A file to be loaded after "plain.mf".
screen_rows:=512; screen_cols:=768;
mode_def cheapo = % cheapo mode: to generate fonts for cheapo
lowres_; % do as in lowres mode, except:
fillin:=-.1; % compensate for lighter diagonals
mode_def luxo = % luxo mode: to generate fonts for luxo
proofing:=0; % no, we™re not making proofs
fontmaking:=1; % yes, we are making a font
tracingtitles:=1; % yes, show titles online
pixels_per_inch:=2000; % almost 30 pixels per pt
Appendix B: Basic Operations 279

blacker:=.2; % make pens a teeny bit blacker bye
fillin:=.1; % but compensate for heavy diagonals
o_correction:=1; % and keep the full overshoot Computer Modern
localfont:=cheapo; DRAYTON
The macro ˜bye™ might also be rede¬ned, as suggested at the close of Appendix F.
To prepare a preloaded base ¬le at this installation, a suitably privileged
person should run INIMF in the following way:
This is METAFONT, Version 2.0 (INIMF) 8 NOV 1989 10:09
Preloading the plain base, version 2.0)
*input local
Beginning to dump on file plain.base
(The stu¬ after ˜**™ or ˜*™ is typed by the user; everything else is typed by the system.
A few more messages actually come out.)
Notice that local.mf does not include any new macros or features that a
programmer could use in a special way. Therefore it doesn™t make plain
incompatible with implementations at other computing centers.
Changes and/or extensions to the plain.mf macros should never be made,
unless the resulting base ¬le is clearly distinguished from the standard plain base. But
new, di¬erently named bases are welcome. For example, the author prepared a special
base for the Computer Modern fonts, so that they could be generated without ¬rst
reading the same 700 lines of macro de¬nitions each time. To load this base at high
speed, he can type ˜&cm™ after ™s initial ˜**™. (Or, on some machines, he has
a special version called ˜cmmf™ in which the new base is already present.)

None but the Base, in baseness doth delight.
” MICHAEL DRAYTON, Robert, Duke of Normandy (1605)

So far all was plain sailing, as the saying is;
but Mr. Till knew that his main di¬culties were yet to come.
” FRANCIS E. PAGET, Milford Malvoisin (1842)
(page 280)

Appendix C: Character Codes 281

Di¬erent computers tend to have di¬erent ways of representing the characters in codes
¬les of text, but gives the same results on all machines, because it oct
converts everything to a standard internal code when it reads a ¬le. hex
also converts back from its internal representation to the appropriate external
code, when it writes a ¬le of text; therefore most users need not be aware of the
fact that the codes have actually switched back and forth inside the machine.
The purpose of this appendix is to de¬ne ™s internal code,
which has the same characteristics on all implementations of . The
existence of such a code is important, because it makes programs
portable. ™s scheme is based on the American Standard Code for
Information Interchange, known popularly as “ASCII.” There are 128 codes,
numbered 0 to 127; we conventionally express the numbers in octal notation, from
oct"000" to oct"177", or in hexadecimal notation, from hex"00" to hex"7F".
Thus, the value of ASCII"b" is normally called oct"142" or hex"62", not 98.
In the ASCII scheme, codes oct"000" through oct"037" and code oct"177"
are assigned to special functions; for example, code oct"007" is called BEL, and
it means “Ring the bell.” The other 95 codes are assigned to visible symbols and
to the blank space character. Here is a chart that shows ASCII codes in such a
way that octal and hexadecimal equivalents can easily be read o¬:
´0 ´1 ´2 ´3 ´4 ´5 ´6 ´7
´04x ! " # $ % & ™
´05x ( ) * + , - . /
´06x 0 1 2 3 4 5 6 7
´07x 8 9 : ; < = > ?
´10x @ A B C D E F G
´11x H I J K L M N O
´12x P Q R S T U V W
´13x X Y Z [ \ ] ^ _
´14x ˜ a b c d e f g
´15x h i j k l m n o
´16x p q r s t u v w
´17x x y z { | } ˜ DEL
˝8 ˝9 ˝A ˝B ˝C ˝D ˝E ˝F
Ever since ASCII was established in the early 1960s, people have had
di¬erent ideas about what to do with positions oct"000" thru oct"037" and
oct"177", because most of the functions assigned to those codes are appropriate
282 Appendix C: Character Codes

only for special purposes like ¬le transmission, not for applications to printing Knuth
or to interactive computing. Manufacturers soon started producing line printers ¡=
that were capable of generating 128 characters, 33 of which were tailored to ¿=
the special needs of particular customers; part of the advantage of a standard
code was therefore lost. An extended ASCII code intended for text editing and
interactive computing was developed at several universities about 1965, and for
many years there have been terminals in use at Stanford, MIT, Carnegie-Mellon,
and elsewhere that have 120 or 121 symbols, not just 95. For example, the author
developed on a keyboard that includes the symbols ˜≠™, ˜¤™, ˜≥™, and
˜←™, which are easier to use than the character pairs ˜<>™, ˜<=™, ˜>=™, and ˜:=™. The
full character set looks like this:

´0 ´1 ´2 ´3 ´4 ´5 ´6 ´7
´00x … “ ± β § ¬ ∈ π
´01x » γ δ ‘ ± • ∞ ‚
´02x ‚ ⊃ © ∪ ∀ ∃ — ”
´03x ← ’ ≠ — ¤ ≥ ≡ ∨
´04x ! " # $ % & ™
´05x ( ) * + , ’ . /
´06x 0 1 2 3 4 5 6 7
´07x 8 9 : ; < = > ?
´10x @ A B C D E F G
´11x H I J K L M N O
´12x P Q R S T U V W
´13x X Y Z [ \ ] ^ _
´14x ˜ a b c d e f g
´15x h i j k l m n o
´16x p q r s t u v w
´17x x y z { | } ˜ «
˝8 ˝9 ˝A ˝B ˝C ˝D ˝E ˝F

can also be con¬gured to accept any or all of the character codes
128“255. However, programs that make use of anything in addition
to the 95 standard ASCII characters cannot be expected to run on other systems,
so the use of extended character sets is discouraged.
A possible middle ground has been suggested, based on the fact that it™s
easy to write a program that converts extended-character ¬les into standard ¬les
by substituting ˜<>™ for ˜≠™, etc. In the author™s implementation at Stanford, the
symbols ˜≠™, ˜¤™, ˜≥™, and ˜←™ are considered to be in the same class as ˜<™, ˜=™, ˜:™,
and ˜>™ when tokens are formed (see Chapter 6). Tokens like ˜≠=™ and ˜<≥™ are
therefore distinct, although they both become ˜<>=™ after conversion. As long as
Appendix C: Character Codes 283

such tokens are avoided, the author™s programs can easily be expurgated into a WILKINS
portable form for general distribution. (Another feasible approach would have
been to convert nonstandard codes to character pairs during ™s input
process; that would have been slightly less e¬cient.)
Computers with non-ASCII character sets should specify a correspon-
dence between 95 distinct characters and the standard ASCII codes oct"040"
thru oct"176". programs written on any such machines will be
completely interchangeable with each other.

If any shall suggest, that some of the Enquiries here insisted upon
(as particularly those about the Letters of the Alphabet)
do seem too minute and trivial, for any prudent Man
to bestow his serious thoughts and time about.
Such Persons may know, that the discovery
of the true nature and Cause of any the most minute thing,
doth promote real Knowledge,
and therefore cannot be un¬t for any Mans endeauours,
who is willing to contribute to the advancement of Learning.
” JOHN WILKINS, Towards a Real Character (1668)

Clearly even the simple A.B.C. is a thing of mystery.
Like all codes, it should not be tri¬‚ed with,
but it is to be feared that in modern times
it has not always been respected.
” STANLEY MORISON, On Type Faces (1923)
(page 284)

Dirty Tricks
Appendix D: Dirty Tricks 285

Any powerful computer language can be used in ways that go considerably be- Hobby
yond what the language designer originally had in mind, especially when macro mouth
expansion is possible. Sometimes the unexpected constructions are just amus- stomach
ing; sometimes they are disgustingly arcane. But sometimes they turn out to be for
quite useful, and they graduate from “tricks” to the status of “techniques.” (For
example, several of the macros now in Appendix B started out as suggestions
for Appendix D.) In any case, gurus of a language always like to explore its
limits. The depths of have hardly been plumbed, but this appendix
probably reached a new low at the time it was written.
Acknowledgment: More than half of the ideas in this appendix are due
to John Hobby, who has been a tireless and inspiring co-worker during the entire
development of the new system.
Please don™t read this material until you™ve had
plenty of experience with plain .
After you have read and understood the secrets below, you™ll know all sorts of devi-
ous combinations of commands, and you will often be tempted to write
inscrutable macros. Always remember, however, that there™s usually a simpler and
better way to do something than the ¬rst way that pops into your head. You may not
have to resort to any subterfuge at all, since is able to do lots of things in
a straightforward way. Try for simple solutions ¬rst.
1. Macro madness. If you need to write complicated macros, you™ll need to be familiar
with the ¬ne points in Chapter 20. ™s symbolic tokens are divided into two
main categories, “expandable” and “unexpandable”; the former category includes all
macros and if . . . ¬ tests and for . . . endfor loops, as well as special operations like
input, while the latter category includes the primitive operators and commands listed
in Chapters 25 and 26. The expansion of expandable tokens takes place in ™s
“mouth,” but primitive statements (including equations, declarations, and the various
types of commands) are done in ™s “stomach.” There™s a communication
between the two, since the stomach evaluates expressions that are needed as arguments
to the mouth™s macros; any statement can be embedded in a group expression, so
arbitrarily complicated things can be done as part of the expansion process.
Let™s begin by considering a toy problem that is treated at the beginning of
Appendix D in The TEXbook, in case some readers are interested in comparing TEX to
. Given a numeric variable n ≥ 0, we wish to de¬ne a macro asts whose
replacement text consists of precisely n asterisks. This task is somewhat tricky because
expansion is suppressed when a replacement text is being read; we want to use a for
loop, but loops are special cases of expansion. In other words,
def asts = for x=1 upto n: * endfor enddef
de¬nes asts to be a macro with a for loop in its replacement text; in practice, asts
would behave as if it contained n asterisks (using possibly di¬erent values of n), but
we have not solved the stated problem. The alternative
def makedef primary n =
def asts = for x=1 upto n: * endfor enddef enddef;
makedef n
“freezes” the present value of n; but this doesn™t solve the problem either.
286 Appendix D: Dirty Tricks

One solution is to build up the de¬nition by adding one asterisk at a time, expandafter
using expandafter as follows:
def asts = enddef; outer
forbidden token
for x=1 upto n: inner
expandafter def expandafter asts expandafter = asts * enddef; *
pool size
The three expandafters provide a “¬nger” into the replacement text, before def sup-
bu¬er size
presses expansion; without them the replacement text would turn out to be ˜asts *™, scantokens
causing in¬nite recursion. quote
This solution involves a running time proportional to n2 , so the reader might debugging tricks
wonder why a simpler approach like
expandafter def expandafter asts expandafter =
for x = 1 upto n: * endfor enddef
wasn™t suggested? The reason is that this doesn™t work, unless n = 0! A for loop isn™t
entirely expanded by expandafter; only ™s ¬rst step in loop expansion is
carried out. Namely, the loop text is read, and a special inaccessible token ˜ENDFOR™
is placed at its end. Later on when ™s mouth encounters ˜ENDFOR™ (which
incidentally is an expandable token, but it wasn™t listed in Chapter 20), the loop text
is re-inserted into the input stream, unless of course the loop has ¬nished. The special
ENDFOR is an ˜outer™ token, hence it should not appear in replacement texts; -
will therefore stop with a “forbidden token” error if you try the above with n ≥ 1.
You might try to defeat the outerness by saying
for x=1: inner endfor;
but won™t let you. And even if this had worked, it wouldn™t have solved the
problem; it would simply have put ENDFOR into the replacement text of ast, because
expansion is inhibited when the replacement text is being read.
There™s another way to solve the problem that seems to have running time
proportional to n rather than n2 :
scantokens("def asts=" for x=1 upto n: & "* " endfor) enddef;
but actually ™s string concatenation operation takes time proportional to
the length of the strings it deals with, so the running time is still order n2 . Furthermore,
the string operations in are rather primitive, because this isn™t a major
aspect of the language; so it turns out that this approach uses order n2 storage cells
in the string pool, although they are recycled later. Even if the pool size were in¬nite,
™s “bu¬er size” would be exceeded for large n, because scantokens puts
the string into the input bu¬er before scanning it.
Is there a solution of order n? Yes, of course. For example,
def a=a* enddef;
for x=0 upto n:
if x=n: def a=quote quote def asts = enddef; fi
expandafter endfor a enddef;
showtoken asts.
(The ¬rst ˜quote™ is removed by the for, hence one will survive until a is rede¬ned.
If you don™t understand this program, try running it with n = 3; insert an isolated
Appendix D: Dirty Tricks 287

expression ˜0;™ just before the ˜if™, and look at the lines of context that are shown input stack size
when gives you four error messages.) The only ¬‚aw in this method is
that it uses up n cells of stack space; ™s input stack size may have to be end
increased, if n is bigger than 25 or so. outer
The asterisk problem is just a puzzle; let™s turn now to a genuine application. Runaway
File ended...
Suppose we want to de¬ne a macro called ˜ten ™ whose replacement text is the contents
end of a ¬le
of the parameter ¬le logo10.mf in Chapter 11, up to but not including the last two endinput
lines of that ¬le. Those last two lines say
input logo % now generate the font
end % and stop.
The ten macro will make it possible to set up the 10-point parameters repeatedly
(perhaps alternating with 9-point parameters in a nine macro); Appendix E explains
how to create a meta-design tool via such macros.
One idea would be to try to input the entire ¬le logo10.mf as the replacement
text for ten . We could nullify the e¬ect of the last three unwanted tokens by saying
save input,logo,end;
forsuffixes s=input,logo,end: let s=\; endfor
just before ten is used. To get the entire ¬le as a replacement text, we can try one of
the approaches that worked in the asterisk problem, say
expandafter def expandafter ten expandafter = input logo10 enddef.
But this ¬rst attempt runs awry if we haven™t already rede¬ned ˜end™; Appendix B
makes ˜end™ an ˜outer™ token, preventing its appearance in replacement texts. So we
say ˜inner end™ and try again, only to discover an unwritten law that somehow never
came up in Chapters 20 or 26:
Runaway definition?
font_size10pt#;ht#:=6pt#;xgap#:=0.6pt#;u#:=4/9pt#;s#:=0;o#:=1/ ETC.
! File ended while scanning the definition of ten.
<inserted text>
l.2 ...fter ten expandafter = input logo10
The end of a ¬le is invisible; but it™s treated like an ˜outer™ token, in the sense that a
¬le should never end when is passing rapidly over text.
Therefore this whole approach is doomed to failure. We™ll have to ¬nd a way
to stop the replacement text before the ¬le ends. OK, we™ll rede¬ne ˜input™ so that it
means ˜enddef ™, and rede¬ne logo so that it means ˜endinput™.
let INPUT = input; let input = enddef; let logo = endinput;
expandafter def expandafter ten expandafter = INPUT logo10;
showtoken ten.
It works! By the way, the line with three expandafters can be replaced by a more
elegant construction that uses scantokens as follows:
scantokens "def ten=" INPUT logo10;
288 Appendix D: Dirty Tricks

This does the job because always looks ahead and expands the token im- delimiter
text argument
mediately following an expression that is being evaluated. (The expression in this
case is the string "def ten=", which is an argument to scantokens. The token that tracingall
immediately follows an expression almost always needs to be examined in order to and
conditional and
be sure that the expression has ended, so always examines it.) Curi- conditional or
ously, the expandafter alternative causes ten ™s replacement text to begin with the
tokens ˜font_size10pt#;ht#:=...™, while the scantokens way makes it start with
˜designsize:=(10);ht#:=...™. Do you see why? In the second case, expansion con-
tinued until an unexpandable token (˜designsize™) was found, so the font_size macro
was changed into its replacement text; but expandafter just expanded ˜INPUT™.
Now let™s make the problem a bit harder. Suppose we know that ˜input™ comes
at the end of where we want to read, but we don™t know that ˜logo™ will follow. We
know that some program ¬le name will be there, but it might not be for the logo font.
Furthermore, let™s assume that ˜end™ might not be present; therefore we can™t simply
rede¬ne it to be enddef . In this case we can make ˜input™ into a right delimiter,
and read the ¬le as a delimited text argument; that will give us enough time to insert
other tokens, which will terminate the input and ¬‚ush the unwanted ¬le name. But
the construction is more complex:
let INPUT = input; delimiters begintext input;
def makedef(expr name)(text t) =
expandafter def scantokens name = t enddef;
endinput flushfilename enddef;
def flushfilename suffix s = enddef;
makedef("ten") expandafter begintext INPUT logo10;
showtoken ten.
This example merits careful study, perhaps with ˜tracingall™ to show exactly how
proceeds. We have assumed that the unknown ¬le name can be parsed
as a su¬x; this solves the problem that a ¬le cannot end inside of a text parameter
or a false condition. (If we knew that ˜end™ were present, we could have replaced
˜endinput flushfilename™ by ˜if false:™ and rede¬ned ˜end™ to be ˜fi™.)
Let™s turn now to a simpler problem. allows you to consider the
˜and™ of two Boolean expressions, but it always evaluates both expressions. This is
problematical in situations like
if pair x and (x>(0,0)): A else: B fi
because the expression ˜x>(0,0)™ will stop with an error message unless x is of type
pair. The obvious way to avoid this error,
if pair x: if x>(0,0): A else: B fi else: B fi
is cumbersome and requires B to appear twice. What we want is a “conditional and”
operation in which the second Boolean expression is evaluated only if the ¬rst one turns
out to be true; then we can safely write
if pair x cand (x>(0,0)): A else: B fi.
Similarly we might want “conditional or” in which the second operand is evaluated
only if the ¬rst is false, for situations like
if unknown x cor (x<0): A else: B fi.
Appendix D: Dirty Tricks 289

Such cand and cor macros can be de¬ned as follows: cand
def cand(text q) = startif true q else: false fi enddef; text arguments
def cor(text q) = startif true true else: q fi enddef;
tertiarydef p startif true = if p: enddef; group delimiters
the text arguments are now evaluated only when necessary. We have essentially replaced endgroup
the original line by
if if pair x: x<(0,0) else: false fi: A else: B fi. gobble
vacuous expressions
This construction has one catch; namely, the right-hand operands of cand and cor
must be explicitly enclosed in delimiters. But delimiters are only a minor nuisance,
because the operands to ˜and™ and ˜or™ usually need them anyway. It would be impos-
sible to make cand and cor obey the normal expression hierarchy; when macros make
primary/secondary/tertiary distinctions, they evaluate their arguments, and such eval-
uation is precisely what cand and cor want to avoid.
If these cand and cor macros were changed so that they took undelimited text
arguments, the text argument wouldn™t stop at a colon. We could, however, use such
modi¬ed macros with group delimiters instead. For example, after
let {{ = begingroup; let }} = endgroup;
def cand text q = startif true q else: false fi enddef

we could write things like

if {{pair x cand x>(0,0)}}: A else: B fi.

(Not that this buys us anything; it just illustrates a property of undelimited text
arguments.) Group delimiters are not valid delimiters of delimited text arguments.
Speaking of group delimiters, the gratuitous begingroup and endgroup to-
kens added by vardef are usually helpful, but they can be a nuisance. For example,
suppose we want to write a zz macro such that ˜zz1..zz2..zz3™ expands into


It would be trivial to do this with def :

def zz suffix $ = z${dz$} enddef;

but this makes zz a “spark.” Let™s suppose that we want to use vardef , so that zz
will be usable in su¬xes of variable names. Additional begingroup and endgroup
delimiters will mess up the syntax for paths, so we need to get rid of them. Here™s one
way to ¬nesse the problem:
vardef zz@# =
endgroup gobbled true z@#{dz@#} gobble begingroup enddef.

The gobbled and gobble functions of Appendix B will remove the vacuous expressions
˜begingroup endgroup™ at the beginning and end of the replacement text.
(The initial begingroup endgroup won™t be gobbled if the vardef is being
read as a primary instead of as a secondary, tertiary, or expression. But in such cases
you probably don™t mind having begingroup present.)
290 Appendix D: Dirty Tricks

2. Fortuitous loops. The ˜max™ and ˜min™ macros in Appendix B make use of the fact max
that commas are like ˜)(™ in argument lists. Although the de¬nition heading is
equally spaced
def max(expr x)(text t) text argument
we can write ˜max(a, b, c)™ and this makes x = a and t = ˜b, c™. Of course, a person isn™t endgroup
supposed to say ˜max(a)(b)(c)™. whatever
Here are two more applications of the idea: We want ˜inorder(a, b, c)™ to be
true if and only if a ¤ b ¤ c; and we want ˜equally spaced(x1 , x2 , x3 ) dx ™ to produce
the equations ˜x2 ’ x1 = x3 ’ x2 = dx™.
def inorder(expr x)(text t) =
((x for u=t: <= u)
and (u endfor gobbled true true)) enddef;
def equally_spaced(expr x)(text t) expr dx =
x for u=t: - u = u endfor gobbled true
- dx enddef.
Isn™t this fun? (Look closely.)
There is a problem, however, if we try to use these macros with loops in the
arguments. Consider the expressions
inorder(for n=1 upto 10: a[n], endfor infinity),
inorder(a[1] for n=2 upto 10: ,a[n] endfor),
inorder(a[1],a[2] for n=3 upto 10: ,a[n] endfor);
the ¬rst two give error messages, but the third one works! The reason is that, in the
¬rst two cases, the for loop begins to be expanded before begins to read
the text argument, hence ENDFOR rears its ugly head again. We can avoid this problem
by rewriting the macros in a more complicated way that doesn™t try to single out the
¬rst argument x:
def inorder(text t) =
expandafter startinorder for u=t:
<= u endgroup and begingroup u endfor
gobbled true true endgroup) enddef;
def startinorder text t =
(begingroup true enddef;
def equally_spaced(text t) expr dx =
if pair dx: (whatever,whatever) else: whatever fi
for u=t: - u = u endfor gobbled true
- dx enddef;
Two separate tricks have been used here: (1) The ˜endgroup™ within ˜inorder™ will stop
an undelimited text argument; this gets rid of the unwanted ˜<= u™ at the beginning.
(2) A throwaway variable, ˜whatever ™, nulli¬es an unwanted equation at the beginning
of ˜equally spaced™. With the new de¬nitions, all three of the expressions above will
be understood, and so will things like
equally_spaced(for n=1 upto 10: x[n], endfor whatever) dx.
Furthermore the single-argument cases now work: ˜inorder(a)™ will always be true, and
˜equally spaced(x) dx ™ will produce no new equations.
Appendix D: Dirty Tricks 291

If we want to improve max and min in the same way, so that a person can max
specify loop arguments like
max(a[1] for n=2 upto 10: ,a[n] endfor) scaled
and so that ˜max(a) = a™ in the case of a single argument, we have to work harder, yscaled
because max and min treat their ¬rst argument in quite a special way; they need to
apply the special macro setu , which de¬nes the type of the auxiliary variable u . The
fastest way to solve this problem is probably to use a token whose meaning changes
during the ¬rst time through the loop:
vardef max(text t) =
let switch_ = firstset_;
for u=t: switch_ u>u_: u_ := u ;fi endfor
u_ enddef;
vardef min(text t) =
let switch_ = firstset_;
for u=t: switch_ u<u_: u_ := u ;fi endfor
u_ enddef;
def firstset_ primary u =
setu_ u; let switch_ = if; if false: enddef.
Incidentally, the author™s ¬rst programs for max and min contained an interesting bug.
They started with ˜save u ™, and they tried to recognize the ¬rst time through the loop
by testing if u was unknown. This failed because u could be constantly unknown in
well-de¬ned cases like max(x, x + 1, x + 2).
3. Types. Our programs for inorder, equally_spaced, and max are careful not to make
unnecessary assumptions about the type of an expression. The ˜round™ and ˜byte™ func-
tions in Appendix B are further examples of macros that change behavior based on the
types of their expr arguments. Let™s look more closely at applications of type testing.
When the author was developing macros for plain , his ¬rst “cor-
rect” solution for max had the following form:
vardef max(text t) =
save u_; boolean u_;
for u=t: if boolean u_: setu_ u
elseif u_<u: u_ := u fi; endfor
u_ enddef.
This was interesting because it showed that there was no need to set u to true or false;
the simple fact that it was boolean was enough to indicate the ¬rst time through the
loop. (A slightly di¬erent setu macro was used at that time.)
We might want to generalize the ˜scaled™ operation of so that
˜scaled (x, y)™ is shorthand for ˜xscaled x yscaled y™. That™s pretty easy:
let SCALED = scaled;
def scaled primary z =
if pair z: xscaled xpart z yscaled ypart z
else: SCALED z fi enddef;
It™s better to keep the primitive operation ˜SCALED z™ here than to replace it by the
slower variant ˜xscaled z yscaled z™.
292 Appendix D: Dirty Tricks

allows you to compare booleans, numerics, pairs, strings, and equality test
transforms for equality; but it doesn™t allow the expression ˜p = q™ where p and q are
Nonlinear equations
paths or pens or pictures. Let™s write a general equality test macro such that ˜p == q™ solve
will be true if and only if p and q are known and equal, whatever their type.
tertiarydef p == q =
if unknown p or unknown q: false
elseif boolean p and boolean q: p=q
elseif numeric p and numeric q: p=q
elseif pair p and pair q: p=q
elseif string p and string q: p=q
elseif transform p and transform q: p=q
elseif path p and path q:
if (cycle p = cycle q) and (length p = length q)
and (point 0 of p = point 0 of q): patheq p of q
else: false fi
elseif pen p and pen q: (makepath p == makepath q)
elseif picture p and picture q: piceq p of q
elseif vacuous p and vacuous q: true
else: false fi enddef;
vardef vacuous primary p =
not(boolean p or numeric p or pair p or path p
or pen p or picture p or string p or transform p) enddef;
vardef patheq expr p of q =
save t; boolean t; t=true;
for k=1 upto length p:
t := (postcontrol k-1 of p = postcontrol k-1 of q)
and (precontrol k of p = precontrol k of q)
and (point k of p = point k of q);
exitunless t; endfor
t enddef;
vardef piceq expr p of q =
save t; picture t;
t=p; addto t also -q;
cull t dropping origin;
(totalweight t=0) enddef;

If p and q are numeric or pair expressions, we could relax the condition that they both
be known by saying ˜if known p ’ q: p = q else false ¬™; transforms could be handled
similarly by testing each of their six parts. But there™s no way to tell if booleans, paths,
etc., have been equated when they™re both unknown, without the risk of irrevocably
changing the values of other variables.

4. Nonlinear equations. has a built-in solution mechanism for linear equa-
tions, but it balks at nonlinear ones. You might be able to solve a set of nonlinear
equations yourself by means of algebra or calculus, but in di¬cult cases it is probably
simplest to use the ˜solve ™ macro of plain . This makes it possible to solve
n equations in n unknowns, provided that at most one of the equations is nonlinear
when one of the unknowns is ¬xed.
Appendix D: Dirty Tricks 293

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
logosl10.mf, which says simply
% 10-point slanted METAFONT logo
slant := 1/4;
input logo10
The slant parameter a¬ects currenttransform as explained in Chapter 15.
There isn™t a great deal of “meta-ness” in the logo.mf design, because
only a few forms of the logo are needed. However, some interesting
variations are possible; for example, if we use the parameter ¬les
font_size 30pt#; font_size 10pt#;
ht#:=25pt#; ht#:=6pt#;
xgap#:=1.5pt#; xgap#:=2pt#;
u#:=3/9pt#; u#:=4/3pt#;
s#:=1/3pt#; s#:=-2/3pt#;
o#:=2/9pt#; o#:=1/9pt#;
px#:=1pt#; px#:=1/3pt#;

we get and , respectively.
302 Appendix E: Examples

% Routines for the METAFONT logo, as found in The METAFONTbook
% (logo10.mf is a typical parameter file)
if unknown slant: slant:=0 else: currenttransform:=
identity slanted slant yscaled aspect_ratio fi;
ygap#:=(ht#/13.5u#)*xgap#; % vertical adjustment
ho#:=o#; % horizontal overshoot
leftstemloc#:=2.5u#+s#; % position of left stem
barheight#:=.45ht#; % height of bar lines
py#:=.9px#; % vertical pen thickness
pickup pencircle xscaled px yscaled py;
define_good_y_pixels(barheight); (Figure Eb will be inserted here;
define_corrected_pixels(o); too bad you can™t see it now.)

def beginlogochar(expr code, unit_width) =
pickup logo_pen enddef;
def super_half(suffix i,j,k) =
draw z.i{0,y.j-y.i}
... (.8[x.j,x.i],.8[y.i,y.j]){z.j-z.i}
... z.j{x.k-x.i,0} (Figure A18a will be inserted here;
too bad you can™t see it now.)
... (.8[x.j,x.k],.8[y.k,y.j]){z.k-z.j}
... z.k{0,y.k-y.j} enddef;
x1=x2=leftstemloc; x4=x5=w-x1; x3=w-x3;
y1=y5; y2=y4; bot y1=-o;
top y2=h+o; y3=y1+ygap;
draw z1--z2--z3--z4--z5;
labels(1,2,3,4,5); endchar;
(Figure Ea will be inserted here;
beginlogochar("E",14); too bad you can™t see it now.)
x4=x6=w-x1+ho; x5=x4-xgap;
y1=y6; y2=y5; y3=y4;
bot y1=0; top y3=h; y2=barheight;
Appendix E: Examples 303

draw z6--z1--z3--z4; draw z2--z5;
labels(1,2,3,4,5,6); endchar;
italcorr ht#*slant + .5u#;
(Figure 18a will be inserted here; too
if .5w<>good.x .5w: change_width; fi
bad you can™t see it now.)
lft x1=-eps; x2=w-x1; x3=x4=.5w;
y1=y2=y3; top y1=h; bot y4=-o;
draw z1--z2; draw z3--z4;
labels(1,2,3,4); endchar;
x1=.5w; x2=x4=leftstemloc; x3=x5=w-x2;
top y1=h+o; y2=y3=barheight;
bot y4=bot y5=-o;
draw z4--z2--z3--z5; super_half(2,1,3); (Figure 4c will be inserted here; too bad you

labels(1,2,3,4,5); endchar; can™t see it now.)

x4=w-x1+ho; x5=x4-xgap;
y2=y5; y3=y4; bot y1=-o;
top y3=h; y2=barheight;
draw z1--z3--z4; draw z2--z5;
labels(1,2,3,4,5); endchar;
beginlogochar("O",15); (Figure 11a will be inserted here;
x1=x4=.5w; top y1=h+o; bot y4=-o; too bad you can™t see it now.)

x2=w-x3=good.x(1.5u+s); y2=y3=barheight;
super_half(2,1,3); super_half(2,4,3);
labels(1,2,3,4); endchar;
x1=x2=leftstemloc; x3=x4=x5=w-x1;
bot y1=bot y4=-o;
top y2=top y5=h+o; y3=y4+ygap;
draw z1--z2--z3; draw z4--z5;
(Figure 21a will be inserted here; too
labels(1,2,3,4,5); endchar; bad you can™t see it now.)

ligtable "T": "A" kern -.5u#;
ligtable "F": "O" kern -u#;
font_identifier:="MFLOGO" if slant<>0: & "SL" fi;
font_coding_scheme:="AEFMNOT only";
304 Appendix E: Examples

Everything in logo.mf has already been explained previously in this font identi¬er
font coding scheme
book except for the very last two lines, which de¬ne a ˜font identi¬er™ and italic correction
a ˜font coding scheme™. These are optional bits of information that are dis- Knuth
Computer Modern
cussed in Appendix F. Furthermore an italic correction has been speci¬ed for parameter ¬les
the letter ˜ ™, since it™s the ¬nal letter of ˜ ™. driver ¬les
program ¬les
base ¬le
The program for a complete typeface will di¬er from the program for this
simple logo font primarily in degree; there will be lots more parameters, lots more
subroutines, lots more characters, lots more ligatures and kerns and whatnot. But
there will probably also be more administrative machinery, designed to facilitate the
creation, testing, and modi¬cation of characters, since a large enterprise requires good
organization. The remainder of this appendix is devoted to an example of how this
might be done: We shall discuss the additional kinds of routines that the author found
helpful while he was developing the Computer Modern family of typefaces.
The complete, unexpurgated programs for Computer Modern appear in Com-
puters & Typesetting, Volume E; but since they have evolved over a long period of
time, they are rather complex. We shall simplify the details so that it will be easier to
grasp the important issues without being distracted by irrelevant technicalities.
The simple logo fonts discussed above are generated by two types of ¬les:
There are parameter ¬les like logo10.mf, and there is a program ¬le logo.mf. The
Computer Modern fonts, being more extensive, are generated by four types of ¬les:
There are parameter ¬les like ˜cmr10.mf™, which specify the ad hoc dimensions for par-
ticular sizes and styles of type; there are driver ¬les like ˜roman.mf™, which serve as chief
executives of the font-generation process; there are program ¬les like ˜punct.mf™, which
contain programs for individual characters; and there™s a base ¬le called ˜cmbase.mf™,
which contains the subroutines and other macros used throughout the system.
Our logo example could have been cast in this more general mold by moving
the character programs into a program ¬le ˜METAFON.mf™, and by moving most of the
opening material into a base ¬le ˜logobase.mf™ that looks like this:

% Base file for the METAFONT logo
logobase:=1; % when logobase is known, this file has been input
def font_setup =
if unknown slant: slant:=0 else: currenttransform:=
. (the previous code is unchanged)
define_horizontal_corrected_pixels(ho); enddef;
followed by the de¬nitions of beginlogochar and super_half. Then we™re left with a
driver ¬le logo.mf that looks like this:

% Driver file for the METAFONT logo
if unknown logobase: input logobase fi
mode_setup; font_setup; % establish pixel-oriented units
input METAFON % generate the characters
ligtable "T": "A" kern -.5u#;
and so on, concluding as before.
Appendix E: Examples 305

In general, a parameter ¬le calls on a driver ¬le, which calls on one or more cmr10.mf
program ¬les; the base ¬le contains prede¬ned macros shared by all. There may be
several driver ¬les, each using a di¬erent combination of program ¬les; for example, end
Computer Modern has ˜roman.mf™ and ˜italic.mf™, both of which call on punct.mf to font slant
font quad
generate punctuation marks, although they use di¬erent program ¬les to generate the font normal space
lowercase alphabets. Characters are partitioned into program ¬les so that they can be font normal stretch
font normal shrink
shared by di¬erent drivers.
Parameter ¬les in Computer Modern don™t quite follow the conventions of
logo10.mf. Here, for example, are the opening and closing lines of cmr10.mf:
% Computer Modern Roman 10 point
if unknown cmbase: input cmbase fi
font_identifier "CMR"; font_size 10pt#;
u#:=20/36pt#; % unit width
serif_fit:=0pt#; % extra sidebar near serifs
letter_fit:=0pt#; % extra space added to all sidebars
serifs:=true; % should serifs and bulbs be attached?
monospace:=false; % should all characters have the same width?
generate roman % switch to the driver file
The main di¬erences are: (1) There™s special code at the beginning, to make sure that
cmbase.mf has been loaded. The base ¬le includes several things that are needed right
away; for example, cmbase declares the variables ˜serifs ™ and ˜monospace ™ to be of type
boolean, so that boolean-valued parameter assignments like ˜serifs := true™ will be
legal. (2) The font identi¬er is de¬ned in the parameter ¬le, not in the driver ¬le.
(3) The last line says ˜generate™ instead of ˜input™; the base ¬le de¬nes generate to
be the same as input, but other meanings are assigned by utility routines that we™ll
study later. (4) The ¬nal ˜end™ is no longer present in the parameter ¬le.
The roman.mf driver looks like this (vastly simpli¬ed):
% The Computer Modern Roman family of fonts
mode_setup; font_setup;
input romanu; % upper case (majuscules)
input romanl; % lower case (minuscules)
input romand; % numerals
input punct; % punctuation marks
font_slant slant;
if monospace: font_quad 18u#;
font_normal_space 9u#; % no stretching or shrinking
else: font_quad 18u#+4letter_fit#;
font_normal_space 6u#+2letter_fit#; % interword spacing
font_normal_stretch 3u#; % with ˜˜glue™™
font_normal_shrink 2u#;
input romlig; % f ligatures
ligtable "f": "i" =: oct"014", "f" =: oct"013", "l" =: oct"015",
"™" kern u#, "?" kern u#, "!" kern u#;
306 Appendix E: Examples

ligtable oct"013": "i" =: oct"016", "l" =: oct"016", % ffi and ffl bye.
"™" kern u#, "?" kern u#, "!" kern u#;
ligtable "-": "-" =: oct"173"; % en dash .
em dash
ligtable oct"173": "-" =: oct"174"; % em dash
punctuation marks
ligtable "˜": "˜" =: oct"134"; % open quotes
ligtable "™": "™" =: oct"042", % close quotes
"?" kern 2u#, "!" kern 2u#;


. 10
( 13)