diff options
Diffstat (limited to 'Build/source/texk/web2c/luatexdir/tex/packaging.c')
-rw-r--r-- | Build/source/texk/web2c/luatexdir/tex/packaging.c | 2142 |
1 files changed, 2142 insertions, 0 deletions
diff --git a/Build/source/texk/web2c/luatexdir/tex/packaging.c b/Build/source/texk/web2c/luatexdir/tex/packaging.c new file mode 100644 index 00000000000..e2b007f11ef --- /dev/null +++ b/Build/source/texk/web2c/luatexdir/tex/packaging.c @@ -0,0 +1,2142 @@ +/* + +Copyright 2009-2010 Taco Hoekwater <taco@luatex.org> + +This file is part of LuaTeX. + +LuaTeX is free software; you can redistribute it and/or modify it under the terms +of the GNU General Public License as published by the Free Software Foundation; +either version 2 of the License, or (at your option) any later version. + +LuaTeX is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + +You should have received a copy of the GNU General Public License along with +LuaTeX; if not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "ptexlib.h" + +/*tex + + We're essentially done with the parts of \TeX\ that are concerned with the + input (|get_next|) and the output (|ship_out|). So it's time to get heavily + into the remaining part, which does the real work of typesetting. + + After lists are constructed, \TeX\ wraps them up and puts them into boxes. + Two major subroutines are given the responsibility for this task: |hpack| + applies to horizontal lists (hlists) and |vpack| applies to vertical lists + (vlists). The main duty of |hpack| and |vpack| is to compute the dimensions + of the resulting boxes, and to adjust the glue if one of those dimensions is + pre-specified. The computed sizes normally enclose all of the material inside + the new box; but some items may stick out if negative glue is used, if the + box is overfull, or if a \.{\\vbox} includes other boxes that have been + shifted left. + + The subroutine call |hpack(p,w,m)| returns a pointer to an |hlist_node| for a + box containing the hlist that starts at |p|. Parameter |w| specifies a width; + and parameter |m| is either `|exactly|' or `|additional|'. Thus, + |hpack(p,w,exactly)| produces a box whose width is exactly |w|, while + |hpack(p,w,additional)| yields a box whose width is the natural width plus + |w|. It is convenient to define a macro called `|natural|' to cover the most + common case, so that we can say |hpack(p,natural)| to get a box that has the + natural width of list |p|. + + Similarly, |vpack(p,w,m)| returns a pointer to a |vlist_node| for a box + containing the vlist that starts at |p|. In this case |w| represents a height + instead of a width; the parameter |m| is interpreted as in |hpack|. + + The parameters to |hpack| and |vpack| correspond to \TeX's primitives like + `\.{\\hbox} \.{to} \.{300pt}', `\.{\\hbox} \.{spread} \.{10pt}'; note that + `\.{\\hbox}' with no dimension following it is equivalent to `\.{\\hbox} + \.{spread} \.{0pt}'. The |scan_spec| subroutine scans such constructions in + the user's input, including the mandatory left brace that follows them, and + it puts the specification onto |save_stack| so that the desired box can later + be obtained by executing the following code: $$\vbox{\halign{#\hfil\cr + |save_ptr:=save_ptr-1;|\cr |hpack(p,saved_value(0),saved_level(0)).|\cr}}$$ + +*/ + +/*tex Scan a box specification and left brace: */ + +void scan_spec(group_code c) +{ + int spec_code; + boolean done = false ; + do { + get_x_token(); + } while ((cur_cmd == spacer_cmd) || (cur_cmd == relax_cmd)); + if (cur_cmd == left_brace_cmd) { + spec_code = additional; + cur_val = 0; + done = true; + } else { + /*tex todo: attr */ + back_input(); + if (scan_keyword("to")) { + spec_code = exactly; + scan_normal_dimen(); + } else if (scan_keyword("spread")) { + spec_code = additional; + scan_normal_dimen(); + } else { + spec_code = additional; + cur_val = 0; + } + } + set_saved_record(0, saved_boxspec, spec_code, cur_val); + save_ptr++; + new_save_level(c); + if (!done) { + scan_left_brace(); + } +} + +/*tex + + When scanning, special care is necessary to ensure that the special + |save_stack| codes are placed just below the new group code, because scanning + can change |save_stack| when \.{\\csname} appears. + + This coincides with the text on |dir| and |attr| keywords, as these are + exaclty the uses of \.{\\hbox}, \.{\\vbox}, and \.{\\vtop} in the input + stream (the others are \.{\\vcenter}, \.{\\valign}, and \.{\\halign}). + +*/ + +/*tex Scan a box specification and left brace: */ + +#define first_char_is(a,A) (cur_cs == 0 && (cur_chr == a || cur_chr == A)) + +void scan_full_spec(group_code c, int spec_direction, int just_pack) +{ + int s, i, v, spec_code; + boolean done = false ; + halfword attr_list; + boolean attr_done = false ; + boolean dir_done = false ; + if (attr_list_cache == cache_disabled) + update_attribute_cache(); + attr_list = attr_list_cache; + /*tex The box context: */ + s = saved_value(0); + do { + get_x_token(); + } while ((cur_cmd == spacer_cmd) || (cur_cmd == relax_cmd)); + if (cur_cmd == left_brace_cmd) { + goto QUICK; + } else { + back_input(); + goto KEYWORDS; + } + CONTINUE: + while (cur_cmd == relax_cmd || cur_cmd == spacer_cmd) { + get_x_token(); + if (cur_cmd == left_brace_cmd) { + goto QUICK; + } else if (cur_cmd != relax_cmd && cur_cmd != spacer_cmd) { + back_input(); + break; + } + } + KEYWORDS: + /*tex Multiple |attr| keys possible (before or after dir). */ + if (scan_keyword("attr")) { + scan_register_num(); + i = cur_val; + scan_optional_equals(); + scan_int(); + v = cur_val; + if (! attr_done) { + attr_list = copy_attribute_list(attr_list_cache); + attr_done = true; + } + attr_list = do_set_attribute(attr_list, i, v); + goto CONTINUE; + } + /*tex We only permit one |(b)dir| directive. */ + if (! dir_done) { + if (scan_keyword("bdir")) { + scan_int(); + check_dir_value(cur_val); + spec_direction = cur_val; + dir_done = true; + goto CONTINUE; + } + if (scan_keyword("dir")) { + scan_direction(); + spec_direction = cur_val; + dir_done = true; + goto CONTINUE; + } + } + /*tex Only one |to| or |spread| key possible and it comes last. */ + if (scan_keyword("to")) { + spec_code = exactly; + } else if (scan_keyword("spread")) { + spec_code = additional; + } else { + spec_code = additional; + cur_val = 0; + goto FOUND; + } + scan_normal_dimen(); + goto FOUND; + QUICK: + spec_code = additional; + cur_val = 0; + done = true; + FOUND: + add_node_attr_ref(attr_list); + set_saved_record(0, saved_boxcontext, 0, s); + set_saved_record(1, saved_boxspec, spec_code, cur_val); + /*tex Adjust |text_dir_ptr| for |scan_spec|. */ + if (spec_direction != -1) { + set_saved_record(2, saved_boxdir, spec_direction, text_dir_ptr); + text_dir_ptr = new_dir(spec_direction); + } else { + set_saved_record(2, saved_boxdir, spec_direction, null); + } + set_saved_record(3, saved_boxattr, 0, attr_list); + set_saved_record(4, saved_boxpack, 0, just_pack); + save_ptr += 5; + new_save_level(c); + if (! done) { + scan_left_brace(); + } + /*tex No gain in |if (body_direction_par != spec_direction)| etc. */ + eq_word_define(int_base + body_direction_code, spec_direction); + eq_word_define(int_base + par_direction_code, spec_direction); + eq_word_define(int_base + text_direction_code, spec_direction); +} + +/*tex + + To figure out the glue setting, |hpack| and |vpack| determine how much + stretchability and shrinkability are present, considering all four orders of + infinity. The highest order of infinity that has a nonzero coefficient is + then used as if no other orders were present. + + For example, suppose that the given list contains six glue nodes with the + respective stretchabilities 3pt, 8fill, 5fil, 6pt, $-3$fil, $-8$fill. Then + the total is essentially 2fil; and if a total additional space of 6pt is to + be achieved by stretching, the actual amounts of stretch will be 0pt, 0pt, + 15pt, 0pt, $-9$pt, and 0pt, since only `fil' glue will be considered. (The + `fill' glue is therefore not really stretching infinitely with respect to + `fil'; nobody would actually want that to happen.) + + The arrays |total_stretch| and |total_shrink| are used to determine how much + glue of each kind is present. A global variable |last_badness| is used to + implement \.{\\badness}. + +*/ + +/*tex Glue found by |hpack| or |vpack|. */ + +scaled total_stretch[5]; +scaled total_shrink[5]; + +/*tex Badness of the most recently packaged box. */ + +int last_badness; + +/*tex + + If the global variable |adjust_tail| is non-null, the |hpack| routine also + removes all occurrences of |ins_node|, |mark_node|, and |adjust_node| items + and appends the resulting material onto the list that ends at location + |adjust_tail|. + +*/ + +/*tex Tail of adjustment list. */ + +halfword adjust_tail; + +/*tex + + Materials in \.{\\vadjust} used with \.{pre} keyword will be appended to + |pre_adjust_tail| instead of |adjust_tail|. + +*/ + +halfword pre_adjust_tail; + +halfword last_leftmost_char; +halfword last_rightmost_char; + +/*tex Pointers to the prev and next char of an implicit kern. */ + +halfword next_char_p; +halfword prev_char_p; + +/*tex This procedure is called repeatedly from inside the line break algorithm. */ + +void set_prev_char_p(halfword p) +{ + prev_char_p = p; +} + +/*tex + + The kern stretch / shrink code was (or had become) rather weird ... the width + field is set, and then used in a second calculation, repeatedly, so why is + that ... maybe some some weird left-over ... anyway, the values are so small + that in practice they are not significant at all when the backend sees them + because a few hundred sp positive or negative are just noise there (so + adjustlevel 3 has hardly any consequence for the result but is more + efficient). + +*/ + +scaled char_stretch(halfword p) +{ + internal_font_number f = font(p); + int m = font_max_stretch(f); + if (m > 0) { + int c = character(p); + int ef = get_ef_code(f, c); + if (ef > 0) { + scaled dw = calc_char_width(f, c, m) - char_width(f, c); + if (dw > 0) { + return round_xn_over_d(dw, ef, 1000); + } + } + } + return 0; +} + +scaled char_shrink(halfword p) +{ + internal_font_number f = font(p); + int m = font_max_shrink(f); + if (m > 0) { + int c = character(p); + int ef = get_ef_code(f, c); + if (ef > 0) { + scaled dw = char_width(f, c) - calc_char_width(f, c, -m); + if (dw > 0) { + return round_xn_over_d(dw, ef, 1000); + } + } + } + return 0; +} + +scaled kern_stretch(halfword p) +{ + int m; + scaled d, e, x; + scaled w = width(p) ; + halfword l; + halfword r; + if (w == 0) { + /*tex Why bother about zero kerns. */ + return 0; + } + l = prev_char_p ; + if ((l == null) || (vlink(l) != p)) { + /*tex We only care about kerns following a char. */ + return 0; + } + r = vlink(p); + if (r == null) { + /*tex We only care about kerns between a char and something else. */ + } + if (!(is_char_node(l) && is_char_node(r))) { + /*tex We want two chars (but but don't care about the fonts). */ + return 0; + } + /*tex We use the old logic, kind of, but average the ef as we might depend on proper overlap. */ + m = (font_max_stretch(font(l)) + font_max_stretch(font(r)))/2; + if (m == 0) { + /*tex nothing to kern */ + return 0; + } + d = round_xn_over_d(w, 1000 + m, 1000); + /*tex We use the old logic, kind of, but average the ef as we might depend on proper overlap. */ + e = (get_ef_code(font(l), character(l)) + get_ef_code(font(r), character(r)))/2 ; + if (e == 1000) { + x = d - w; + } else { + x = round_xn_over_d(d - w, e, 1000); + } + return x; +} + +scaled kern_shrink(halfword p) +{ + int m; + scaled d, e, x; + scaled w = width(p) ; + halfword l; + halfword r; + if (w == 0) { + /*tex Why bother about zero kerns. */ + return 0; + } + l = prev_char_p ; + if ((l == null) || (vlink(l) != p)) { + /*tex We only care about kerns following a char. */ + return 0; + } + r = vlink(p); + if (r == null) { + /*tex We only care about kerns between a char and something else. */ + } + if (!(is_char_node(l) && is_char_node(r))) { + /*tex We want two chars (but but don't care about the fonts). */ + return 0; + } + /*tex We use the old logic, kind of, but average the ef as we might depend on proper overlap. */ + m = (font_max_shrink(font(l)) + font_max_shrink(font(r)))/2; + if (m == 0) { + /*tex Nothing to kern. */ + return 0; + } + d = round_xn_over_d(w, 1000 - m, 1000); + e = (get_ef_code(font(l), character(l)) + get_ef_code(font(r), character(r)))/2 ; + if (e == 1000) { + x = w - d ; + } else { + x = round_xn_over_d(w - d, e, 1000); + } + return x; +} + +void do_subst_font(halfword p, int ex_ratio) +{ + if (type(p) == disc_node) { + halfword r = vlink(pre_break(p)); + while (r != null) { + if (is_char_node(r)) + do_subst_font(r, ex_ratio); + r = vlink(r); + } + r = vlink(post_break(p)); + while (r != null) { + if (is_char_node(r)) + do_subst_font(r, ex_ratio); + r = vlink(r); + } + r = vlink(no_break(p)); + while (r != null) { + if (is_char_node(r)) + do_subst_font(r, ex_ratio); + r = vlink(r); + } + return; + } + if (! is_char_node(p)) { + normal_error("font expansion", "invalid node type"); + return; + } else { + internal_font_number f = font(p); + int ef = get_ef_code(f, character(p)); + if (ef == 0) + return; + if ((font_max_stretch(f) > 0) && (ex_ratio > 0)) { + int ex_stretch = ext_xn_over_d(ex_ratio * ef, font_max_stretch(f), 1000000); + ex_glyph(p) = fix_expand_value(f, ex_stretch)*1000; + } else if ((font_max_shrink(f) > 0) && (ex_ratio < 0)) { + int ex_shrink = ext_xn_over_d(ex_ratio * ef, font_max_shrink(f), 1000000); + ex_glyph(p) = fix_expand_value(f, ex_shrink)*1000; + } + } +} + +scaled char_pw(halfword p, int side) +{ + internal_font_number f; + int c, w; + if (side == left_side) + last_leftmost_char = null; + else + last_rightmost_char = null; + if (p == null) + return 0; + if (!is_char_node(p)) + return 0; + f = font(p); + if (side == left_side) { + c = get_lp_code(f, character(p)); + last_leftmost_char = p; + } else { + c = get_rp_code(f, character(p)); + last_rightmost_char = p; + } + if (c == 0) + return 0; + w = quad(f); + return round_xn_over_d(w, c, 1000); +} + +halfword new_margin_kern(scaled w, halfword p, int side) +{ + halfword k, q; + k = new_node(margin_kern_node, side); + width(k) = w; + if (p == null) + normal_error("margin kerning", "invalid pointer to marginal char node"); + q = new_char(font(p), character(p)); + margin_char(k) = q; + return k; +} + +/*tex + + Here we prepare for |hpack|, which is place where we do font substituting + when font expansion is being used. + +*/ + +/*tex The current expansion ratio, needed for recursive call. */ + +int font_expand_ratio = 0; + +int ignore_math_skip(halfword p) +{ + if (math_skip_mode == 6) { + if (subtype(p) == after) { + if (math_skip_boundary(vlink(p))) { + return 0; + } + } else { + if (math_skip_boundary(alink(p))) { + return 0; + } + } + } else if (math_skip_mode == 7) { + if (subtype(p) == after) { + if (! math_skip_boundary(vlink(p))) { + return 0; + } + } else { + if (! math_skip_boundary(alink(p))) { + return 0; + } + } + } else { + return 0; + } + reset_glue_to_zero(p); + return 1; +} + +halfword hpack(halfword p, scaled w, int m, int pack_direction) +{ + /*tex the box node that will be returned */ + halfword r; + /*tex trails behind |p| */ + halfword q; + /*tex height */ + scaled h = 0; + /*tex depth */ + scaled d = 0; + /*tex natural width */ + scaled x = 0; + scaled_whd whd; + /*tex shift amount */ + scaled s; + /*tex order of infinity */ + int o; + /*tex for managing the direction stack */ + halfword dir_ptr1 = null; + /*tex the current direction */ + int hpack_dir; + int disc_level = 0; + halfword pack_interrupt[8]; + scaled font_stretch = 0; + scaled font_shrink = 0; + int adjust_spacing = adjust_spacing_par; + last_badness = 0; + /*tex the box node that will be returned */ + r = new_node(hlist_node, min_quarterword); + if (pack_direction == -1) { + hpack_dir = text_direction_par; + } else { + hpack_dir = pack_direction; + } + box_dir(r) = hpack_dir; + /*tex + + A potential optimimization, save a little but neglectable in practice + (not so many empty boxes are used): + + \starttyping + if (p == null) { + width(r) = w; + return r; + } + \stoptyping + + */ + /*tex push null */ + push_dir(dir_ptr1,hpack_dir); + /*tex hm, adding something to a node address? */ + q = r + list_offset; + vlink(q) = p; + if (m == cal_expand_ratio) { + /*tex Why not always: */ + prev_char_p = null; + } + if (adjust_spacing > 2) { + adjust_spacing = 0; + } + total_stretch[normal] = 0; + total_shrink[normal] = 0; + total_stretch[sfi] = 0; + total_shrink[sfi] = 0; + total_stretch[fil] = 0; + total_shrink[fil] = 0; + total_stretch[fill] = 0; + total_shrink[fill] = 0; + total_stretch[filll] = 0; + total_shrink[filll] = 0; + RESWITCH: + while ((p != null) || (disc_level > 0)) { + if (p == null) { + decr(disc_level); + p = pack_interrupt[disc_level]; + goto RESWITCH; + } + /*tex + + Examine node |p| in the hlist, taking account of its effect on the + dimensions of the new box, or moving it to the adjustment list; then + advance |p| to the next node. + + */ + while (is_char_node(p)) { + /*tex + + Incorporate character dimensions into the dimensions of the hbox + that will contain~it, then move to the next node. + + The following code is part of \TeX's inner loop; i.e., adding + another character of text to the user's input will cause each of + these instructions to be exercised one more time. + + */ + if (m >= cal_expand_ratio) { + prev_char_p = p; + if (m == cal_expand_ratio) { + font_stretch += char_stretch(p); + font_shrink += char_shrink(p); + } else if (m == subst_ex_font) { + do_subst_font(p, font_expand_ratio); + } + } + whd = pack_width_height_depth(hpack_dir, dir_TRT, p, true); + x += whd.wd; + if (whd.ht > h) + h = whd.ht; + if (whd.dp > d) + d = whd.dp; + p = vlink(p); + } + if (p != null) { + switch (type(p)) { + case hlist_node: + case vlist_node: + /*tex + + Incorporate box dimensions into the dimensions of the + hbox that will contain~it + + The code here implicitly uses the fact that running + dimensions are indicated by |null_flag|, which will be + ignored in the calculations because it is a highly + negative number. + + */ + s = shift_amount(p); + whd = pack_width_height_depth(hpack_dir, box_dir(p), p, false); + x += whd.wd; + if (whd.ht - s > h) + h = whd.ht - s; + if (whd.dp + s > d) + d = whd.dp + s; + break; + case rule_node: + case unset_node: + x += width(p); + if (height(p) > h) + h = height(p); + if (depth(p) > d) + d = depth(p); + break; + case math_node: + /*tex Begin mathskip code. */ + if (glue_is_zero(p) || ignore_math_skip(p)) { + x += surround(p); + break; + } else { + /*tex Fall through. */ + } + /*tex End mathskip code. */ + case glue_node: + /*tex Incorporate glue into the horizontal totals. */ + x += width(p); + o = stretch_order(p); + total_stretch[o] = total_stretch[o] + stretch(p); + o = shrink_order(p); + total_shrink[o] = total_shrink[o] + shrink(p); + if (subtype(p) >= a_leaders) { + halfword g = leader_ptr(p); + if (height(g) > h) + h = height(g); + if (depth(g) > d) + d = depth(g); + } + break; + case kern_node: + x += width(p); + if (subtype(p) == font_kern && adjust_spacing) { + /*tex So only when 1 or 2. */ + if (m == cal_expand_ratio) { + font_stretch = font_stretch + kern_stretch(p); + font_shrink = font_shrink + kern_shrink(p); + } else if (m == subst_ex_font) { + /*tex This is the finalizer. */ + int k = 0; + if (font_expand_ratio > 0) { + k = kern_stretch(p); + } else if (font_expand_ratio < 0) { + k = kern_shrink(p); + } + ex_kern(p) = k; + x += k; + } + } + break; + case disc_node: + if (m == subst_ex_font) + do_subst_font(p, font_expand_ratio); + if ((subtype(p) != select_disc) && (vlink(no_break(p)) != null)) { + pack_interrupt[disc_level] = vlink(p); + incr(disc_level); + p = no_break(p); + } + break; + case dir_node: + /*tex Adjust the dir stack for the |hpack| routine. */ + if (subtype(p) == normal_dir) { + hpack_dir = dir_dir(p); + push_dir_node(dir_ptr1,p); + } else { + pop_dir_node(dir_ptr1); + if (dir_ptr1 != null) + hpack_dir = dir_dir(dir_ptr1); + } + break; + case margin_kern_node: + if (m == cal_expand_ratio) { + int f = font(margin_char(p)); + do_subst_font(margin_char(p), 1000); + if (f != font(margin_char(p))) + font_stretch = font_stretch - width(p) - char_pw(margin_char(p), subtype(p)); + font(margin_char(p)) = f; + do_subst_font(margin_char(p), -1000); + if (f != font(margin_char(p))) + font_shrink = font_shrink - width(p) - char_pw(margin_char(p), subtype(p)); + font(margin_char(p)) = f; + } else if (m == subst_ex_font) { + do_subst_font(margin_char(p), font_expand_ratio); + width(p) = -char_pw(margin_char(p), subtype(p)); + } + x += width(p); + break; + case ins_node: + case mark_node: + case adjust_node: + /*tex + + Transfer node |p| to the adjustment list.\Although node + |q| is not necessarily the immediate predecessor of node + |p|, it always points to some node in the list preceding + |p|. Thus, we can delete nodes by moving |q| when + necessary. The algorithm takes linear time, and the extra + computation does not intrude on the inner loop unless it + is necessary to make a deletion. + + */ + if (adjust_tail != null || pre_adjust_tail != null) { + while (vlink(q) != p) + q = vlink(q); + if (type(p) == adjust_node) { + if (adjust_pre(p) != 0) + update_adjust_list(pre_adjust_tail); + else + update_adjust_list(adjust_tail); + p = vlink(p); + adjust_ptr(vlink(q)) = null; + flush_node(vlink(q)); + } else { + vlink(adjust_tail) = p; + adjust_tail = p; + p = vlink(p); + } + vlink(q) = p; + p = q; + } + break; + default: + break; + } + p = vlink(p); + } + } + + if (adjust_tail != null) + vlink(adjust_tail) = null; + if (pre_adjust_tail != null) + vlink(pre_adjust_tail) = null; + height(r) = h; + depth(r) = d; + /*tex + + Determine the value of |width(r)| and the appropriate glue setting; then + |return| or |goto common_ending|. + + When we get to the present part of the program, |x| is the natural width + of the box being packaged. + + */ + if (m == additional) + w = x + w; + width(r) = w; + x = w - x; + /*tex Now |x| is the excess to be made up. */ + if (x == 0) { + glue_sign(r) = normal; + glue_order(r) = normal; + set_glue_ratio_zero(glue_set(r)); + goto EXIT; + } else if (x > 0) { + /*tex + + Determine horizontal glue stretch setting, then |return| or + |goto common_ending|. + + If |hpack| is called with |m=cal_expand_ratio| we calculate + |font_expand_ratio| and return without checking for overfull or + underfull box. + + */ + if (total_stretch[filll] != 0) + o = filll; + else if (total_stretch[fill] != 0) + o = fill; + else if (total_stretch[fil] != 0) + o = fil; + else if (total_stretch[sfi] != 0) + o = sfi; + else + o = normal; + if ((m == cal_expand_ratio) && (o == normal) && (font_stretch > 0)) { + font_expand_ratio = divide_scaled_n(x, font_stretch, 1000.0); + goto EXIT; + } + glue_order(r) = (quarterword) o; + glue_sign(r) = stretching; + if (total_stretch[o] != 0) { + glue_set(r) = unfloat((double) x / total_stretch[o]); + } else { + /*tex There's nothing to stretch. */ + glue_sign(r) = normal; + set_glue_ratio_zero(glue_set(r)); + } + if (o == normal) { + if (list_ptr(r) != null) { + /*tex + + Report an underfull hbox and |goto common_ending|, if this + box is sufficiently bad. + + */ + last_badness = badness(x, total_stretch[normal]); + if (last_badness > hbadness_par) { + int callback_id = callback_defined(hpack_quality_callback); + if (callback_id > 0) { + halfword rule = null; + if (last_badness > 100) { + run_callback(callback_id, "SdNdd->N","underfull",last_badness,r,abs(pack_begin_line),line,&rule); + } else { + run_callback(callback_id, "SdNdd->N","loose",last_badness,r,abs(pack_begin_line),line,&rule); + } + if (rule != null) { + while (vlink(q) != null) { + q = vlink(q); + } + couple_nodes(q,rule); + } + } else { + print_ln(); + if (last_badness > 100) { + tprint_nl("Underfull \\hbox (badness "); + } else { + tprint_nl("Loose \\hbox (badness "); + } + print_int(last_badness); + goto COMMON_ENDING; + } + } + } + } + goto EXIT; + } else { + /*tex + + Determine horizontal glue shrink setting, then |return| or |goto + common_ending|, + + */ + if (total_shrink[filll] != 0) + o = filll; + else if (total_shrink[fill] != 0) + o = fill; + else if (total_shrink[fil] != 0) + o = fil; + else if (total_shrink[sfi] != 0) + o = sfi; + else + o = normal; + if ((m == cal_expand_ratio) && (o == normal) && (font_shrink > 0)) { + font_expand_ratio = divide_scaled_n(x, font_shrink, 1000.0); + goto EXIT; + } + glue_order(r) = (quarterword) o; + glue_sign(r) = shrinking; + if (total_shrink[o] != 0) { + glue_set(r) = unfloat((double) (-x) / (double) total_shrink[o]); + } else { + /*tex There's nothing to shrink. */ + glue_sign(r) = normal; + set_glue_ratio_zero(glue_set(r)); + } + if ((total_shrink[o] < -x) && (o == normal) && (list_ptr(r) != null)) { + int overshoot = -x - total_shrink[normal] ; + last_badness = 1000000; + /*tex Use the maximum shrinkage */ + set_glue_ratio_one(glue_set(r)); + /*tex + + Report an overfull hbox and |goto common_ending|, if this box is + sufficiently bad. + + */ + if ((overshoot > hfuzz_par) || (hbadness_par < 100)) { + int callback_id = callback_defined(hpack_quality_callback); + halfword rule = null; + if (callback_id > 0) { + run_callback(callback_id, "SdNdd->N","overfull",overshoot,r,abs(pack_begin_line),line,&rule); + } else if (overfull_rule_par > 0) { + rule = new_rule(normal_rule); + rule_dir(rule) = box_dir(r); + width(rule) = overfull_rule_par; + } + if (rule != null) { + while (vlink(q) != null) { + q = vlink(q); + } + couple_nodes(q,rule); + } + if (callback_id == 0) { + print_ln(); + tprint_nl("Overfull \\hbox ("); + print_scaled(overshoot); + tprint("pt too wide"); + goto COMMON_ENDING; + } + } + } else if (o == normal) { + if (list_ptr(r) != null) { + /*tex + + Report a tight hbox and |goto common_ending|, if this box is + sufficiently bad. + + */ + last_badness = badness(-x, total_shrink[normal]); + if (last_badness > hbadness_par) { + int callback_id = callback_defined(hpack_quality_callback); + if (callback_id > 0) { + halfword rule = null; + run_callback(callback_id, "SdNdd->N","tight",last_badness,r,abs(pack_begin_line),line,&rule); + if (rule != null) { + while (vlink(q) != null) { + q = vlink(q); + } + couple_nodes(q,rule); + } + } else { + print_ln(); + tprint_nl("Tight \\hbox (badness "); + print_int(last_badness); + goto COMMON_ENDING; + } + } + } + } + goto EXIT; + } + + COMMON_ENDING: + /*tex Finish issuing a diagnostic message for an overfull or underfull hbox. */ + if (output_active) { + tprint(") has occurred while \\output is active"); + } else { + if (pack_begin_line != 0) { + if (pack_begin_line > 0) { + tprint(") in paragraph at lines "); + } else { + tprint(") in alignment at lines "); + } + print_int(abs(pack_begin_line)); + tprint("--"); + } else { + tprint(") detected at line "); + } + print_int(line); + } + + print_ln(); + font_in_short_display = null_font; + short_display(list_ptr(r)); + print_ln(); + begin_diagnostic(); + show_box(r); + end_diagnostic(true); + EXIT: + if ((m == cal_expand_ratio) && (font_expand_ratio != 0)) { + font_expand_ratio = fix_int(font_expand_ratio, -1000, 1000); + q = list_ptr(r); + list_ptr(r) = null; + flush_node(r); + /*tex This nested call uses the more or less global font_expand_ratio. */ + r = hpack(q, w, subst_ex_font, hpack_dir); + } + while (dir_ptr1 != null) + pop_dir_node(dir_ptr1); + /*tex Here we reset the |font_expand_ratio|. */ + font_expand_ratio = 0; + return r; +} + +halfword filtered_hpack(halfword p, halfword qt, scaled w, int m, int grp, int pac, int just_pack, halfword attr) +{ + halfword q; + if (just_pack) { + q = vlink(p); + } else if (type(p) == temp_node && vlink(p) == null) { + q = vlink(p); + } else { + new_hyphenation(p, qt); + /*tex We don't care about the tail in this case. */ + (void) new_ligkern(p, qt); + q = vlink(p); + /*tex Maybe here: |alink(p) = null|. */ + /*tex ignores empty anyway. Maybe also pass tail? */ + q = lua_hpack_filter(q, w, m, grp, pac, attr); + } + return hpack(q, w, m, pac); +} + +/*tex + + Here is a function to calculate the natural whd of a (horizontal) node list. + +*/ + +scaled_whd natural_sizes(halfword p, halfword pp, glue_ratio g_mult, int g_sign, int g_order, int pack_direction) +{ + /*tex shift amount */ + scaled s; + /*tex points to a glue specification */ + halfword g; + int hpack_dir; + /*tex For recursion */ + scaled_whd xx; + scaled_whd whd, siz = { 0, 0, 0 }; + scaled gp = 0; + scaled gm = 0; + if (pack_direction == -1) { + hpack_dir = text_direction_par; + } else { + hpack_dir = pack_direction; + } + while (p != pp && p != null) { + while (is_char_node(p) && p != pp) { + whd = pack_width_height_depth(hpack_dir, dir_TRT, p, true); + siz.wd += whd.wd; + if (whd.ht > siz.ht) + siz.ht = whd.ht; + if (whd.dp > siz.dp) + siz.dp = whd.dp; + p = vlink(p); + } + if (p != pp && p != null) { + switch (type(p)) { + case hlist_node: + case vlist_node: + s = shift_amount(p); + whd = pack_width_height_depth(hpack_dir, box_dir(p), p, false); + siz.wd += whd.wd; + if (whd.ht - s > siz.ht) + siz.ht = whd.ht - s; + if (whd.dp + s > siz.dp) + siz.dp = whd.dp + s; + break; + case rule_node: + case unset_node: + siz.wd += width(p); + if (height(p) > siz.ht) + siz.ht = height(p); + if (depth(p) > siz.dp) + siz.dp = depth(p); + break; + case math_node: + /*tex Begin mathskip code. */ + if (glue_is_zero(p) || ignore_math_skip(p)) { + siz.wd += surround(p); + break; + } else { + /*tex Fall through. */ + } + /*tex End mathskip code. */ + case glue_node: + siz.wd += width(p); + if (g_sign != normal) { + if (g_sign == stretching) { + if (stretch_order(p) == g_order) { + /*tex + |siz.wd += float_round(float_cast(g_mult) * float_cast(stretch(p)))| + */ + gp += stretch(p); + } + } else if (shrink_order(p) == g_order) { + /*tex + |siz.wd -= float_round(float_cast(g_mult) * float_cast(shrink(p)));| + */ + gm += shrink(p); + } + } + if (subtype(p) >= a_leaders) { + g = leader_ptr(p); + if (height(g) > siz.ht) + siz.ht = height(g); + if (depth(g) > siz.dp) + siz.dp = depth(g); + } + break; + case margin_kern_node: + siz.wd += width(p); + break; + case kern_node: + siz.wd += width(p) + ex_kern(p); + break; + case disc_node: + xx = natural_sizes(no_break(p), null, g_mult, g_sign, g_order, hpack_dir); + siz.wd += xx.wd; + if (xx.ht > siz.ht) + siz.ht = xx.ht; + if (xx.dp > siz.dp) + siz.dp = xx.dp; + break; + default: + break; + } + p = vlink(p); + } + + } + if (g_sign != normal) { + if (g_sign == stretching) { + siz.wd += float_round(float_cast(g_mult) * float_cast(gp)); + } else { + siz.wd -= float_round(float_cast(g_mult) * float_cast(gm)); + } + } + return siz; +} + +/*tex + + In order to provide a decent indication of where an overfull or underfull box + originated, we use a global variable |pack_begin_line| that is set nonzero + only when |hpack| is being called by the paragraph builder or the alignment + finishing routine. + + The source file line where the current paragraph or alignment began; a + negative value denotes alignment: + +*/ + +int pack_begin_line; + +/*tex + + The |vpack| subroutine is actually a special case of a slightly more general + routine called |vpackage|, which has four parameters. The fourth parameter, + which is |max_dimen| in the case of |vpack|, specifies the maximum depth of + the page box that is constructed. The depth is first computed by the normal + rules; if it exceeds this limit, the reference point is simply moved down + until the limiting depth is attained. + +*/ + +halfword vpackage(halfword p, scaled h, int m, scaled l, int pack_direction) +{ + /*tex the box node that will be returned */ + halfword r; + /*tex width */ + scaled w = 0; + /*tex depth */ + scaled d = 0; + /*tex natural height */ + scaled x = 0; + scaled_whd whd; + /*tex shift amount */ + scaled s; + /*tex order of infinity */ + int o; + last_badness = 0; + r = new_node(vlist_node, 0); + if (pack_direction == -1) { + box_dir(r) = body_direction_par; + } else { + box_dir(r) = pack_direction; + } + subtype(r) = min_quarterword; + shift_amount(r) = 0; + list_ptr(r) = p; + total_stretch[normal] = 0; + total_shrink[normal] = 0; + total_stretch[sfi] = 0; + total_shrink[sfi] = 0; + total_stretch[fil] = 0; + total_shrink[fil] = 0; + total_stretch[fill] = 0; + total_shrink[fill] = 0; + total_stretch[filll] = 0; + total_shrink[filll] = 0; + while (p != null) { + /*tex + + Examine node |p| in the vlist, taking account of its effect on the + dimensions of the new box; then advance |p| to the next node. + + */ + if (is_char_node(p)) { + confusion("vpack"); + } else { + switch (type(p)) { + case hlist_node: + case vlist_node: + /*tex + + Incorporate box dimensions into the dimensions of the vbox + that will contain it. + + */ + s = shift_amount(p); + whd = pack_width_height_depth(box_dir(r), box_dir(p), p, false); + if (whd.wd + s > w) + w = whd.wd + s; + x += d + whd.ht; + d = whd.dp; + break; + case rule_node: + case unset_node: + x += d + height(p); + d = depth(p); + if (width(p) > w) + w = width(p); + break; + case glue_node: + /*tex Incorporate glue into the vertical totals. */ + x += d; + d = 0; + x += width(p); + o = stretch_order(p); + total_stretch[o] = total_stretch[o] + stretch(p); + o = shrink_order(p); + total_shrink[o] = total_shrink[o] + shrink(p); + if (subtype(p) >= a_leaders) { + halfword g = leader_ptr(p); + if (width(g) > w) + w = width(g); + } + break; + case kern_node: + x += d + width(p); + d = 0; + break; + default: + break; + } + } + p = vlink(p); + } + width(r) = w; + if (d > l) { + x += d - l; + depth(r) = l; + } else { + depth(r) = d; + } + /*tex + + Determine the value of |height(r)| and the appropriate glue setting; then + |return| or |goto common_ending|. + + When we get to the present part of the program, |x| is the natural height + of the box being packaged. + */ + if (m == additional) + h = x + h; + height(r) = h; + x = h - x; + /*tex Now |x| is the excess to be made up. */ + if (x == 0) { + glue_sign(r) = normal; + glue_order(r) = normal; + set_glue_ratio_zero(glue_set(r)); + return r; + } else if (x > 0) { + /*tex + + Determine vertical glue stretch setting, then |return| or |goto + common_ending|. + + */ + if (total_stretch[filll] != 0) + o = filll; + else if (total_stretch[fill] != 0) + o = fill; + else if (total_stretch[fil] != 0) + o = fil; + else if (total_stretch[sfi] != 0) + o = sfi; + else + o = normal; + glue_order(r) = (quarterword) o; + glue_sign(r) = stretching; + if (total_stretch[o] != 0) { + glue_set(r) = unfloat((double) x / total_stretch[o]); + } else { + /*tex There's nothing to stretch. */ + glue_sign(r) = normal; + set_glue_ratio_zero(glue_set(r)); + } + if (o == normal) { + if (list_ptr(r) != null) { + /*tex + + Report an underfull vbox and |goto common_ending|, if this + box is sufficiently bad. + + */ + last_badness = badness(x, total_stretch[normal]); + if (last_badness > vbadness_par) { + int callback_id = callback_defined(vpack_quality_callback); + if (callback_id > 0) { + if (last_badness > 100) { + run_callback(callback_id, "SdNdd->","underfull",last_badness,r,abs(pack_begin_line),line); + } else { + run_callback(callback_id, "SdNdd->","loose",last_badness,r,abs(pack_begin_line),line); + } + goto EXIT; + } else { + print_ln(); + if (last_badness > 100) { + tprint_nl("Underfull \\vbox (badness "); + } else { + tprint_nl("Loose \\vbox (badness "); + } + print_int(last_badness); + goto COMMON_ENDING; + } + } + } + } + return r; + } else { + /*tex + + Determine vertical glue shrink setting, then |return| or |goto + common_ending|. + + */ + if (total_shrink[filll] != 0) + o = filll; + else if (total_shrink[fill] != 0) + o = fill; + else if (total_shrink[fil] != 0) + o = fil; + else if (total_shrink[sfi] != 0) + o = sfi; + else + o = normal; + glue_order(r) = (quarterword) o; + glue_sign(r) = shrinking; + if (total_shrink[o] != 0) { + glue_set(r) = unfloat((double) (-x) / total_shrink[o]); + } else { + /*tex There's nothing to shrink. */ + glue_sign(r) = normal; + set_glue_ratio_zero(glue_set(r)); + } + if ((total_shrink[o] < -x) && (o == normal) && (list_ptr(r) != null)) { + int overshoot = -x - total_shrink[normal]; + last_badness = 1000000; + /*tex Use the maximum shrinkage */ + set_glue_ratio_one(glue_set(r)); + /*tex + + Report an overfull vbox and |goto common_ending|, if this box is + sufficiently bad. + + */ + if ((overshoot > vfuzz_par) || (vbadness_par < 100)) { + int callback_id = callback_defined(vpack_quality_callback); + if (callback_id > 0) { + run_callback(callback_id, "SdNdd->","overfull",overshoot,r,abs(pack_begin_line),line); + goto EXIT; + } else { + print_ln(); + tprint_nl("Overfull \\vbox ("); + print_scaled(-x - total_shrink[normal]); + tprint("pt too high"); + goto COMMON_ENDING; + } + } + } else if (o == normal) { + if (list_ptr(r) != null) { + /*tex + + Report a tight vbox and |goto common_ending|, if this box is + sufficiently bad. + + */ + last_badness = badness(-x, total_shrink[normal]); + if (last_badness > vbadness_par) { + int callback_id = callback_defined(vpack_quality_callback); + if (callback_id > 0) { + run_callback(callback_id, "SdNdd->","tight",last_badness,r,abs(pack_begin_line),line); + goto EXIT; + } else { + print_ln(); + tprint_nl("Tight \\vbox (badness "); + print_int(last_badness); + goto COMMON_ENDING; + } + } + } + } + return r; + } + + COMMON_ENDING: + /*tex Finish issuing a diagnostic message or an overfull or underfull vbox. */ + if (output_active) { + tprint(") has occurred while \\output is active"); + } else { + if (pack_begin_line != 0) { + /*tex It's actually negative. */ + tprint(") in alignment at lines "); + print_int(abs(pack_begin_line)); + tprint("--"); + } else { + tprint(") detected at line "); + } + print_int(line); + print_ln(); + } + begin_diagnostic(); + show_box(r); + end_diagnostic(true); + EXIT: + return r; +} + +halfword filtered_vpackage(halfword p, scaled h, int m, scaled l, int grp, int pack_direction, int just_pack, halfword attr) +{ + halfword q = p; + if (!just_pack) + q = lua_vpack_filter(q, h, m, l, grp, pack_direction, attr); + return vpackage(q, h, m, l, pack_direction); +} + +void finish_vcenter(void) +{ + halfword p; + unsave(); + save_ptr--; + p = vpack(vlink(cur_list.head_field), saved_value(0), saved_level(0), -1); + pop_nest(); + p = math_vcenter_group(p); + tail_append(p); +} + +void package(int c) +{ + halfword saved0, saved2, saved3, saved4; + int grp = cur_group; + scaled d = box_max_depth_par; + unsave(); + save_ptr -= 5; + saved0 = saved_value(0); + saved2 = saved_value(2); + saved3 = saved_value(3); + saved4 = saved_value(4); + if (cur_list.mode_field == -hmode) { + cur_box = filtered_hpack(cur_list.head_field, cur_list.tail_field, + saved_value(1), saved_level(1), grp, saved_level(2), saved4, saved3); + subtype(cur_box) = hbox_list; + } else { + cur_box = filtered_vpackage(vlink(cur_list.head_field), + saved_value(1), saved_level(1), d, grp, saved_level(2), saved4, saved3); + if (c == vtop_code) { + /*tex + + Read just the height and depth of |cur_box|, for \.{\\vtop}. The + height of a `\.{\\vtop}' box is inherited from the first item on + its list, if that item is an |hlist_node|, |vlist_node|, or + |rule_node|; otherwise the \.{\\vtop} height is zero. + + */ + scaled h = 0; + halfword p = list_ptr(cur_box); + if ((p != null) && (type(p) <= rule_node)) { + /* hlist, vlist, rule */ + h = height(p); + } + depth(cur_box) = depth(cur_box) - h + height(cur_box); + height(cur_box) = h; + } + } + if (saved2 != null) { + /*tex Adjust back |text_dir_ptr| for |scan_spec| */ + flush_node_list(text_dir_ptr); + text_dir_ptr = saved2; + + } + replace_attribute_list(cur_box, saved3); + pop_nest(); + box_end(saved0); +} + +/*tex + + When a box is being appended to the current vertical list, the baselineskip + calculation is handled by the |append_to_vlist| routine. + +*/ + +void append_to_vlist(halfword b, int location) +{ + /*tex The deficiency of space between baselines: */ + scaled d; + /*tex A new glue node. */ + halfword p; + boolean mirrored = (type(b) == hlist_node) && is_mirrored(box_dir(b)) ; + halfword result = null; + halfword next_depth = ignore_depth; + boolean prev_set = false ; + if (lua_appendtovlist_callback(b,location,prev_depth_par,mirrored,&result,&next_depth,&prev_set)) { + while (result != null) { + couple_nodes(cur_list.tail_field, result); + cur_list.tail_field = result; + result = vlink(result); + } + if (prev_set) { + prev_depth_par = next_depth; + } + } else { + if (prev_depth_par > ignore_depth) { + if (mirrored) { + d = width(baseline_skip_par) - prev_depth_par - depth(b); + } else { + d = width(baseline_skip_par) - prev_depth_par - height(b); + } + if (d < line_skip_limit_par) { + p = new_param_glue(line_skip_code); + } else { + p = new_skip_param(baseline_skip_code); + width(p) = d; + } + couple_nodes(cur_list.tail_field, p); + cur_list.tail_field = p; + } + couple_nodes(cur_list.tail_field, b); + cur_list.tail_field = b; + if (mirrored) { + prev_depth_par = height(b); + } else { + prev_depth_par = depth(b); + } + } +} + +/*tex + + When |saving_vdiscards| is positive then the glue, kern, and penalty nodes + removed by the page builder or by \.{\\vsplit} from the top of a vertical + list are saved in special lists instead of being discarded. + +*/ + +/*tex last item removed by page builder */ + +#define tail_page_disc disc_ptr[copy_code] + +/*tex first item removed by page builder */ + +#define page_disc disc_ptr[last_box_code] + +/*tex first item removed by \.{\\vsplit} */ + +#define split_disc disc_ptr[vsplit_code] + +/*tex List pointers. */ + +halfword disc_ptr[(vsplit_code + 1)]; + +/*tex + + The |vsplit| procedure, which implements \TeX's \.{\\vsplit} operation, is + considerably simpler than |line_break| because it doesn't have to worry about + hyphenation, and because its mission is to discover a single break instead of + an optimum sequence of breakpoints. But before we get into the details of + |vsplit|, we need to consider a few more basic things. + + A subroutine called |prune_page_top| takes a pointer to a vlist and returns a + pointer to a modified vlist in which all glue, kern, and penalty nodes have + been deleted before the first box or rule node. However, the first box or + rule is actually preceded by a newly created glue node designed so that the + topmost baseline will be at distance |split_top_skip| from the top, whenever + this is possible without backspacing. + + When the second argument |s| is |false| the deleted nodes are destroyed, + otherwise they are collected in a list starting at |split_disc|. + +*/ + +halfword prune_page_top(halfword p, boolean s) +{ + halfword q; + /*tex Lags one step behind |p|. */ + halfword prev_p = temp_head; + halfword r = null; + vlink(temp_head) = p; + while (p != null) { + switch (type(p)) { + case hlist_node: + case vlist_node: + case rule_node: + /*tex Insert glue for |split_top_skip| and set |p:=null|. */ + q = new_skip_param(split_top_skip_code); + vlink(prev_p) = q; + vlink(q) = p; + if (width(q) > height(p)) + width(q) = width(q) - height(p); + else + width(q) = 0; + p = null; + break; + case boundary_node: + case whatsit_node: + case mark_node: + case ins_node: + prev_p = p; + p = vlink(prev_p); + break; + case glue_node: + case kern_node: + case penalty_node: + q = p; + p = vlink(q); + vlink(q) = null; + vlink(prev_p) = p; + if (s) { + if (split_disc == null) + split_disc = q; + else + vlink(r) = q; + r = q; + } else { + flush_node_list(q); + } + break; + default: + confusion("pruning"); + break; + } + } + return vlink(temp_head); +} + +/*tex + + The next subroutine finds the best place to break a given vertical list so as + to obtain a box of height~|h|, with maximum depth~|d|. A pointer to the + beginning of the vertical list is given, and a pointer to the optimum + breakpoint is returned. The list is effectively followed by a forced break, + i.e., a penalty node with the |eject_penalty|; if the best break occurs at + this artificial node, the value |null| is returned. + +*/ + +/*tex The distance from first active node to |cur_p|: */ + +scaled active_height[10]; + +/*tex + + An array of six |scaled| distances is used to keep track of the height from + the beginning of the list to the current place, just as in |line_break|. In + fact, we use one of the same arrays, only changing its name to reflect its + new significance. + +*/ + +#define do_all_six(A) A(1);A(2);A(3);A(4);A(5);A(6);A(7) +#define set_height_zero(A) active_height[A]=0 /* initialize the height to zero */ + +/*tex + + A global variable |best_height_plus_depth| will be set to the natural size of + the box that corresponds to the optimum breakpoint found by |vert_break|. + (This value is used by the insertion-splitting algorithm of the page + builder.) + +*/ + +/*tex The height of the best box, without stretching or shrinking: */ + +scaled best_height_plus_depth; + +halfword vert_break(halfword p, scaled h, scaled d) +{ + /*tex + If |p| is a glue node, |type(prev_p)| determines whether |p| is a legal + breakpoint, an initial glue node is not a legal breakpoint. + */ + halfword prev_p = p; + /*tex penalty value */ + int pi = 0; + /*tex badness at a trial breakpoint */ + int b; + /*tex |type| of the node following a kern */ + int t; + /*tex the smallest badness plus penalties found so far */ + int least_cost; + /*tex the most recent break that leads to |least_cost| */ + halfword best_place = null; + /*tex depth of previous box in the list */ + scaled prev_dp = 0; + least_cost = awful_bad; + do_all_six(set_height_zero); + while (1) { + /*tex + + If node |p| is a legal breakpoint, check if this break is the best + known, and |goto done| if |p| is null or if the page-so-far is + already too full to accept more stuff. + + A subtle point to be noted here is that the maximum depth~|d| might + be negative, so |cur_height| and |prev_dp| might need to be corrected + even after a glue or kern node. + */ + if (p == null) { + pi = eject_penalty; + } else { + /*tex + + Use node |p| to update the current height and depth measurements; + if this node is not a legal breakpoint, |goto not_found| or + |update_heights|, otherwise set |pi| to the associated penalty at + the break. + + */ + switch (type(p)) { + case hlist_node: + case vlist_node: + case rule_node: + cur_height = cur_height + prev_dp + height(p); + prev_dp = depth(p); + goto NOT_FOUND; + break; + case boundary_node: + case whatsit_node: + goto NOT_FOUND; + break; + case glue_node: + if (precedes_break(prev_p)) + pi = 0; + else + goto UPDATE_HEIGHTS; + break; + case kern_node: + if (vlink(p) == null) + t = penalty_node; + else + t = type(vlink(p)); + if (t == glue_node) + pi = 0; + else + goto UPDATE_HEIGHTS; + break; + case penalty_node: + pi = penalty(p); + break; + case mark_node: + case ins_node: + goto NOT_FOUND; + break; + default: + confusion("vertbreak"); + break; + } + } + /*tex + + Check if node |p| is a new champion breakpoint; then |goto done| if + |p| is a forced break or if the page-so-far is already too full. + + */ + if (pi < inf_penalty) { + /*tex Compute the badness, |b|, using |awful_bad| if the box is too full. */ + if (cur_height < h) { + if ((active_height[3] != 0) || (active_height[4] != 0) || + (active_height[5] != 0) || (active_height[6] != 0)) + b = 0; + else + b = badness(h - cur_height, active_height[2]); + } else if (cur_height - h > active_height[7]) { + b = awful_bad; + } else { + b = badness(cur_height - h, active_height[7]); + } + if (b < awful_bad) { + if (pi <= eject_penalty) + b = pi; + else if (b < inf_bad) + b = b + pi; + else + b = deplorable; + } + if (b <= least_cost) { + best_place = p; + least_cost = b; + best_height_plus_depth = cur_height + prev_dp; + } + if ((b == awful_bad) || (pi <= eject_penalty)) + goto DONE; + } + if ((type(p) < glue_node) || (type(p) > kern_node)) + goto NOT_FOUND; + UPDATE_HEIGHTS: + /*tex + + Update the current height and depth measurements with respect to a + glue or kern node~|p|. Vertical lists that are subject to the + |vert_break| procedure should not contain infinite shrinkability, + since that would permit any amount of information to ``fit'' on one + page. + + */ + if (type(p) != kern_node) { + active_height[2 + stretch_order(p)] += stretch(p); + active_height[7] += shrink(p); + if ((shrink_order(p) != normal) && (shrink(p) != 0)) { + print_err("Infinite glue shrinkage found in box being split"); + help4( + "The box you are \\vsplitting contains some infinitely", + "shrinkable glue, e.g., `\\vss' or `\\vskip 0pt minus 1fil'.", + "Such glue doesn't belong there; but you can safely proceed,", + "since the offensive shrinkability has been made finite." + ); + error(); + shrink_order(p) = normal; + } + } + cur_height = cur_height + prev_dp + width(p); + prev_dp = 0; + NOT_FOUND: + if (prev_dp > d) { + cur_height = cur_height + prev_dp - d; + prev_dp = d; + } + prev_p = p; + p = vlink(prev_p); + } + DONE: + return best_place; +} + +/*tex + + Now we are ready to consider |vsplit| itself. Most of its work is + accomplished by the two subroutines that we have just considered. + + Given the number of a vlist box |n|, and given a desired page height |h|, the + |vsplit| function finds the best initial segment of the vlist and returns a + box for a page of height~|h|. The remainder of the vlist, if any, replaces + the original box, after removing glue and penalties and adjusting for + |split_top_skip|. Mark nodes in the split-off box are used to set the values + of |split_first_mark| and |split_bot_mark|; we use the fact that + |split_first_mark(x)=null| if and only if |split_bot_mark(x)=null|. + + The original box becomes ``void'' if and only if it has been entirely + extracted. The extracted box is ``void'' if and only if the original box was + void (or if it was, erroneously, an hlist box). + +*/ + +/*tex Extract a page of height |h| from box |n|: */ + +halfword vsplit(halfword n, scaled h, int m) +{ + /*tex the box to be split */ + halfword v; + /*tex the direction of the box to be split */ + int vdir; + /*tex runs through the vlist */ + halfword p; + /*tex points to where the break occurs */ + halfword q; + /*tex for traversing marks lists */ + halfword i; + v = box(n); + vdir = box_dir(v); + flush_node_list(split_disc); + split_disc = null; + for (i = 0; i <= biggest_used_mark; i++) { + delete_split_first_mark(i); + delete_split_bot_mark(i); + } + /*tex Dispense with trivial cases of void or bad boxes. */ + if (v == null) { + return null; + } + if (type(v) != vlist_node) { + print_err("\\vsplit needs a \\vbox"); + help2( + "The box you are trying to split is an \\hbox.", + "i can't split such a box, so I''ll leave it alone." + ); + error(); + return null; + } + q = vert_break(list_ptr(v), h, split_max_depth_par); + /*tex + + Look at all the marks in nodes before the break, and set the final link + to |null| at the break. It's possible that the box begins with a penalty + node that is the ``best'' break, so we must be careful to handle this + special case correctly. + + */ + p = list_ptr(v); + if (p == q) { + list_ptr(v) = null; + } else { + while (1) { + if (type(p) == mark_node) { + if (split_first_mark(mark_class(p)) == null) { + set_split_first_mark(mark_class(p), mark_ptr(p)); + set_split_bot_mark(mark_class(p), split_first_mark(mark_class(p))); + set_token_ref_count(split_first_mark(mark_class(p)), + token_ref_count(split_first_mark(mark_class(p))) + 2); + } else { + delete_token_ref(split_bot_mark(mark_class(p))); + set_split_bot_mark(mark_class(p), mark_ptr(p)); + add_token_ref(split_bot_mark(mark_class(p))); + } + } + if (vlink(p) == q) { + vlink(p) = null; + break; + } + p = vlink(p); + } + } + q = prune_page_top(q, saving_vdiscards_par > 0); + p = list_ptr(v); + list_ptr(v) = null; + flush_node(v); + if (q == null) { + /*tex The |eq_level| of the box stays the same. */ + box(n) = null; + } else { + box(n) = filtered_vpackage(q, 0, additional, max_depth_par, split_keep_group, vdir, 0, 0); + } + if (m == exactly) { + return filtered_vpackage(p, h, exactly, split_max_depth_par, split_off_group, vdir, 0, 0); + } else { + return filtered_vpackage(p, 0, additional, max_depth_par, split_off_group, vdir, 0, 0); + } +} + +/*tex + + Now that we can see what eventually happens to boxes, we can consider the + first steps in their creation. The |begin_box| routine is called when + |box_context| is a context specification, |cur_chr| specifies the type of box + desired, and |cur_cmd=make_box|. + +*/ + +void begin_box(int box_context) +{ + /*tex run through the current list */ + halfword q; + /*tex 0 or |vmode| or |hmode| */ + halfword k; + /*tex a box number */ + int n; + int spec_direction = -1; + int just_pack = 0; + int split_mode = exactly ; + switch (cur_chr) { + case box_code: + scan_register_num(); + cur_box = box(cur_val); + /*tex The box becomes void, at the same level. */ + box(cur_val) = null; + break; + case copy_code: + scan_register_num(); + cur_box = copy_node_list(box(cur_val)); + break; + case last_box_code: + /*tex + + If the current list ends with a box node, delete it from the list + and make |cur_box| point to it; otherwise set |cur_box:=null|. + + */ + cur_box = null; + if (abs(cur_list.mode_field) == mmode) { + you_cant(); + help1("Sorry; this \\lastbox will be void."); + error(); + } else if ((cur_list.mode_field == vmode) && (cur_list.head_field == cur_list.tail_field)) { + you_cant(); + help2( + "Sorry...I usually can't take things from the current page.", + "This \\lastbox will therefore be void." + ); + error(); + } else { + if (cur_list.head_field != cur_list.tail_field) { + if ((type(cur_list.tail_field) == hlist_node) || (type(cur_list.tail_field) == vlist_node)) { + /*tex Remove the last box */ + q = alink(cur_list.tail_field); + if (q == null || vlink(q) != cur_list.tail_field) { + q = cur_list.head_field; + while (vlink(q) != cur_list.tail_field) + q = vlink(q); + } + uncouple_node(cur_list.tail_field); + cur_box = cur_list.tail_field; + shift_amount(cur_box) = 0; + cur_list.tail_field = q; + vlink(cur_list.tail_field) = null; + } + } + } + break; + case vsplit_code: + /*tex + + Split off part of a vertical box, make |cur_box| point to it. + Here we deal with things like `\.{\\vsplit 13 to 100pt}'. + + */ + scan_register_num(); + n = cur_val; + if (scan_keyword("upto")) { + split_mode = additional ; + } else if (!scan_keyword("to")) { + print_err("Missing `to' inserted"); + help2( + "I'm working on `\\vsplit<box number> to <dimen>';", + "will look for the <dimen> next." + ); + error(); + } + scan_normal_dimen(); + cur_box = vsplit(n, cur_val, split_mode); + break; + default: + /*tex + + Initiate the construction of an hbox or vbox, then |return|. Here + is where we enter restricted horizontal mode or internal vertical + mode, in order to make a box. + + */ + switch (cur_chr) { + case tpack_code: + cur_chr = vtop_code; + just_pack = 1; + break; + case vpack_code: + cur_chr = vtop_code + vmode; + just_pack = 1; + break; + case hpack_code: + cur_chr = vtop_code + hmode; + just_pack = 1; + break; + } + k = cur_chr - vtop_code; + set_saved_record(0, saved_boxcontext, 0, box_context); + switch (abs(cur_list.mode_field)) { + case vmode: + spec_direction = body_direction_par; + break; + case hmode: + spec_direction = text_direction_par; + break; + case mmode: + spec_direction = math_direction_par; + break; + } + if (k == hmode) { + if ((box_context < box_flag) && (abs(cur_list.mode_field) == vmode)) + scan_full_spec(adjusted_hbox_group, spec_direction,just_pack); + else + scan_full_spec(hbox_group, spec_direction,just_pack); + } else { + if (k == vmode) { + scan_full_spec(vbox_group, spec_direction,just_pack); + } else { + scan_full_spec(vtop_group, spec_direction,just_pack); + k = vmode; + } + normal_paragraph(); + } + push_nest(); + cur_list.mode_field = -k; + if (k == vmode) { + prev_depth_par = ignore_depth; + if (every_vbox_par != null) + begin_token_list(every_vbox_par, every_vbox_text); + } else { + space_factor_par = 1000; + if (every_hbox_par != null) + begin_token_list(every_hbox_par, every_hbox_text); + } + return; + break; + } + /*tex In simple cases, we use the box immediately. */ + box_end(box_context); +} |