<< . .

. 32
( : 45)

. . >>

 
 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]

<< . .

. 32
( : 45)

. . >>