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]