. 9
( 13)


18.11. According to the rule just stated, the ¬rst comma is an abbreviation for ˜}} {{™.
Hence the ¬rst argument is a capsule containing the value of x; the second is the text
˜(,™ ; the third is the text ˜(}})™.

18.12. This snares future pens before they™re converted to pens, because pickup
wants to yscale by aspect ratio before ellipses change to polygons.
250 Appendix A: Answers to All the Exercises

18.13. The construction ˜hide ( statement list )™ expands into ˜gobble begingroup vacuous
statement list ; endgroup™, so the argument to gobble must be evaluated. The
begingroup causes to start executing statements. When that has been str
done, the ¬nal statement turns out to be empty , so the argument to gobble turns
out to be a vacuous expression (cf. Chapter 25). Finally, gobble ™s replacement text is
empty, so the hidden text has indeed disappeared. (The hide macro in Appendix B is
actually a bit more e¬cient, but a bit trickier.)

™s “stomach” would see ˜;™ if mag is known, but there would
19.1. Then
be no change if mag is unknown. An extra semicolon is harmless, since
statements can be empty . But it™s wise to get in the habit of putting ˜;™ before ¬,
because it saves a wee bit of time and because ˜;™ de¬nitely belongs before endfor.

19.2. No; that would be shocking.
19.3. Yes, if and only if n ’ is an even integer. (Because ambiguous values are
rounded up.)

19.4. No.

19.5. def even = not odd enddef .

19.6. The ¬rst is 5, because the pair is not considered to be a path. The second and
third are 0, because the pair is forced to become a path.

19.7. (a) The loop text is never executed. (b) It™s executed only once, for x = 1.
(c) It™s executed in¬nitely often, for x = 1, 1, 1, . . . . (d) Since ten times -
™s internal representation of .1 is slightly larger than 1, the answer is not what
you probably expect! The loop text is executed for x = 0, 0.1, 0.20001, 0.30002,
0.40002, 0.50003, 0.60004, 0.70004, 0.80005, and 0.90005 only. (If you want the values
(0, .1, .2, . . . , 1), say ˜ for xx = 0 upto 10: x := xx /10; text endfor™ instead.)

19.8. m = 1, n = 0.

19.9. def exitunless expr b = exitif not b enddef . (The simpler alternative
˜def exitunless = exitif not enddef ™ wouldn™t work, since ˜not™ applies only to the
following primary .)

19.10. numeric p[]; boolean n_is_prime; p[1]=2; k:=1;
for n=3 step 2 until infinity:
for j=2 upto k: if n mod p[j]=0: n_is_prime:=false; fi
exitif n/p[j]<p[j]; endfor
if n_is_prime: p[incr k]:=n; exitif k=30; fi
endfor fi
show for k=1 upto 30: str p[k]&"="&decimal p[k], endfor "done" end.

19.11. ˜0; exitif true;™.

20.1. False; consider ˜a1(2)™.

20.2. A value very close to z2 .
Appendix A: Answers to All the Exercises 251

20.3. vardef lo_cube(expr x)=x*x*x<10 enddef; **
show solve lo_cube(0,10), 10**1/3; end.
With the default tolerance of 0.1, this will show the respective values 2.14844 and (SUFFIX
2.1544. A more general routine could also be written, with ˜10™ as a parameter:
at sharp
vardef lo_cube[](expr x)=x*x*x<@ enddef;
sharp at
show solve lo_cube10(0,10); origin
if we ask for minimum tolerance (tolerance := epsilon ), the result is 2.15445; the true histogram
value is ≈ 2.15443469.

20.4. begingroup(p5dx,p5dy)endgroup.

20.5. Say ˜first#@™ after de¬ning ˜vardef first.a[]@#=@ enddef™. (There are
other solutions, e.g., using substrings of str #@, but this one is perhaps the most

20.6. The machine answers thus:

Parameters to a macro are numbered sequentially, starting with zero, and classi¬ed as
either (EXPRn ), (SUFFIXn ), or (TEXTn ). In a vardef, (SUFFIX0) and (SUFFIX1) are
always reserved for the implicit parameters #@ and @; (SUFFIX2) will be @#, if it is used
in the parameter heading, otherwise it will be the ¬rst explicit parameter, if it happens
to be a su¬x parameter.

20.7. secondarydef t transum tt =
begingroup save T; transform T;
for z=origin,up,right:
z transformed t + z transformed tt = z transformed T; endfor
T endgroup enddef.

21.1. False; about twice as often (2/3 versus 1/3).

21.2. 1+floor uniformdeviate n.

21.3. A random point on the straight line segment from z1 to z2 . (The point z1
itself will occur with probability about 1/65536; but point z2 will never occur.)

21.4. A random “skyline” texture, 100 pt wide — 10 pt tall:
The density decreases uniformly as you go up in altitude.

21.5. A more-or-less bell-shaped histogram:

22.1. (a) I¬ n is an integer between 0 and 255. (b) I¬ s is a string of length 1.

22.2. Whoever says that there™s no such primitive operation has forgotten about
252 Appendix A: Answers to All the Exercises

22.3. vardef octal primary n = Hobby
save m,s; m:=abs round n; string s; s=decimal(m mod 8);
forever: m:=m div 8; exitif m=0;
s:=decimal(m mod 8) & s; endfor
s enddef;
˜str[m mod 8]™ could also be used instead of ˜decimal(m mod 8)™.
23.1. Point (x, y) is the upper left corner, (x + c1 ’ c0 , y) is the upper right corner,
(x, y ’ r1 + r0 ) is the lower left corner, and (x + c1 ’ c0 , y ’ r1 + r0 ) is the lower right
corner. (Pixels outside this rectangle will not be displayed.)
23.2. Rede¬ne openit so that it puts the top left at (’50, 280).
23.3. (This routine is due to John Hobby.)
newinternal n_windows; % the number of windows allocated so far
newinternal screen_bot; % the first untouched screen row
pair screen_corner; % the upper left corner of next window
def wipescreen = % do this to initialize or reinitialize
for i:=1 upto n_windows: display blankpicture inwindow i; endfor
n_windows := screen_bot := 0; screen_corner := origin enddef;
vardef new_window@#(expr u,v) = save r,c,up_lft; pair up_lft;
if n_windows=15: errmessage "No more windows left"
else: window@# := incr n_windows;
up_lft = (min(xpart u,xpart v), max(ypart u, ypart v));
(r,c) = (u+v-2up_lft) rotated 90;
if ypart screen_corner + c > screen_cols:
screen_corner:=(screen_bot,0); fi
openwindow window@# from screen_corner
to screen_corner+(r,c) at up_lft;
screen_bot := max(screen_bot,xpart screen_corner + r);
screen_corner := screen_corner + (0,c) fi; enddef;

24.1. The entire path now has negative y coordinates except at point (0, 0), so
the outline of the ¬lled region is (0, ’1) - - (10, ’1) - - (10, 0) - - (0, 0) - - (0, 1) - - cycle.
(Notice that the digitized outline actually goes up to (0, 1) before coming straight down
again. This ¬lls no pixels, but correctly puts “cancelling” edges from (0, 0)
to (0, 1) and back to (0, 0) into its edge structure, because the point (0, .5) is on the
boundary and rounds to (0, 1).)
24.2. The horizontal tangents are already taken care of by the equations top y1 =
h + o and bot y4 = ’o, so nothing needs to be done there. We should, however, say
x2 = w ’ x3 = good.x (1.5u + s)
so that vertical tangents will occur in good places. Since w is an integer, and since the
logo pen has left-right symmetry, w ’ x3 will be good if and only if x3 is.
24.3. Let b be the pen breadth. Then .5w is a good x value if and only if lft .5w is
an integer; but lft .5w = .5w ’ .5b, and this is an integer if and only if w ’ b is even.
Appendix A: Answers to All the Exercises 253

24.4. There are no ambiguous points on the outlines of this stroke, except perhaps draw
on the top and bottom edges; the latter can occur only if round py is odd. Hence
there is always left-right symmetry, but top-bottom symmetry might fail because of a
missing row at the bottom (e.g., when px = py = 3). In a case like the ˜ ™ we do have
both symmetries, because y1 and x4 are in good positions.
24.5. No matter where you place the octagon so that it isn™t touching any ambiguous
points, exactly seven ambiguous points are inside it; hence every one-point draw ¬lls
exactly seven pixels. (In fact, you always get one of the patterns , , , or .)
24.6. f = .5(x4 ’x3 ); the desired equation is ˜x1 ’x2 = round(x1 ’x2 +.5(x4 ’x3 ))™.
24.7. Let x3 = n + 1 + θ, where n is an integer and 0 ¤ θ < 1. By drawing lines of
slope 30—¦ from the pixel centers, we ¬nd that there are three cases for the rightmost
four columns:
Case A, ; Case B, ; Case C, .
√ √ √
Case A occurs for 0 ¤ θ < 2 3 ’ 3; Case B occurs for 2 3 ’ 3 ¤ θ < 3 ’ 1; Case C

occurs for 3 ’ 1 ¤ θ < 1. The tip in Case A looks a bit too sharp, and Case C looks
too blunt, so Case B seems best. This case occurs when x3 is near an integer, so it™s
OK to let x3 be an integer.
√ √
24.8. Let y1 = n + θ. If θ lies between 1 3 ’ 1 and 1 3 + 1 , the top row after
6√ √
2 2 2
digitization will contain two black pixels. If θ lies between 1 3 + 1 and 5 3 ’ 1 , we
6 2 6 2
get the desired shape. Otherwise we get ˜ ™.

24.9. (We choose θ = 1 3 in the previous exercise, since this is the midpoint of the
desirable interval.) The equations are changed to
x1 = x2 = w ’ x3 = round s;
y3 = .5 + ¬‚oor .5h;
z1 ’ z2 = (z3 ’ z2 ) rotated 60;
y1 := .5 sqrt 3 + round(y1 ’ .5 sqrt 3);
y2 := h ’ y1 ;
and then we ¬ll z1 - - z2 - - z3 - - cycle as before.
24.10. vardef vround primary v =
¬‚oor(v — aspect ratio + .5)/aspect ratio enddef ;
vardef good.y primary y =
vround (y + pen top ) ’ pen top enddef .
24.11. (m + 1/2, (n + 1/2)/aspect ratio ). These are the points that currenttransform
maps into pixel centers.
25.1. By looking at the syntax rules, we ¬nd, for example,
boolean expression true
numeric expression 0
pair expression (0,0)
path expression makepath pencircle
pen expression nullpen
254 Appendix A: Answers to All the Exercises

picture expression nullpicture delimiter
Missing token has been inserted
string expression ""
transform expression Impossible!
vacuous expression begingroup endgroup

Every transform expression includes either a variable or a capsule. Incidentally, there
are some amusing alternative 5-token solutions for pair expression :
postcontrol 0 of makepath nullpen
makepath pencircle intersectiontimes makepath nullpen

26.1. The responses are
> a=left delimiter that matches ::
> b=(outer) a
> c=a

because: a has been rede¬ned from internal quantity to delimiter; b is still an internal
quantity (named a), and it has been stamped outer; c denotes the same internal
quantity, but it hasn™t got outerness.

27.1. We want to delete

0 [ 1 + sqrt 43 ]

from the sequence of tokens that is about to read next, in order to get
rid of the right bracket, which we can see is going to be just as erroneous as the left
bracket was. However, there is another way to proceed (and indeed, this alternative
would be preferable to counting tokens, if the bracketed expression were longer): We
could simply delete 2 tokens, then ˜I(™. This would produce another error stop,
! Missing ˜)™ has been inserted.
<to be read again>
<*> show round[1 + sqrt43]
I found no right delimiter to match a left one. So I™ve
put one in, behind the scenes; this may fix the problem.

after which it™s easy to delete the ˜]™ and continue successfully.

27.2. looked ahead, to see if the expression being evaluated was going
to be something like ˜round 0[1+sqrt43,x]™. But when it found no comma, it put
back several tokens so that they could be read again. (The subexpression 1+sqrt43
had already been evaluated, so a “capsule” for its value, 7.55743, was inserted among
the tokens to be reread.) The expression ended with ˜0™, and ˜round 0™ was shown.
Then found extra tokens following the show command; a semicolon should
have come next. To continue, the user should just plunge ahead recklessly once again,
letting delete those unwanted tokens.
Appendix A: Answers to All the Exercises 255

27.3. The little program counterclockwise
path p,q; p=flex((-32,481),(-42,455),(-62,430)); KNUTH
show p intersectiontimes q, p intersectionpoint q,
angle -direction 2 of p, angle direction 0 of q; end
gives the following results:
>> (1.88403,0.07692)
>> (-59.32149,432.59523)
>> 43.14589
>> 45.47263
(Actually, the paths would also cross if 452 were 451, but it™s such a close call that
doesn™t call the path strange; prefers to turn counterclockwise
when the amount of turn is close enough to 180 , even if it™s slightly more.)
27.4. If this exercise isn™t just a joke, the title of this appendix is a lie. (When
you™ve solved this exercise you might also try to ¬nd all the lies and/or jokes that are
the same in both this book and The TEXbook.)

Looke into this Businesse thorowly,
And call these foule O¬endors to their Answeres.
” WILLIAM SHAKESPEARE, Second Part of Henry the Sixth (1594)

If you can™t solve a problem,
you can always look up the answer.
But please, try ¬rst to solve it by yourself;
then you™ll learn more and you™ll learn faster.
” DONALD E. KNUTH, The book (1986)
(page 256)

Appendix B: Basic Operations 257

This appendix de¬nes the macros of the plain base. Let™s begin with table, useful
an informal inventory of all the features that are available. Boolean
Boolean things: true, false; expression ;
odd numeric ; charexists numeric ;
± 
 boolean 
 numeric  ±
  ± <± 
 
 
 pair   boolean  <=  boolean
   
 numeric     numeric 
     
 
     
expression ; pair pair ;
    <>  
 string     string 
 

 picture     
  >=  

 

  
  transform transform
 string 
 
  >
not boolean ; boolean and boolean ; boolean or boolean .
Numeric things: tracingtitles, . . . , yoffset (see Chapter 25);
eps, epsilon, infinity; tolerance, join_radius, displaying; constant ;
± lft 
±sqrt ± floor 
 rt 
 
  round 
  
   
    
 top 
    
cosd numeric ; numeric ; numeric ;
mlog  vround   bot 
     
    good.x
     
 
 
mexp ceiling

xypart  ASCII
xpart pair
; transform ; string ;
ypart transform   hex
normaldeviate; uniformdeviate numeric ; whatever;
angle pair ; turningnumber cycle ; totalweight picture ;
- numeric ; variable ; byte ;
+ ++
numeric numeric ; numeric numeric ;
- +-+
numeric numeric ; numeric numeric ;
± 
numeric 

 
pair dotprod pair ; ( numerics ); ;
length  path 
min  
numeric [ numeric , numeric ]; solve function ( numeric , numeric );
directiontime pair of path .
258 Appendix B: Basic Operations

Pair things: left, right, up, down, origin; ( numeric , numeric ); Pair
z su¬x ; dir numeric ; unitvector pair ; round pair ;
±  ±good.lft ±  Pen
lft     Picture
   good.rt    String
rt precontrol Transform
pair ; pair ; numeric of path ;
top good.top postcontrol
     
bot direction
- pair ; pair pair ; numeric [ pair , pair ];
numeric * pair ; pair numeric ; pair transformer ;
path path ; ( pairs );
penoffset pair of pen ; directionpoint pair of path .

Path things: quartercircle, halfcircle, fullcircle;
unitsquare; flex( pairs ); makepath pen ;
superellipse( pair , pair , pair , pair , numeric );
reverse path ; counterclockwise path ; tensepath path ;
path transformer ; interpath( numeric , path , path );
± 
 
 
 
 
 
  .. tension ..  ±

±  ± 
 
{ pair }   { pair }  pair 
  
pair .. controls ..
{ curl } { curl } path ;
path     
 
 
empty empty cycle
 
 
 
 
 
 
 
subpath pair of path .

Pen things: pencircle, pensquare, penrazor, penspeck;
nullpen; currentpen; makepen path ; pen transformer .

Picture things: nullpicture, blankpicture; unitpixel;
+ +
picture ; picture picture ;
- -
picture transformer .

String things: "constant"; ditto; jobname; readstring;
str su¬x ; decimal numeric ; char numeric ;
string & string ; ( strings ); substring pair of string .

Transform things: identity; currenttransform;
inverse transform ; transform transformer .
Appendix B: Basic Operations 259

Transformers: transformed transform ; Transformers
scaled Loops
rotated shifted Diagnostic things
numeric ; numeric ; pair ;
xscaled Starting a job
slanted zscaled
yscaled Conversion to pixel units

reflectedabout( pair , pair ); rotatedaround( pair , numeric ).

else: text
if boolean : text {elseif boolean : text } ≥0 fi.

Loops: forever: text endfor;
± 
 
numeric upto numeric
for ν numeric downto numeric : text(ν) endfor;
:=  
numeric step numeric until numeric
expressions : text( ) endfor;
forsuffixes σ su¬xes : text(σ) endfor;
exitif boolean ; ; exitunless boolean ; .

Diagnostic things: ???; interact; hide( statements );
loggingall, tracingall, tracingnone.
Starting a job: \mode= modename ; mag= ;
magstep numeric
screenchars; screenstrokes; imagerules; gfcorners; nodisplays;
notransforms; input ¬lename .

Conversion to pixel units: mode_setup; fix_units;
pixels_per_inch, blacker, fillin, o_correction;
mm#, cm#, pt#, pc#, dd#, cc#, bp#, in#;
mm, cm, pt, pc, dd, cc, bp, in;
mode_def; extra_setup;
± 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
( names ).
 
 
 
 
 

define_whole_vertical_blacker_pixels 
 
 
 
 
 
 
 
260 Appendix B: Basic Operations

Character and font administration: Drawing
Screen display
beginchar( code , width# , height# , depth# ); extra_beginchar; Statements
italcorr numeric# ; change_width; endchar; extra_endchar;
± 
 
 
 
  ± 

 font_normal_space   ligtable ligs/kerns
  
   
   charlist codes 
font_normal_stretch  
numeric# ;
:= extensible codes ;
 font_normal_shrink   
   fontdimen info 
   
   
 
 
  headerbytes info
 
 
 
font_identifier := string .
font_coding_scheme empty
Drawing: penpos su¬x ( length , angle ); penstroke path(e) ;
; pen number :=savepen; clear_pen_memory;
saved pen number
pen_lft, pen_rt, pen_top, pen_bot;
± 
 
  draw
unfill drawdot
cycle ; path ; pair ;
 filldraw 
  undrawdot
erase picture command ; cutoff( pair , angle );
addto picture variable also picture ;
contour cycle withpen pen
addto picture variable ;
doublepath path withweight numeric
withweight numeric
cull picture variable pair .
Screen display: currentwindow; screen_rows, screen_cols;
openwindow numeric from screen pair to screen pair at pair ;
display picture variable inwindow numeric .
Statements: empty ; string ; begingroup statements endgroup;
± 
± ± ± 
 boolean   ≥1
 boolean    boolean 
 numeric  
 numeric   
  numeric 

 
 
   
 
   
 
   pair 

 
  
    
pair  
   
  
  
path = path
; names ;
  :=  
  
pen  
   
 picture  
 picture   
 
 picture 
 
  
  
    
 
   string 

 string  
 string   
 
   
 
   
transform transform
save names ; interim internal := numeric ; let name name ;
Appendix B: Basic Operations 261

name parameters text enddef; Proofsheet
vardef base ¬le
± name β text(±, β) enddef;
showit; shipit; cullit; openit; clearit; clearxy; clearpen;
errmessage string ;
stop string ; show expressions ;
showvariable showdependencies
names ; ;
showtoken showstats
see also Chapter 26 for some more exotic commands.

Proofsheet information:
± top 
 lft 
 
 
  nodot
rt ( su¬xes );
penlabels   bot  empty

 
 
± top 
± 

 lft  
  titlefont
   
nodot labelfont
rt ( string , pair ); name ;
 bot  empty  grayfont 
   
 
  slantfont
( pair , pair ); makegrid( pairs )( pairs );
proofrulethickness numeric ; proofoffset pair .

Hacks: gobble, gobbled, killtext; capsule_def; numtok.
The remainder of this appendix contains an edited transcript of the “plain
base ¬le,” which is a set of macros that come with normal implementations
of . These macros serve three basic purposes: (1) They make -
usable, because ™s primitive capabilities operate at a very low level.
A “virgin” system that has no macros is like a newborn baby that has
an immense amount to learn about the real world; but it is capable of learning fast.
(2) The plain macros provide a basis for more elaborate and powerful bases
tailored to individual tastes and applications. You can do a lot with plain ,
but pretty soon you™ll want to do even more. (3) The macros also serve to illustrate
how additional bases can be designed.
Somewhere in your computer system you should be able to ¬nd a ¬le called
plain.mf that contains what has been preloaded into the running system
that you use. That ¬le should match the code discussed below, except that it might
do some things in an equivalent but slightly more e¬cient manner.
262 Appendix B: Basic Operations

When we come to macros whose use has not yet been explained”for example, INIMF
somehow softjoin and stop never made it into Chapters 1 through 27”we shall
consider them from a user™s viewpoint. But most of the comments that follow are message
addressed to a potential base-¬le designer. blash blash
A special program called INIMF is used to install ; INIMF is just downto
like except that it is able to ˜dump™ a base ¬le suitable for preloading. exitunless
This operation requires additional program space, so INIMF generally has less memory
available than you would expect to ¬nd in a production version of . “

1. Getting started. A base ¬le has to have a delimiters command near the beginning, ...
since INIMF doesn™t have any delimiters built in. The ¬rst few lines usually also give gobble
the base ¬le a name and version number as shown here. gobbled
% This is the plain METAFONT base that™s described in The METAFONTbook.
% N.B.: Please change "base_version" whenever this file is modified! stop
% And don™t modify the file under any circumstances.
string base_name, base_version; base_name="plain"; base_version="2.71"; vacuous
message "Preloading the plain base, version " & base_version;
internal quantities
delimiters (); % this makes parentheses behave like parentheses smoothing
Next we de¬ne some of the simplest macros, which provide “syntactic sugar” turningcheck
for commonly occurring idioms. For example, ˜stop "hello"™ displays ˜hello™ on the
terminal and waits until return is typed.
def upto = step 1 until enddef; def downto = step -1 until enddef;
def exitunless expr c = exitif not c enddef;
let relax = \; % ignore the word ˜relax™, as in TeX
let \\ = \; % double relaxation is like single
def ]] = ] ] enddef; % right brackets should be loners
def -- = {curl 1}..{curl 1} enddef;
def --- = .. tension infinity .. enddef;
def ... = .. tension atleast 1 .. enddef;
def gobble primary g = enddef; def killtext text t = enddef;
primarydef g gobbled gg = enddef;
def hide(text t) = exitif numeric begingroup t; endgroup; enddef;
def ??? = hide(interim showstopping:=1; showdependencies) enddef;
def stop expr s = message s; gobble readstring enddef;
(Chapter 20 points out that ˜\™ is an expandable token that expands into nothing.
Plain allows also ˜\\™, because there™s a formatting program called MFT
that uses \\ to insert extra spacing in a pretty-printed listing.) The “clever” code for
hide is based on the fact that a vacuous expression is not numeric; hence no loop is
exited, and the computer doesn™t mind the fact that we may not be in a loop at all.
The values of internal quantities are next on the agenda:
smoothing:=1; autorounding:=2; % this adjusts curves to the raster
turningcheck:=2; % this will warn about a "strange path"
granularity:=1; % this says that pixels are pixels
def interact = % prepares to make "show" commands stop
hide(showstopping:=1; tracingonline:=1) enddef;
Appendix B: Basic Operations 263

def loggingall = % puts tracing info into the log loggingall
tracingcommands:=3; tracingedges:=2; tracingtitles:=1;
tracingequations:=1; tracingcapsules:=1; tracingspecs:=1; semicolon
tracingpens:=1; tracingchoices:=1; tracingstats:=1;
tracingoutput:=1; tracingmacros:=1; tracingrestores:=1; in¬nity
def tracingall = % turns on every form of tracing down
tracingonline:=1; showstopping:=1; loggingall enddef; right
def tracingnone = % turns off every form of tracing quartercircle
tracingcommands:=0; tracingonline:=0; showstopping:=0; halfcircle
tracingedges:=0; tracingtitles:=0; tracingequations:=0;
tracingcapsules:=0; tracingspecs:=0; tracingpens:=0; identity
tracingchoices:=0; tracingstats:=0; tracingoutput:=0;
tracingmacros:=0; tracingrestores:=0; enddef; ditto

The user can say interact in the midst of a statement; but loggingall, tracingall,
and tracingnone should come between statements. (You don™t need a semicolon after
them, because they come equipped with their own closing ˜;™.)

2. Math routines. The second major part of plain.mf contains the de¬nitions of basic
constants and mathematical macros that extend the primitive capabilities of -
™s expressions.

% numeric constants
newinternal eps,epsilon,infinity;
eps := .00049; % this is a pretty small positive number
epsilon := 1/256/256; % but this is the smallest
infinity := 4095.99998; % and this is the largest
% pair constants
pair right,left,up,down,origin;
origin=(0,0); up=-down=(0,1); right=-left=(1,0);
% path constants
path quartercircle,halfcircle,fullcircle,unitsquare;
quartercircle=(right{up}..(right+up)/sqrt2..up{left}) scaled .5;
halfcircle=quartercircle & quartercircle rotated 90;
fullcircle=halfcircle & halfcircle rotated 180 & cycle;
% transform constants
transform identity;
for z=origin,right,up: z transformed identity = z; endfor
% picture constants
picture blankpicture,unitpixel;
blankpicture=nullpicture; % ˜display blankpicture...™
unitpixel=nullpicture; addto unitpixel contour unitsquare;
% string constants
string ditto; ditto = char 34; % ASCII double-quote mark
264 Appendix B: Basic Operations

% pen constants pensquare
def capsule_def(suffix s) primary u = def s = u enddef enddef; future pens
capsule_def(pensquare) makepen(unitsquare shifted -(.5,.5)); capsule
capsule def
capsule_def(penrazor) makepen((-.5,0)--(.5,0)--cycle);
pen penspeck; penspeck=pensquare scaled eps; whatever
The pensquare and penrazor constants are de¬ned here in a surprisingly roundabout
way, just so that they can be future pens instead of pens. can transform a vround
future pen much faster than a pen, since pens have a complex internal data structure, ceiling
so this trick saves time. But how does it work? Well, a variable cannot be a future pen,
but a capsule can; hence pensquare and penrazor are de¬ned, via capsule def , to unitvector
be macros that expand into single capsules. Incidentally, penspeck is an extremely inverse
tiny little pen that is used by the drawdot macro. Since it is not intended to be autorounding
transformed, we are better o¬ making it a pen; then it™s immediately ready for use. turningnumber
Now that the basic constants have been de¬ned, we turn to mathematical
operations. There™s one operation that has no arguments:

% nullary operators
vardef whatever = save ?; ? enddef;

The reasoning behind this is discussed in exercise 17.2.
Operations that take one argument are introduced next.

% unary operators
let abs = length;
vardef round primary u =
if numeric u: floor(u+.5)
elseif pair u: (hround xpart u, vround ypart u)
else: u fi enddef;
vardef hround primary x = floor(x+.5) enddef;
vardef vround primary y = floor(y.o_+.5)_o_ enddef;
vardef ceiling primary x = -floor(-x) enddef;
vardef byte primary s = if string s: ASCII fi s enddef;
vardef dir primary d = right rotated d enddef;
vardef unitvector primary z = z/abs z enddef;
vardef inverse primary T =
transform T_; T_ transformed T = identity; T_ enddef;
vardef counterclockwise primary c =
if turningcheck>0:
interim autorounding:=0;
if turningnumber c <= 0: reverse fi fi c enddef;
vardef tensepath expr r =
for k=0 upto length r - 1: point k of r --- endfor
if cycle r: cycle else: point infinity of r fi enddef;
Appendix B: Basic Operations 265

Notice that the variable ˜T_™ was not saved by the inverse function. The plain base e¬ciency
routines gain e¬ciency by using “private” tokens that are assumed to be distinct from
any of the user™s tokens; these private tokens always end with the underscore charac- mod
ter, ˜_™. If ordinary user programs never contain such token names, no surprises will div
occur, provided that di¬erent macro designers who combine their routines are careful takepower
that their private names are not in con¬‚ict. **
The private tokens ˜o_™ and ˜_o_™ used in vround stand for ˜*aspect_ratio™
and ˜/aspect_ratio™, respectively, as we shall see shortly. directiontime
Now we de¬ne ˜mod™ and ˜div™, being careful to do this in such a way that the intersectionpoint
identities a(x mod y) = (ax) mod (ay) and (ax) div (ay) = x div y are valid. internal quantity
% binary operators
primarydef x mod y = (x-y*floor(x/y)) enddef;
primarydef x div y = floor(x/y) enddef;
primarydef w dotprod z = (xpart w * xpart z + ypart w * ypart z) enddef;
The ˜**™ operator is designed to be most e¬cient when it™s used for squaring.
A separate ˜takepower™ routine is used for exponents other than 2, so that
doesn™t have to skip over lots of tokens in the common case. The takepower routine is
careful to give the correct answer in expressions like ˜(-2)**(-3)™ and ˜0**0™.

primarydef x ** y = if y=2: x*x else: takepower y of x fi enddef;
def takepower expr y of x =
if x>0: mexp(y*mlog x)
elseif (x=0) and (y>0): 0
else: 1
if y=floor y:
if y>=0: for n=1 upto y: *x endfor
else: for n=-1 downto y: /x endfor fi
else: hide(errmessage "Undefined power: " & decimal x&"**"&decimal y)
fi fi enddef;
™s primitive path operations have been de¬ned in such a way that
the following higher-level operations are easy:

vardef direction expr t of p =
postcontrol t of p - precontrol t of p enddef;
vardef directionpoint expr z of p =
a_:=directiontime z of p;
if a_<0: errmessage("The direction doesn™t occur"); fi
point a_ of p enddef;
secondarydef p intersectionpoint q =
begingroup save x_,y_; (x_,y_)=p intersectiontimes q;
if x_<0: errmessage("The paths don™t intersect"); (0,0)
else: .5[point x_ of p, point y_ of q] fi endgroup
The private token ˜a_™ will be declared as an internal quantity. Internal quantities are
more e¬cient than ordinary numeric variables.
266 Appendix B: Basic Operations

Plain ™s ˜softjoin™ operation provides a way to hook paths together softjoin
join radius
without the abrupt change of direction implied by ˜&™. Assuming that the ¬nal point
of p is the ¬rst point of q, the path ˜p softjoin q™ begins on p until coming within fullcircle
join radius of this common point; then it curves over and ¬nishes q in essentially the incr
same way. The internal quantity join radius should be set to the desired value before transform
softjoin is applied. (This routine is due to N. N. Billawala.) re¬‚ectedabout
tertiarydef p softjoin q = rotatedabout
begingroup c_:=fullcircle scaled 2join_radius shifted point 0 of q; min
a_:=ypart(c_ intersectiontimes p); b_:=ypart(c_ intersectiontimes q); setu
if a_<0:point 0 of p{direction 0 of p} else: subpath(0,a_) of p fi
... if b_<0:{direction infinity of q}point infinity of q
else: subpath(b_,infinity) of q fi endgroup enddef;
newinternal join_radius,a_,b_; path c_;
The remaining math operators don™t fall into the ordinary patterns; something
is unusual about each of them. First we have ˜incr™ and ˜decr™, which apply only to
variables; they have the side e¬ect of changing the variable™s value.
vardef incr suffix $ = $:=$+1; $ enddef;
vardef decr suffix $ = $:=$-1; $ enddef;
You can say either ˜incr x™ or ˜incr (x)™, within an expression; but ˜incr x™ by itself
is not a valid statement.
To re¬‚ect about a line, we compute a transform on the ¬‚y:
def reflectedabout(expr w,z) = % reflects about the line w..z
begingroup transform T_;
w transformed T_ = w; z transformed T_ = z;
xxpart T_ = -yypart T_; xypart T_ = yxpart T_; % T_ is a reflection
T_ endgroup enddef;
def rotatedaround(expr z, d) = % rotates d degrees around z
shifted -z rotated d shifted z enddef;
let rotatedabout = rotatedaround; % for roundabout people
Now we come to an interesting trick: The user writes something like ˜min(a, b)™
or ˜max(a, b, c, d)™, and ™s notation for macro calls makes it easy to separate
the ¬rst argument from the rest”assuming that at least two arguments are present.
vardef max(expr u)(text t) = % t is a list of numerics, pairs, or strings
save u_; setu_ u; for uu = t: if uu>u_: u_:=uu; fi endfor
u_ enddef;
vardef min(expr u)(text t) = % t is a list of numerics, pairs, or strings
save u_; setu_ u; for uu = t: if uu<u_: u_:=uu; fi endfor
u_ enddef;
def setu_ primary u =
if pair u: pair u_ elseif string u: string u_ fi;
u_=u enddef;
Appendix D discusses some variations on this theme.
Appendix B: Basic Operations 267

The flex routine de¬nes part of a path whose directions at the endpoints will ¬‚ex
depend on the environment, because this path is not enclosed in parentheses.
def flex(text t) = % t is a list of pairs tolerance
hide(n_:=0; for z=t: z_[incr n_]:=z; endfor mm
z_1 for k=2 upto n_-1: ...z_[k]{dz_} endfor ...z_[n_] enddef; pc
newinternal n_; pair z_[],dz_; dd
The ¬ve parameters to ˜superellipse™ are the right, the top, the left, the bottom, bp
and the superness.
¬x units
pixels per inch
def superellipse(expr r,t,l,b,s)=
r{up}...(s[xpart t,xpart r],s[ypart r,ypart t]){t-r}...
t{left}...(s[xpart t,xpart l],s[ypart l,ypart t]){l-t}...
l{down}...(s[xpart b,xpart l],s[ypart l,ypart b]){b-l}...
b{right}...(s[xpart b,xpart r],s[ypart r,ypart b]){r-b}...cycle enddef;
Chapter 14 illustrates the ˜interpath™ routine, which interpolates between
paths to ¬nd a path that would be written ˜a[p, q]™ if ™s macro notation
were more general.
vardef interpath(expr a,p,q) =
for t=0 upto length p-1: a[point t of p, point t of q]
..controls a[postcontrol t of p, postcontrol t of q]
and a[precontrol t+1 of p, precontrol t+1 of q] .. endfor
if cycle p: cycle
else: a[point infinity of p, point infinity of q] fi enddef;
Finally we come to the solve macro, which has already been presented in
Chapter 20. Appendix D gives further illustrations of its use.
vardef solve@#(expr true_x,false_x)= % @#(true_x)=true, @#(false_x)=false
tx_:=true_x; fx_:=false_x;
forever: x_:=.5[tx_,fx_]; exitif abs(tx_-fx_)<=tolerance;
if @#(x_): tx_ else: fx_ fi :=x_; endfor
x_ enddef; % now x_ is near where @# changes from true to false
newinternal tolerance, tx_,fx_,x_; tolerance:=.1;
3. Conversion to pixels. The next main subdivision of plain.mf contains macros and
constants that help convert dimensions from device-independent “sharped” or “true”
units into the pixel units corresponding to a particular device. First comes a subroutine
that computes eight basic units, assuming that the number of pixels per inch is known:
def fix_units = % define the conversion factors, given pixels_per_inch
mm:=pixels_per_inch/25.4; cm:=pixels_per_inch/2.54;
pt:=pixels_per_inch/72.27; pc:=pixels_per_inch/6.0225;
dd:=1238/1157pt; cc:=12dd;
bp:=pixels_per_inch/72; in:=pixels_per_inch;
hppp:=pt; % horizontal pixels per point
vppp:=aspect_ratio*hppp; % vertical pixels per point
268 Appendix B: Basic Operations

Sharped units are actually expressed in terms of points, but a virtuous user Sharped units
pixels per inch
will not write programs that exploit this fact.
o correction
mm#=2.84528; pt#=1; dd#=1.07001; bp#=1.00375; ¬llin
cm#=28.45276; pc#=12; cc#=12.84010; in#=72.27; de¬ne pixels (and nine others)
lowres ¬x
A particular device is supposed to be modeled by four parameters, called
pixels per inch , blacker , o correction , and ¬llin , as discussed in Chapter 11. Appro-
priate values will be assigned to these internal quantities by mode setup.
newinternal pixels_per_inch; % the given resolution
newinternal blacker, o_correction; % device-oriented corrections
(The fourth parameter, ¬llin , is already an internal quantity of .)
Here are the ten principal ways to convert from sharped units to pixels:
def define_pixels(text t) =
forsuffixes $=t: $:=$.#*hppp; endfor enddef;
def define_whole_pixels(text t) =
forsuffixes $=t: $:=hround($.#*hppp); endfor enddef;
def define_whole_vertical_pixels(text t) =
forsuffixes $=t: $:=vround($.#*hppp); endfor enddef;
def define_good_x_pixels(text t) =
forsuffixes $=t: $:=good.x($.#*hppp); endfor enddef;
def define_good_y_pixels(text t) =
forsuffixes $=t: $:=good.y($.#*hppp); endfor enddef;
def define_blacker_pixels(text t) =
forsuffixes $=t: $:=$.#*hppp+blacker; endfor enddef;
def define_whole_blacker_pixels(text t) =
forsuffixes $=t: $:=hround($.#*hppp+blacker);
if $<=0: $:=1; fi endfor enddef;
def define_whole_vertical_blacker_pixels(text t) =
forsuffixes $=t: $:=vround($.#*hppp+blacker);
if $<=0: $:=1_o_; fi endfor enddef;
def define_corrected_pixels(text t) =
forsuffixes $=t: $:=vround($.#*hppp*o_correction)+eps; endfor enddef;
def define_horizontal_corrected_pixels(text t) =
forsuffixes $=t: $:=hround($.#*hppp*o_correction)+eps; endfor enddef;
Chapter 24 discusses the lowres ¬x routine, which helps to correct anomalies
that may have occurred when sharped dimensions were rounded to whole pixels.
def lowres_fix(text t) expr ratio =
begingroup save min,max,first;
forsuffixes $=t:
if unknown min: min=max=first=$; min#=max#=$.#;
elseif $.#<min#: min:=$; min#:=$.#;
elseif $.#>max#: max:=$; max#:=$.#; fi endfor
if max/min>ratio*max#/min#: forsuffixes $=t: $:=first; endfor fi
endgroup enddef;
Appendix B: Basic Operations 269

4. Modes of operation. The standard way to create a font with plain is to **
start up the program by saying
\mode= mode name ; mag= magni¬cation ; input font ¬le name smode
mode setup
in response to ™s initial ˜**™. The mag is omitted if the magni¬cation is 1, smode
and the mode is omitted if mode=proof. Additional commands like ˜screenchars™ might displaying
extra setup
be given before the ˜input™; we shall discuss them later. If you are using another
mode name
base ¬le, called say the ˜super™ base, this whole command line should be preceded scantokens
by ˜&super™. The mode name should have been predeclared in your base ¬le, by the
mode_def routine below. If, however, you need a special mode that isn™t in the base,
you can put its commands into a ¬le (e.g., ˜specmode.mf™) and invoke it by saying
\smode="specmode"; mag= · · ·
instead of giving a predeclared mode name.
Here is the mode setup routine, which is usually one of the ¬rst macros to
be called in a program:
def mode_setup =
if unknown mode: mode=proof; fi
numeric aspect_ratio; transform currenttransform;
scantokens if string mode:("input "&mode) else: mode_name[mode] fi;
if unknown mag: mag=1; fi
if unknown aspect_ratio: aspect_ratio=1; fi
if aspect_ratio=1: let o_=\; let _o_=\
else: def o_=*aspect_ratio enddef; def _o_=/aspect_ratio enddef fi;
scantokens extra_setup; % the user™s special last-minute adjustments
if unknown currenttransform: identity else: currenttransform fi
yscaled aspect_ratio;
pickup pencircle scaled (.4pt+blacker);
warningcheck:=1; enddef;
def smode = string mode; mode enddef;
string extra_setup, mode_name[];
extra_setup=""; % usually there™s nothing special to do
newinternal displaying; % if positive, endchar will ˜showit™
The ¬rst ˜scantokens™ in mode setup either reads a special ¬le or calls a macro that
expands into commands de¬ning the mode. Notice that aspect ratio is always cleared to
an unde¬ned value when these commands are performed; you can™t simply give a value
to aspect ratio when you set mode and mag . If the aspect ratio isn™t assigned a de¬nite
value by the mode routine, it will become unity, and the ˜o_™ and ˜_o_™ operations will
be omitted from subsequent calculations. Notice also that the mode commands might
do something special to mag , since mag isn™t examined until after the mode routine
has acted. The currenttransform might also be given a special value. ™s
270 Appendix B: Basic Operations

warningcheck is temporarily disabled during these computations, since there might be warningcheck
more than 4096 pixels per inch. After mode setup is ¬nished, the currentpicture will
be null, the currenttransform will take the aspect ratio into account, and the currentpen underscore
will be a circular nib with the standard default thickness of 0.4 pt. (You should save expandafter
this pen if you want to use it in a character, because beginchar will clear it away.) mode def
Plain TEX has a convention for magnifying fonts in terms of “magsteps,” where Leban
magstep m = 1.2m . A geometric progression of font sizes is convenient, because scaling proof
by magstep m followed by magstep n is equivalent to scaling by magstep m + n. fontmaking
vardef magstep primary m = mexp(46.67432m) enddef;
When a mode is de¬ned (e.g., ˜proof™), a numeric variable of that name is
created and assigned a unique number (e.g., 1). Then an underscore character is ap-
pended, and a macro is de¬ned for the resulting name (e.g., ˜proof_™). The mode name
array is used to convert between number and name (e.g., mode name 1 = "proof_").
def mode_def suffix $ =
$:=incr number_of_modes;
mode_name[$]:=str$ & "_";
expandafter quote def scantokens mode_name[$] enddef;
newinternal number_of_modes;
(This mode def strategy was suggested by Bruce Leban.)
Three basic modes are now de¬ned, starting with two for proo¬ng:
% proof mode: for initial design of characters
mode_def proof =
proofing:=2; % yes, we™re making full proofs
fontmaking:=0; % no, we™re not making a font
tracingtitles:=1; % yes, show titles online
pixels_per_inch:=2601.72; % that™s 36 pixels per pt
blacker:=0; % no additional blackness
fillin:=0; % no compensation for fillin
o_correction:=1; % no reduction in overshoot
% smoke mode: for label-free proofs to mount on the wall
mode_def smoke =
proof_; % same as proof mode, except:
proofing:=1; % yes, we™re making unlabeled proofs
extra_setup:=extra_setup&"grayfont black"; % with solid black pixels
let makebox=maketicks; % make the boxes less obtrusive
Notice that smoke mode saves a lot of fuss by calling on ˜proof_™; this is the macro
that was de¬ned by the ¬rst mode def .
A typical mode for font generation appears next.
% lowres mode: for certain devices that print 200 pixels per inch
mode_def lowres =
proofing:=0; % no, we™re not making proofs
fontmaking:=1; % yes, we are making a font
Appendix B: Basic Operations 271

tracingtitles:=0; % no, don™t show titles at all currentpen
pixels_per_inch:=200; % that™s pretty low resolution
blacker:=.65; % make pens a bit blacker ¬ll
fillin:=.2; % compensate for diagonal fillin
o_correction:=.4; % but don™t overshoot as much drawdot
enddef; un¬ll
localfont:=lowres; % the mode most commonly used to make fonts un¬lldraw
Installations of typically have several more prede¬ned modes, and they erase
generally set localfont to something else. Such alterations should not be made in the cutdraw
master ¬le plain.mf; they should appear in a separate ¬le, as discussed below.
5. Drawing and ¬lling. Now we come to the macros that provide an interface between
the user and ™s primitive picture commands. First, some important program
variables are introduced:
pen currentpen;
path currentpen_path;
picture currentpicture;
transform currenttransform;
def t_ = transformed currenttransform enddef;
The key macros are ¬ll, draw, ¬lldraw, and drawdot.
def fill expr c = addto_currentpicture contour c.t_ enddef;
def addto_currentpicture = addto currentpicture enddef;
def draw expr p =
addto_currentpicture doublepath p.t_ withpen currentpen enddef;
def filldraw expr c = fill counterclockwise c withpen currentpen enddef;
def drawdot expr z = if unknown currentpen_path: def_pen_path_ fi
addto_currentpicture contour
currentpen_path shifted (z.t_) withpen penspeck enddef;
def def_pen_path_ =
hide(currentpen_path=tensepath makepath currentpen) enddef;
And they have negative counterparts:
def unfill expr c = fill c withweight -1 enddef;
def undraw expr p = draw p withweight -1 enddef;
def unfilldraw expr c = filldraw c withweight -1 enddef;
def undrawdot expr z = drawdot z withweight -1 enddef;
def erase text t = begingroup interim default_wt_:=-1;
cullit; t withweight -1; cullit; endgroup enddef;
newinternal default_wt_; default_wt_:=1;
It™s more di¬cult to cut o¬ the ends of a stroke, but the following macros
(discussed near the end of Chapter 16) do the job:
def cutdraw expr p = % caution: you may need autorounding=0
cutoff(point 0 of p, 180+angle direction 0 of p);
cutoff(point infinity of p, angle direction infinity of p);
culldraw p enddef;
272 Appendix B: Basic Operations

def culldraw expr p = addto pic_ doublepath p.t_ withpen currentpen; culldraw
cull pic_ dropping(-infinity,0) withweight default_wt_;
default wt
addto_currentpicture also pic_; pic_:=nullpicture; killtext enddef; erase
pen lft
vardef cutoff(expr z,theta) =
pen rt
interim autorounding := 0; interim smoothing := 0; pen top
pen bot
addto pic_ doublepath z.t_ withpen currentpen;
addto pic_ contour savepen
(cut_ scaled (1+max(-pen_lft,pen_rt,pen_top,-pen_bot)) clearpen
rotated theta shifted z)t_;
cull pic_ keeping (2,2) withweight -default_wt_;
addto currentpicture also pic_; pic_:=nullpicture enddef;
picture pic_; pic_:=nullpicture;
path cut_; cut_ = ((0,-1)--(1,-1)--(1,1)--(0,1)--cycle) scaled 1.42;
The use of default wt here makes ˜erase cutdraw™ work. The private variable pic is
usually kept equal to nullpicture in order to conserve memory space.
Picking up a pen not only sets currentpen , it also establishes the values of
pen lft , pen rt , pen top , and pen bot , which are used by lft , rt , top , and bot .

def pickup secondary q =
if numeric q: numeric_pickup_ else: pen_pickup_ fi q enddef;
def numeric_pickup_ primary q =
if unknown pen_[q]: errmessage "Unknown pen"; clearpen
else: currentpen:=pen_[q];
pen_lft:=pen_lft_[q]; pen_rt:=pen_rt_[q];
pen_top:=pen_top_[q]; pen_bot:=pen_bot_[q];
currentpen_path:=pen_path_[q] fi; enddef;
def pen_pickup_ primary q =
currentpen:=q yscaled aspect_ratio;
pen_lft:=xpart penoffset down of currentpen;
pen_rt:=xpart penoffset up of currentpen;
pen_top:=(ypart penoffset left of currentpen)_o_;
pen_bot:=(ypart penoffset right of currentpen)_o_;
path currentpen_path; enddef;
newinternal pen_lft,pen_rt,pen_top,pen_bot,pen_count_;
And saving a pen saves all the relevant values for later retrieval.

vardef savepen = pen_[incr pen_count_]=currentpen;
pen_count_ enddef;
def clearpen = currentpen:=nullpen;
path currentpen_path; enddef;
Appendix B: Basic Operations 273

def clear_pen_memory = clear pen memory
numeric pen_lft_[],pen_rt_[],pen_top_[],pen_bot_[]; top
pen currentpen,pen_[];
path currentpen_path, pen_path_[]; good.x
enddef; good.y
The four basic pen-edge functions o¬er no surprises:
vardef lft primary x = x + if pair x: (pen_lft,0) else: pen_lft fi enddef; penpos
vardef rt primary x = x + if pair x: (pen_rt,0) else: pen_rt fi enddef; penstroke
vardef top primary y = y + if pair y: (0,pen_top) else: pen_top fi enddef;
vardef bot primary y = y + if pair y: (0,pen_bot) else: pen_bot fi enddef;
There are six functions that round to good positions for pen placement.
vardef good.x primary x = hround(x+pen_lft)-pen_lft enddef;
vardef good.y primary y = vround(y+pen_top)-pen_top enddef;
vardef good.lft primary z = save z_; pair z_;
(z_+(pen_lft,0))t_=round((z+(pen_lft,0))t_); z_ enddef;
vardef good.rt primary z = save z_; pair z_;
(z_+(pen_rt,0))t_=round((z+(pen_rt,0))t_); z_ enddef;
vardef good.top primary z = save z_; pair z_;
(z_+(0,pen_top))t_=round((z+(0,pen_top))t_); z_ enddef;
vardef good.bot primary z = save z_; pair z_;
(z_+(0,pen_bot))t_=round((z+(0,pen_bot))t_); z_ enddef;
So much for ¬xed pens. When pen-like strokes are de¬ned by outlines, the
penpos macro is of primary importance. Since penpos may be used quite frequently,
we might as well write out the x and y coordinates explicitly instead of using the
(somewhat slower) z convention:

vardef penpos@#(expr b,d) =
(x@#r-x@#l,y@#r-y@#l)=(b,0) rotated d;
x@#=.5(x@#l+x@#r); y@#=.5(y@#l+y@#r) enddef;
Simulated pen strokes are provided by the convenient penstroke command.
def penstroke text t =
forsuffixes e = l,r: path_.e:=t; endfor
if cycle path_.l: cyclestroke_
else: fill path_.l -- reverse path_.r -- cycle fi enddef;
def cyclestroke_ =
begingroup interim turningcheck:=0;
addto pic_ contour path_.l.t_ withweight 1;
addto pic_ contour path_.r.t_ withweight -1;
cull pic_ dropping origin withweight default_wt_;
addto_currentpicture also pic_;
pic_:=nullpicture endgroup enddef;
path path_.l,path_.r;
274 Appendix B: Basic Operations

6. Proof labels and rules. The next main section of plain.mf is devoted to macros for special
the annotations on proofsheets. These macros are discussed in Appendix H, and they
use the special and numspecial commands discussed in Appendix G. makelabel
Labels are generated at the lowest level by makelabel : proo¬ng
vardef makelabel@#(expr s,z) = % puts string s at point z
if known z: special lcode_@# & s; numtok
numspecial xpart(z.t_); numspecial ypart(z.t_) fi enddef; thru
string lcode_,lcode_.top,lcode_.lft,lcode_.rt,lcode_.bot, proofrule
lcode_.top.nodot,lcode_.lft.nodot,lcode_.rt.nodot,lcode_.bot.nodot; screenrule
lcode_.top=" 1"; lcode_.lft=" 2"; lcode_.rt=" 3"; lcode_.bot=" 4";
lcode_=" 0"; % change to " /" to avoid listing in overflow column
lcode_.top.nodot=" 5"; lcode_.lft.nodot=" 6";
lcode_.rt.nodot=" 7"; lcode_.bot.nodot=" 8";
Users generally don™t invoke makelabel directly, because there™s a conve-
nient shorthand. For example, ˜labels(1, 2, 3)™ expands into ˜makelabel ("1", z1 );
makelabel ("2", z2 ); makelabel ("3", z3 )™. (But nothing happens if proo¬ng ¤ 1.)
vardef labels@#(text t) =
if proofing>1: forsuffixes $=t: makelabel@#(str$,z$); endfor fi enddef;
vardef penlabels@#(text t) =
if proofing>1: forsuffixes $$=l,,r: forsuffixes $=t:
makelabel@#(str$.$$,z$.$$); endfor endfor fi enddef;
When there are lots of purely numeric labels, you can say, e.g.,
labels(1, range 5 thru 9, range 100 thru 124, 223)
which is equivalent to ˜labels(1, 5, 6, 7, 8, 9, 100, 101, . . . , 124, 223)™. Labels are omitted
from the proofsheets if the corresponding z value isn™t known, so it doesn™t hurt (much)
to include unused subscript numbers in a range.
def range expr x = numtok[x] enddef;
def numtok suffix x=x enddef;
tertiarydef m thru n =
m for x=m+1 step 1 until n: , numtok[x] endfor enddef;


. 9
( 13)