<< . .

. 20
( : 45)

. . >>

Would the program for ˜ ™ still work if the two calls of super_half had been
˜super_half(3,1,2)™ and ˜super_half(3,4,2)™ ?
Guess the program for ™s ˜ ™, which has the same width as ˜ ™.
Besides parameters of type expr and su¬x, also allows a third
type called text. In this case the actual argument is any sequence of tokens,
and this sequence is not evaluated beforehand; a text argument is simply copied in
place of the corresponding parameter. This makes it possible to write macros that deal
with lists of things. For example, Appendix B™s ˜de¬ne pixels™ macro is de¬ned thus:
def define_pixels(text t) =
forsuffixes a=t: a := a# * hppp; endfor enddef;
This means that ˜define_pixels(em,cap)™ will expand into
forsuffixes a=em,cap: a := a# * hppp; endfor
which, in turn, expands into the tokens ˜em := em# * hppp; cap := cap# * hppp;™ as we
will see in Chapter 19.
162 Chapter 18: De¬nitions (also called Macros)

Let™s look now at a subroutine for drawing serifs, since this typi¬es the sort serifs
of special-purpose macro one expects to see in the design of a meta-typeface.
Serifs can take many forms, so we must choose from myriads of possibilities. We shall ]]
consider two rather di¬erent approaches, one based on outline-¬lling and the other
based on the use of a ¬xed pen nib. In both cases it will be necessary to omit some
of the re¬nements that would be desirable in a complete typeface design, to keep the
examples from getting too complicated.

Our ¬rst example is a serif routine that
constructs six points z$a , z$b , . . . , z$f
around a given triple of “penpos” points z$l , z$ ,
z$r ; here $ is a su¬x that™s a parameter to the
serif macro. Other parameters are: breadth ,
the distance between the parallel lines that run (Figure 18b will be inserted here; too bad
you can™t see it now.)
from z$l to z$a and from z$r to z$f ; theta , the
direction angle of those two lines; left jut , the
distance from z$l to z$b ; and right jut , the dis-
tance from z$r to z$e . (The serif “juts out” by
the amounts of the jut parameters.) There™s
also a serif edge macro, which constructs the
path shown. The routines refer to three variables that are assumed to apply to all
serifs: slab , the vertical distance from z$b and z$e to z$c and z$d ; bracket , the vertical
distance from z$a and z$f to z$l and z$r ; and serif darkness , a fraction that controls
how much of the triangular regions (z$a , z$l , z$b ) and (z$f , z$r , z$e ) will be ¬lled in.

def serif (su¬x $)(expr breadth , theta , left jut , right jut ) =
penpos$ (breadth /abs sind theta , 0);
z$a ’ z$l = z$f ’ z$r = (bracket /abs sind theta ) — dir theta ;
y$c = y$d ; y$b = y$e = y$ ; y$b ’ y$c = if theta < 0 : ’ ¬ slab ;
x$b = x$c = x$l ’ left jut ; x$d = x$e = x$r + right jut ;
labels($a, $b, $c, $d, $e, $f ) enddef ;
def serif edge su¬x $ =
(serif bracket ($a, $l, $b) - - z$c
- - z$d - - reverse serif bracket ($f, $r, $e)) enddef ;
def serif bracket (su¬x i, j, k) =
(z.i{z.j ’ z.i} . . . serif darkness [z.j, .5[z.i, z.k] ]{z.k ’ z.i}
. . . z.k{z.k ’ z.j}) enddef ;

Under what circumstances will the serif edge go through points z$l and z$r ?

Should this serif macro be used before points z$l , z$ , and z$r have been
de¬ned, or should those points be de¬ned ¬rst?

Here are two sample letters that show how these serif routines might be used.
The programs assume that the font has several additional ad hoc parameters:
u, a unit of character width; ht , the character height; thin and thick , the two stroke
weights; and jut , the amount by which serifs protrude on a “normal” letter like ˜H™.
Chapter 18: De¬nitions (also called Macros) 163

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

beginchar ("A", 13u#, ht#, 0);
z1 = (.5w, 1.05h); % top point
x4l = w ’ x5r = u; y4l = y5r = slab ; % bottom points
numeric theta [ ];
theta 4 = angle(z1 ’ z4l ); % left stroke angle
theta 5 = angle(z1 ’ z5r ); % right stroke angle
serif (4, thin , theta 4 , .6jut , jut ); % left serifs
serif (5, thick , theta 5 , jut , .6jut ); % right serifs
z0 = z4r + whatever — dir theta 4
= z5l + whatever — dir theta 5 ; % inside top point
¬ll z1 - - serif edge 4 - - z0 % the left stroke
& z0 - - serif edge 5 - - z1 & cycle; % the right stroke
penpos2 (whatever , theta 4 );
penpos3 (whatever , theta 5 );
y2r = y3r = .5[y4 , y0 ]; % crossbar height
y2l = y3l = y2r ’ thin ; % crossbar thickness
z2 = whatever [z1 , z4r ];
z3 = whatever [z1 , z5l ];
penstroke z2e - - z3e ; % the crossbar
penlabels(0, 1, 2, 3, 4, 5); endchar;
beginchar ("I", 6u#, ht#, 0);
x1 = x2 = .5w;
y1 = h ’ y2 ; y2 = slab ;
serif (1, thick , ’90, 1.1jut, 1.1jut); % upper serifs
serif (2, thick , 90, 1.1jut, 1.1jut); % lower serifs
¬ll serif edge 2 - - reverse serif edge 1 - - cycle; % the stroke
penlabels(1, 2); endchar;
The illustration was prepared with thin = .5pt , thick = 1.1pt , u = .6pt , ht = 7pt ,
slab = .25pt , jut = .9pt , bracket = pt , and serif darkness = 1/3.
Could the equations de¬ning y1 and y2 in the program for "I" have been
replaced by ˜y1c = h™ and ˜y2c = 0™ ?
Write the program for an "H" to go with these letters.
164 Chapter 18: De¬nitions (also called Macros)

A second approach to serifs can be based on the example at the end of Chap-
ter 16. In this case we assume that broad pen is a ˜pensquare xscaled px
yscaled py rotated phi ™ for some px > py and some small angle phi . Thicker strokes
will be made by using this pen to ¬ll a larger region; the serif routine is given the
distance xx between z$l and z$r . There™s a pair variable called dishing that controls
the curvature between z$c and z$d . Top and bottom serifs are similar, but they are
su¬ciently di¬erent that it™s easier to write separate macros for each case.
def bot serif (su¬x $)(expr xx , theta , left jut , right jut ) =
penpos$ (xx , 0); z$a ’ z$l = z$f ’ z$r = (bracket /abs sind theta ) — dir theta ;
y$c = top y$l ; y$d = y$r ; x$c = x$l ’ left jut ; x$d = x$r + right jut ;
z$b = z$l + whatever — dir theta = z$c + whatever — dir phi ;
z$e = z$r + whatever — dir theta = z$d + whatever — dir ’phi ;
labels($a, $b, $c, $d, $e, $f ) enddef ;
def bot serif edge su¬x $ =
(z$a . . controls z$b . . z$c
- - (¬‚ex (z$c , .5[z$c , z$d ] + dishing , z$d )) shifted (0, ’epsilon )
- - z$d . . controls z$e . . z$f ) enddef ;

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

beginchar ("A", 13u#, ht #, 0); pickup broad pen ;
z1 = (.5w, top h); lft x4l = w ’ rt x5r = 1.2u; y4l = y5r = 0;
numeric theta [ ]; theta 4 = angle(z1 ’ z4l ); theta 5 = angle(z1 ’ z5r );
numeric xxx ; px — sind(theta 5 ’ phi ) + xxx — sind theta 5 = px — cosd phi + xx ;
bot serif (4, 0, theta 4 , .8jut , .8jut ); bot serif (5, xxx , theta 5 , .6jut , .8jut );
z0 = z4r + whatever — dir theta 4 = z5l + whatever — dir theta 5 ;
¬lldraw z1 - - bot serif edge 4 - - z0 & z0 - - bot serif edge 5 - - z1 & cycle;
top y2 = top y3 = .45bot y0 ; z2 = whatever [z1 , z4r ]; z3 = whatever [z1 , z5l ];
draw z2 - - z3 ; penlabels(0, 1, 2, 3, 4, 5); endchar;
beginchar ("I", 6u#, ht #, 0); pickup broad pen ;
x1 = x2 = .5w; y1 = h; y2 = 0;
top serif (1, xx , ’90, 1.1jut , 1.1jut ); bot serif (2, xx , 90, 1.1jut , 1.1jut );
¬lldraw bot serif edge 2 - - reverse top serif edge 1 - - cycle;
penlabels(1, 2); endchar;
In the illustration, px = .8pt , py = .2pt , phi = 20, xx = .3pt , u = .6pt , ht = 7pt ,
jut = .9pt , bracket = pt , and dishing = (.25pt , 0) rotated 20.
Chapter 18: De¬nitions (also called Macros) 165

EXERCISE 18.7 de¬nition
Write the missing code for top serif and top serif edge .
EXERCISE 18.8 :=
(For mathematicians.) Explain the equation for xxx in the program for "A". de¬nition heading
parameter heading
EXERCISE 18.9 delimited parameters
Write the program for an "H" to go with these letters. (
parameter type
A close look at the serif edge routines in these examples will reveal that some
parentheses are curiously lacking: We said ˜def serif edge su¬x $™ instead su¬x
of ˜def serif edge (su¬x $)™, and we used the macro by saying ˜serif edge 5 ™ instead of text
parameter tokens
˜serif edge (5)™. The reason is that allows the ¬nal parameter of a macro ,
to be without delimiters; this is something that could not have been guessed from a undelimited parameters
study of previous examples. It is time now to stop looking at speci¬c cases and to start
examining the complete set of rules for macro de¬nitions. Here is the syntax: tertiary
de¬nition ’’ de¬nition heading is replacement text enddef expr
is ’’ = | := su¬x
de¬nition heading ’’ def symbolic token parameter heading text
| vardef heading vardef heading
leveldef heading
| leveldef heading
parameter heading ’’ delimited parameters undelimited parameters
delimited parameters ’’ empty
| delimited parameters ( parameter type parameter tokens )
parameter type ’’ expr
| suffix
| text
parameter tokens ’’ symbolic token
| parameter tokens , symbolic token
undelimited parameters ’’ empty
| primary symbolic token
| secondary symbolic token
| tertiary symbolic token
| expr symbolic token
| expr symbolic token of symbolic token
| suffix symbolic token
| text symbolic token

(We™ll discuss vardef heading and leveldef heading in Chapter 20.) The basic idea is
that we name the macro to be de¬ned, then we name zero or more delimited parameters
(i.e., parameters in parentheses), then we name zero or more undelimited parameters.
Then comes an ˜=™ sign, followed by the replacement text, and enddef . The ˜=™ sign
might also be ˜:=™ ; both mean the same thing.

Delimited parameters are of type expr, su¬x, or text; two or more param-
eters of the same type may be listed together, separated by commas. For
example, ˜(expr a, b)™ means exactly the same thing as ˜(expr a)(expr b)™. Undelim-
ited parameters have eight possible forms, as shown in the syntax.
166 Chapter 18: De¬nitions (also called Macros)

The replacement text is simply ¬led away for future use, not interpreted, vardef
when reads a de¬nition. But a few tokens are treated specially:
def , vardef , primarydef , secondarydef , and tertiarydef are considered quote
to introduce de¬nitions inside de¬nitions. capsule
enddef ends the replacement text, unless it matches a previous def -like token
(as listed in the preceding rule).
Each symbolic token that stands for a parameter, by virtue of its appear-
ance in the parameter heading or leveldef heading , is changed to a special
internal “parameter token” wherever it occurs in the replacement text. When-
ever this special token is subsequently encountered, will substitute
the appropriate argument.
quote disables any special interpretation of the immediately following token.
A ˜quote™ doesn™t survive in the replacement text (unless, of course, it has
been quoted).
Check your understanding of these rules by ¬guring out what the replacement
text is, in the following weird de¬nition:
def foo(text t) expr e of p :=
def t = e enddef; quote def quote t = p enddef
does not expand macros when it reads a de¬nition ; but at almost
all other times it will replace a de¬ned token by the corresponding replacement
text, after ¬nding all the arguments. The replacement text will then be read as if it
had been present in the program all along.
How does determine the arguments to a macro? Well, it knows
what kinds of arguments to expect, based on the parameter heading. Let™s
consider delimited arguments ¬rst:
A delimited expr argument should be of the form ˜( expression )™; the expres-
sion is evaluated and put into a special “capsule” token that will be substituted
for the parameter wherever it appears in the replacement text.
A delimited su¬x argument should be of the form ˜( su¬x )™; subscripts that
occur in the su¬x are evaluated and replaced by numeric tokens. The result
is a list of zero or more tokens that will be substituted for the parameter
wherever it appears in the replacement text.
A delimited text argument should be of the form ˜( text )™, where text is any
sequence of tokens that is balanced with respect to the delimiters surrounding
it. This sequence of tokens will be substituted for the parameter wherever it
appears in the replacement text.
When there are two or more delimited parameters, you can separate the ar-
guments by commas instead of putting parentheses around each one. For
example, three delimited arguments could be written either as ˜(a)(b)(c)™ or
˜(a, b)(c)™ or ˜(a)(b, c)™ or ˜(a, b, c)™. However, this abbreviation doesn™t work
after text arguments, which must be followed by ˜)™ because text arguments
can include commas.
Chapter 18: De¬nitions (also called Macros) 167

Chapter 8 points out that you can use other delimiters besides parentheses. In delimiters
general, a comma following a delimited expr or su¬x argument is equivalent
to two tokens ˜) (™, corresponding to whatever delimiters enclose that comma. tertiary
EXERCISE 18.11 of
After ˜def f(expr a)(text b,c)=...enddef™ and ˜delimiters {{ }}™, what
are the arguments in ˜f{{x,(,}}((}}))™ ? endgroup
The rules for undelimited arguments are similar. An undelimited primary, group
secondary, tertiary, or expr is the longest syntactically correct primary , ¬ll
secondary , tertiary , or expression that immediately follows the delimited argu- hide
ments. An undelimited ˜expr x of y™ speci¬es two arguments, found by taking the JOHNSON
longest syntactically correct expression of primary . In each of these cases, the ex-
pression might also be preceded by an optional ˜=™ or ˜:=™. An undelimited su¬x is
the longest su¬x that immediately follows the delimited arguments; also
allows ˜( su¬x )™ in this case, but not ˜= su¬x ™ or ˜:= su¬x ™. An undelimited text
essentially runs to the end of the current statement; more precisely, it runs to the ¬rst
˜;™ or ˜endgroup™ or ˜end™ that is not part of a group within the argument.
Appendix B contains lots of macros that illustrate these rules. For example,
def ¬ll expr c = addto currentpicture contour c enddef ;
def erase text t = cullit; t withweight ’1; cullit enddef ;
these are slight simpli¬cations of the real de¬nitions, but they retain the basic ideas.
The command ˜erase ¬ll p™ causes ˜¬ll p™ to be the text argument to erase, after
which ˜p™ becomes the expr argument to ¬ll.
The ˜pickup™ macro in Appendix B starts with ˜def pickup secondary q™;
why is the argument a secondary instead of an expression?
Explain why the following ˜hide ™ macro allows you to hide any sequence of
statements in the midst of an expression:
def hide (text t) = gobble begingroup t; endgroup enddef ;
def gobble primary g = enddef ;

DEFINI TION, s. [de¬nitio, Latin.]
1. A short description of a thing by its properties.
” SAMUEL JOHNSON, A Dictionary of the English Language (1755)

DEFINI TION, n. [L. de¬nitio. See De¬ne.]
1. A brief description of a thing by its properties;
as a de¬nition of wit or of a circle.
” NOAH WEBSTER, An American Dictionary of the English Language (1828)
(page 168)

and Loops
Chapter 19: Conditions and Loops 169

If decisions never had to be made, life would be much easier, and so would pro- loops
gramming. But sometimes it is necessary to choose between alternatives, and mouth
allows programs to take di¬erent paths depending on the circum- stomach
stances. You just say something like else
if not decisions : life := programming := easier (much ) condition
elseif choice = a: program a if
else: program b ¬ ¬
which reduces, for example, to ˜program b ™ if and only if decisions = true and else
choice = a. The normal left-to-right order of program interpretation can also be
modi¬ed by specifying “loops,” which tell the computer to read certain tokens :
repeatedly, with minor variations, until some condition becomes true. We have
seen many examples of these mechanisms already; the purpose of the present
chapter is to discuss the entire range of possibilities.
™s conditions and loops are di¬erent from those in most other
programming languages, because the conditional or iterated code does not have
to ¬t into the syntactic structure. For example, you can write strange things like
p = (if b: 0,0)..(1,5 else: u,v fi)
where the conditional text ˜0, 0) . . (1, 5™ makes no sense by itself, although it
becomes meaningful when read in context. In this respect conditions and loops
behave like macros. They specify rules of token transformation that can be said
to take place in ™s “mouth” before the tokens are actually digested
in the computer™s “stomach.”
The ¬rst conditional example above has three alternatives, in the form
if boolean1 : text1 elseif boolean2 : text2 else: text3 ¬
and the second example has just two; there can be any number of ˜elseif ™ clauses
before ˜else:™. Only one of the conditional texts will survive, namely the ¬rst one
whose condition is true; ˜else:™ is always true. You can also omit ˜else:™ entirely,
in which case ˜else: empty ™ is implied just before the closing ˜¬™. For example,
plain ™s mode setup routine includes the conditional command
if unknown mag : mag := 1; ¬
whose e¬ect is to set mag equal to 1 if it hasn™t already received a value; in this
case there™s only one alternative.
Would it be wrong to put the ˜;™ after the ˜¬™ in the example just given?
The informal rules just stated can, of course, be expressed more formally as
rules of syntax:
condition ’’ if boolean expression : conditional text alternatives fi
alternatives ’’ empty
| else : conditional text
| elseif boolean expression : conditional text alternatives
170 Chapter 19: Conditions and Loops

Every conditional construction begins with ˜if ™ and ends with ˜¬™. The conditional texts if
Boolean expressions
are any sequences of tokens that are balanced with respect to ˜if ™ and ˜¬™; furthermore,
˜elseif ™ and ˜else™ can occur in a conditional text only when enclosed by ˜if ™ and ˜¬™. boolean
boolean primary
Each ˜if ™ and ˜elseif ™ must be followed by a boolean expression , i.e., by an true
expression whose value is either ˜true™ or ˜false™. Boolean expressions are (
named after George Boole, the founder of algebraic approaches to logic. Chapter 7 )
points out that variables can be of type boolean, and numerous examples of boolean
expressions appear in Chapter 8. It™s time now to be more systematic, so that we known
will know the facts about boolean expressions just as we have become well-versed in unknown
numeric expressions, pair expressions, picture expressions, path expressions, transform
expressions, and pen expressions. Here are the relevant syntax rules: not
boolean secondary
boolean primary ’’ boolean variable and
boolean tertiary
| true | false or
| ( boolean expression ) boolean expression
| begingroup statement list boolean expression endgroup relation

<< . .

. 20
( : 45)

. . >>