§719  TEX82  PART 36: TYPESETTING MATH FORMULAS 265

719. Typesetting math formulas. TEX’s most important routine for dealing with formulas is called mlist to hlist . After a formula has been scanned and represented as an mlist, this routine converts it to an hlist that can be placed into a box or incorporated into the text of a paragraph. There are three implicit parameters, passed in global variables: cur mlist points to the first node or noad in the given mlist (and it might be null ); cur style is a style code; and mlist penalties is true if penalty nodes for potential line breaks are to be inserted into the resulting hlist. After mlist to hlist has acted, link (temp head ) points to the translated hlist.

Since mlists can be inside mlists, the procedure is recursive. And since this is not part of TEX’s inner loop, the program has been written in a manner that stresses compactness over efficiency. ⟨ Global variables 13 ⟩ +≡ cur mlist : pointer ; { beginning of mlist to be translated } cur style: small number ; { style code at current place in the list } cur size: small number ; { size code corresponding to cur style } cur mu: scaled ; { the math unit width corresponding to cur size } mlist penalties : boolean; { should mlist to hlist insert penalties? }

720. The recursion in mlist to hlist is due primarily to a subroutine called clean box that puts a given noad field into a box using a given math style; mlist to hlist can call clean box , which can call mlist to hlist .

The box returned by clean box is “clean” in the sense that its shift amount is zero. procedure mlist to hlist ; forward ; function clean box (p : pointer ; s : small number ): pointer ;

label found ;

var q: pointer ; { beginning of a list to be boxed } save style: small number ; { cur style to be restored } x: pointer ; { box to be returned } r: pointer ; { temporary pointer }

begin case math type(p) of

math char : begin cur mlist ← new noad ; mem[nucleus (cur mlist )] ← mem[p];

end; sub box : begin q ← info(p); goto found ;

end; sub mlist : cur mlist ← info(p); othercases begin q ← new null box ; goto found ;

end endcases; save style ← cur style; cur style ← s; mlist penalties ← false; mlist to hlist ; q ← link (temp head ); { recursive call } cur style ← save style; { restore the style } ⟨ Set up the values of cur size and cur mu, based on cur style 703 ⟩;

found : if is char node(q) ∨ (q = null ) then x ← hpack (q, natural )

else if (link (q) = null ) ∧ (type(q) ≤ vlist node) ∧ (shift amount (q) = 0) then x ← q

{ it’s already clean } else x ← hpack (q, natural );

⟨ Simplify a trivial box 721 ⟩;

clean box ← x;

266  PART 36: TYPESETTING MATH FORMULAS  TEX82 §721

end; 721. Here we save memory space in a common case. ⟨ Simplify a trivial box 721 ⟩ ≡

q ← list ptr (x); if is char node(q) then

begin r ← link (q);

if r ≠ null then

if link (r) = null then

if ¬is char node(r) then

if type(r) = kern node then begin free node(r, small node size); link (q) ← null ; end;

end

This code is used in section 720.

722. It is convenient to have a procedure that converts a math char field to an “unpacked” form. The fetch routine sets cur f , cur c, and cur i to the font code, character code, and character information bytes of a given noad field. It also takes care of issuing error messages for nonexistent characters; in such cases, char exists (cur i ) will be false after fetch has acted, and the field will also have been reset to empty . procedure fetch(a : pointer ); { unpack the math char field a }

begin cur c ← character (a); cur f ← fam fnt (fam(a) + cur size); if cur f = null font then ⟨ Complain about an undefined family and set cur i null 723 ⟩ else begin if (qo(cur c) ≥ font bc[cur f ]) ∧ (qo(cur c) ≤ font ec[cur f ]) then

cur i ← char info(cur f )(cur c) else cur i ← null character ; if ¬(char exists (cur i )) then

begin char warning (cur f , qo(cur c)); math type(a) ← empty ; end;

end; end;

723. ⟨ Complain about an undefined family and set cur i null 723 ⟩ ≡ begin print err ( ); print size(cur size); print char ( ␣ ); print int (fam(a)); print ( ␣ ␣ ␣ ␣ ); print ASCII (qo(cur c)); print char ( );

help4 (  ␣  ␣   ␣    ␣       ␣    ␣      ␣   ␣    ␣  )
(  ␣         ␣    ␣  ␣         ␣    ␣       ␣   ␣  )
(  ␣   ␣     ´ ␣     ␣   ␣  ␣   ␣  ␣          ␣  )

(  ␣ ´ ␣   ␣  ␣      ␣    ␣ ␣      ␣    ␣  ); error ; cur i ← null character ;

math type(a) ← empty ; end

This code is used in section 722.

724. The outputs of fetch are placed in global variables. ⟨ Global variables 13 ⟩ +≡ cur f : internal font number ; { the font field of a math char } cur c: quarterword ; { the character field of a math char } cur i : four quarters ; { the char info of a math char , or a lig/kern instruction } 725. We need to do a lot of different things, so mlist to hlist makes two passes over the given mlist.

The first pass does most of the processing: It removes “mu” spacing from glue, it recursively evaluates all subsidiary mlists so that only the top-level mlist remains to be handled, it puts fractions and square roots and such things into boxes, it attaches subscripts and superscripts, and it computes the overall height and depth of the top-level mlist so that the size of delimiters for a left noad and a right noad will be known. The hlist resulting from each noad is recorded in that noad’s new hlist field, an integer field that replaces the nucleus or thickness .

The second pass eliminates all noads and inserts the correct glue and penalties between nodes. define new hlist (#) ≡ mem[nucleus (#)].int { the translation of an mlist }

726. Here is the overall plan of mlist to hlist , and the list of its local variables.

define done with noad = 80 { go here when a noad has been fully translated } define done with node = 81 { go here when a node has been fully converted } define check dimensions = 82 { go here to update max h and max d } define delete q = 83 { go here to delete q and move to the next node }

⟨ Declare math construction procedures 734 ⟩ procedure mlist to hlist ;

label reswitch, check dimensions , done with noad , done with node, delete q , done; var mlist : pointer ; { beginning of the given list }

penalties : boolean; { should penalty nodes be inserted? }

style: small number ; { the given style }

save style: small number ; { holds cur style during recursion }

q: pointer ; { runs through the mlist }

r: pointer ; { the most recent noad preceding q }

r type: small number ; { the type of noad r, or op noad if r = null }

t: small number ; { the effective type of noad q during the second pass } p, x, y, z: pointer ; { temporary registers for list construction } pen: integer ; { a penalty to be inserted } s: small number ; { the size of a noad to be deleted } max h, max d : scaled ; { maximum height and depth of the list translated so far } delta: scaled ; { offset between subscript and superscript }

begin mlist ← cur mlist ; penalties ← mlist penalties ; style ← cur style;

{ tuck global parameters away as local variables } q ← mlist ; r ← null ; r type ← op noad ; max h ← 0; max d ← 0; ⟨ Set up the values of cur size and cur mu, based on cur style 703 ⟩; while q ≠ null do ⟨ Process node-or-noad q as much as possible in preparation for the second pass of

mlist to hlist , then move to the next item in the mlist 727 ⟩; ⟨ Convert a final bin noad to an ord noad 729 ⟩; ⟨ Make a second pass over the mlist, removing all noads and inserting the proper spacing and

penalties 760 ⟩; end; 727. We use the fact that no character nodes appear in an mlist, hence the field type(q) is always present. ⟨ Process node-or-noad q as much as possible in preparation for the second pass of mlist to hlist , then move

to the next item in the mlist 727 ⟩ ≡ begin ⟨ Do first-pass processing based on type(q); goto done with noad if a noad has been fully

processed, goto check dimensions if it has been translated into new hlist (q), or goto done with node if a node has been fully processed 728 ⟩;

check dimensions : z ← hpack (new hlist (q), natural ); if height (z) > max h then max h ← height (z); if depth(z) > max d then max d ← depth(z); free node(z, box node size);

done with noad : r ← q; r type ← type(r); done with node: q ← link (q);

end

This code is used in section 726.

728. One of the things we must do on the first pass is change a bin noad to an ord noad if the bin noad is not in the context of a binary operator. The values of r and r type make this fairly easy. ⟨ Do first-pass processing based on type(q); goto done with noad if a noad has been fully processed, goto

check dimensions if it has been translated into new hlist (q), or goto done with node if a node has been fully processed 728 ⟩ ≡

reswitch: delta ← 0; case type(q) of bin noad : case r type of

bin noad , op noad , rel noad , open noad , punct noad , left noad : begin type(q) ← ord noad ; goto reswitch; end;

othercases do nothing

endcases; rel noad , close noad , punct noad , right noad : begin

⟨ Convert a final bin noad to an ord noad 729 ⟩;

if type(q) = right noad then goto done with noad ;

end; ⟨ Cases for noads that can follow a bin noad 733 ⟩ ⟨ Cases for nodes that can appear in an mlist, after which we goto done with node 730 ⟩ othercases confusion( ) endcases; ⟨ Convert nucleus (q) to an hlist and attach the sub/superscripts 754 ⟩

This code is used in section 727.

729. ⟨ Convert a final bin noad to an ord noad 729 ⟩ ≡ if r type = bin noad then type(r) ← ord noad

This code is used in sections 726 and 728.

730. ⟨ Cases for nodes that can appear in an mlist, after which we goto done with node 730 ⟩ ≡ style node: begin cur style ← subtype(q);

⟨ Set up the values of cur size and cur mu, based on cur style 703 ⟩; goto done with node; end;

choice node: ⟨ Change this node to a style node followed by the correct choice, then goto

done with node 731 ⟩;

ins node, mark node, adjust node, whatsit node, penalty node, disc node: goto done with node; rule node: begin if height (q) > max h then max h ← height (q);

if depth(q) > max d then max d ← depth(q); goto done with node; end;

glue node: begin ⟨ Convert math glue to ordinary glue 732 ⟩; goto done with node; end;

kern node: begin math kern(q, cur mu); goto done with node;

end;

This code is used in section 728.

731. define choose mlist (#) ≡ begin p ← #(q); #(q) ← null ; end

⟨ Change this node to a style node followed by the correct choice, then goto done with node 731 ⟩ ≡ begin case cur style div 2 of 0: choose mlist (display mlist ); { display style = 0 } 1: choose mlist (text mlist ); { text style = 2 } 2: choose mlist (script mlist ); { script style = 4 } 3: choose mlist (script script mlist ); { script script style = 6 } end; { there are no other cases } flush node list (display mlist (q)); flush node list (text mlist (q)); flush node list (script mlist (q)); flush node list (script script mlist (q)); type(q) ← style node; subtype(q) ← cur style; width(q) ← 0; depth(q) ← 0; if p ≠ null then

begin z ← link (q); link (q) ← p; while link (p) ≠ null do p ← link (p); link (p) ← z; end;

goto done with node; end

This code is used in section 730.

732. Conditional math glue (‘ ’) results in a glue node pointing to zero glue, with subtype(q) = cond math glue; in such a case the node following will be eliminated if it is a glue or kern node and if the current size is different from text size. Unconditional math glue (‘ ’) is converted to normal glue by multiplying the dimensions by cur mu. ⟨ Convert math glue to ordinary glue 732 ⟩ ≡

if subtype(q) = mu glue then begin x ← glue ptr (q); y ← math glue(x, cur mu); delete glue ref (x); glue ptr (q) ← y; subtype(q) ← normal ; end

else if (cur size ≠ text size) ∧ (subtype(q) = cond math glue) then begin p ← link (q); if p ≠ null then

if (type(p) = glue node) ∨ (type(p) = kern node) then begin link (q) ← link (p); link (p) ← null ; flush node list (p); end;

end

This code is used in section 730.

733. ⟨ Cases for noads that can follow a bin noad 733 ⟩ ≡ left noad : goto done with noad ; fraction noad : begin make fraction(q); goto check dimensions ;

end; op noad : begin delta ← make op(q);

if subtype(q) = limits then goto check dimensions ;

end; ord noad : make ord (q); open noad , inner noad : do nothing ; radical noad : make radical (q); over noad : make over (q); under noad : make under (q); accent noad : make math accent (q); vcenter noad : make vcenter (q);

This code is used in section 728.

734. Most of the actual construction work of mlist to hlist is done by procedures with names like make fraction, make radical , etc. To illustrate the general setup of such procedures, let’s begin with a couple of simple ones. ⟨ Declare math construction procedures 734 ⟩ ≡ procedure make over (q : pointer );

begin info(nucleus (q)) ← overbar (clean box (nucleus (q), cramped style(cur style)),

3 ∗ default rule thickness , default rule thickness ); math type(nucleus (q)) ← sub box ; end;

See also sections 735, 736, 737, 738, 743, 749, 752, 756, and 762. This code is used in section 726.

735. ⟨ Declare math construction procedures 734 ⟩ +≡ procedure make under (q : pointer );

var p, x, y: pointer ; { temporary registers for box construction }

delta: scaled ; { overall height plus depth } begin x ← clean box (nucleus (q), cur style); p ← new kern(3 ∗ default rule thickness ); link (x) ← p; link (p) ← fraction rule(default rule thickness ); y ← vpack (x, natural ); delta ← height (y) + depth(y) + default rule thickness ; height (y) ← height (x); depth(y) ← delta − height (y); info(nucleus (q)) ← y; math type(nucleus (q)) ← sub box ; end;

736. ⟨ Declare math construction procedures 734 ⟩ +≡ procedure make vcenter (q : pointer );

var v: pointer ; { the box that should be centered vertically }

delta: scaled ; { its height plus depth } begin v ← info(nucleus (q)); if type(v) ≠ vlist node then confusion( ); delta ← height (v) + depth(v); height (v) ← axis height (cur size) + half (delta); depth(v) ← delta − height (v); end;

737. According to the rules in the file specifications, we ensure alignment between a square root sign and the rule above its nucleus by assuming that the baseline of the square-root symbol is the same as the bottom of the rule. The height of the square-root symbol will be the thickness of the rule, and the depth of the square-root symbol should exceed or equal the height-plus-depth of the nucleus plus a certain minimum clearance clr . The symbol will be placed so that the actual clearance is clr plus half the excess. ⟨ Declare math construction procedures 734 ⟩ +≡ procedure make radical (q : pointer );

var x, y: pointer ; { temporary registers for box construction }

delta, clr : scaled ; { dimensions involved in the calculation } begin x ← clean box (nucleus (q), cramped style(cur style)); if cur style < text style then { display style }

clr ← default rule thickness + (abs (math x height (cur size)) div 4) else begin clr ← default rule thickness ; clr ← clr + (abs (clr ) div 4);

end; y ← var delimiter (left delimiter (q), cur size, height (x) + depth(x) + clr + default rule thickness ); delta ← depth(y) − (height (x) + depth(x) + clr ); if delta > 0 then clr ← clr + half (delta); { increase the actual clearance } shift amount (y) ← −(height (x) + clr ); link (y) ← overbar (x, clr , height (y)); info(nucleus (q)) ← hpack (y, natural ); math type(nucleus (q)) ← sub box ; end; 738. Slants are not considered when placing accents in math mode. The accenter is centered over the accentee, and the accent width is treated as zero with respect to the size of the final box. ⟨ Declare math construction procedures 734 ⟩ +≡ procedure make math accent (q : pointer );

label done, done1 ; var p, x, y: pointer ; { temporary registers for box construction }

a: integer ; { address of lig/kern instruction } c: quarterword ; { accent character } f: internal font number ; { its font } i: four quarters ; { its char info } s: scaled ; { amount to skew the accent to the right } h: scaled ; { height of character being accented } delta: scaled ; { space to remove between accent and accentee } w: scaled ; { width of the accentee, not including sub/superscripts }

begin fetch(accent chr (q)); if char exists (cur i ) then

begin i ← cur i ; c ← cur c; f ← cur f ; ⟨ Compute the amount of skew 741 ⟩; x ← clean box (nucleus (q), cramped style(cur style)); w ← width(x); h ← height (x); ⟨ Switch to a larger accent if available and appropriate 740 ⟩; if h < x height (f) then delta ← h else delta ← x height (f); if (math type(supscr (q)) ≠ empty ) ∨ (math type(subscr (q)) ≠ empty ) then

if math type(nucleus (q)) = math char then ⟨ Swap the subscript and superscript into box x 742 ⟩; y ← char box (f, c); shift amount (y) ← s + half (w − width(y)); width(y) ← 0; p ← new kern(−delta); link (p) ← x; link (y) ← p; y ← vpack (y, natural ); width(y) ← width(x); if height (y) < h then ⟨ Make the height of box y equal to h 739 ⟩; info(nucleus (q)) ← y; math type(nucleus (q)) ← sub box ; end;

end;

739. ⟨ Make the height of box y equal to h 739 ⟩ ≡ begin p ← new kern(h − height (y)); link (p) ← list ptr (y); list ptr (y) ← p; height (y) ← h; end

This code is used in section 738.

740. ⟨ Switch to a larger accent if available and appropriate 740 ⟩ ≡

loop begin if char tag (i) ≠ list tag then goto done; y ← rem byte(i); i ← char info(f)(y); if ¬char exists (i) then goto done; if char width(f)(i) > w then goto done; c ← y; end;

done:

This code is used in section 738.

741. ⟨ Compute the amount of skew 741 ⟩ ≡ s ← 0; if math type(nucleus (q)) = math char then

begin fetch(nucleus (q));

if char tag (cur i ) = lig tag then begin a ← lig kern start (cur f )(cur i ); cur i ← font info[a].qqqq ; if skip byte(cur i ) > stop flag then

begin a ← lig kern restart (cur f )(cur i ); cur i ← font info[a].qqqq ; end;

loop begin if qo(next char (cur i )) = skew char [cur f ] then

begin if op byte(cur i ) ≥ kern flag then

if skip byte(cur i ) ≤ stop flag then s ← char kern(cur f )(cur i ); goto done1 ; end;

if skip byte(cur i ) ≥ stop flag then goto done1 ;

a ← a + qo(skip byte(cur i )) + 1; cur i ← font info[a].qqqq ; end; end;

end; done1 :

This code is used in section 738.

742. ⟨ Swap the subscript and superscript into box x 742 ⟩ ≡ begin flush node list (x); x ← new noad ; mem[nucleus (x)] ← mem[nucleus (q)]; mem[supscr (x)] ← mem[supscr (q)]; mem[subscr (x)] ← mem[subscr (q)]; mem[supscr (q)].hh ← empty field ; mem[subscr (q)].hh ← empty field ; math type(nucleus (q)) ← sub mlist ; info(nucleus (q)) ← x; x ← clean box (nucleus (q), cur style); delta ← delta + height (x) − h; h ← height (x); end

This code is used in section 738.

743. The make fraction procedure is a bit different because it sets new hlist (q) directly rather than making a sub-box. ⟨ Declare math construction procedures 734 ⟩ +≡ procedure make fraction(q : pointer );

var p, v, x, y, z: pointer ; { temporary registers for box construction }

delta, delta1 , delta2 , shift up, shift down, clr : scaled ; { dimensions for box calculations } begin if thickness (q) = default code then thickness (q) ← default rule thickness ; ⟨ Create equal-width boxes x and z for the numerator and denominator, and compute the default amounts

shift up and shift down by which they are displaced from the baseline 744 ⟩; if thickness (q) = 0 then ⟨ Adjust shift up and shift down for the case of no fraction line 745 ⟩ else ⟨ Adjust shift up and shift down for the case of a fraction line 746 ⟩; ⟨ Construct a vlist box for the fraction, according to shift up and shift down 747 ⟩; ⟨ Put the fraction into a box with its delimiters, and make new hlist (q) point to it 748 ⟩; end; 744. ⟨ Create equal-width boxes x and z for the numerator and denominator, and compute the default

amounts shift up and shift down by which they are displaced from the baseline 744 ⟩ ≡ x ← clean box (numerator (q), num style(cur style)); z ← clean box (denominator (q), denom style(cur style)); if width(x) < width(z) then x ← rebox (x, width(z)) else z ← rebox (z, width(x)); if cur style < text style then { text style }

begin shift up ← num1 (cur size); shift down ← denom1 (cur size);

end else begin shift down ← denom2 (cur size);

if thickness (q) ≠ 0 then shift up ← num2 (cur size)

else shift up ← num3 (cur size);

end

This code is used in section 743.

745. The numerator and denominator must be separated by a certain minimum clearance, called clr in the following program. The difference between clr and the actual clearance is 2delta. ⟨ Adjust shift up and shift down for the case of no fraction line 745 ⟩ ≡

begin if cur style < text style then clr ← 7 ∗ default rule thickness else clr ← 3 ∗ default rule thickness ; delta ← half (clr − ((shift up − depth(x)) − (height (z) − shift down))); if delta > 0 then

begin shift up ← shift up + delta; shift down ← shift down + delta;

end; end

This code is used in section 743.

746. In the case of a fraction line, the minimum clearance depends on the actual thickness of the line. ⟨ Adjust shift up and shift down for the case of a fraction line 746 ⟩ ≡

begin if cur style < text style then clr ← 3 ∗ thickness (q) else clr ← thickness (q); delta ← half (thickness (q)); delta1 ← clr − ((shift up − depth(x)) − (axis height (cur size) + delta)); delta2 ← clr − ((axis height (cur size) − delta) − (height (z) − shift down)); if delta1 > 0 then shift up ← shift up + delta1 ; if delta2 > 0 then shift down ← shift down + delta2 ; end

This code is used in section 743.

747. ⟨ Construct a vlist box for the fraction, according to shift up and shift down 747 ⟩ ≡ v ← new null box ; type(v) ← vlist node; height (v) ← shift up + height (x); depth(v) ← depth(z) + shift down; width(v) ← width(x); { this also equals width(z) } if thickness (q) = 0 then

begin p ← new kern((shift up − depth(x)) − (height (z) − shift down)); link (p) ← z; end

else begin y ← fraction rule(thickness (q)); p ← new kern((axis height (cur size) − delta) − (height (z) − shift down)); link (y) ← p; link (p) ← z; p ← new kern((shift up − depth(x)) − (axis height (cur size) + delta)); link (p) ← y; end;

link (x) ← p; list ptr (v) ← x

This code is used in section 743.

748. ⟨ Put the fraction into a box with its delimiters, and make new hlist (q) point to it 748 ⟩ ≡ if cur style < text style then delta ← delim1 (cur size) else delta ← delim2 (cur size); x ← var delimiter (left delimiter (q), cur size, delta); link (x) ← v; z ← var delimiter (right delimiter (q), cur size, delta); link (v) ← z; new hlist (q) ← hpack (x, natural )

This code is used in section 743.

749. If the nucleus of an op noad is a single character, it is to be centered vertically with respect to the axis, after first being enlarged (via a character list in the font) if we are in display style. The normal convention for placing displayed limits is to put them above and below the operator in display style.

The italic correction is removed from the character if there is a subscript and the limits are not being displayed. The make op routine returns the value that should be used as an offset between subscript and superscript.

After make op has acted, subtype(q) will be limits if and only if the limits have been set above and below the operator. In that case, new hlist (q) will already contain the desired final box. ⟨ Declare math construction procedures 734 ⟩ +≡ function make op(q : pointer ): scaled ;

var delta: scaled ; { offset between subscript and superscript } p, v, x, y, z: pointer ; { temporary registers for box construction } c: quarterword ; i: four quarters ; { registers for character examination } shift up, shift down: scaled ; { dimensions for box calculation }

begin if (subtype(q) = normal ) ∧ (cur style < text style) then subtype(q) ← limits ; if math type(nucleus (q)) = math char then

begin fetch(nucleus (q));

if (cur style < text style) ∧ (char tag (cur i ) = list tag ) then { make it larger } begin c ← rem byte(cur i ); i ← char info(cur f )(c); if char exists (i) then

begin cur c ← c; cur i ← i; character (nucleus (q)) ← c; end; end;

delta ← char italic(cur f )(cur i ); x ← clean box (nucleus (q), cur style);

if (math type(subscr (q)) ≠ empty ) ∧ (subtype(q) ≠ limits ) then width(x) ← width(x) − delta;

{ remove italic correction } shift amount (x) ← half (height (x) − depth(x)) − axis height (cur size); { center vertically } math type(nucleus (q)) ← sub box ; info(nucleus (q)) ← x; end

else delta ← 0; if subtype(q) = limits then ⟨ Construct a box with limits above and below it, skewed by delta 750 ⟩; make op ← delta; end; 750. The following program builds a vlist box v for displayed limits. The width of the box is not affected by the fact that the limits may be skewed. ⟨ Construct a box with limits above and below it, skewed by delta 750 ⟩ ≡

begin x ← clean box (supscr (q), sup style(cur style)); y ← clean box (nucleus (q), cur style); z ← clean box (subscr (q), sub style(cur style)); v ← new null box ; type(v) ← vlist node; width(v) ← width(y); if width(x) > width(v) then width(v) ← width(x); if width(z) > width(v) then width(v) ← width(z); x ← rebox (x, width(v)); y ← rebox (y, width(v)); z ← rebox (z, width(v)); shift amount (x) ← half (delta); shift amount (z) ← −shift amount (x); height (v) ← height (y); depth(v) ← depth(y); ⟨ Attach the limits to y and adjust height (v), depth(v) to account for their presence 751 ⟩; new hlist (q) ← v; end

This code is used in section 749.

751. We use shift up and shift down in the following program for the amount of glue between the displayed operator y and its limits x and z. The vlist inside box v will consist of x followed by y followed by z, with kern nodes for the spaces between and around them. ⟨ Attach the limits to y and adjust height (v), depth(v) to account for their presence 751 ⟩ ≡

if math type(supscr (q)) = empty then begin free node(x, box node size); list ptr (v) ← y; end

else begin shift up ← big op spacing3 − depth(x); if shift up < big op spacing1 then shift up ← big op spacing1 ; p ← new kern(shift up); link (p) ← y; link (x) ← p; p ← new kern(big op spacing5 ); link (p) ← x; list ptr (v) ← p; height (v) ← height (v) + big op spacing5 + height (x) + depth(x) + shift up; end;

if math type(subscr (q)) = empty then free node(z, box node size) else begin shift down ← big op spacing4 − height (z);

if shift down < big op spacing2 then shift down ← big op spacing2 ; p ← new kern(shift down); link (y) ← p; link (p) ← z; p ← new kern(big op spacing5 ); link (z) ← p; depth(v) ← depth(v) + big op spacing5 + height (z) + depth(z) + shift down; end

This code is used in section 750.

752. A ligature found in a math formula does not create a ligature node, because there is no question of hyphenation afterwards; the ligature will simply be stored in an ordinary char node, after residing in an ord noad .

The math type is converted to math text char here if we would not want to apply an italic correction to the current character unless it belongs to a math font (i.e., a font with space = 0).

No boundary characters enter into these ligatures. ⟨ Declare math construction procedures 734 ⟩ +≡ procedure make ord (q : pointer );

label restart , exit ;

var a: integer ; { address of lig/kern instruction }

p, r: pointer ; { temporary registers for list manipulation } begin restart : if math type(subscr (q)) = empty then

if math type(supscr (q)) = empty then

if math type(nucleus (q)) = math char then begin p ← link (q); if p ≠ null then

if (type(p) ≥ ord noad ) ∧ (type(p) ≤ punct noad ) then

if math type(nucleus (p)) = math char then if fam(nucleus (p)) = fam(nucleus (q)) then

begin math type(nucleus (q)) ← math text char ; fetch(nucleus (q)); if char tag (cur i ) = lig tag then

begin a ← lig kern start (cur f )(cur i ); cur c ← character (nucleus (p));

cur i ← font info[a].qqqq ;

if skip byte(cur i ) > stop flag then begin a ← lig kern restart (cur f )(cur i ); cur i ← font info[a].qqqq ; end;

loop begin ⟨ If instruction cur i is a kern with cur c, attach the kern after q; or if it is a ligature with cur c, combine noads q and p appropriately; then return if the cursor has moved past a noad, or goto restart 753 ⟩;

if skip byte(cur i ) ≥ stop flag then return;

a ← a + qo(skip byte(cur i )) + 1; cur i ← font info[a].qqqq ; end; end; end;

end;

exit : end; 753. Note that a ligature between an ord noad and another kind of noad is replaced by an ord noad , when the two noads collapse into one. But we could make a parenthesis (say) change shape when it follows certain letters. Presumably a font designer will define such ligatures only when this convention makes sense. ⟨ If instruction cur i is a kern with cur c, attach the kern after q; or if it is a ligature with cur c,

combine noads q and p appropriately; then return if the cursor has moved past a noad, or goto

restart 753 ⟩ ≡

if next char (cur i ) = cur c then

if skip byte(cur i ) ≤ stop flag then

if op byte(cur i ) ≥ kern flag then begin p ← new kern(char kern(cur f )(cur i )); link (p) ← link (q); link (q) ← p; return; end

else begin check interrupt ; { allow a way out of infinite ligature loop } case op byte(cur i ) of qi (1), qi (5): character (nucleus (q)) ← rem byte(cur i ); { , } qi (2), qi (6): character (nucleus (p)) ← rem byte(cur i ); { , } qi (3), qi (7), qi (11): begin r ← new noad ; { , , }

character (nucleus (r)) ← rem byte(cur i ); fam(nucleus (r)) ← fam(nucleus (q)); link (q) ← r; link (r) ← p; if op byte(cur i ) < qi (11) then math type(nucleus (r)) ← math char else math type(nucleus (r)) ← math text char ; { prevent combination } end;

othercases begin link (q) ← link (p); character (nucleus (q)) ← rem byte(cur i ); { } mem[subscr (q)] ← mem[subscr (p)]; mem[supscr (q)] ← mem[supscr (p)]; free node(p, noad size); end

endcases; if op byte(cur i ) > qi (3) then return; math type(nucleus (q)) ← math char ; goto restart ; end

This code is used in section 752.

754. When we get to the following part of the program, we have “fallen through” from cases that did not lead to check dimensions or done with noad or done with node. Thus, q points to a noad whose nucleus may need to be converted to an hlist, and whose subscripts and superscripts need to be appended if they are present.

If nucleus (q) is not a math char , the variable delta is the amount by which a superscript should be moved right with respect to a subscript when both are present. ⟨ Convert nucleus (q) to an hlist and attach the sub/superscripts 754 ⟩ ≡

case math type(nucleus (q)) of math char , math text char : ⟨ Create a character node p for nucleus (q), possibly followed by a kern node

for the italic correction, and set delta to the italic correction if a subscript is present 755 ⟩; empty : p ← null ; sub box : p ← info(nucleus (q)); sub mlist : begin cur mlist ← info(nucleus (q)); save style ← cur style; mlist penalties ← false;

mlist to hlist ; { recursive call }

cur style ← save style; ⟨ Set up the values of cur size and cur mu, based on cur style 703 ⟩; p ← hpack (link (temp head ), natural ); end;

othercases confusion( ) endcases; new hlist (q) ← p; if (math type(subscr (q)) = empty ) ∧ (math type(supscr (q)) = empty ) then goto check dimensions ; make scripts (q, delta)

This code is used in section 728.

755. ⟨ Create a character node p for nucleus (q), possibly followed by a kern node for the italic correction,

and set delta to the italic correction if a subscript is present 755 ⟩ ≡ begin fetch(nucleus (q)); if char exists (cur i ) then

begin delta ← char italic(cur f )(cur i ); p ← new character (cur f , qo(cur c)); if (math type(nucleus (q)) = math text char ) ∧ (space(cur f ) ≠ 0) then delta ← 0;

{ no italic correction in mid-word of text font } if (math type(subscr (q)) = empty ) ∧ (delta ≠ 0) then

begin link (p) ← new kern(delta); delta ← 0;

end; end

else p ← null ; end

This code is used in section 754.

756. The purpose of make scripts (q, delta) is to attach the subscript and/or superscript of noad q to the list that starts at new hlist (q), given that subscript and superscript aren’t both empty. The superscript will appear to the right of the subscript by a given distance delta.

We set shift down and shift up to the minimum amounts to shift the baseline of subscripts and superscripts based on the given nucleus. ⟨ Declare math construction procedures 734 ⟩ +≡ procedure make scripts (q : pointer ; delta : scaled );

var p, x, y, z: pointer ; { temporary registers for box construction } shift up, shift down, clr : scaled ; { dimensions in the calculation } t: small number ; { subsidiary size code }

begin p ← new hlist (q); if is char node(p) then

begin shift up ← 0; shift down ← 0;

end else begin z ← hpack (p, natural );

if cur style < script style then t ← script size else t ← script script size;

shift up ← height (z) − sup drop(t); shift down ← depth(z) + sub drop(t); free node(z, box node size); end;

if math type(supscr (q)) = empty then ⟨ Construct a subscript box x when there is no superscript 757 ⟩ else begin ⟨ Construct a superscript box x 758 ⟩;

if math type(subscr (q)) = empty then shift amount (x) ← −shift up

else ⟨ Construct a sub/superscript combination box x, with the superscript offset by delta 759 ⟩; end;

if new hlist (q) = null then new hlist (q) ← x else begin p ← new hlist (q);

while link (p) ≠ null do p ← link (p);

link (p) ← x;

end; end;

757. When there is a subscript without a superscript, the top of the subscript should not exceed the baseline plus four-fifths of the x-height. ⟨ Construct a subscript box x when there is no superscript 757 ⟩ ≡

begin x ← clean box (subscr (q), sub style(cur style)); width(x) ← width(x) + script space; if shift down < sub1 (cur size) then shift down ← sub1 (cur size); clr ← height (x) − (abs (math x height (cur size) ∗ 4) div 5); if shift down < clr then shift down ← clr ; shift amount (x) ← shift down; end

This code is used in section 756.

758. The bottom of a superscript should never descend below the baseline plus one-fourth of the x-height. ⟨ Construct a superscript box x 758 ⟩ ≡

begin x ← clean box (supscr (q), sup style(cur style)); width(x) ← width(x) + script space; if odd (cur style) then clr ← sup3 (cur size) else if cur style < text style then clr ← sup1 (cur size)

else clr ← sup2 (cur size); if shift up < clr then shift up ← clr ; clr ← depth(x) + (abs (math x height (cur size)) div 4); if shift up < clr then shift up ← clr ; end

This code is used in section 756.

759. When both subscript and superscript are present, the subscript must be separated from the superscript by at least four times default rule thickness . If this condition would be violated, the subscript moves down, after which both subscript and superscript move up so that the bottom of the superscript is at least as high as the baseline plus four-fifths of the x-height. ⟨ Construct a sub/superscript combination box x, with the superscript offset by delta 759 ⟩ ≡

begin y ← clean box (subscr (q), sub style(cur style)); width(y) ← width(y) + script space; if shift down < sub2 (cur size) then shift down ← sub2 (cur size); clr ← 4 ∗ default rule thickness − ((shift up − depth(x)) − (height (y) − shift down)); if clr > 0 then

begin shift down ← shift down + clr ;

clr ← (abs (math x height (cur size) ∗ 4) div 5) − (shift up − depth(x));

if clr > 0 then begin shift up ← shift up + clr ; shift down ← shift down − clr ; end;

end; shift amount (x) ← delta; { superscript is delta to the right of the subscript } p ← new kern((shift up − depth(x)) − (height (y) − shift down)); link (x) ← p; link (p) ← y; x ← vpack (x, natural ); shift amount (x) ← shift down; end

This code is used in section 756.

760. We have now tied up all the loose ends of the first pass of mlist to hlist . The second pass simply goes through and hooks everything together with the proper glue and penalties. It also handles the left noad and right noad that might be present, since max h and max d are now known. Variable p points to a node at the current end of the final hlist. ⟨ Make a second pass over the mlist, removing all noads and inserting the proper spacing and penalties 760 ⟩ ≡

p ← temp head ; link (p) ← null ; q ← mlist ; r type ← 0; cur style ← style; ⟨ Set up the values of cur size and cur mu, based on cur style 703 ⟩; while q ≠ null do

begin ⟨ If node q is a style node, change the style and goto delete q ; otherwise if it is not a noad, put it into the hlist, advance q, and goto done; otherwise set s to the size of noad q, set t to the associated type (ord noad . . inner noad ), and set pen to the associated penalty 761 ⟩;

⟨ Append inter-element spacing based on r type and t 766 ⟩;

⟨ Append any new hlist entries for q, and any appropriate penalties 767 ⟩;

r type ← t; delete q : r ← q; q ← link (q); free node(r, s); done: end

This code is used in section 726.

761. Just before doing the big case switch in the second pass, the program sets up default values so that most of the branches are short. ⟨ If node q is a style node, change the style and goto delete q ; otherwise if it is not a noad, put it into the

hlist, advance q, and goto done; otherwise set s to the size of noad q, set t to the associated type (ord noad . . inner noad ), and set pen to the associated penalty 761 ⟩ ≡

t ← ord noad ; s ← noad size; pen ← inf penalty ; case type(q) of op noad , open noad , close noad , punct noad , inner noad : t ← type(q); bin noad : begin t ← bin noad ; pen ← bin op penalty ;

end; rel noad : begin t ← rel noad ; pen ← rel penalty ;

end; ord noad , vcenter noad , over noad , under noad : do nothing ; radical noad : s ← radical noad size; accent noad : s ← accent noad size; fraction noad : begin t ← inner noad ; s ← fraction noad size;

end; left noad , right noad : t ← make left right (q, style, max d , max h); style node: ⟨ Change the current style and goto delete q 763 ⟩; whatsit node, penalty node, rule node, disc node, adjust node, ins node, mark node, glue node, kern node:

begin link (p) ← q; p ← q; q ← link (q); link (p) ← null ; goto done; end;

othercases confusion( ) endcases

This code is used in section 760.

762. The make left right function constructs a left or right delimiter of the required size and returns the value open noad or close noad . The right noad and left noad will both be based on the original style, so they will have consistent sizes.

We use the fact that right noad − left noad = close noad − open noad . ⟨ Declare math construction procedures 734 ⟩ +≡ function make left right (q : pointer ; style : small number ; max d , max h : scaled ): small number ;

var delta, delta1 , delta2 : scaled ; { dimensions used in the calculation } begin if style < script style then cur size ← text size else cur size ← 16 ∗ ((style − text style) div 2); delta2 ← max d + axis height (cur size); delta1 ← max h + max d − delta2 ; if delta2 > delta1 then delta1 ← delta2 ; { delta1 is max distance from axis } delta ← (delta1 div 500) ∗ delimiter factor ; delta2 ← delta1 + delta1 − delimiter shortfall ; if delta < delta2 then delta ← delta2 ; new hlist (q) ← var delimiter (delimiter (q), cur size, delta); make left right ← type(q) − (left noad − open noad ); { open noad or close noad } end;

763. ⟨ Change the current style and goto delete q 763 ⟩ ≡ begin cur style ← subtype(q); s ← style node size; ⟨ Set up the values of cur size and cur mu, based on cur style 703 ⟩; goto delete q ; end

This code is used in section 761.

764. The inter-element spacing in math formulas depends on a 8 × 8 table that TEX preloads as a 64-digit string. The elements of this string have the following significance:

means no space;

means a conditional thin space ( ); means a thin space ( ); means a conditional medium space ( ); means a conditional thick space ( ); means an impossible case.

This is all pretty cryptic, but The TEXbook explains what is supposed to happen, and the string makes it happen.

A global variable magic offset is computed so that if a and b are in the range ord noad . . inner noad , then str pool [a ∗ 8 + b + magic offset ] is the digit for spacing between noad types a and b.

If Pascal had provided a good way to preload constant arrays, this part of the program would not have been so strange.

define math spacing =

⟨ Global variables 13 ⟩ +≡ magic offset : integer ; { used to find inter-element spacing }

765. ⟨ Compute the magic offset 765 ⟩ ≡ magic offset ← str start [math spacing ] − 9 ∗ ord noad

This code is used in section 1337.

766. ⟨ Append inter-element spacing based on r type and t 766 ⟩ ≡

if r type > 0 then { not the first noad } begin case so(str pool [r type ∗ 8 + t + magic offset ]) of : x ← 0; : if cur style < script style then x ← thin mu skip code else x ← 0; : x ← thin mu skip code; : if cur style < script style then x ← med mu skip code else x ← 0; : if cur style < script style then x ← thick mu skip code else x ← 0; othercases confusion( ) endcases; if x ≠ 0 then

begin y ← math glue(glue par (x), cur mu); z ← new glue(y); glue ref count (y) ← null ; link (p) ← z; p ← z; subtype(z) ← x + 1; { store a symbolic subtype } end; end

This code is used in section 760.

767. We insert a penalty node after the hlist entries of noad q if pen is not an “infinite” penalty, and if the node immediately following q is not a penalty node or a rel noad or absent entirely. ⟨ Append any new hlist entries for q, and any appropriate penalties 767 ⟩ ≡

if new hlist (q) ≠ null then begin link (p) ← new hlist (q); repeat p ← link (p); until link (p) = null ; end;

if penalties then

if link (q) ≠ null then

if pen < inf penalty then begin r type ← type(link (q)); if r type ≠ penalty node then if r type ≠ rel noad then

begin z ← new penalty (pen); link (p) ← z; p ← z; end;

end

This code is used in section 760.