<< . .

. 32
( : 45)



. . >>

 
 picture 

 
  
  

    
 
   string 

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


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

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

 
 
empty
± top 
± 

 lft  
  titlefont
   
nodot labelfont
rt ( string , pair ); name ;
makelabel
 bot  empty  grayfont 
   
 
  slantfont
empty
proofrule
( pair , pair ); makegrid( pairs )( pairs );
screenrule
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
dump
somehow softjoin and stop never made it into Chapters 1 through 27”we shall
delimiters
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
upto
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
relax
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
killtext
the base ¬le a name and version number as shown here. gobbled
hide
% This is the plain METAFONT base that™s described in The METAFONTbook.
???
% N.B.: Please change "base_version" whenever this file is modified! stop
MFT
% And don™t modify the file under any circumstances.
pretty-printed
string base_name, base_version; base_name="plain"; base_version="2.71"; vacuous
exitif
message "Preloading the plain base, version " & base_version;
internal quantities
delimiters (); % this makes parentheses behave like parentheses smoothing
autorounding
Next we de¬ne some of the simplest macros, which provide “syntactic sugar” turningcheck
granularity
for commonly occurring idioms. For example, ˜stop "hello"™ displays ˜hello™ on the
interact
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
tracingall
tracingcommands:=3; tracingedges:=2; tracingtitles:=1;
tracingnone
tracingequations:=1; tracingcapsules:=1; tracingspecs:=1; semicolon
eps
tracingpens:=1; tracingchoices:=1; tracingstats:=1;
epsilon
tracingoutput:=1; tracingmacros:=1; tracingrestores:=1; in¬nity
origin
enddef;
up
def tracingall = % turns on every form of tracing down
tracingonline:=1; showstopping:=1; loggingall enddef; right
left
def tracingnone = % turns off every form of tracing quartercircle
tracingcommands:=0; tracingonline:=0; showstopping:=0; halfcircle
fullcircle
tracingedges:=0; tracingtitles:=0; tracingequations:=0;
unitsquare
tracingcapsules:=0; tracingspecs:=0; tracingpens:=0; identity
blankpicture
tracingchoices:=0; tracingstats:=0; tracingoutput:=0;
unitpixel
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;
unitsquare=(0,0)--(1,0)--(1,1)--(0,1)--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
penrazor
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);
penspeck
pen penspeck; penspeck=pensquare scaled eps; whatever
abs
round
The pensquare and penrazor constants are de¬ned here in a surprisingly roundabout
hround
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
byte
so this trick saves time. But how does it work? Well, a variable cannot be a future pen,
dir
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
counterclockwise
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
tensepath
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
private
routines gain e¬ciency by using “private” tokens that are assumed to be distinct from
underscore
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
dotprod
occur, provided that di¬erent macro designers who combine their routines are careful takepower
that their private names are not in con¬‚ict. **
direction
The private tokens ˜o_™ and ˜_o_™ used in vround stand for ˜*aspect_ratio™
directionpoint
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
intersectiontimes
identities a(x mod y) = (ax) mod (ay) and (ax) div (ay) = x div y are valid. internal quantity
e¬cient
% 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
enddef;
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
Billawala
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
decr
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
rotatedaround
tertiarydef p softjoin q = rotatedabout
max
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
transformed
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
superellipse
depend on the environment, because this path is not enclosed in parentheses.
interpath
solve
def flex(text t) = % t is a list of pairs tolerance
hide(n_:=0; for z=t: z_[incr n_]:=z; endfor mm
cm
dz_:=z_[n_]-z_1)
pt
z_1 for k=2 upto n_-1: ...z_[k]{dz_} endfor ...z_[n_] enddef; pc
newinternal n_; pair z_[],dz_; dd
cc
The ¬ve parameters to ˜superellipse™ are the right, the top, the left, the bottom, bp
in
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)



. . >>