<< . .

. 33
( : 45)



. . >>

..controls a[postcontrol t of p, postcontrol t of q]
and a[precontrol t+1 of p, precontrol t+1 of q] .. endfor
if cycle p: cycle
else: a[point infinity of p, point infinity of q] fi enddef;
Finally we come to the solve macro, which has already been presented in
Chapter 20. Appendix D gives further illustrations of its use.
vardef solve@#(expr true_x,false_x)= % @#(true_x)=true, @#(false_x)=false
tx_:=true_x; fx_:=false_x;
forever: x_:=.5[tx_,fx_]; exitif abs(tx_-fx_)<=tolerance;
if @#(x_): tx_ else: fx_ fi :=x_; endfor
x_ enddef; % now x_ is near where @# changes from true to false
newinternal tolerance, tx_,fx_,x_; tolerance:=.1;
3. Conversion to pixels. The next main subdivision of plain.mf contains macros and
constants that help convert dimensions from device-independent “sharped” or “true”
units into the pixel units corresponding to a particular device. First comes a subroutine
that computes eight basic units, assuming that the number of pixels per inch is known:
def fix_units = % define the conversion factors, given pixels_per_inch
mm:=pixels_per_inch/25.4; cm:=pixels_per_inch/2.54;
pt:=pixels_per_inch/72.27; pc:=pixels_per_inch/6.0225;
dd:=1238/1157pt; cc:=12dd;
bp:=pixels_per_inch/72; in:=pixels_per_inch;
hppp:=pt; % horizontal pixels per point
vppp:=aspect_ratio*hppp; % vertical pixels per point
enddef;
268 Appendix B: Basic Operations


Sharped units are actually expressed in terms of points, but a virtuous user Sharped units
pixels per inch
will not write programs that exploit this fact.
blacker
o correction
mm#=2.84528; pt#=1; dd#=1.07001; bp#=1.00375; ¬llin
cm#=28.45276; pc#=12; cc#=12.84010; in#=72.27; de¬ne pixels (and nine others)
lowres ¬x
A particular device is supposed to be modeled by four parameters, called
pixels per inch , blacker , o correction , and ¬llin , as discussed in Chapter 11. Appro-
priate values will be assigned to these internal quantities by mode setup.
newinternal pixels_per_inch; % the given resolution
newinternal blacker, o_correction; % device-oriented corrections
(The fourth parameter, ¬llin , is already an internal quantity of .)
Here are the ten principal ways to convert from sharped units to pixels:
def define_pixels(text t) =
forsuffixes $=t: $:=$.#*hppp; endfor enddef;
def define_whole_pixels(text t) =
forsuffixes $=t: $:=hround($.#*hppp); endfor enddef;
def define_whole_vertical_pixels(text t) =
forsuffixes $=t: $:=vround($.#*hppp); endfor enddef;
def define_good_x_pixels(text t) =
forsuffixes $=t: $:=good.x($.#*hppp); endfor enddef;
def define_good_y_pixels(text t) =
forsuffixes $=t: $:=good.y($.#*hppp); endfor enddef;
def define_blacker_pixels(text t) =
forsuffixes $=t: $:=$.#*hppp+blacker; endfor enddef;
def define_whole_blacker_pixels(text t) =
forsuffixes $=t: $:=hround($.#*hppp+blacker);
if $<=0: $:=1; fi endfor enddef;
def define_whole_vertical_blacker_pixels(text t) =
forsuffixes $=t: $:=vround($.#*hppp+blacker);
if $<=0: $:=1_o_; fi endfor enddef;
def define_corrected_pixels(text t) =
forsuffixes $=t: $:=vround($.#*hppp*o_correction)+eps; endfor enddef;
def define_horizontal_corrected_pixels(text t) =
forsuffixes $=t: $:=hround($.#*hppp*o_correction)+eps; endfor enddef;
Chapter 24 discusses the lowres ¬x routine, which helps to correct anomalies
that may have occurred when sharped dimensions were rounded to whole pixels.
def lowres_fix(text t) expr ratio =
begingroup save min,max,first;
forsuffixes $=t:
if unknown min: min=max=first=$; min#=max#=$.#;
elseif $.#<min#: min:=$; min#:=$.#;
elseif $.#>max#: max:=$; max#:=$.#; fi endfor
if max/min>ratio*max#/min#: forsuffixes $=t: $:=first; endfor fi
endgroup enddef;
Appendix B: Basic Operations 269


4. Modes of operation. The standard way to create a font with plain is to **
mag
start up the program by saying
mode
input
\mode= mode name ; mag= magni¬cation ; input font ¬le name smode
mode setup
in response to ™s initial ˜**™. The mag is omitted if the magni¬cation is 1, smode
and the mode is omitted if mode=proof. Additional commands like ˜screenchars™ might displaying
extra setup
be given before the ˜input™; we shall discuss them later. If you are using another
mode name
base ¬le, called say the ˜super™ base, this whole command line should be preceded scantokens
by ˜&super™. The mode name should have been predeclared in your base ¬le, by the
mode_def routine below. If, however, you need a special mode that isn™t in the base,
you can put its commands into a ¬le (e.g., ˜specmode.mf™) and invoke it by saying
\smode="specmode"; mag= · · ·
instead of giving a predeclared mode name.
Here is the mode setup routine, which is usually one of the ¬rst macros to
be called in a program:
def mode_setup =
warningcheck:=0;
if unknown mode: mode=proof; fi
numeric aspect_ratio; transform currenttransform;
scantokens if string mode:("input "&mode) else: mode_name[mode] fi;
if unknown mag: mag=1; fi
if unknown aspect_ratio: aspect_ratio=1; fi
displaying:=proofing;
pixels_per_inch:=pixels_per_inch*mag;
if aspect_ratio=1: let o_=\; let _o_=\
else: def o_=*aspect_ratio enddef; def _o_=/aspect_ratio enddef fi;
fix_units;
scantokens extra_setup; % the user™s special last-minute adjustments
currenttransform:=
if unknown currenttransform: identity else: currenttransform fi
yscaled aspect_ratio;
clearit;
pickup pencircle scaled (.4pt+blacker);
warningcheck:=1; enddef;
def smode = string mode; mode enddef;
string extra_setup, mode_name[];
extra_setup=""; % usually there™s nothing special to do
newinternal displaying; % if positive, endchar will ˜showit™
The ¬rst ˜scantokens™ in mode setup either reads a special ¬le or calls a macro that
expands into commands de¬ning the mode. Notice that aspect ratio is always cleared to
an unde¬ned value when these commands are performed; you can™t simply give a value
to aspect ratio when you set mode and mag . If the aspect ratio isn™t assigned a de¬nite
value by the mode routine, it will become unity, and the ˜o_™ and ˜_o_™ operations will
be omitted from subsequent calculations. Notice also that the mode commands might
do something special to mag , since mag isn™t examined until after the mode routine
has acted. The currenttransform might also be given a special value. ™s
270 Appendix B: Basic Operations


warningcheck is temporarily disabled during these computations, since there might be warningcheck
mexp
more than 4096 pixels per inch. After mode setup is ¬nished, the currentpicture will
magstep
be null, the currenttransform will take the aspect ratio into account, and the currentpen underscore
will be a circular nib with the standard default thickness of 0.4 pt. (You should save expandafter
quote
this pen if you want to use it in a character, because beginchar will clear it away.) mode def
Plain TEX has a convention for magnifying fonts in terms of “magsteps,” where Leban
magstep m = 1.2m . A geometric progression of font sizes is convenient, because scaling proof
smoke
by magstep m followed by magstep n is equivalent to scaling by magstep m + n. fontmaking
lowres
vardef magstep primary m = mexp(46.67432m) enddef;
When a mode is de¬ned (e.g., ˜proof™), a numeric variable of that name is
created and assigned a unique number (e.g., 1). Then an underscore character is ap-
pended, and a macro is de¬ned for the resulting name (e.g., ˜proof_™). The mode name
array is used to convert between number and name (e.g., mode name 1 = "proof_").
def mode_def suffix $ =
$:=incr number_of_modes;
mode_name[$]:=str$ & "_";
expandafter quote def scantokens mode_name[$] enddef;
newinternal number_of_modes;
(This mode def strategy was suggested by Bruce Leban.)
Three basic modes are now de¬ned, starting with two for proo¬ng:
% proof mode: for initial design of characters
mode_def proof =
proofing:=2; % yes, we™re making full proofs
fontmaking:=0; % no, we™re not making a font
tracingtitles:=1; % yes, show titles online
pixels_per_inch:=2601.72; % that™s 36 pixels per pt
blacker:=0; % no additional blackness
fillin:=0; % no compensation for fillin
o_correction:=1; % no reduction in overshoot
enddef;
% smoke mode: for label-free proofs to mount on the wall
mode_def smoke =
proof_; % same as proof mode, except:
proofing:=1; % yes, we™re making unlabeled proofs
extra_setup:=extra_setup&"grayfont black"; % with solid black pixels
let makebox=maketicks; % make the boxes less obtrusive
enddef;
Notice that smoke mode saves a lot of fuss by calling on ˜proof_™; this is the macro
that was de¬ned by the ¬rst mode def .
A typical mode for font generation appears next.
% lowres mode: for certain devices that print 200 pixels per inch
mode_def lowres =
proofing:=0; % no, we™re not making proofs
fontmaking:=1; % yes, we are making a font
Appendix B: Basic Operations 271


tracingtitles:=0; % no, don™t show titles at all currentpen
currentpicture
pixels_per_inch:=200; % that™s pretty low resolution
currenttransform
blacker:=.65; % make pens a bit blacker ¬ll
draw
fillin:=.2; % compensate for diagonal fillin
¬lldraw
o_correction:=.4; % but don™t overshoot as much drawdot
enddef; un¬ll
undraw
localfont:=lowres; % the mode most commonly used to make fonts un¬lldraw
undrawdot
Installations of typically have several more prede¬ned modes, and they erase
generally set localfont to something else. Such alterations should not be made in the cutdraw
master ¬le plain.mf; they should appear in a separate ¬le, as discussed below.
5. Drawing and ¬lling. Now we come to the macros that provide an interface between
the user and ™s primitive picture commands. First, some important program
variables are introduced:
pen currentpen;
path currentpen_path;
picture currentpicture;
transform currenttransform;
def t_ = transformed currenttransform enddef;
The key macros are ¬ll, draw, ¬lldraw, and drawdot.
def fill expr c = addto_currentpicture contour c.t_ enddef;
def addto_currentpicture = addto currentpicture enddef;
def draw expr p =
addto_currentpicture doublepath p.t_ withpen currentpen enddef;
def filldraw expr c = fill counterclockwise c withpen currentpen enddef;
def drawdot expr z = if unknown currentpen_path: def_pen_path_ fi
addto_currentpicture contour
currentpen_path shifted (z.t_) withpen penspeck enddef;
def def_pen_path_ =
hide(currentpen_path=tensepath makepath currentpen) enddef;
And they have negative counterparts:
def unfill expr c = fill c withweight -1 enddef;
def undraw expr p = draw p withweight -1 enddef;
def unfilldraw expr c = filldraw c withweight -1 enddef;
def undrawdot expr z = drawdot z withweight -1 enddef;
def erase text t = begingroup interim default_wt_:=-1;
cullit; t withweight -1; cullit; endgroup enddef;
newinternal default_wt_; default_wt_:=1;
It™s more di¬cult to cut o¬ the ends of a stroke, but the following macros
(discussed near the end of Chapter 16) do the job:
def cutdraw expr p = % caution: you may need autorounding=0
cutoff(point 0 of p, 180+angle direction 0 of p);
cutoff(point infinity of p, angle direction infinity of p);
culldraw p enddef;
272 Appendix B: Basic Operations


def culldraw expr p = addto pic_ doublepath p.t_ withpen currentpen; culldraw
cuto¬
cull pic_ dropping(-infinity,0) withweight default_wt_;
default wt
addto_currentpicture also pic_; pic_:=nullpicture; killtext enddef; erase
pen lft
vardef cutoff(expr z,theta) =
pen rt
interim autorounding := 0; interim smoothing := 0; pen top
pen bot
addto pic_ doublepath z.t_ withpen currentpen;
pickup
addto pic_ contour savepen
(cut_ scaled (1+max(-pen_lft,pen_rt,pen_top,-pen_bot)) clearpen
rotated theta shifted z)t_;
cull pic_ keeping (2,2) withweight -default_wt_;
addto currentpicture also pic_; pic_:=nullpicture enddef;
picture pic_; pic_:=nullpicture;
path cut_; cut_ = ((0,-1)--(1,-1)--(1,1)--(0,1)--cycle) scaled 1.42;
The use of default wt here makes ˜erase cutdraw™ work. The private variable pic is
usually kept equal to nullpicture in order to conserve memory space.
Picking up a pen not only sets currentpen , it also establishes the values of
pen lft , pen rt , pen top , and pen bot , which are used by lft , rt , top , and bot .

def pickup secondary q =
if numeric q: numeric_pickup_ else: pen_pickup_ fi q enddef;
def numeric_pickup_ primary q =
if unknown pen_[q]: errmessage "Unknown pen"; clearpen
else: currentpen:=pen_[q];
pen_lft:=pen_lft_[q]; pen_rt:=pen_rt_[q];
pen_top:=pen_top_[q]; pen_bot:=pen_bot_[q];
currentpen_path:=pen_path_[q] fi; enddef;
def pen_pickup_ primary q =
currentpen:=q yscaled aspect_ratio;
pen_lft:=xpart penoffset down of currentpen;
pen_rt:=xpart penoffset up of currentpen;
pen_top:=(ypart penoffset left of currentpen)_o_;
pen_bot:=(ypart penoffset right of currentpen)_o_;
path currentpen_path; enddef;
newinternal pen_lft,pen_rt,pen_top,pen_bot,pen_count_;
And saving a pen saves all the relevant values for later retrieval.

vardef savepen = pen_[incr pen_count_]=currentpen;
pen_lft_[pen_count_]=pen_lft;
pen_rt_[pen_count_]=pen_rt;
pen_top_[pen_count_]=pen_top;
pen_bot_[pen_count_]=pen_bot;
pen_path_[pen_count_]=currentpen_path;
pen_count_ enddef;
def clearpen = currentpen:=nullpen;
pen_lft:=pen_rt:=pen_top:=pen_bot:=0;
path currentpen_path; enddef;
Appendix B: Basic Operations 273


def clear_pen_memory = clear pen memory
lft
pen_count_:=0;
rt
numeric pen_lft_[],pen_rt_[],pen_top_[],pen_bot_[]; top
bot
pen currentpen,pen_[];
round
path currentpen_path, pen_path_[]; good.x
enddef; good.y
good.lft
good.rt
The four basic pen-edge functions o¬er no surprises:
good.top
good.bot
vardef lft primary x = x + if pair x: (pen_lft,0) else: pen_lft fi enddef; penpos
vardef rt primary x = x + if pair x: (pen_rt,0) else: pen_rt fi enddef; penstroke
vardef top primary y = y + if pair y: (0,pen_top) else: pen_top fi enddef;
vardef bot primary y = y + if pair y: (0,pen_bot) else: pen_bot fi enddef;
There are six functions that round to good positions for pen placement.
vardef good.x primary x = hround(x+pen_lft)-pen_lft enddef;
vardef good.y primary y = vround(y+pen_top)-pen_top enddef;
vardef good.lft primary z = save z_; pair z_;
(z_+(pen_lft,0))t_=round((z+(pen_lft,0))t_); z_ enddef;
vardef good.rt primary z = save z_; pair z_;
(z_+(pen_rt,0))t_=round((z+(pen_rt,0))t_); z_ enddef;
vardef good.top primary z = save z_; pair z_;
(z_+(0,pen_top))t_=round((z+(0,pen_top))t_); z_ enddef;
vardef good.bot primary z = save z_; pair z_;
(z_+(0,pen_bot))t_=round((z+(0,pen_bot))t_); z_ enddef;
So much for ¬xed pens. When pen-like strokes are de¬ned by outlines, the
penpos macro is of primary importance. Since penpos may be used quite frequently,
we might as well write out the x and y coordinates explicitly instead of using the
(somewhat slower) z convention:

vardef penpos@#(expr b,d) =
(x@#r-x@#l,y@#r-y@#l)=(b,0) rotated d;
x@#=.5(x@#l+x@#r); y@#=.5(y@#l+y@#r) enddef;
Simulated pen strokes are provided by the convenient penstroke command.
def penstroke text t =
forsuffixes e = l,r: path_.e:=t; endfor
if cycle path_.l: cyclestroke_
else: fill path_.l -- reverse path_.r -- cycle fi enddef;
def cyclestroke_ =
begingroup interim turningcheck:=0;
addto pic_ contour path_.l.t_ withweight 1;
addto pic_ contour path_.r.t_ withweight -1;
cull pic_ dropping origin withweight default_wt_;
addto_currentpicture also pic_;
pic_:=nullpicture endgroup enddef;
path path_.l,path_.r;
274 Appendix B: Basic Operations


6. Proof labels and rules. The next main section of plain.mf is devoted to macros for special
numspecial
the annotations on proofsheets. These macros are discussed in Appendix H, and they
lcode
use the special and numspecial commands discussed in Appendix G. makelabel
Labels are generated at the lowest level by makelabel : proo¬ng
labels
penlabels
vardef makelabel@#(expr s,z) = % puts string s at point z
range
if known z: special lcode_@# & s; numtok
numspecial xpart(z.t_); numspecial ypart(z.t_) fi enddef; thru
forsu¬xes
string lcode_,lcode_.top,lcode_.lft,lcode_.rt,lcode_.bot, proofrule
lcode_.top.nodot,lcode_.lft.nodot,lcode_.rt.nodot,lcode_.bot.nodot; screenrule
lcode_.top=" 1"; lcode_.lft=" 2"; lcode_.rt=" 3"; lcode_.bot=" 4";
lcode_=" 0"; % change to " /" to avoid listing in overflow column
lcode_.top.nodot=" 5"; lcode_.lft.nodot=" 6";
lcode_.rt.nodot=" 7"; lcode_.bot.nodot=" 8";
Users generally don™t invoke makelabel directly, because there™s a conve-
nient shorthand. For example, ˜labels(1, 2, 3)™ expands into ˜makelabel ("1", z1 );
makelabel ("2", z2 ); makelabel ("3", z3 )™. (But nothing happens if proo¬ng ¤ 1.)
vardef labels@#(text t) =
if proofing>1: forsuffixes $=t: makelabel@#(str$,z$); endfor fi enddef;
vardef penlabels@#(text t) =
if proofing>1: forsuffixes $$=l,,r: forsuffixes $=t:
makelabel@#(str$.$$,z$.$$); endfor endfor fi enddef;
When there are lots of purely numeric labels, you can say, e.g.,
labels(1, range 5 thru 9, range 100 thru 124, 223)
which is equivalent to ˜labels(1, 5, 6, 7, 8, 9, 100, 101, . . . , 124, 223)™. Labels are omitted
from the proofsheets if the corresponding z value isn™t known, so it doesn™t hurt (much)
to include unused subscript numbers in a range.
def range expr x = numtok[x] enddef;
def numtok suffix x=x enddef;
tertiarydef m thru n =
m for x=m+1 step 1 until n: , numtok[x] endfor enddef;
(This range abbreviation will work in any forsu¬xes list; and in a ˜for™ list you can
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

<< . .

. 33
( : 45)



. . >>