Section 719: Typesetting math formulas
’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 ’s inner loop, the program has been written in a manner that stresses compactness over efficiency.
⟨ Global variables 13 ⟩+≡
pointer cur_mlist; // beginning of mlist to be translated
small_number cur_style; // style code at current place in the list
small_number cur_size; // size code corresponding to |cur_style|
scaled cur_mu; // the math unit width corresponding to |cur_size|
bool mlist_penalties; // should |mlist_to_hlist| insert penalties?
Section 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.
// << Start file |math_typesetting.c|, 1382 >>
pointer clean_box(pointer p, small_number s) {
pointer q; // beginning of a list to be boxed
small_number save_style; // |cur_style| to be restored
pointer x; // box to be returned
pointer r; // temporary pointer
switch (math_type(p)) {
case MATH_CHAR:
cur_mlist = new_noad();
mem[nucleus(cur_mlist)] = mem[p];
break;
case SUB_BOX:
q = info(p);
goto found;
case SUB_MLIST:
cur_mlist = info(p);
break;
default:
q = new_null_box();
goto found;
}
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) {
x = hpack(q, NATURAL);
}
else if (link(q) == null
&& type(q) <= VLIST_NODE
&& shift_amount(q) == 0)
{
x = q; // it's already clean
}
else {
x = hpack(q, NATURAL);
}
// << Simplify a trivial box, 721 >>
return x;
}
Section 721
Here we save memory space in a common case.
⟨ Simplify a trivial box 721 ⟩≡
q = list_ptr(x);
if (is_char_node(q)) {
r = link(q);
if (r != null
&& link(r) == null
&& !is_char_node(r)
&& type(r) == KERN_NODE)
{
// unneeded italic correction
free_node(r, SMALL_NODE_SIZE);
link(q) = null;
}
}
Section 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.
// unpack the |MATH_CHAR| field |a|
void fetch(pointer a) {
cur_c = character(a);
cur_f = fam_fnt(fam(a) + cur_size);
if (cur_f == NULL_FONT) {
// << Complain about an undefined family and set |cur_i| null, 723 >>
}
else {
if (cur_c >= font_bc[cur_f] && cur_c <= font_ec[cur_f]) {
cur_i = char_info(cur_f, cur_c);
}
else {
cur_i = null_character;
}
if (!char_exists(cur_i)) {
char_warning(cur_f, cur_c);
math_type(a) = EMPTY;
cur_i = null_character;
}
}
}
Section 723
⟨ Complain about an undefined family and set cur_i null 723 ⟩≡
print_err("");
print_size(cur_size);
print_char(' ');
print_int(fam(a));
print(" is undefined (character ");
print_strnumber(cur_c);
print_char(')');
help4("Somewhere in the math formula just ended, you used the")
("stated character from an undefined font family. For example,")
("plain TeX doesn't allow \\it or \\sl in subscripts. Proceed,")
("and I'll try to forget that I needed that character.");
error();
cur_i = null_character;
math_type(a) = EMPTY;
Section 724
The outputs of fetch are placed in global variables.
⟨ Global variables 13 ⟩+≡
internal_font_number cur_f; // the |font| field of a |MATH_CHAR|
quarterword cur_c; // the |character| field of a |MATH_CHAR|
memory_word cur_i; // the |char_info| of a |MATH_CHAR|, or a lig/kern instruction
Section 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(X) mem[nucleus((X))].integer // the translation of an mlist
Section 726
Here is the overall plan of mlist_to_hlist, and the list of its local variables.
The MATH_SPACING string is only used for mlist_to_hlist so it must be declared beforehand. Its value is given in section 764.
// << Declare math construction procedures, 734 >>
// << Declare |MATH_SPACING| for |mlist_to_hlist|, 764 >>
void mlist_to_hlist() {
pointer mlist; // beginning of the given list
bool penalties; // should penalty nodes be inserted?
small_number style; // the given style
small_number save_style; // holds |cur_style| during recursion
pointer q; // runs through the mlist
pointer r; // the most recent noad preceding |q|
small_number r_type; // the |type| of noad |r|, or |OP_NOAD| if |r = null|
small_number t; // the effective |type| of noad |q| during the second pass
pointer p, x, y, z; // temporary registers for list construction
int pen; // a penalty to be inserted
small_number s; // the size of a noad to be deleted
scaled max_h, max_d; // maximum height and depth of the list translated so far
scaled delta; // offset between subscript and superscript
p = null;
x = null;
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) {
// << 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 >>
}
Section 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 ⟩≡
// << 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) {
max_h = height(z);
}
if (depth(z) > max_d) {
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);
Section 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;
switch (type(q)) {
case BIN_NOAD:
switch (r_type) {
case BIN_NOAD:
case OP_NOAD:
case REL_NOAD:
case OPEN_NOAD:
case PUNCT_NOAD:
case LEFT_NOAD:
type(q) = ORD_NOAD;
goto reswitch;
default:
do_nothing;
}
break;
case REL_NOAD:
case CLOSE_NOAD:
case PUNCT_NOAD:
case RIGHT_NOAD:
// << Convert a final |BIN_NOAD| to an |ORD_NOAD|, 729 >>
if (type(q) == RIGHT_NOAD) {
goto done_with_noad;
}
break;
// << 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 >>
default:
confusion("mlist1");
}
// << Convert |nucleus(q)| to an hlist and attach the sub/superscripts, 754 >>
Section 729
⟨ Convert a final BIN_NOAD to an ORD_NOAD 729 ⟩≡
if (r_type == BIN_NOAD) {
type(r) = ORD_NOAD;
}
Section 730
⟨ Cases for nodes that can appear in an mlist, after which we goto done_with_node 730 ⟩≡
case STYLE_NODE:
cur_style = subtype(q);
// << Set up the values of |cur_size| and |cur_mu|, based on |cur_style|, 703 >>
goto done_with_node;
case CHOICE_NODE:
// << Change this node to a style node followed by the correct choice, then |goto done_with_node|, 731 >>
case INS_NODE:
case MARK_NODE:
case ADJUST_NODE:
case WHATSIT_NODE:
case PENALTY_NODE:
case DISC_NODE:
goto done_with_node;
case RULE_NODE:
if (height(q) > max_h) {
max_h = height(q);
}
if (depth(q) > max_d) {
max_d = depth(q);
}
goto done_with_node;
case GLUE_NODE:
// << Convert math glue to ordinary glue, 732 >>
goto done_with_node;
case KERN_NODE:
math_kern(q, cur_mu);
goto done_with_node;
Section 731
#define choose_mlist(X) p = X(q); X(q) = null
⟨ Change this node to a style node followed by the correct choice, then goto done_with_node 731 ⟩≡
switch (cur_style / 2) {
case 0:
// |DISPLAY_STYLE = 0|
choose_mlist(display_mlist);
break;
case 1:
// |TEXT_STYLE = 2|
choose_mlist(text_mlist);
break;
case 2:
// |SCRIPT_STYLE = 4|
choose_mlist(script_mlist);
break;
default:
// case 3:
// |SCRIPT_SCRIPT_STYLE = 6|
choose_mlist(script_script_mlist);
} // 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){
z = link(q);
link(q) = p;
while (link(p) != null) {
p = link(p);
}
link(p) = z;
}
goto done_with_node;
Section 732
Conditional math glue (‘\nonscript
’) 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 (‘\muskip
’) is converted to normal glue by multiplying the dimensions by cur_mu.
⟨ Convert math glue to ordinary glue 732 ⟩≡
if (subtype(q) == MU_GLUE) {
x = glue_ptr(q);
y = math_glue(x, cur_mu);
delete_glue_ref(x);
glue_ptr(q) = y;
subtype(q) = NORMAL;
}
else if (cur_size != TEXT_SIZE && subtype(q) == COND_MATH_GLUE) {
p = link(q);
if (p != null
&& (type(p) == GLUE_NODE || type(p) == KERN_NODE))
{
link(q) = link(p);
link(p) = null;
flush_node_list(p);
}
}
Section 733
⟨ Cases for noads that can follow a BIN_NOAD 733 ⟩≡
case LEFT_NOAD:
goto done_with_noad;
case FRACTION_NOAD:
make_fraction(q);
goto check_dimensions;
case OP_NOAD:
delta = make_op(q);
if (subtype(q) == LIMITS) {
goto check_dimensions;
}
break;
case ORD_NOAD:
make_ord(q);
break;
case OPEN_NOAD:
case INNER_NOAD:
do_nothing;
break;
case RADICAL_NOAD:
make_radical(q);
break;
case OVER_NOAD:
make_over(q);
break;
case UNDER_NOAD:
make_under(q);
break;
case ACCENT_NOAD:
make_math_accent(q);
break;
case VCENTER_NOAD:
make_vcenter(q);
break;
Section 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 ⟩≡
void make_over(pointer q) {
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;
}
Section 735
⟨ Declare math construction procedures 734 ⟩+≡
void make_under(pointer q) {
pointer p, x, y; // temporary registers for box construction
scaled delta; // overall height plus depth
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;
}
Section 736
⟨ Declare math construction procedures 734 ⟩+≡
void make_vcenter(pointer q) {
pointer v; // the box that should be centered vertically
scaled delta; // its height plus depth
v = info(nucleus(q));
if (type(v) != VLIST_NODE) {
confusion("vcenter");
}
delta = height(v) + depth(v);
height(v) = axis_height(cur_size) + half(delta);
depth(v) = delta - height(v);
}
Section 737
According to the rules in the DVI
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 ⟩+≡
void make_radical(pointer q) {
pointer x, y; // temporary registers for box construction
scaled delta, clr; // dimensions involved in the calculation
x = clean_box(nucleus(q), cramped_style(cur_style));
if (cur_style < TEXT_STYLE) {
// display style
clr = default_rule_thickness + abs(math_x_height(cur_size)) / 4;
}
else {
clr = default_rule_thickness;
clr += abs(clr) / 4;
}
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) {
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;
}
Section 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 ⟩+≡
void make_math_accent(pointer q) {
pointer p, x, y; // temporary registers for box construction
int a; // address of lig/kern instruction
quarterword c; // accent character
internal_font_number f; // its font
memory_word i; // its |char_info|
scaled s; // amount to skew the accent to the right
scaled h; // height of character being accented
scaled delta; // space to remove between accent and accentee
scaled w; // width of the accentee, not including sub/superscripts
fetch(accent_chr(q));
if (char_exists(cur_i)) {
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)) {
delta = h;
}
else {
delta = x_height(f);
}
if ((math_type(supscr(q)) != EMPTY || math_type(subscr(q)) != EMPTY)
&& math_type(nucleus(q)) == MATH_CHAR)
{
// << 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) {
// << Make the height of box |y| equal to |h|, 739 >>
}
info(nucleus(q)) = y;
math_type(nucleus(q)) = SUB_BOX;
}
}
Section 739
⟨ Make the height of box y equal to h 739 ⟩≡
p = new_kern(h - height(y));
link(p) = list_ptr(y);
list_ptr(y) = p;
height(y) = h;
Section 740
⟨ Switch to a larger accent if available and appropriate 740 ⟩≡
while(true) {
if (char_tag(i) != LIST_TAG) {
break; // goto done
}
y = rem_byte(i);
i = char_info(f, y);
if (!char_exists(i) || char_width(f, i) > w) {
break; // goto done
}
c = y;
}
// done:
Section 741
⟨ Compute the amount of skew 741 ⟩≡
s = 0;
if (math_type(nucleus(q)) == MATH_CHAR) {
fetch(nucleus(q));
if (char_tag(cur_i) == LIG_TAG) {
a = lig_kern_start(cur_f, cur_i);
cur_i = font_info[a];
if (skip_byte(cur_i) > STOP_FLAG) {
a = lig_kern_restart(cur_f, cur_i);
cur_i = font_info[a];
}
while(true) {
if (next_char(cur_i) == skew_char[cur_f]) {
if (op_byte(cur_i) >= KERN_FLAG && skip_byte(cur_i) <= STOP_FLAG) {
s = char_kern(cur_f, cur_i);
}
break; // goto done1
}
if (skip_byte(cur_i) >= STOP_FLAG) {
break; // goto done1
}
a += skip_byte(cur_i) + 1;
cur_i = font_info[a];
}
}
}
// done1:
Section 742
⟨ Swap the subscript and superscript into box x 742 ⟩≡
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)] = empty_field;
mem[subscr(q)] = empty_field;
math_type(nucleus(q)) = SUB_MLIST;
info(nucleus(q)) = x;
x = clean_box(nucleus(q), cur_style);
delta += height(x) - h;
h = height(x);
Section 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 ⟩+≡
void make_fraction(pointer q) {
pointer p, v, x, y, z; // temporary registers for box construction
scaled delta, delta1, delta2, shift_up, shift_down, clr; // dimensions for box calculations
if (thickness(q) == DEFAULT_CODE) {
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) {
// << 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 >>
}
Section 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)) {
x = rebox(x, width(z));
}
else {
z = rebox(z, width(x));
}
if (cur_style < TEXT_STYLE) {
// display style
shift_up = num1(cur_size);
shift_down = denom1(cur_size);
}
else {
shift_down = denom2(cur_size);
if (thickness(q) != 0) {
shift_up = num2(cur_size);
}
else {
shift_up = num3(cur_size);
}
}
Section 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 twice delta.
⟨ Adjust shift_up and shift_down for the case of no fraction line 745 ⟩≡
if (cur_style < TEXT_STYLE) {
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) {
shift_up += delta;
shift_down += delta;
}
Section 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 ⟩≡
if (cur_style < TEXT_STYLE) {
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) {
shift_up += delta1;
}
if (delta2 > 0) {
shift_down += delta2;
}
Section 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) {
p = new_kern((shift_up - depth(x)) - (height(z) - shift_down));
link(p) = z;
}
else {
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;
}
link(x) = p;
list_ptr(v) = x;
Section 748
⟨ Put the fraction into a box with its delimiters, and make new_hlist(q) point to it 748 ⟩≡
if (cur_style < TEXT_STYLE) {
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);
Section 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 ⟩+≡
scaled make_op(pointer q) {
scaled delta; // offset between subscript and superscript
pointer p, v, x, y, z; // temporary registers for box construction
quarterword c; // registers for character examination
memory_word i;
scaled shift_up, shift_down; // dimensions for box calculation
if (subtype(q) == NORMAL && cur_style < TEXT_STYLE) {
subtype(q) = LIMITS;
}
if (math_type(nucleus(q)) == MATH_CHAR) {
fetch(nucleus(q));
if (cur_style < TEXT_STYLE && char_tag(cur_i) == LIST_TAG) {
// make it larger
c = rem_byte(cur_i);
i = char_info(cur_f, c);
if (char_exists(i)) {
cur_c = c;
cur_i = i;
character(nucleus(q)) = c;
}
}
delta = char_italic(cur_f, cur_i);
x = clean_box(nucleus(q), cur_style);
if (math_type(subscr(q)) != EMPTY && subtype(q) != LIMITS) {
// remove italic correction
width(x) -= delta;
}
// center vertically
shift_amount(x) = half(height(x) - depth(x)) - axis_height(cur_size);
math_type(nucleus(q)) = SUB_BOX;
info(nucleus(q)) = x;
}
else {
delta = 0;
}
if (subtype(q) == LIMITS) {
// << Construct a box with limits above and below it, skewed by |delta|, 750 >>
}
return delta;
}
Section 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 ⟩≡
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)) {
width(v) = width(x);
}
if (width(z) > width(v)) {
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;
Section 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) {
free_node(x, BOX_NODE_SIZE);
list_ptr(v) = y;
}
else {
shift_up = big_op_spacing3 - depth(x);
if (shift_up < big_op_spacing1) {
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) += big_op_spacing5 + height(x) + depth(x) + shift_up;
}
if (math_type(subscr(q)) == EMPTY) {
free_node(z, BOX_NODE_SIZE);
}
else {
shift_down = big_op_spacing4 - height(z);
if (shift_down < big_op_spacing2) {
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) += big_op_spacing5 + height(z) + depth(z) + shift_down;
}
Section 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 ⟩+≡
void make_ord(pointer q) {
int a; // address of lig/kern instruction
pointer p, r; // temporary registers for list manipulation
restart:
if (math_type(subscr(q)) == EMPTY
&& math_type(supscr(q)) == EMPTY
&& math_type(nucleus(q)) == MATH_CHAR)
{
p = link(q);
if (p != null
&& type(p) >= ORD_NOAD
&& type(p) <= PUNCT_NOAD
&& math_type(nucleus(p)) == MATH_CHAR
&& fam(nucleus(p)) == fam(nucleus(q)))
{
math_type(nucleus(q)) = MATH_TEXT_CHAR;
fetch(nucleus(q));
if (char_tag(cur_i) == LIG_TAG) {
a = lig_kern_start(cur_f, cur_i);
cur_c = character(nucleus(p));
cur_i = font_info[a];
if (skip_byte(cur_i) > STOP_FLAG) {
a = lig_kern_restart(cur_f, cur_i);
cur_i = font_info[a];
}
while(true) {
// << 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) {
return;
}
a += skip_byte(cur_i) + 1;
cur_i = font_info[a];
}
}
}
}
}
Section 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 && skip_byte(cur_i) <= STOP_FLAG) {
if (op_byte(cur_i) >= KERN_FLAG) {
p = new_kern(char_kern(cur_f, cur_i));
link(p) = link(q);
link(q) = p;
return;
}
check_interrupt; // allow a way out of infinite ligature loop
switch (op_byte(cur_i)) {
case 1:
case 5:
// =:|, =:|>
character(nucleus(q)) = rem_byte(cur_i);
break;
case 2:
case 6:
// |=:, |=:>
character(nucleus(p)) = rem_byte(cur_i);
break;
case 3:
case 7:
case 11:
// |=:|, |=:|>, |=:|>>
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) < 11) {
math_type(nucleus(r)) = MATH_CHAR;
}
else {
// prevent combination
math_type(nucleus(r)) = MATH_TEXT_CHAR;
}
break;
default:
// =:
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);
}
if (op_byte(cur_i) > 3) {
return;
}
math_type(nucleus(q)) = MATH_CHAR;
goto restart;
}
Section 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 be moved right with respect to a subscript when both are present.
⟨ Convert nucleus(q) to an hlist and attach the sub/superscripts 754 ⟩≡
switch (math_type(nucleus(q))) {
case MATH_CHAR:
case 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 >>
break;
case EMPTY:
p = null;
break;
case SUB_BOX:
p = info(nucleus(q));
break;
case SUB_MLIST:
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);
break;
default:
confusion("mlist2");
}
new_hlist(q) = p;
if (math_type(subscr(q)) == EMPTY && math_type(supscr(q)) == EMPTY) {
goto check_dimensions;
}
make_scripts(q, delta);
Section 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 ⟩≡
fetch(nucleus(q));
if (char_exists(cur_i)) {
delta = char_italic(cur_f, cur_i);
p = new_character(cur_f, cur_c);
if (math_type(nucleus(q)) == MATH_TEXT_CHAR && space(cur_f) != 0) {
// no italic correction in mid-word of text font
delta = 0;
}
if (math_type(subscr(q)) == EMPTY && delta != 0) {
link(p) = new_kern(delta);
delta = 0;
}
}
else {
p = null;
}
Section 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 the 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 ⟩+≡
void make_scripts(pointer q, scaled delta) {
pointer p, x, y, z; // temporary registers for box construction
scaled shift_up, shift_down, clr; // dimensions in the calculation
small_number t; // subsidiary size code
p = new_hlist(q);
if (is_char_node(p)) {
shift_up = 0;
shift_down = 0;
}
else {
z = hpack(p, NATURAL);
if (cur_style < SCRIPT_STYLE) {
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);
}
if (math_type(supscr(q)) == EMPTY) {
// << Construct a subscript box |x| when there is no superscript, 757 >>
}
else {
// << Construct a superscript box |x|, 758 >>
if (math_type(subscr(q)) == EMPTY) {
shift_amount(x) = -shift_up;
}
else {
// << Construct a sub/superscript combination box |x|, with the superscript offset by |delta|, 759 >>
}
}
if (new_hlist(q) == null) {
new_hlist(q) = x;
}
else {
p = new_hlist(q);
while (link(p) != null) {
p = link(p);
}
link(p) = x;
}
}
Section 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 ⟩≡
x = clean_box(subscr(q), sub_style(cur_style));
width(x) += script_space;
if (shift_down < sub1(cur_size)) {
shift_down = sub1(cur_size);
}
clr = height(x) - (abs(math_x_height(cur_size) * 4) / 5);
if (shift_down < clr) {
shift_down = clr;
}
shift_amount(x) = shift_down;
Section 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 ⟩≡
x = clean_box(supscr(q), sup_style(cur_style));
width(x) += script_space;
if (odd(cur_style)) {
clr = sup3(cur_size);
}
else if (cur_style < TEXT_STYLE) {
clr = sup1(cur_size);
}
else {
clr = sup2(cur_size);
}
if (shift_up < clr) {
shift_up = clr;
}
clr = depth(x) + (abs(math_x_height(cur_size)) / 4);
if (shift_up < clr) {
shift_up = clr;
}
Section 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 ⟩≡
y = clean_box(subscr(q), sub_style(cur_style));
width(y) += script_space;
if (shift_down < sub2(cur_size)) {
shift_down = sub2(cur_size);
}
clr = 4 * default_rule_thickness - ((shift_up - depth(x)) - (height(y) - shift_down));
if (clr > 0) {
shift_down += clr;
clr = (abs(math_x_height(cur_size) * 4) / 5) - (shift_up - depth(x));
if (clr > 0) {
shift_up += clr;
shift_down -= clr;
}
}
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;
Section 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) {
// << 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: (equivalent to continue the loop)
}
Section 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;
switch (type(q)) {
case OP_NOAD:
case OPEN_NOAD:
case CLOSE_NOAD:
case PUNCT_NOAD:
case INNER_NOAD:
t = type(q);
break;
case BIN_NOAD:
t = BIN_NOAD;
pen = bin_op_penalty;
break;
case REL_NOAD:
t = REL_NOAD;
pen = rel_penalty;
break;
case ORD_NOAD:
case VCENTER_NOAD:
case OVER_NOAD:
case UNDER_NOAD:
do_nothing;
break;
case RADICAL_NOAD:
s = RADICAL_NOAD_SIZE;
break;
case ACCENT_NOAD:
s = ACCENT_NOAD_SIZE;
break;
case FRACTION_NOAD:
s = FRACTION_NOAD_SIZE;
break;
case LEFT_NOAD:
case RIGHT_NOAD:
t = make_left_right(q, style, max_d, max_h);
break;
case STYLE_NODE:
// << Change the current style and |goto delete_q|, 763 >>
case WHATSIT_NODE:
case PENALTY_NODE:
case RULE_NODE:
case DISC_NODE:
case ADJUST_NODE:
case INS_NODE:
case MARK_NODE:
case GLUE_NODE:
case KERN_NODE:
link(p) = q;
p = q;
q = link(q);
link(p) = null;
continue; // goto done
default:
confusion("mlist3");
}
Section 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 ⟩+≡
small_number make_left_right(pointer q, small_number style, scaled max_d, scaled max_h) {
scaled delta, delta1, delta2; // dimensions used in the calculation
if (style < SCRIPT_STYLE) {
cur_size = TEXT_SIZE;
}
else {
cur_size = 16 * ((style - TEXT_STYLE) / 2);
}
delta2 = max_d + axis_height(cur_size);
delta1 = max_h + max_d - delta2;
if (delta2 > delta1) {
// |delta1| is max distance from axis
delta1 = delta2;
}
delta = (delta1 / 500) * delimiter_factor;
delta2 = delta1 + delta1 - delimiter_shortfall;
if (delta < delta2) {
delta = delta2;
}
new_hlist(q) = var_delimiter(delimiter(q), cur_size, delta);
return type(q) - (LEFT_NOAD - OPEN_NOAD); // |OPEN_NOAD| or |CLOSE_NOAD|
}
Section 763
⟨ Change the current style and goto delete_q 763 ⟩≡
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;
Section 764
The inter-element spacing in math formulas depends on an 88 table that preloads as a 64-digit string. The elements of this string have the following significance:
0
means no space;
1
means a conditional thin space (\nonscript\mskip\thinmuskip
);
2
means a thin space (\mskip\thinmuskip
);
3
means a conditional medium space (\nonscript\mskip\medmuskip
);
4
means a conditional thick space (\nonscript\mskip\thickmuskip
);
*
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.
We can define without any problem a constant array in C, so the magic_offset global variable is not needed.
Written in capital since it is a const, and not in
global.c
because it is used only inmath_typesetting.c
.
⟨ Declare MATH_SPACING for mlist_to_hlist 764 ⟩≡
const char *MATH_SPACING = "0234000122*4000133**3**344*0400400*000000234000111*1111112341011";
Section 765
No need for the magic offset, because its value does not depend of the position of MATH_SPACING in the pool. Its constant value is −9ORD_NOAD, directly used in next section.
Section 766
⟨ Append inter-element spacing based on r_type and t 766 ⟩≡
if (r_type > 0) {
// not the first noad
switch (MATH_SPACING[r_type * 8 + t - 9 * ORD_NOAD]) {
case '0':
x = 0;
break;
case '1':
if (cur_style < SCRIPT_STYLE) {
x = THIN_MU_SKIP_CODE;
}
else {
x = 0;
}
break;
case '2':
x = THIN_MU_SKIP_CODE;
break;
case '3':
if (cur_style < SCRIPT_STYLE) {
x = MED_MU_SKIP_CODE;
}
else {
x = 0;
}
break;
case '4':
if (cur_style < SCRIPT_STYLE) {
x = THICK_MU_SKIP_CODE;
}
else {
x = 0;
}
break;
default:
confusion("mlist4");
}
if (x != 0) {
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
}
}
Section 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) {
link(p) = new_hlist(q);
do {
p = link(p);
} while (link(p) != null);
}
if (penalties
&& link(q) != null
&& pen < INF_PENALTY)
{
r_type = type(link(q));
if (r_type != PENALTY_NODE && r_type != REL_NOAD) {
z = new_penalty(pen);
link(p) = z;
p = z;
}
}