Section 699: Subroutines for math mode

In order to convert mlists to hlists, i.e., noads to nodes, we need several subroutines that are conveniently dealt with now.

Let us first introduce the macros that make it easy to get at the parameters and other font information. A size code, which is a multiple of 16, is added to a family number to get an index into the table of internal font numbers for each combination of family and size. (Be alert: Size codes get larger as the type gets smaller.)

constants.h
#define TEXT_SIZE          0  // size code for the largest size in a family
#define SCRIPT_SIZE        16 // size code for the medium size in a family
#define SCRIPT_SCRIPT_SIZE 32 // size code for the smallest size in a family
display_math.c
void print_size(int s) {
    if (s == TEXT_SIZE) {
        print_esc("textfont");
    }
    else if (s == SCRIPT_SIZE) {
        print_esc("scriptfont");
    }
    else {
        print_esc("scriptscriptfont");
    }
}

Section 700

Before an mlist is converted to an hlist, makes sure that the fonts in family 2 have enough parameters to be math-symbol fonts, and that the fonts in family 3 have enough parameters to be math-extension fonts. The math-symbol parameters are referred to by using the following macros, which take a size code as their parameter; for example, num1(cur_size) gives the value of the num1 parameter for the current size.

constants.h
#define TOTAL_MATHSY_PARAMS 22
texmath.h
#define mathsy(X, Y)     font_info[(X) + param_base[fam_fnt(2 + (Y))]].sc
#define math_x_height(X) mathsy(5, (X))  // height of 'x'
#define math_quad(X)     mathsy(6, (X))  // 18mu
#define num1(X)          mathsy(8, (X))  // numerator shift-up in display styles
#define num2(X)          mathsy(9, (X))  // numerator shift-up in non - display, non-\atop
#define num3(X)          mathsy(10, (X)) // numerator shift-up in non-display \atop
#define denom1(X)        mathsy(11, (X)) // denominator shift-down in display styles
#define denom2(X)        mathsy(12, (X)) // denominator shift-down in non-display styles
#define sup1(X)          mathsy(13, (X)) // superscript shift-up in uncramped display style
#define sup2(X)          mathsy(14, (X)) // superscript shift-up in uncramped non-display
#define sup3(X)          mathsy(15, (X)) // superscript shift-up in cramped styles
#define sub1(X)          mathsy(16, (X)) // subscript shift-down if superscript is absent
#define sub2(X)          mathsy(17, (X)) // subscript shift-down if superscript is present
#define sup_drop(X)      mathsy(18, (X)) // superscript baseline below top of large box
#define sub_drop(X)      mathsy(19, (X)) // subscript baseline below bottom of large box
#define delim1(X)        mathsy(20, (X)) // size of \atopwithdelims delimiters in display styles
#define delim2(X)        mathsy(21, (X)) // size of \atopwithdelims delimiters in non - displays
#define axis_height(X)   mathsy(22, (X)) // height of fraction lines above the baseline

Section 701

The math-extension parameters have similar macros, but the size code is omitted (since it is always cur_size when we refer to such parameters).

constants.h
#define TOTAL_MATHEX_PARAMS 13
texmath.h
#define mathex(X)              font_info[(X) + param_base[fam_fnt(3 + cur_size)]].sc
#define default_rule_thickness mathex(8)  // thickness of \\over} bars
#define big_op_spacing1        mathex(9)  // minimum clearance above a displayed op
#define big_op_spacing2        mathex(10) // minimum clearance below a displayed op
#define big_op_spacing3        mathex(11) // minimum baselineskip above displayed op
#define big_op_spacing4        mathex(12) // minimum baselineskip below displayed op
#define big_op_spacing5        mathex(13) // padding above and below displayed limits

Section 702

We also need to compute the change in style between mlists and their subsidiaries. The following macros define the subsidiary style for an overlined nucleus (cramped_style), for a subscript or a superscript (sub_style or sup_style), or for a numerator or denominator (num_style or denom_style).

texmath.h
#define cramped_style(X) (2*((X) / 2) + CRAMPED)                   // cramp the style
#define sub_style(X)     (2*((X) / 4) + SCRIPT_STYLE + CRAMPED)    // smaller and cramped
#define sup_style(X)     (2*((X) / 4) + SCRIPT_STYLE + ((X) % 2))  // smaller
#define num_style(X)     ((X) + 2 - 2*((X) / 6))                   // smaller unless already script-script
#define denom_style(X)   (2*((X) / 2) + CRAMPED + 2 - 2*((X) / 6)) // smaller, cramped

Section 703

When the style changes, the following piece of program computes associated information:

⟨ Set up the values of cur_size and cur_mu, based on cur_style 703 ⟩≡

if (cur_style < SCRIPT_STYLE) {
    cur_size = TEXT_SIZE;
}
else {
    cur_size = 16*((cur_style - TEXT_STYLE) / 2);
}
cur_mu = x_over_n(math_quad(cur_size), 18);

Section 704

Here is a function that returns a pointer to a rule node having a given thickness t. The rule will extend horizontally to the boundary of the vlist that eventually contains it.

math_subroutines.c
// << Start file |math_subroutines.c|, 1382 >>

// construct the bar for a fraction
pointer fraction_rule(scaled t) {
    pointer p; // the new node
    p = new_rule();
    height(p) = t;
    depth(p) = 0;
    return p;
}

Section 705

The overbar function returns a pointer to a vlist box that consists of a given box b, above which has been placed a kern of height k under a fraction rule of thickness t under additional space of height t.

math_subroutines.c
pointer overbar(pointer b, scaled k, scaled t) {
    pointer p, q; // nodes being constructed
    p = new_kern(k);
    link(p) = b;
    q = fraction_rule(t);
    link(q) = p;
    p = new_kern(t);
    link(p) = q;
    return vpack(p, NATURAL);
}

Section 706

The var_delimiter function, which finds or constructs a sufficiently large delimiter, is the most interesting of the auxiliary functions that currently concern us. Given a pointer d to a delimiter field in some noad, together with a size code s and a vertical distance v, this function returns a pointer to a box that contains the smallest variant of d whose height plus depth is v or more. (And if no variant is large enough, it returns the largest available variant.) In particular, this routine will construct arbitrarily large delimiters from extensible components, if d leads to such characters.

The value returned is a box whose shift_amount has been set so that the box is vertically centered with respect to the axis in the given size. If a built-up symbol is returned, the height of the box before shifting will be the height of its topmost component.

math_subroutines.c
// << Declare subprocedures for |var_delimiter|, 709 >>

pointer var_delimiter(pointer d, small_number s, scaled v) {
    pointer b;                 // the box that will be constructed
    internal_font_number f, g; // best-so-far and tentative font codes
    quarterword c, x, y;       // best-so-far and tentative character codes
    int m, n;                  // the number of extensible pieces
    scaled u;                  // height-plus-depth of a tentative character
    scaled w;                  // largest height-plus-depth so far
    memory_word q;             // character info
    eight_bits hd;             // height-depth byte
    memory_word r;             // extensible pieces
    small_number z;            // runs through font family members
    bool large_attempt;        // are we trying the "large" variant?
    
    c = 0;
    f = NULL_FONT;
    w = 0;
    large_attempt = false;
    z = small_fam(d);
    x = small_char(d);
    while(true) {
        // << Look at the variants of |(z, x)|; set |f| and |c| whenever a better character is found; |goto found| as soon as a large enough variant is encountered, 707 >>
        if (large_attempt) {
            goto found; // there were none large enough
        }
        large_attempt = true;
        z = large_fam(d);
        x = large_char(d);
    }
found:
    if (f != NULL_FONT) {
        // << Make variable |b| point to a box for |(f, c)|, 710 >>
    }
    else {
        b = new_null_box();
        width(b) = null_delimiter_space; // use this width if no delimiter was found
    }
    shift_amount(b) = half(height(b) - depth(b)) - axis_height(s);
    return b;
}

Section 707

The search process is complicated slightly by the facts that some of the characters might not be present in some of the fonts, and they might not be probed in increasing order of height.

⟨ Look at the variants of (z, x); set f and c whenever a better character is found; goto found as soon as a large enough variant is encountered 707 ⟩≡

if (z != 0 || x != MIN_QUARTERWORD) {
    z += s + 16;
    do {
        z -= 16;
        g = fam_fnt(z);
        if (g != NULL_FONT) {
            // << Look at the list of characters starting with |x| in font |g|; set |f| and |c| whenever a better character is found; |goto found| as soon as a large enough variant is encountered, 708 >>
        }
    } while (z >= 16);
}

Section 708

⟨ Look at the list of characters starting with x in font g; set f and c whenever a better character is found; goto found as soon as a large enough variant is encountered 708 ⟩≡

y = x;
if (y >= font_bc[g] && y <= font_ec[g]) {
continue_lbl:
    q = char_info(g, y);
    if (char_exists(q)) {
        if (char_tag(q) == EXT_TAG) {
            f = g;
            c = y;
            goto found;
        }
        hd = height_depth(q);
        u = char_height(g, hd) + char_depth(g, hd);
        if (u > w) {
            f = g;
            c = y;
            w = u;
            if (u >= v) {
                goto found;
            }
        }
        if (char_tag(q) == LIST_TAG) {
            y = rem_byte(q);
            goto continue_lbl;
        }
    }
}

Section 709

Here is a subroutine that creates a new box, whose list contains a single character, and whose width includes the italic correction for that character. The height or depth of the box will be negative, if the height or depth of the character is negative; thus, this routine may deliver a slightly different result than hpack would produce.

⟨ Declare subprocedures for var_delimiter 709 ⟩≡

pointer char_box(internal_font_number f, quarterword c) {
    memory_word q;
    eight_bits hd; // |height_depth| byte
    pointer b, p;  // the new box and its character node
    q = char_info(f, c);
    hd = height_depth(q);
    b = new_null_box();
    width(b) = char_width(f, q) + char_italic(f, q);
    height(b) = char_height(f, hd);
    depth(b) = char_depth(f, hd);
    p = get_avail();
    character(p) = c;
    font(p) = f;
    list_ptr(b) = p;
    return b;
}

Section 710

When the following code is executed, char_tag(q) will be equal to EXT_TAG if and only if a built-up symbol is supposed to be returned.

⟨ Make variable b point to a box for (f, c) 710 ⟩≡

if (char_tag(q) == EXT_TAG) {
    // << Construct an extensible character in a new box |b|, using recipe |rem_byte(q)| and font |f|, 713 >>
}
else {
    b = char_box(f, c);
}

Section 711

When we build an extensible character, it’s handy to have the following subroutine, which puts a given character on top of the characters already in box b:

⟨ Declare subprocedures for var_delimiter 709 ⟩+≡

void stack_into_box(pointer b, internal_font_number f, quarterword c) {
    pointer p; // new node placed into |b|
    p = char_box(f, c);
    link(p) = list_ptr(b);
    list_ptr(b) = p;
    height(b) = height(p);
}

Section 712

Another handy subroutine computes the height plus depth of a given character:

⟨ Declare subprocedures for var_delimiter 709 ⟩+≡

scaled height_plus_depth(internal_font_number f, quarterword c) {
    memory_word q;
    eight_bits hd; // |height_depth| byte
    q = char_info(f, c);
    hd = height_depth(q);
    return char_height(f, hd) + char_depth(f, hd);
}

Section 713

⟨ Construct an extensible character in a new box b, using recipe rem_byte(q) and font f 713 ⟩≡

b = new_null_box();
type(b) = VLIST_NODE;
r = font_info[exten_base[f] + rem_byte(q)];
// << Compute the minimum suitable height, |w|, and the corresponding number of extension steps, |n|; also set |width(b)|, 714 >>
c = ext_bot(r);
if (c != MIN_QUARTERWORD) {
    stack_into_box(b, f, c);
}
c = ext_rep(r);
for(m = 1; m <= n; m++) {
    stack_into_box(b, f, c);
}
c = ext_mid(r);
if (c != MIN_QUARTERWORD) {
    stack_into_box(b, f, c);
    c = ext_rep(r);
    for(m = 1; m <= n; m++) {
        stack_into_box(b, f, c);
    }
}
c = ext_top(r);
if (c != MIN_QUARTERWORD) {
    stack_into_box(b, f, c);
}
depth(b) = w - height(b);

Section 714

The width of an extensible character is the width of the repeatable module. If this module does not have positive height plus depth, we don’t use any copies of it, otherwise we use as few as possible (in groups of two if there is a middle part).

⟨ Compute the minimum suitable height, w, and the corresponding number of extension steps, n; also set width(b) 714 ⟩≡

c = ext_rep(r);
u = height_plus_depth(f, c);
w = 0;
q = char_info(f, c);
width(b) = char_width(f, q) + char_italic(f, q);
c = ext_bot(r);
if (c != MIN_QUARTERWORD) {
    w += height_plus_depth(f, c);
}
c = ext_mid(r);
if (c != MIN_QUARTERWORD) {
    w += height_plus_depth(f, c);
}
c = ext_top(r);
if (c != MIN_QUARTERWORD) {
    w += height_plus_depth(f, c);
}
n = 0;
if (u > 0) {
    while (w < v) {
        w += u;
        incr(n);
        if (ext_mid(r) != MIN_QUARTERWORD) {
            w += u;
        }
    }
}

Section 715

The next subroutine is much simpler; it is used for numerators and denominators of fractions as well as for displayed operators and their limits above and below. It takes a given box b and changes it so that the new box is centered in a box of width w. The centering is done by putting \hss glue at the left and right of the list inside b, then packaging the new box; thus, the actual box might not really be centered, if it already contains infinite glue.

The given box might contain a single character whose italic correction has been added to the width of the box; in this case a compensating kern is inserted.

math_subroutines.c
pointer rebox(pointer b, scaled w) {
    pointer p;              // temporary register for list manipulation
    internal_font_number f; // font in a one-character box
    scaled v;               // width of a character without italic correction
    
    if (width(b) != w && list_ptr(b) != null) {
        if (type(b) == VLIST_NODE) {
            b = hpack(b, NATURAL);
        }
        p = list_ptr(b);
        if (is_char_node(p) && link(p) == null) {
            f = font(p);
            v = char_width(f, char_info(f, character(p)));
            if (v != width(b)) {
                link(p) = new_kern(width(b) - v);
            }
        }
        free_node(b, BOX_NODE_SIZE);
        b = new_glue(SS_GLUE);
        link(b) = p;
        while (link(p) != null) {
            p = link(p);
        }
        link(p) = new_glue(SS_GLUE);
        return hpack(b, w, EXACTLY);
    }
    else {
        width(b) = w;
        return b;
    }
}

Section 716

Here is a subroutine that creates a new glue specification from another one that is expressed in ‘mu’, given the value of the math unit.

texmath.h
#define mu_mult(X) nx_plus_y(n, (X), xn_over_d((X), f, 0x10000))
math_subroutines.c
pointer math_glue(pointer g, scaled m) {
    pointer p; // the new glue specification
    int n;     // integer part of |m|
    scaled f;  // fraction part of |m|
    n = x_over_n(m, 0x10000);
    f = remainder_;
    if (f < 0) {
        decr(n);
        f = f + 0x10000;
    }
    p = get_node(GLUE_SPEC_SIZE);
    width(p) = mu_mult(width(g)); // convert mu to pt
    stretch_order(p) = stretch_order(g);
    if (stretch_order(p) == NORMAL) {
        stretch(p) = mu_mult(stretch(g));
    }
    else {
        stretch(p) = stretch(g);
    }
    shrink_order(p) = shrink_order(g);
    if (shrink_order(p) == NORMAL) {
        shrink(p) = mu_mult(shrink(g));
    }
    else {
        shrink(p) = shrink(g);
    }
    return p;
}

Section 717

The math_kern subroutine removes MU_GLUE from a kern node, given the value of the math unit.

math_subroutines.c
void math_kern(pointer p, scaled m) {
    int n;    // integer part of |m|
    scaled f; // fraction part of |m|
    if (subtype(p) == MU_GLUE) {
        n = x_over_n(m, 0x10000);
        f = remainder_;
        if (f < 0) {
            decr(n);
            f += 0x10000;
        }
        width(p) = mu_mult(width(p));
        subtype(p) = EXPLICIT;
    }
}

Section 718

Sometimes it is necessary to destroy an mlist. The following subroutine empties the current list, assuming that abs(mode) = MMODE.

math_subroutines.c
void flush_math() {
    flush_node_list(link(head));
    flush_node_list(incompleat_noad);
    link(head) = null;
    tail = head;
    incompleat_noad = null;
}