ńņš. 10 |

even omit the word ā˜rangeā™. But you might ļ¬ll up ā™s main memory if too

many values are involved.)

A straight line will be drawn on the proofsheet by proofrule. Although

makelabel takes the current transform into account, proofrule does not. Thereā™s also

a corresponding routine ā˜screenruleā™ that puts a straight line in the current picture,

so that design guidelines will be visible on your screen:

def proofrule(expr w,z) =

special "rule"; numspecial xpart w; numspecial ypart w;

numspecial xpart z; numspecial ypart z enddef;

def screenrule(expr w,z) =

addto currentpicture doublepath w--z withpen rulepen enddef;

pen rulepen; rulepen = pensquare scaled 2;

Appendix B: Basic Operations 275

(The rulepen is two pixels wide, because screen rules are usually drawn exactly over rulepen

pensquare

raster lines. A two-pixel-wide pen straddles the pixel edges so that you can āseeā the

makegrid

correct line position. If a two-pixel-wide line proves to be too dark, you can redeļ¬ne titlefont

rulepen to be simply pensquare; then will draw the thinnest possible labelfont

grayfont

screen rule, but it will be a half-pixel too high and a half-pixel too far to the right.) slantfont

You can produce lots of proof rules with makegrid, which connects an arbi- proofoļ¬set

proofrulethickness

trary list of x coordinates with an arbitrary list of y coordinates:

beginchar

endchar

def makegrid(text xlist,ylist) = charcode

charwd

xmin_ := min(xlist); xmax_ := max(xlist);

charht

ymin_ := min(ylist); ymax_ := max(ylist); chardp

w

for x=xlist: proofrule((x,ymin_), (x,ymax_)); endfor

h

for y=ylist: proofrule((xmin_,y), (xmax_,y)); endfor d

italic correction

enddef;

italcorr

Finally we have a few macros that allow further communication with the

hardcopy proof-drawing routine of Appendix H. You can change the fonts, the thickness

of proof rules, and the position of the image on its page.

vardef titlefont suffix $ = special "titlefont "&str$ enddef;

vardef labelfont suffix $ = special "labelfont "&str$ enddef;

vardef grayfont suffix $ = special "grayfont "&str$ enddef;

vardef slantfont suffix $ = special "slantfont "&str$ enddef;

def proofoffset primary z = % shift proof output by z

special "offset"; numspecial xpart z; numspecial ypart z enddef;

vardef proofrulethickness expr x =

special "rulethickness"; numspecial x enddef;

7. Character and font administration. After this elaborate preparation, weā™re ļ¬nally

ready to consider the beginchar . . . endchar framework for the individual characters of

a font. Each beginchar begins a group, which should end at the next endchar. Then

beginchar stores the given character code and device-independent box dimensions

ā™s internal variables charcode , charwd , charht , and chardp . Then it

in

computes the device-dependent box dimensions w , h , and d . Finally it clears the z

variables, the current picture, and the current pen.

def beginchar(expr c,w_sharp,h_sharp,d_sharp) =

begingroup

charcode:=if known c: byte c else: 0 fi;

charwd:=w_sharp; charht:=h_sharp; chardp:=d_sharp;

w:=hround(charwd*hppp); h:=vround(charht*hppp); d:=vround(chardp*hppp);

charic:=0; clearxy; clearit; clearpen; scantokens extra_beginchar;

enddef;

The italic correction is normally zero, unless the user gives an ā˜italcorrā™ command;

even then, the correction stays zero unless the given value is positive:

def italcorr expr x_sharp = if x_sharp>0: charic:=x_sharp fi enddef;

276 Appendix B: Basic Operations

When we want to change the pixel width w from even to odd or vice versa, the change width

chardx

change width macro does the right thing.

extra beginchar

extra endchar

def change_width = bounding box

w:=w if w>charwd*hppp:- else:+ fi 1 enddef; makebox

italic correction

(The user might also decide to change w in some other way.) The current value of w maketicks

font size etc

at the time of endchar will be the āoļ¬cialā pixel width of the character, chardx , that

is shipped to the gf output ļ¬le.

def endchar =

scantokens extra_endchar;

if proofing>0: makebox(proofrule); fi

chardx:=w; % desired width of the character in pixels

shipit;

if displaying>0: makebox(screenrule); showit; fi

endgroup enddef;

Extensions to these routines can be provided by putting commands in the string vari-

ables extra beginchar and extra endchar .

string extra_beginchar, extra_endchar;

extra_beginchar=extra_endchar="";

A ābounding boxā that surrounds the character according to the speciļ¬cations

given in beginchar is produced by makebox, which takes into account the possibility

that pixels might not be square. An extra line is drawn to mark the width of the

character with its italic correction included, if this correction is nonzero.

def makebox(text r) =

for y=0,h.o_,-d.o_: r((0,y),(w,y)); endfor % horizontals

for x=0,w: r((x,-d.o_),(x,h.o_)); endfor % verticals

if charic<>0: r((w+charic*hppp,h.o_),(w+charic*hppp,.5h.o_)); fi

enddef;

The maketicks routine is an alternative to makebox that draws less con-

spicuous lines. This makes it easier to visualize a characterā™s appearance near the edges

of its bounding box.

def maketicks(text r) =

for y=0,h.o_,-d.o_: r((0,y),(10,y)); r((w-10,y),(w,y)); endfor

for x=0,w: r((x,10-d.o_),(x,-d.o_)); r((x,h.o_-10),(x,h.o_)); endfor

if charic<>0: r((w+charic*hppp,h.o_-10),(w+charic*hppp,h.o_)); fi

enddef;

Overall information about the font as a whole is generally supplied by the

following commands, which are explained in Appendix F.

def font_size expr x = designsize:=x enddef;

def font_slant expr x = fontdimen 1: x enddef;

def font_normal_space expr x = fontdimen 2: x enddef;

def font_normal_stretch expr x = fontdimen 3: x enddef;

def font_normal_shrink expr x = fontdimen 4: x enddef;

Appendix B: Basic Operations 277

def font_x_height expr x = fontdimen 5: x enddef; z

screen rows

def font_quad expr x = fontdimen 6: x enddef;

screen cols

def font_extra_space expr x = fontdimen 7: x enddef; openit

showit

def font_identifier expr x = font_identifier_:=x enddef; clearxy

def font_coding_scheme expr x = font_coding_scheme_:=x enddef; clearit

shipit

string font_identifier_, font_coding_scheme_;

cullit

font_identifier_=font_coding_scheme_="UNSPECIFIED"; command line

screenchars

screenstrokes

8. The endgame. What have we left out? A few miscellaneous things still need to be

handled. First, we almost forgot to deļ¬ne the z convention for points:

vardef z@#=(x@#,y@#) enddef;

Then we need to do something rudimentary about ā™s āwindows.ā

newinternal screen_rows, screen_cols, currentwindow;

screen_rows:=400; % these values should be corrected,

screen_cols:=500; % by reading in a separate file after plain.mf

def openit = openwindow currentwindow

from origin to (screen_rows,screen_cols) at (-50,300) enddef;

def showit = openit; let showit=showit_; showit enddef; % first time only

def showit_ = display currentpicture inwindow currentwindow enddef;

Plain has several other terse commands like ā˜openitā™ and ā˜showitā™:

def clearxy = save x,y enddef;

def clearit = currentpicture:=nullpicture enddef;

def shipit = shipout currentpicture enddef;

def cullit = cull currentpicture dropping (-infinity,0) enddef;

The next several macros are handy things to put on your command line when

you are starting a job (i.e., just before ā˜input font ļ¬le name ā™):

screenchars. Say this when youā™re making a font but want the characters to

be displayed just before they are shipped out.

screenstrokes. Say this when youā™re in proof mode and want to see each

stroke as itā™s added to the current picture.

imagerules. Say this when you want to include the bounding box in the

current character, before you begin to draw it.

gfcorners. Say this when you expect to make proofsheets with large pixels,

from a low-resolution font.

nodisplays. Say this to save computer time when you donā™t want proof mode

to display each character automatically.

notransforms. Say this to save computer time when you know that the current

transform is the identity.

def screenchars = % endchar should ā˜showitā™

extra_endchar:=extra_endchar&"showit;" enddef;

def screenstrokes = % every stroke should ā˜showitā™

def addto_currentpicture text t=

addto currentpicture t; showit enddef; enddef;

278 Appendix B: Basic Operations

def imagerules = % a box should be part of the character image imagerules

gfcorners

extra_beginchar:=extra_beginchar & "makebox(screenrule);" enddef;

nodisplays

notransforms

def gfcorners = % ā˜maketicksā™ should send rules to the gf file

bye

extra_setup:=extra_setup & "let makebox=maketicks;proofing:=1;" enddef; end

clear pen memory

def nodisplays = % endchar shouldnā™t ā˜showitā™

mode setup

extra_setup:=extra_setup & "displaying:=0;" enddef; mode

mag

def notransforms = % currenttransform should not be used

localfont

let t_ = \ enddef; screen rows

screen cols

We make ā˜byeā™ synonymous with ā˜endā™, just in case TEX users expect - ļ¬llin

programs to end like TEX documents do.

let bye = end; outer end,bye;

And ļ¬nally, we provide the default environment that a user gets when simple

experiments like those at the beginning of Chapter 5 are desired.

clear_pen_memory; % initialize the ā˜savepenā™ mechanism

mode_setup; % establish proof mode as the default

numeric mode,mag; % but leave mode and mag undefined

Whew! Thatā™s the end of the plain.mf ļ¬le.

9. Adapting to local conditions. In order to make plain programs inter-

changeable between diļ¬erent computers, everybody should use the same plain.mf base.

But there are some things that clearly should be customized at each installation:

Additional modes should be deļ¬ned, so that fonts can be made for whatever

output devices are of interest.

The proper localfont mode should be established.

The correct numbers should be assigned to screen rows and screen cols .

Hereā™s an example of a supplementary ļ¬le ā˜local.mfā™ that would be appropriate for

a computer center with the hypothetical cheapo and luxo printers described in Chap-

ter 11. We assume that cheapo mode is to be identical to lowres mode, except that

the cheapo fonts should be generated with a negative value of ļ¬llin (because cheapo

tends to make diagonal lines lighter than normal, not heavier). The terminal screens

are assumed to be 768 pixels wide and 512 pixels high.

% A file to be loaded after "plain.mf".

base_version:=base_version&"/drofnats";

screen_rows:=512; screen_cols:=768;

mode_def cheapo = % cheapo mode: to generate fonts for cheapo

lowres_; % do as in lowres mode, except:

fillin:=-.1; % compensate for lighter diagonals

enddef;

mode_def luxo = % luxo mode: to generate fonts for luxo

proofing:=0; % no, weā™re not making proofs

fontmaking:=1; % yes, we are making a font

tracingtitles:=1; % yes, show titles online

pixels_per_inch:=2000; % almost 30 pixels per pt

Appendix B: Basic Operations 279

blacker:=.2; % make pens a teeny bit blacker bye

INIMF

fillin:=.1; % but compensate for heavy diagonals

dump

o_correction:=1; % and keep the full overshoot Computer Modern

asterisk

enddef;

ampersand

localfont:=cheapo; DRAYTON

PAGET

The macro ā˜byeā™ might also be redeļ¬ned, as suggested at the close of Appendix F.

To prepare a preloaded base ļ¬le at this installation, a suitably privileged

person should run INIMF in the following way:

This is METAFONT, Version 2.0 (INIMF) 8 NOV 1989 10:09

**plain

(plain.mf

Preloading the plain base, version 2.0)

*input local

(local.mf)

*dump

Beginning to dump on file plain.base

(The stuļ¬ after ā˜**ā™ or ā˜*ā™ is typed by the user; everything else is typed by the system.

A few more messages actually come out.)

Notice that local.mf does not include any new macros or features that a

programmer could use in a special way. Therefore it doesnā™t make plain

incompatible with implementations at other computing centers.

Changes and/or extensions to the plain.mf macros should never be made,

unless the resulting base ļ¬le is clearly distinguished from the standard plain base. But

new, diļ¬erently named bases are welcome. For example, the author prepared a special

base for the Computer Modern fonts, so that they could be generated without ļ¬rst

reading the same 700 lines of macro deļ¬nitions each time. To load this base at high

speed, he can type ā˜&cmā™ after ā™s initial ā˜**ā™. (Or, on some machines, he has

a special version called ā˜cmmfā™ in which the new base is already present.)

None but the Base, in baseness doth delight.

ā” MICHAEL DRAYTON, Robert, Duke of Normandy (1605)

So far all was plain sailing, as the saying is;

but Mr. Till knew that his main diļ¬culties were yet to come.

ā” FRANCIS E. PAGET, Milford Malvoisin (1842)

(page 280)

C

Character

Codes

Appendix C: Character Codes 281

Diļ¬erent computers tend to have diļ¬erent ways of representing the characters in codes

ASCII

ļ¬les of text, but gives the same results on all machines, because it oct

converts everything to a standard internal code when it reads a ļ¬le. hex

ASCII

also converts back from its internal representation to the appropriate external

code, when it writes a ļ¬le of text; therefore most users need not be aware of the

fact that the codes have actually switched back and forth inside the machine.

The purpose of this appendix is to deļ¬ne ā™s internal code,

which has the same characteristics on all implementations of . The

existence of such a code is important, because it makes programs

portable. ā™s scheme is based on the American Standard Code for

Information Interchange, known popularly as āASCII.ā There are 128 codes,

numbered 0 to 127; we conventionally express the numbers in octal notation, from

oct"000" to oct"177", or in hexadecimal notation, from hex"00" to hex"7F".

Thus, the value of ASCII"b" is normally called oct"142" or hex"62", not 98.

In the ASCII scheme, codes oct"000" through oct"037" and code oct"177"

are assigned to special functions; for example, code oct"007" is called BEL, and

it means āRing the bell.ā The other 95 codes are assigned to visible symbols and

to the blank space character. Here is a chart that shows ASCII codes in such a

way that octal and hexadecimal equivalents can easily be read oļ¬:

Ā“0 Ā“1 Ā“2 Ā“3 Ā“4 Ā“5 Ā“6 Ā“7

Ā“00x NUL SOH STX ETX EOT ENQ ACK BEL

Ė0x

Ā“01x BS HT LF VT FF CR SO SI

Ā“02x DLE DC1 DC2 DC3 DC4 NAK SYN ETB

Ė1x

Ā“03x CAN EM SUB ESC FS GS RS US

Ā“04x ! " # $ % & ā™

Ė2x

Ā“05x ( ) * + , - . /

Ā“06x 0 1 2 3 4 5 6 7

Ė3x

Ā“07x 8 9 : ; < = > ?

Ā“10x @ A B C D E F G

Ė4x

Ā“11x H I J K L M N O

Ā“12x P Q R S T U V W

Ė5x

Ā“13x X Y Z [ \ ] ^ _

Ā“14x ā˜ a b c d e f g

Ė6x

Ā“15x h i j k l m n o

Ā“16x p q r s t u v w

Ė7x

Ā“17x x y z { | } ˜ DEL

Ė8 Ė9 ĖA ĖB ĖC ĖD ĖE ĖF

Ever since ASCII was established in the early 1960s, people have had

diļ¬erent ideas about what to do with positions oct"000" thru oct"037" and

oct"177", because most of the functions assigned to those codes are appropriate

282 Appendix C: Character Codes

only for special purposes like ļ¬le transmission, not for applications to printing Knuth

Ā”Āæ

or to interactive computing. Manufacturers soon started producing line printers Ā”=

that were capable of generating 128 characters, 33 of which were tailored to Āæ=

:=

the special needs of particular customers; part of the advantage of a standard

code was therefore lost. An extended ASCII code intended for text editing and

interactive computing was developed at several universities about 1965, and for

many years there have been terminals in use at Stanford, MIT, Carnegie-Mellon,

and elsewhere that have 120 or 121 symbols, not just 95. For example, the author

developed on a keyboard that includes the symbols ā˜ā ā™, ā˜ā¤ā™, ā˜ā„ā™, and

ā˜āā™, which are easier to use than the character pairs ā˜<>ā™, ā˜<=ā™, ā˜>=ā™, and ā˜:=ā™. The

full character set looks like this:

Ā“0 Ā“1 Ā“2 Ā“3 Ā“4 Ā“5 Ā“6 Ā“7

Ā“00x ā… ā“ Ī± Ī² ā§ Ā¬ ā Ļ

Ė0x

Ā“01x Ī» Ī³ Ī“ ā‘ Ā± ā• ā ā‚

Ā“02x ā‚ ā ā© āŖ ā ā ā— ā”

Ė1x

Ā“03x ā ā’ ā ā— ā¤ ā„ ā” āØ

Ā“04x ! " # $ % & ā™

Ė2x

Ā“05x ( ) * + , ā’ . /

Ā“06x 0 1 2 3 4 5 6 7

Ė3x

Ā“07x 8 9 : ; < = > ?

Ā“10x @ A B C D E F G

Ė4x

Ā“11x H I J K L M N O

Ā“12x P Q R S T U V W

Ė5x

Ā“13x X Y Z [ \ ] ^ _

Ā“14x ā˜ a b c d e f g

Ė6x

Ā“15x h i j k l m n o

Ā“16x p q r s t u v w

Ė7x

Ā“17x x y z { | } ˜ ā«

Ė8 Ė9 ĖA ĖB ĖC ĖD ĖE ĖF

can also be conļ¬gured to accept any or all of the character codes

128ā“255. However, programs that make use of anything in addition

to the 95 standard ASCII characters cannot be expected to run on other systems,

so the use of extended character sets is discouraged.

A possible middle ground has been suggested, based on the fact that itā™s

easy to write a program that converts extended-character ļ¬les into standard ļ¬les

by substituting ā˜<>ā™ for ā˜ā ā™, etc. In the authorā™s implementation at Stanford, the

symbols ā˜ā ā™, ā˜ā¤ā™, ā˜ā„ā™, and ā˜āā™ are considered to be in the same class as ā˜<ā™, ā˜=ā™, ā˜:ā™,

and ā˜>ā™ when tokens are formed (see Chapter 6). Tokens like ā˜ā =ā™ and ā˜<ā„ā™ are

therefore distinct, although they both become ā˜<>=ā™ after conversion. As long as

Appendix C: Character Codes 283

such tokens are avoided, the authorā™s programs can easily be expurgated into a WILKINS

MORISON

portable form for general distribution. (Another feasible approach would have

been to convert nonstandard codes to character pairs during ā™s input

process; that would have been slightly less eļ¬cient.)

Computers with non-ASCII character sets should specify a correspon-

dence between 95 distinct characters and the standard ASCII codes oct"040"

thru oct"176". programs written on any such machines will be

completely interchangeable with each other.

If any shall suggest, that some of the Enquiries here insisted upon

(as particularly those about the Letters of the Alphabet)

do seem too minute and trivial, for any prudent Man

to bestow his serious thoughts and time about.

Such Persons may know, that the discovery

of the true nature and Cause of any the most minute thing,

doth promote real Knowledge,

and therefore cannot be unļ¬t for any Mans endeauours,

who is willing to contribute to the advancement of Learning.

ā” JOHN WILKINS, Towards a Real Character (1668)

Clearly even the simple A.B.C. is a thing of mystery.

Like all codes, it should not be triļ¬‚ed with,

but it is to be feared that in modern times

it has not always been respected.

ā” STANLEY MORISON, On Type Faces (1923)

(page 284)

D

Dirty Tricks

Appendix D: Dirty Tricks 285

Any powerful computer language can be used in ways that go considerably be- Hobby

macros

yond what the language designer originally had in mind, especially when macro mouth

expansion is possible. Sometimes the unexpected constructions are just amus- stomach

expansion

ing; sometimes they are disgustingly arcane. But sometimes they turn out to be for

quite useful, and they graduate from ātricksā to the status of ātechniques.ā (For

example, several of the macros now in Appendix B started out as suggestions

for Appendix D.) In any case, gurus of a language always like to explore its

limits. The depths of have hardly been plumbed, but this appendix

probably reached a new low at the time it was written.

Acknowledgment: More than half of the ideas in this appendix are due

to John Hobby, who has been a tireless and inspiring co-worker during the entire

development of the new system.

Please donā™t read this material until youā™ve had

plenty of experience with plain .

After you have read and understood the secrets below, youā™ll know all sorts of devi-

ous combinations of commands, and you will often be tempted to write

inscrutable macros. Always remember, however, that thereā™s usually a simpler and

better way to do something than the ļ¬rst way that pops into your head. You may not

have to resort to any subterfuge at all, since is able to do lots of things in

a straightforward way. Try for simple solutions ļ¬rst.

1. Macro madness. If you need to write complicated macros, youā™ll need to be familiar

with the ļ¬ne points in Chapter 20. ā™s symbolic tokens are divided into two

main categories, āexpandableā and āunexpandableā; the former category includes all

macros and if . . . ļ¬ tests and for . . . endfor loops, as well as special operations like

input, while the latter category includes the primitive operators and commands listed

in Chapters 25 and 26. The expansion of expandable tokens takes place in ā™s

āmouth,ā but primitive statements (including equations, declarations, and the various

types of commands) are done in ā™s āstomach.ā Thereā™s a communication

between the two, since the stomach evaluates expressions that are needed as arguments

to the mouthā™s macros; any statement can be embedded in a group expression, so

arbitrarily complicated things can be done as part of the expansion process.

Letā™s begin by considering a toy problem that is treated at the beginning of

Appendix D in The TEXbook, in case some readers are interested in comparing TEX to

. Given a numeric variable n ā„ 0, we wish to deļ¬ne a macro asts whose

replacement text consists of precisely n asterisks. This task is somewhat tricky because

expansion is suppressed when a replacement text is being read; we want to use a for

loop, but loops are special cases of expansion. In other words,

def asts = for x=1 upto n: * endfor enddef

deļ¬nes asts to be a macro with a for loop in its replacement text; in practice, asts

would behave as if it contained n asterisks (using possibly diļ¬erent values of n), but

we have not solved the stated problem. The alternative

def makedef primary n =

def asts = for x=1 upto n: * endfor enddef enddef;

makedef n

āfreezesā the present value of n; but this doesnā™t solve the problem either.

286 Appendix D: Dirty Tricks

One solution is to build up the deļ¬nition by adding one asterisk at a time, expandafter

inaccessible

using expandafter as follows:

ENDFOR

def asts = enddef; outer

forbidden token

for x=1 upto n: inner

expandafter def expandafter asts expandafter = asts * enddef; *

concatenation

endfor.

string

pool size

The three expandafters provide a āļ¬ngerā into the replacement text, before def sup-

buļ¬er size

presses expansion; without them the replacement text would turn out to be ā˜asts *ā™, scantokens

causing inļ¬nite recursion. quote

This solution involves a running time proportional to n2 , so the reader might debugging tricks

wonder why a simpler approach like

expandafter def expandafter asts expandafter =

for x = 1 upto n: * endfor enddef

wasnā™t suggested? The reason is that this doesnā™t work, unless n = 0! A for loop isnā™t

entirely expanded by expandafter; only ā™s ļ¬rst step in loop expansion is

carried out. Namely, the loop text is read, and a special inaccessible token ā˜ENDFORā™

is placed at its end. Later on when ā™s mouth encounters ā˜ENDFORā™ (which

incidentally is an expandable token, but it wasnā™t listed in Chapter 20), the loop text

is re-inserted into the input stream, unless of course the loop has ļ¬nished. The special

ENDFOR is an ā˜outerā™ token, hence it should not appear in replacement texts; -

will therefore stop with a āforbidden tokenā error if you try the above with n ā„ 1.

You might try to defeat the outerness by saying

for x=1: inner endfor;

but wonā™t let you. And even if this had worked, it wouldnā™t have solved the

problem; it would simply have put ENDFOR into the replacement text of ast, because

expansion is inhibited when the replacement text is being read.

Thereā™s another way to solve the problem that seems to have running time

proportional to n rather than n2 :

scantokens("def asts=" for x=1 upto n: & "* " endfor) enddef;

but actually ā™s string concatenation operation takes time proportional to

the length of the strings it deals with, so the running time is still order n2 . Furthermore,

the string operations in are rather primitive, because this isnā™t a major

aspect of the language; so it turns out that this approach uses order n2 storage cells

in the string pool, although they are recycled later. Even if the pool size were inļ¬nite,

ā™s ābuļ¬er sizeā would be exceeded for large n, because scantokens puts

the string into the input buļ¬er before scanning it.

Is there a solution of order n? Yes, of course. For example,

def a=a* enddef;

for x=0 upto n:

if x=n: def a=quote quote def asts = enddef; fi

expandafter endfor a enddef;

showtoken asts.

(The ļ¬rst ā˜quoteā™ is removed by the for, hence one will survive until a is redeļ¬ned.

If you donā™t understand this program, try running it with n = 3; insert an isolated

Appendix D: Dirty Tricks 287

expression ā˜0;ā™ just before the ā˜ifā™, and look at the lines of context that are shown input stack size

Derek

when gives you four error messages.) The only ļ¬‚aw in this method is

logo10.mf

that it uses up n cells of stack space; ā™s input stack size may have to be end

increased, if n is bigger than 25 or so. outer

inner

The asterisk problem is just a puzzle; letā™s turn now to a genuine application. Runaway

File ended...

Suppose we want to deļ¬ne a macro called ā˜ten ā™ whose replacement text is the contents

end of a ļ¬le

of the parameter ļ¬le logo10.mf in Chapter 11, up to but not including the last two endinput

lines of that ļ¬le. Those last two lines say

input logo % now generate the font

end % and stop.

The ten macro will make it possible to set up the 10-point parameters repeatedly

(perhaps alternating with 9-point parameters in a nine macro); Appendix E explains

how to create a meta-design tool via such macros.

One idea would be to try to input the entire ļ¬le logo10.mf as the replacement

text for ten . We could nullify the eļ¬ect of the last three unwanted tokens by saying

save input,logo,end;

forsuffixes s=input,logo,end: let s=\; endfor

just before ten is used. To get the entire ļ¬le as a replacement text, we can try one of

the approaches that worked in the asterisk problem, say

expandafter def expandafter ten expandafter = input logo10 enddef.

But this ļ¬rst attempt runs awry if we havenā™t already redeļ¬ned ā˜endā™; Appendix B

makes ā˜endā™ an ā˜outerā™ token, preventing its appearance in replacement texts. So we

say ā˜inner endā™ and try again, only to discover an unwritten law that somehow never

came up in Chapters 20 or 26:

Runaway definition?

font_size10pt#;ht#:=6pt#;xgap#:=0.6pt#;u#:=4/9pt#;s#:=0;o#:=1/ ETC.

! File ended while scanning the definition of ten.

<inserted text>

enddef

l.2 ...fter ten expandafter = input logo10

enddef;

The end of a ļ¬le is invisible; but itā™s treated like an ā˜outerā™ token, in the sense that a

ļ¬le should never end when is passing rapidly over text.

Therefore this whole approach is doomed to failure. Weā™ll have to ļ¬nd a way

to stop the replacement text before the ļ¬le ends. OK, weā™ll redeļ¬ne ā˜inputā™ so that it

means ā˜enddef ā™, and redeļ¬ne logo so that it means ā˜endinputā™.

let INPUT = input; let input = enddef; let logo = endinput;

expandafter def expandafter ten expandafter = INPUT logo10;

showtoken ten.

It works! By the way, the line with three expandafters can be replaced by a more

elegant construction that uses scantokens as follows:

scantokens "def ten=" INPUT logo10;

288 Appendix D: Dirty Tricks

This does the job because always looks ahead and expands the token im- delimiter

text argument

mediately following an expression that is being evaluated. (The expression in this

argument

case is the string "def ten=", which is an argument to scantokens. The token that tracingall

immediately follows an expression almost always needs to be examined in order to and

conditional and

be sure that the expression has ended, so always examines it.) Curi- conditional or

ously, the expandafter alternative causes ten ā™s replacement text to begin with the

tokens ā˜font_size10pt#;ht#:=...ā™, while the scantokens way makes it start with

ā˜designsize:=(10);ht#:=...ā™. Do you see why? In the second case, expansion con-

tinued until an unexpandable token (ā˜designsizeā™) was found, so the font_size macro

was changed into its replacement text; but expandafter just expanded ā˜INPUTā™.

Now letā™s make the problem a bit harder. Suppose we know that ā˜inputā™ comes

at the end of where we want to read, but we donā™t know that ā˜logoā™ will follow. We

know that some program ļ¬le name will be there, but it might not be for the logo font.

Furthermore, letā™s assume that ā˜endā™ might not be present; therefore we canā™t simply

redeļ¬ne it to be enddef . In this case we can make ā˜inputā™ into a right delimiter,

and read the ļ¬le as a delimited text argument; that will give us enough time to insert

other tokens, which will terminate the input and ļ¬‚ush the unwanted ļ¬le name. But

the construction is more complex:

let INPUT = input; delimiters begintext input;

def makedef(expr name)(text t) =

expandafter def scantokens name = t enddef;

endinput flushfilename enddef;

def flushfilename suffix s = enddef;

makedef("ten") expandafter begintext INPUT logo10;

showtoken ten.

This example merits careful study, perhaps with ā˜tracingallā™ to show exactly how

proceeds. We have assumed that the unknown ļ¬le name can be parsed

as a suļ¬x; this solves the problem that a ļ¬le cannot end inside of a text parameter

or a false condition. (If we knew that ā˜endā™ were present, we could have replaced

ā˜endinput flushfilenameā™ by ā˜if false:ā™ and redeļ¬ned ā˜endā™ to be ā˜fiā™.)

Letā™s turn now to a simpler problem. allows you to consider the

ā˜andā™ of two Boolean expressions, but it always evaluates both expressions. This is

problematical in situations like

if pair x and (x>(0,0)): A else: B fi

because the expression ā˜x>(0,0)ā™ will stop with an error message unless x is of type

pair. The obvious way to avoid this error,

if pair x: if x>(0,0): A else: B fi else: B fi

is cumbersome and requires B to appear twice. What we want is a āconditional andā

operation in which the second Boolean expression is evaluated only if the ļ¬rst one turns

out to be true; then we can safely write

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

Similarly we might want āconditional orā in which the second operand is evaluated

only if the ļ¬rst is false, for situations like

if unknown x cor (x<0): A else: B fi.

Appendix D: Dirty Tricks 289

Such cand and cor macros can be deļ¬ned as follows: cand

cor

def cand(text q) = startif true q else: false fi enddef; text arguments

if

def cor(text q) = startif true true else: q fi enddef;

hierarchy

tertiarydef p startif true = if p: enddef; group delimiters

begingroup

the text arguments are now evaluated only when necessary. We have essentially replaced endgroup

vardef

the original line by

spark

gobbled

if if pair x: x<(0,0) else: false fi: A else: B fi. gobble

vacuous expressions

This construction has one catch; namely, the right-hand operands of cand and cor

must be explicitly enclosed in delimiters. But delimiters are only a minor nuisance,

because the operands to ā˜andā™ and ā˜orā™ usually need them anyway. It would be impos-

sible to make cand and cor obey the normal expression hierarchy; when macros make

primary/secondary/tertiary distinctions, they evaluate their arguments, and such eval-

uation is precisely what cand and cor want to avoid.

If these cand and cor macros were changed so that they took undelimited text

arguments, the text argument wouldnā™t stop at a colon. We could, however, use such

modiļ¬ed macros with group delimiters instead. For example, after

let {{ = begingroup; let }} = endgroup;

def cand text q = startif true q else: false fi enddef

we could write things like

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

(Not that this buys us anything; it just illustrates a property of undelimited text

arguments.) Group delimiters are not valid delimiters of delimited text arguments.

Speaking of group delimiters, the gratuitous begingroup and endgroup to-

kens added by vardef are usually helpful, but they can be a nuisance. For example,

suppose we want to write a zz macro such that ā˜zz1..zz2..zz3ā™ expands into

z1{dz1}..z2{dz2}..z3{dz3}

It would be trivial to do this with def :

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

but this makes zz a āspark.ā Letā™s suppose that we want to use vardef , so that zz

will be usable in suļ¬xes of variable names. Additional begingroup and endgroup

delimiters will mess up the syntax for paths, so we need to get rid of them. Hereā™s one

way to ļ¬nesse the problem:

vardef zz@# =

endgroup gobbled true z@#{dz@#} gobble begingroup enddef.

The gobbled and gobble functions of Appendix B will remove the vacuous expressions

ā˜begingroup endgroupā™ at the beginning and end of the replacement text.

(The initial begingroup endgroup wonā™t be gobbled if the vardef is being

read as a primary instead of as a secondary, tertiary, or expression. But in such cases

you probably donā™t mind having begingroup present.)

290 Appendix D: Dirty Tricks

2. Fortuitous loops. The ā˜maxā™ and ā˜minā™ macros in Appendix B make use of the fact max

min

that commas are like ā˜)(ā™ in argument lists. Although the deļ¬nition heading is

inorder

equally spaced

def max(expr x)(text t) text argument

ENDFOR

we can write ā˜max(a, b, c)ā™ and this makes x = a and t = ā˜b, cā™. Of course, a person isnā™t endgroup

supposed to say ā˜max(a)(b)(c)ā™. whatever

Here are two more applications of the idea: We want ā˜inorder(a, b, c)ā™ to be

true if and only if a ā¤ b ā¤ c; and we want ā˜equally spaced(x1 , x2 , x3 ) dx ā™ to produce

the equations ā˜x2 ā’ x1 = x3 ā’ x2 = dxā™.

def inorder(expr x)(text t) =

((x for u=t: <= u)

and (u endfor gobbled true true)) enddef;

def equally_spaced(expr x)(text t) expr dx =

x for u=t: - u = u endfor gobbled true

- dx enddef.

Isnā™t this fun? (Look closely.)

There is a problem, however, if we try to use these macros with loops in the

arguments. Consider the expressions

inorder(for n=1 upto 10: a[n], endfor infinity),

inorder(a[1] for n=2 upto 10: ,a[n] endfor),

inorder(a[1],a[2] for n=3 upto 10: ,a[n] endfor);

the ļ¬rst two give error messages, but the third one works! The reason is that, in the

ļ¬rst two cases, the for loop begins to be expanded before begins to read

the text argument, hence ENDFOR rears its ugly head again. We can avoid this problem

by rewriting the macros in a more complicated way that doesnā™t try to single out the

ļ¬rst argument x:

def inorder(text t) =

expandafter startinorder for u=t:

<= u endgroup and begingroup u endfor

gobbled true true endgroup) enddef;

def startinorder text t =

(begingroup true enddef;

def equally_spaced(text t) expr dx =

if pair dx: (whatever,whatever) else: whatever fi

for u=t: - u = u endfor gobbled true

- dx enddef;

Two separate tricks have been used here: (1) The ā˜endgroupā™ within ā˜inorderā™ will stop

an undelimited text argument; this gets rid of the unwanted ā˜<= uā™ at the beginning.

(2) A throwaway variable, ā˜whatever ā™, nulliļ¬es an unwanted equation at the beginning

of ā˜equally spacedā™. With the new deļ¬nitions, all three of the expressions above will

be understood, and so will things like

equally_spaced(for n=1 upto 10: x[n], endfor whatever) dx.

Furthermore the single-argument cases now work: ā˜inorder(a)ā™ will always be true, and

ā˜equally spaced(x) dx ā™ will produce no new equations.

Appendix D: Dirty Tricks 291

If we want to improve max and min in the same way, so that a person can max

min

specify loop arguments like

setu

Knuth

max(a[1] for n=2 upto 10: ,a[n] endfor) scaled

xscaled

and so that ā˜max(a) = aā™ in the case of a single argument, we have to work harder, yscaled

because max and min treat their ļ¬rst argument in quite a special way; they need to

apply the special macro setu , which deļ¬nes the type of the auxiliary variable u . The

fastest way to solve this problem is probably to use a token whose meaning changes

during the ļ¬rst time through the loop:

vardef max(text t) =

let switch_ = firstset_;

for u=t: switch_ u>u_: u_ := u ;fi endfor

u_ enddef;

vardef min(text t) =

let switch_ = firstset_;

for u=t: switch_ u<u_: u_ := u ;fi endfor

u_ enddef;

def firstset_ primary u =

setu_ u; let switch_ = if; if false: enddef.

Incidentally, the authorā™s ļ¬rst programs for max and min contained an interesting bug.

They started with ā˜save u ā™, and they tried to recognize the ļ¬rst time through the loop

by testing if u was unknown. This failed because u could be constantly unknown in

well-deļ¬ned cases like max(x, x + 1, x + 2).

3. Types. Our programs for inorder, equally_spaced, and max are careful not to make

unnecessary assumptions about the type of an expression. The ā˜roundā™ and ā˜byteā™ func-

tions in Appendix B are further examples of macros that change behavior based on the

types of their expr arguments. Letā™s look more closely at applications of type testing.

When the author was developing macros for plain , his ļ¬rst ācor-

rectā solution for max had the following form:

vardef max(text t) =

save u_; boolean u_;

for u=t: if boolean u_: setu_ u

elseif u_<u: u_ := u fi; endfor

u_ enddef.

This was interesting because it showed that there was no need to set u to true or false;

the simple fact that it was boolean was enough to indicate the ļ¬rst time through the

loop. (A slightly diļ¬erent setu macro was used at that time.)

We might want to generalize the ā˜scaledā™ operation of so that

ā˜scaled (x, y)ā™ is shorthand for ā˜xscaled x yscaled yā™. Thatā™s pretty easy:

let SCALED = scaled;

def scaled primary z =

if pair z: xscaled xpart z yscaled ypart z

else: SCALED z fi enddef;

Itā™s better to keep the primitive operation ā˜SCALED zā™ here than to replace it by the

slower variant ā˜xscaled z yscaled zā™.

292 Appendix D: Dirty Tricks

allows you to compare booleans, numerics, pairs, strings, and equality test

==

transforms for equality; but it doesnā™t allow the expression ā˜p = qā™ where p and q are

Nonlinear equations

paths or pens or pictures. Letā™s write a general equality test macro such that ā˜p == qā™ solve

will be true if and only if p and q are known and equal, whatever their type.

tertiarydef p == q =

if unknown p or unknown q: false

elseif boolean p and boolean q: p=q

elseif numeric p and numeric q: p=q

elseif pair p and pair q: p=q

elseif string p and string q: p=q

elseif transform p and transform q: p=q

elseif path p and path q:

if (cycle p = cycle q) and (length p = length q)

and (point 0 of p = point 0 of q): patheq p of q

else: false fi

elseif pen p and pen q: (makepath p == makepath q)

elseif picture p and picture q: piceq p of q

elseif vacuous p and vacuous q: true

else: false fi enddef;

vardef vacuous primary p =

not(boolean p or numeric p or pair p or path p

or pen p or picture p or string p or transform p) enddef;

vardef patheq expr p of q =

save t; boolean t; t=true;

for k=1 upto length p:

t := (postcontrol k-1 of p = postcontrol k-1 of q)

and (precontrol k of p = precontrol k of q)

and (point k of p = point k of q);

exitunless t; endfor

t enddef;

vardef piceq expr p of q =

save t; picture t;

t=p; addto t also -q;

cull t dropping origin;

(totalweight t=0) enddef;

If p and q are numeric or pair expressions, we could relax the condition that they both

be known by saying ā˜if known p ā’ q: p = q else false ļ¬ā™; transforms could be handled

similarly by testing each of their six parts. But thereā™s no way to tell if booleans, paths,

etc., have been equated when theyā™re both unknown, without the risk of irrevocably

changing the values of other variables.

4. Nonlinear equations. has a built-in solution mechanism for linear equa-

tions, but it balks at nonlinear ones. You might be able to solve a set of nonlinear

equations yourself by means of algebra or calculus, but in diļ¬cult cases it is probably

simplest to use the ā˜solve ā™ macro of plain . This makes it possible to solve

n equations in n unknowns, provided that at most one of the equations is nonlinear

when one of the unknowns is ļ¬xed.

Appendix D: Dirty Tricks 293

The general technique will be illustrated here in the case n = 3. Let us try to tolerance

parallelogram

ļ¬nd numbers a, b, and c such that

ā’2a + 3b/c = c ā’ 3;

ac + 2b = c3 ā’ 20;

a3 + b3 = c2 .

When c is ļ¬xed, the ļ¬rst two equations are linear in a and b. We make an inequality

out of the remaining equation by changing ā˜=ā™ to ā˜<ā™, then we embed the system in a

boolean-valued function:

vardef f(expr c) = save a,b;

-2a + 3b/c = c - 3;

a*c + 2b = c*c*c - 20;

a*a*a + b*b*b < c*c enddef;

c = solve f(1,7);

-2a + 3b/c = c - 3;

a*c + 2b = c*c*c - 20;

show a, b, c.

If we set tolerance = epsilon (which is the minimum value that avoids inļ¬nite looping

in the solve routine), the values a = 1, b = 2, and c = 3 are shown (so it is obvious that

the example was rigged). If tolerance has its default value 0.1, we get a = 1.05061,

b = 2.1279, c = 3.01563; this would probably be close enough for practical purposes,

assuming that the numbers represent pixels. (Increasing the tolerance saves time

because it decreases the number of iterations within solve ; you have to balance time

versus necessary accuracy.)

The only tricky thing about this use of solve was the choice of the numbers

1 and 7 in ā˜f (1, 7)ā™. In typical applications weā™ll usually have obvious values of the

unknown where f will be true and false, but a bit of experimentation was necessary

for the problem considered here. In fact, it turns out that f (ā’3) is true and f (ā’1) is

false, in this particular system; setting c = solve f (ā’3, ā’1) leads to another solution:

a = 7.51442, b = ā’7.48274, c = ā’2.3097. Furthermore, itā™s interesting to observe that

this system has no solution with c between ā’1 and +1, even though f (+1) is true and

f (ā’1) is false! When c ā’ 0, the quantity a3 + b3 approaches ā’ā when c is negative,

+ā when c is positive. An attempt to ā˜solve f (1, ā’1)ā™ will divide by zero and come

up with several arithmetic overļ¬‚ows.

Letā™s consider now a real application instead of

a contrived example. We wish to ļ¬nd the vertices of a

parallelogram z1l , z1r , z0l , z0r , such that (Figure Da will be inserted

here; too bad you canā™t see it

now.)

x1l = a; y1r = b; z0r = (c, d);

length(z1r ā’ z1l ) = length(z0r ā’ z0l ) = stem ,

and such that the lines z1r - - z1l and z1r - - z0r meet at a

given angle phi. We can consider the common angle Īø of z1r ā’ z1l and z0r ā’ z0l to be

the ānonlinearā unknown, so the equations to be solved can be written

penpos1 (stem , Īø); penpos0 (stem , Īø);

x1l = a; y1r = b; z0r = (c, d);

angle(z1r ā’ z0r ) = Īø + Ļ.

294 Appendix D: Dirty Tricks

When Īø has a given value, all but the last of these equations are linear; hence we can d

Billawala

solve them by turning the crank in our general method:

black-letter

meta-design

vardef f(expr theta) = save x,y;

interpolate

penpos1(stem,theta); penpos0(stem,theta); function

x1l=a; y1r=b; z0r=(c,d);

angle(z1r-z0r)<theta+phi enddef;

theta=solve f(90,0);

penpos1(stem,theta); penpos0(stem,theta);

x1l=a; y1r=b; z0r=(c,d);

show z1l,z1r,z0l,z0r,theta,angle(z1r-z0r).

For example, if a = 1, b = 28, c = 14, d = 19, stem = 5, and Ļ = 80, we get

(1, 23.703) (3.557, 28) (11.443, 14.703) (14, 19) 59.25 139.25

as answers when tolerance = epsilon , and

(1, 23.702) (3.554, 28) (11.446, 14.702) (14, 19) 59.28 139.25

when tolerance = 0.1. The function f prescribed by the general method can often be

simpliļ¬ed; for example, in this case we can remove redundancies and get just

vardef f(expr theta) = save x,y;

penpos1(stem,theta); x1l=a; y1r=b;

angle(z1r-(c,d))<theta+phi enddef.

The problem just solved can be called the ād problem,ā because it arose in connection

with N. N. Billawalaā™s meta-design of a black-letter ā˜ ā™, and because it appears in

Appendix D.

5. Nonlinear interpolation. Suppose a designer has empirically determined good values

of some quantity f (x) for several values of x; for example, f (x) might be a stroke

weight or a serif length or an amount of overshoot, etc. These empirical values can be

generalized and incorporated into a meta-design if we are able to interpolate between

the original xā™s, obtaining f (x) at intermediate points.

Suppose the data points are known for x = x1 < x2 < Ā· Ā· Ā· < xn . We can

represent f (x) by its graph, which we can assume is well approximated by the -

path deļ¬ned by

F = (x1 , f (x1 )) . . (x2 , f (x2 )) . . etc. . . (xn , f (xn ))

if f (x) is a reasonable function. Therefore interpolation can be done by using path

intersection (!):

vardef interpolate expr F of x = save t; t =

if x < xpart point 0 of F: extrap_error 0

elseif x > xpart point infinity of F: extrap_error infinity

else: xpart(F intersectiontimes verticalline x) fi;

ypart point t of F enddef;

def extrap_error = hide(errhelp "The extreme value will be used.";

errmessage "ā˜interpolateā™ has been asked to extrapolate";

errhelp "") enddef;

vardef verticalline primary x =

(x,-infinity)--(x,infinity) enddef;

Appendix D: Dirty Tricks 295

For example, if f (1) = 1, f (3) = 2, and f (15) = 4, this interpolation scheme gives overlays

Leban

ā˜interpolate (1, 1) . . (3, 2) . . (15, 4) of 7ā™ the value 3.37.

clearit

showit

6. Drawing with overlays. Letā™s leave numerical computations now and go back into shipit

the realm of pictures. Bruce Leban has suggested an extension of plain ā™s ļ¬ll

draw

ā˜clearit/showit/shipitā™ commands by which ā˜ļ¬llā™ and ā˜drawā™ essentially operate on

keepit

imaginary sheets of clear plastic. A new command ā˜keepitā™ places a fresh sheet of totalpicture

plastic on top of whatever has already been drawn, thereby preserving the covered totalnull

currentnull

image against subsequent erasures. shipout

We can implement keepit by introducing a new picture variable totalpicture , gf

transcript

and new boolean variables totalnull , currentnull , then deļ¬ning macros as follows:

log ļ¬le

tracingedges

def clearit = currentpicture:=totalpicture:=nullpicture;

currentnull:=totalnull:=true; enddef;

def keepit = cull currentpicture keeping (1,infinity);

addto totalpicture also currentpicture;

currentpicture:=nullpicture;

totalnull:=currentnull; currentnull:=true; enddef;

def addto_currentpicture =

currentnull:=false; addto currentpicture enddef;

def mergeit (text do) =

if totalnull: do currentpicture

elseif currentnull: do totalpicture

else: begingroup save v; picture v; v:=currentpicture;

cull v keeping (1,infinity); addto v also totalpicture;

do v endgroup fi enddef;

def shipit = mergeit(shipout) enddef;

def showit_ = mergeit(show_) enddef;

def show_ suffix v = display v inwindow currentwindow enddef;

The totalnull and currentnull bookkeeping isnā™t strictly necessary, but it contributes

greatly to the eļ¬ciency of this scheme if the extra generality of keepit is not actually

being used. The ā˜vā™ computations in mergeit involve copying the accumulated picture

before displaying it or shipping it out; this takes time, and it almost doubles the amount

of memory needed, so we try to avoid it when possible.

7. Filing pictures. If you want to store a picture in a ļ¬le and read it in to some other

job, you face two problems: (1) ā™s shipout command implicitly

culls the picture, so that only binary data is left. Pixel values > 0 are distinguished

from pixel values <= 0, but no other information about those values will survive.

(2) The result of shipout can be used in another job only if you have

an auxiliary program that converts from binary gf format to a source

program; can write gf ļ¬les, but it canā™t read them.

These problems can be resolved by using ā™s transcript or log ļ¬le

as the output medium, instead of using the gf ļ¬le. For example, letā™s consider ļ¬rst the

use of tracingedges . Suppose we say

tracingedges := 1;

any sequence of ļ¬ll, draw, or ļ¬lldraw commands

message "Tracing edges completed."; tracingedges := 0;

296 Appendix D: Dirty Tricks

then the log ļ¬le will contain lines such as the following: turningcheck

save

delimiters

Tracing edges at line 15: (weight 1)

tension

(1,5)(1,2)(2,2)(2,1)(3,1)(3,0)(8,0)(8,1)(9,1)(9,2)(10,2)(10,8)(9,8) almost digitized

(9,9)(8,9)(8,10)(3,10)(3,9)(2,9)(2,8)(1,8)(1,5). culling

tracingoutput

Tracing edges at line 15: (weight -1) show

(3,5)(3,2)(4,2)(4,1)(7,1)(7,2)(8,2)(8,8)(7,8)(7,9)(4,9)(4,8)(3,8)(3,5).

Tracing edges at line 18: (weight -1)

(No new edges added.)

Tracing edges completed.

Let us write macros so that these lines are acceptable input to .

def Tracing=begingroup save :,[,],Tracing,edges,at,weight,w;

delimiters []; let Tracing = endfill; interim turningcheck := 0;

vardef at@#(expr wt) = save (,); w := wt;

let ( = lp; let ) = rp; fill[gobble begingroup enddef;

let edges = \; let weight = \; let : = \; enddef;

def lp = [ enddef;

def rp = ] -- enddef;

vardef No@# = origin enddef;

def endfill = cycle] withweight w endgroup; enddef;

def completed = endgroup; enddef;

The precise form of edge-traced output, with its limited vocabulary and its restricted

use of parentheses and commas, has been exploited here.

With slight changes to this code, you can get weird eļ¬ects. For example, if the

deļ¬nition of rp is changed to ā˜]..tension 4..ā™, and if ā˜scaled 5ptā™ is inserted before

ā˜withweightā™, the image will be an āalmost digitizedā character:

(Figure Daa will be inserted here; too bad you canā™t see it now.)

(The bumps at the left here are due to the repeated points ā˜(1,5)ā™ and ā˜(3,5)ā™ in the

original data. You can remove them by adding an extra pass, ļ¬rst tracing the edges

that are output by the unmodiļ¬ed Tracing macros.)

Although the eļ¬ects of ļ¬ll and draw can be captured by tracingedges , other

operations like culling are not traced. Let us therefore consider the more general picture

produces when tracingoutput is positive, or when you

representation that

ask it to show a picture (see Chapter 13). The macros on the next page will recreate

a picture from input of the form

beginpicture

row 1: 1+ -2- | 0+ 2-

row 0: | 0+ 2++ 5---

row -2: 0- -2+ |

endpicture

Appendix D: Dirty Tricks 297

where the middle three lines have been copied verbatim from a transcript ļ¬le. (The xy swap

pen

task would be easier if the token ā˜-ā™ didnā™t have to perform two diļ¬erent functions!)

polygons

taller

let neg_ = -; let colon_ = :;

diamond

def beginpicture = penrazor

begingroup save row, |, :, ---, --, +, ++, +++, v, xx, yy, done; path

picture v; v := nullpicture; interim turningcheck := 0;

let --- = mmm_; let -- = mm_;

let + = p_; let ++ = pp_; let +++ = ppp_;

let row = pic_row; let | = relax; let : = pic_colon; : enddef;

def pic_row primary y = done; yy := y; enddef;

def pic_colon primary x =

if known x colon_ ; xx := x; pic_edge fi enddef;

def pic_edge =

let - = m_;

addto v contour unitsquare xscaled xx shifted(0,yy) enddef;

def mmm_ = withweight 3; let - = neg_; : enddef;

def mm_ = withweight 2; let - = neg_; : enddef;

def m_ = withweight 1; let - = neg_; : enddef;

def p_ = withweight neg_1; let - = neg_; : enddef;

def pp_ = withweight neg_2; let - = neg_; : enddef;

def ppp_ = withweight neg_3; let - = neg_; : enddef;

transform xy_swap; xy_swap = identity rotated 90 xscaled -1;

def endpicture = done;

v transformed xy_swap transformed xy_swap endgroup enddef;

The reader will ļ¬nd it instructive to study these macros closely. When ā˜doneā™ appears,

it is an unknown primary, so pic_colon will not attempt to generate another edge.

Each new edge also inserts a cancelling edge at x = 0. The two applications of xy_swap

at the end will clear away all redundant edges. (Double swapping is a bit faster than

the operation ā˜rotated-90 rotated 90ā™ that was used for this purpose in Chapter 13.)

8. Fattening a pen. Letā™s move on to another aspect of by considering

an operation on pen polygons: Given a pen value p, the task is to construct a pen

ā˜taller pā™ that is one pixel taller. For example, if p is the diamond nib ā˜(0.5, 0) - -

(0, 0.5) - - (ā’0.5, 0) - - (0, ā’0.5) - - cycleā™, the taller nib will be

(0.5, 0.5) - - (0, 1) - - (ā’0.5, 0.5) - - (ā’0.5, ā’0.5) - - (0, ā’1) - - (0.5, ā’0.5) - - cycle;

if p is a tilted penrazor ā˜(ā’x, ā’y) - - (x, y) - - cycleā™, the taller nib will be

(ā’x, ā’y ā’ 0.5) - - (x, y ā’ 0.5) - - (x, y + 0.5) - - (ā’x, ā’y + 0.5) - - cycle,

assuming that x > 0. The macro itself turns out to be fairly simple, but it makes

instructive use of path and pen operations.

We want to split the pen into two parts, a ābottomā half and a ātopā half; the

bottom half should be shifted down by .5 pixels, and the top half should be shifted up.

The dividing points between halves occur at the leftmost and rightmost vertices of the

pen. Hmmm; a potential problem arises if there are two or more leftmost or rightmost

points; for example, what if we try to make ā˜taller taller pā™ ? Fortunately

doesnā™t mind if a pen polygon has three or more consecutive vertices that lie on a line,

hence we can safely choose any leftmost point and any rightmost point.

298 Appendix D: Dirty Tricks

The next question is, āHow should we ļ¬nd leftmost and rightmost points?ā makepath

directiontime

We will, of course, use makepath to ļ¬nd the set of all vertices; so we could simply

penoļ¬set

traverse the path and ļ¬nd the minimum and maximum x coordinates. However, it will velocity

be faster (and more fun) to use either directiontime or penoļ¬set for this purpose. Letā™s tensepath

intersectiontimes

try directiontime ļ¬rst: subpath

future pen

vardef taller primary p =

BernshteĖ˜Ä±n

save r, n, t, T; path r; mediation

r = tensepath makepath p; n = length r;

t = round directiontime up of r;

T = round directiontime down of r;

if t>T: t := t-n; fi

makepen(subpath(T-n,t) of r shifted .5down

--subpath(t,T) of r shifted .5up -- cycle) enddef;

The result of makepath has control points equal to their adjacent vertices, so it could

not be used with directiontime. (If any key point is equal to its precontrol or

postcontrol, the āvelocityā of the path is zero at that point; directiontime assumes

that all directions occur whenever the velocity drops to zero.) Therefore we have used

ā˜tensepathā™. This almost works, once we realize that the values of t and T sometimes

need to be rounded to integers. But it fails for pens like penspeck that have points very

close together, since tensepath is no better than an unadulterated makepath in such

cases. Furthermore, even if we could deļ¬ne a nice path from p (for example by scaling

it up), we would run into problems of numerical instability, in cases like penrazor

where the pen polygon takes a 180ā—¦ turn. Razor-thin pens cannot be recognized easily,

because they might have more than two vertices; for example, rotations of future pens

such as ā˜makepen(left . . origin . . right . . cycle)ā™ are problematical.

We can obtain a more robust result by using penoļ¬set, because this operation

makes use of the convexity of the polygon. The āfastestā solution looks like this:

vardef taller primary p =

save q, r, n, t, T; pen q; q = p;

path r; r = makepath q; n = length r;

t = round xpart(r intersectiontimes penoffset up of q);

T = round xpart(r intersectiontimes penoffset down of q);

if t>T: t := t-n; fi

makepen(subpath(T-n,t) of r shifted .5down

--subpath(t,T) of r shifted .5up -- cycle) enddef;

(The argument p is copied into q, in case itā™s a future pen; this means that the conversion

of future pen to pen need be done only once instead of three times.)

9. BernshteĖ˜ polynomials. And now, for our last trick, letā™s try to extend

Ä±n ā™s

syntax so that it will accept generalized mediation formulas of the form ā˜t[u1 , . . . , un ]ā™

for all n ā„ 2. (This notation was introduced for n = 3 and 4 in Chapter 14, when we

were considering fractional subpaths.) If n > 2, the identity

t[ u1 , . . . , un ] = t[t[u1 , . . . , unā’1 ], t[u2 , . . . , un ] ]

deļ¬nes t[u1 , . . . , un ] recursively, and it can be shown that the alternative deļ¬nition

t[ u1 , . . . , un ] = t[t[u1 , u2 ], . . . , t[unā’1 , un ] ]

Appendix D: Dirty Tricks 299

gives the same result. (Indeed, we have brackets

for list

n

nā’1 empty text arguments

(1 ā’ t)nā’k tkā’1 uk ,

t[u1 , . . . , un ] = for

kā’1 save

k=1

]]

a BernshteĖ˜ polynomial of order n ā’ 1.) text argument

Ä±n BURNS

Our problem is to change the meaning of ā™s brackets so that ex- ANDERSON

CUNDALL

pressions like ā˜1/2[a, b, c, d]ā™ will evaluate to ā˜.125a + .375b + .375c + .125dā™ in accordance

with the formulas just given, but we donā™t want to mess up the other primitive uses

of brackets in contexts like ā˜x[n]ā™ and ā˜path p[][]aā™. We also want to be able to use

brackets inside of brackets.

The reader is challenged to try solving this problem before looking at the

weird solution that follows. Perhaps there is a simpler way?

def lbrack = hide(delimiters []) lookahead [ enddef;

let [[[ = [; let ]]] = ]; let [ = lbrack;

def lookahead(text t) =

hide(let [ = lbrack;

for u=t, hide(n_ := 0; let switch_ = first_): switch_ u; endfor)

if n_<3: [[[t]]] else: Bernshtein n_ fi enddef;

def first_ primary u =

if numeric u: numeric u_[[[]]]; store_ u

elseif pair u: pair u_[[[]]]; store_ u fi;

let switch_ = store_ enddef;

def store_ primary u = u_[[[incr n_]]] := u enddef;

primarydef t Bernshtein nn =

begingroup for n=nn downto 2:

for k=1 upto n-1: u_[[[k]]]:=t[[[u_[[[k]]],u_[[[k+1]]] ]]];

endfor endfor u_[[[1]]] endgroup enddef;

The most subtle thing about this code is the way it uses the ā˜emptyā™ option of a for list

evaluates all the expressions

to dispense with empty text arguments. Since

of a for loop before reading the loop text, and since ā˜n_ā™ and ā˜u_ā™ are used here only

when no recursion is taking place, it is unnecessary to save their values even when

brackets are nested inside of brackets.

Of course this trick slows down tremendously, whenever brackets

appear, so it is just of academic interest. But it seems to work in all cases except with

respect to formulas that involve ā˜]]ā™ (two consecutive brackets); the latter token, which

plain expands to ā˜] ]ā™, is not expanded when lookahead reads its text ar-

gument, hence the user must remember to insert a space between consecutive brackets.

Their tricks anā™ craft hae put me daft,

Theyā™ve taen me in, anā™ aā™ that.

ā” ROBERT BURNS, The Jolly Beggar (1799)

Ebery house hab him dutty carner.

ā” ANDERSON and CUNDALL, Jamaica Proverbs and Sayings (1927)

(page 300)

E

Examples

Appendix E: Examples 301

Weā™ve seen lots of examples of individual letters or parts of letters; letā™s con- logo

command line

centrate now on the problem of getting things all together. The next two pages parameter ļ¬le

contain the entire contents of an example ļ¬le ā˜logo.mfā™, which generates the slant

currenttransform

letters of the logo. The ļ¬le is short, because only seven letters are meta-ness

involved, and because those letters were intentionally done in a style that would

be easy for the system they name. But the ļ¬le is complete, and it illustrates

in simpliļ¬ed form all the essential aspects of larger fonts: Ad hoc dimensions

are converted to pixels; subroutines are deļ¬ned; programs for individual letters

appear; intercharacter and interword spacing conventions are nailed down. Fur-

thermore, the character programs are careful to draw letters that will be well

adapted to the raster, even if pixels on the output device are not square.

Weā™ve been studying the ā˜ ā™ letters oļ¬ and on since Chapter 4,

making our examples slightly more complex as more of the language has been

encountered. Finally weā™re ready to pull out all the stops and look at the real,

professional-quality logo.mf, which incorporates all the best suggestions that

have appeared in the text and in answers to the exercises.

Itā™s easy to generate a font with logo.mf, by proceeding as explained

in Chapter 11. For example, the logo10 font that produces ā˜ ā™ in

10-point size can be created for a low-resolution printer by running

with the command line

\mode=lowres; input logo10

where the parameter ļ¬le logo10.mf appears in that chapter. Furthermore the

slanted version ā˜ ā™ can be created by inputting the parameter ļ¬le

logosl10.mf, which says simply

% 10-point slanted METAFONT logo

slant := 1/4;

input logo10

The slant parameter aļ¬ects currenttransform as explained in Chapter 15.

There isnā™t a great deal of āmeta-nessā in the logo.mf design, because

only a few forms of the logo are needed. However, some interesting

variations are possible; for example, if we use the parameter ļ¬les

font_size 30pt#; font_size 10pt#;

ht#:=25pt#; ht#:=6pt#;

xgap#:=1.5pt#; xgap#:=2pt#;

u#:=3/9pt#; u#:=4/3pt#;

s#:=1/3pt#; s#:=-2/3pt#;

o#:=2/9pt#; o#:=1/9pt#;

px#:=1pt#; px#:=1/3pt#;

slant:=-1/9;

we get and , respectively.

302 Appendix E: Examples

% Routines for the METAFONT logo, as found in The METAFONTbook

% (logo10.mf is a typical parameter file)

mode_setup;

if unknown slant: slant:=0 else: currenttransform:=

identity slanted slant yscaled aspect_ratio fi;

ygap#:=(ht#/13.5u#)*xgap#; % vertical adjustment

ho#:=o#; % horizontal overshoot

leftstemloc#:=2.5u#+s#; % position of left stem

barheight#:=.45ht#; % height of bar lines

py#:=.9px#; % vertical pen thickness

define_pixels(s,u);

define_whole_pixels(xgap);

define_whole_vertical_pixels(ygap);

define_blacker_pixels(px,py);

pickup pencircle xscaled px yscaled py;

logo_pen:=savepen;

define_good_x_pixels(leftstemloc);

define_good_y_pixels(barheight); (Figure Eb will be inserted here;

define_corrected_pixels(o); too bad you canā™t see it now.)

define_horizontal_corrected_pixels(ho);

def beginlogochar(expr code, unit_width) =

beginchar(code,unit_width*u#+2s#,ht#,0);

pickup logo_pen enddef;

def super_half(suffix i,j,k) =

draw z.i{0,y.j-y.i}

... (.8[x.j,x.i],.8[y.i,y.j]){z.j-z.i}

... z.j{x.k-x.i,0} (Figure A18a will be inserted here;

too bad you canā™t see it now.)

... (.8[x.j,x.k],.8[y.k,y.j]){z.k-z.j}

... z.k{0,y.k-y.j} enddef;

beginlogochar("M",18);

x1=x2=leftstemloc; x4=x5=w-x1; x3=w-x3;

y1=y5; y2=y4; bot y1=-o;

top y2=h+o; y3=y1+ygap;

draw z1--z2--z3--z4--z5;

labels(1,2,3,4,5); endchar;

(Figure Ea will be inserted here;

beginlogochar("E",14); too bad you canā™t see it now.)

x1=x2=x3=leftstemloc;

x4=x6=w-x1+ho; x5=x4-xgap;

y1=y6; y2=y5; y3=y4;

bot y1=0; top y3=h; y2=barheight;

Appendix E: Examples 303

draw z6--z1--z3--z4; draw z2--z5;

labels(1,2,3,4,5,6); endchar;

beginlogochar("T",13);

italcorr ht#*slant + .5u#;

(Figure 18a will be inserted here; too

if .5w<>good.x .5w: change_width; fi

bad you canā™t see it now.)

lft x1=-eps; x2=w-x1; x3=x4=.5w;

y1=y2=y3; top y1=h; bot y4=-o;

draw z1--z2; draw z3--z4;

labels(1,2,3,4); endchar;

beginlogochar("A",15);

x1=.5w; x2=x4=leftstemloc; x3=x5=w-x2;

top y1=h+o; y2=y3=barheight;

bot y4=bot y5=-o;

draw z4--z2--z3--z5; super_half(2,1,3); (Figure 4c will be inserted here; too bad you

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

beginlogochar("F",14);

x1=x2=x3=leftstemloc;

x4=w-x1+ho; x5=x4-xgap;

y2=y5; y3=y4; bot y1=-o;

top y3=h; y2=barheight;

draw z1--z3--z4; draw z2--z5;

labels(1,2,3,4,5); endchar;

beginlogochar("O",15); (Figure 11a will be inserted here;

x1=x4=.5w; top y1=h+o; bot y4=-o; too bad you canā™t see it now.)

x2=w-x3=good.x(1.5u+s); y2=y3=barheight;

super_half(2,1,3); super_half(2,4,3);

labels(1,2,3,4); endchar;

beginlogochar("N",15);

x1=x2=leftstemloc; x3=x4=x5=w-x1;

bot y1=bot y4=-o;

top y2=top y5=h+o; y3=y4+ygap;

draw z1--z2--z3; draw z4--z5;

(Figure 21a will be inserted here; too

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

ligtable "T": "A" kern -.5u#;

ligtable "F": "O" kern -u#;

font_quad:=18u#+2s#;

font_normal_space:=6u#+2s#;

font_normal_stretch:=3u#;

font_normal_shrink:=2u#;

font_identifier:="MFLOGO" if slant<>0: & "SL" fi;

font_coding_scheme:="AEFMNOT only";

304 Appendix E: Examples

Everything in logo.mf has already been explained previously in this font identiļ¬er

font coding scheme

book except for the very last two lines, which deļ¬ne a ā˜font identiļ¬erā™ and italic correction

a ā˜font coding schemeā™. These are optional bits of information that are dis- Knuth

Computer Modern

cussed in Appendix F. Furthermore an italic correction has been speciļ¬ed for parameter ļ¬les

the letter ā˜ ā™, since itā™s the ļ¬nal letter of ā˜ ā™. driver ļ¬les

program ļ¬les

base ļ¬le

The program for a complete typeface will diļ¬er from the program for this

simple logo font primarily in degree; there will be lots more parameters, lots more

subroutines, lots more characters, lots more ligatures and kerns and whatnot. But

there will probably also be more administrative machinery, designed to facilitate the

creation, testing, and modiļ¬cation of characters, since a large enterprise requires good

organization. The remainder of this appendix is devoted to an example of how this

might be done: We shall discuss the additional kinds of routines that the author found

helpful while he was developing the Computer Modern family of typefaces.

The complete, unexpurgated programs for Computer Modern appear in Com-

puters & Typesetting, Volume E; but since they have evolved over a long period of

time, they are rather complex. We shall simplify the details so that it will be easier to

grasp the important issues without being distracted by irrelevant technicalities.

The simple logo fonts discussed above are generated by two types of ļ¬les:

There are parameter ļ¬les like logo10.mf, and there is a program ļ¬le logo.mf. The

Computer Modern fonts, being more extensive, are generated by four types of ļ¬les:

There are parameter ļ¬les like ā˜cmr10.mfā™, which specify the ad hoc dimensions for par-

ticular sizes and styles of type; there are driver ļ¬les like ā˜roman.mfā™, which serve as chief

executives of the font-generation process; there are program ļ¬les like ā˜punct.mfā™, which

contain programs for individual characters; and thereā™s a base ļ¬le called ā˜cmbase.mfā™,

which contains the subroutines and other macros used throughout the system.

Our logo example could have been cast in this more general mold by moving

the character programs into a program ļ¬le ā˜METAFON.mfā™, and by moving most of the

opening material into a base ļ¬le ā˜logobase.mfā™ that looks like this:

% Base file for the METAFONT logo

logobase:=1; % when logobase is known, this file has been input

def font_setup =

if unknown slant: slant:=0 else: currenttransform:=

.

. (the previous code is unchanged)

.

define_corrected_pixels(o);

define_horizontal_corrected_pixels(ho); enddef;

followed by the deļ¬nitions of beginlogochar and super_half. Then weā™re left with a

driver ļ¬le logo.mf that looks like this:

% Driver file for the METAFONT logo

if unknown logobase: input logobase fi

mode_setup; font_setup; % establish pixel-oriented units

input METAFON % generate the characters

ligtable "T": "A" kern -.5u#;

and so on, concluding as before.

Appendix E: Examples 305

In general, a parameter ļ¬le calls on a driver ļ¬le, which calls on one or more cmr10.mf

monospace

program ļ¬les; the base ļ¬le contains predeļ¬ned macros shared by all. There may be

generate

several driver ļ¬les, each using a diļ¬erent combination of program ļ¬les; for example, end

Computer Modern has ā˜roman.mfā™ and ā˜italic.mfā™, both of which call on punct.mf to font slant

font quad

generate punctuation marks, although they use diļ¬erent program ļ¬les to generate the font normal space

lowercase alphabets. Characters are partitioned into program ļ¬les so that they can be font normal stretch

font normal shrink

shared by diļ¬erent drivers.

ligtable

Parameter ļ¬les in Computer Modern donā™t quite follow the conventions of

logo10.mf. Here, for example, are the opening and closing lines of cmr10.mf:

% Computer Modern Roman 10 point

if unknown cmbase: input cmbase fi

font_identifier "CMR"; font_size 10pt#;

u#:=20/36pt#; % unit width

serif_fit:=0pt#; % extra sidebar near serifs

letter_fit:=0pt#; % extra space added to all sidebars

.

.

.

serifs:=true; % should serifs and bulbs be attached?

monospace:=false; % should all characters have the same width?

generate roman % switch to the driver file

The main diļ¬erences are: (1) Thereā™s special code at the beginning, to make sure that

cmbase.mf has been loaded. The base ļ¬le includes several things that are needed right

away; for example, cmbase declares the variables ā˜serifs ā™ and ā˜monospace ā™ to be of type

boolean, so that boolean-valued parameter assignments like ā˜serifs := trueā™ will be

legal. (2) The font identiļ¬er is deļ¬ned in the parameter ļ¬le, not in the driver ļ¬le.

(3) The last line says ā˜generateā™ instead of ā˜inputā™; the base ļ¬le deļ¬nes generate to

be the same as input, but other meanings are assigned by utility routines that weā™ll

study later. (4) The ļ¬nal ā˜endā™ is no longer present in the parameter ļ¬le.

The roman.mf driver looks like this (vastly simpliļ¬ed):

% The Computer Modern Roman family of fonts

mode_setup; font_setup;

input romanu; % upper case (majuscules)

input romanl; % lower case (minuscules)

input romand; % numerals

input punct; % punctuation marks

font_slant slant;

if monospace: font_quad 18u#;

font_normal_space 9u#; % no stretching or shrinking

else: font_quad 18u#+4letter_fit#;

font_normal_space 6u#+2letter_fit#; % interword spacing

font_normal_stretch 3u#; % with ā˜ā˜glueā™ā™

font_normal_shrink 2u#;

input romlig; % f ligatures

ligtable "f": "i" =: oct"014", "f" =: oct"013", "l" =: oct"015",

"ā™" kern u#, "?" kern u#, "!" kern u#;

306 Appendix E: Examples

ligtable oct"013": "i" =: oct"016", "l" =: oct"016", % ffi and ffl bye.

monospaced

"ā™" kern u#, "?" kern u#, "!" kern u#;

cmtt10

ligtable "-": "-" =: oct"173"; % en dash .

em dash

ligtable oct"173": "-" =: oct"174"; % em dash

punctuation marks

ligtable "ā˜": "ā˜" =: oct"134"; % open quotes

ligtable "ā™": "ā™" =: oct"042", % close quotes

"?" kern 2u#, "!" kern 2u#;

ńņš. 10 |