diff options
Diffstat (limited to 'Build/source/texk/web2c/luatexdir/tex/buildpage.w')
-rw-r--r-- | Build/source/texk/web2c/luatexdir/tex/buildpage.w | 1057 |
1 files changed, 1057 insertions, 0 deletions
diff --git a/Build/source/texk/web2c/luatexdir/tex/buildpage.w b/Build/source/texk/web2c/luatexdir/tex/buildpage.w new file mode 100644 index 00000000000..de39703c8ae --- /dev/null +++ b/Build/source/texk/web2c/luatexdir/tex/buildpage.w @@ -0,0 +1,1057 @@ +% buildpage.w +% +% 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/>. + +@ @c +static const char _svn_version[] = + "$Id: buildpage.w 3587 2010-04-03 14:32:25Z taco $" + "$URL: http://foundry.supelec.fr/svn/luatex/tags/beta-0.60.0/source/texk/web2c/luatexdir/tex/buildpage.w $"; + +#include "ptexlib.h" + +@ @c +#define box(A) eqtb[box_base+(A)].hh.rh +#define count(A) eqtb[count_base+(A)].hh.rh +#undef skip +#define skip(A) eqtb[skip_base+(A)].hh.rh +#define dimen(A) eqtb[scaled_base+(A)].hh.rh + +#define vbadness int_par(vbadness_code) +#define max_dead_cycles int_par(max_dead_cycles_code) +#define output_box int_par(output_box_code) +#define body_direction int_par(body_direction_code) +#define holding_inserts int_par(holding_inserts_code) + +#define vsize dimen_par(vsize_code) +#define vfuzz dimen_par(vfuzz_code) +#define max_depth dimen_par(max_depth_code) +#define pdf_ignored_dimen dimen_par(pdf_ignored_dimen_code) + +#define output_routine equiv(output_routine_loc) +#define split_top_skip glue_par(split_top_skip_code) + +#define prev_depth cur_list.prev_depth_field +#define mode_line cur_list.ml_field +#define mode cur_list.mode_field +#define tail cur_list.tail_field +#define head cur_list.head_field + +@ When \TeX\ appends new material to its main vlist in vertical mode, it uses +a method something like |vsplit| to decide where a page ends, except that +the calculations are done ``on line'' as new items come in. +The main complication in this process is that insertions must be put +into their boxes and removed from the vlist, in a more-or-less optimum manner. + +We shall use the term ``current page'' for that part of the main vlist that +is being considered as a candidate for being broken off and sent to the +user's output routine. The current page starts at |vlink(page_head)|, and +it ends at |page_tail|. We have |page_head=page_tail| if this list is empty. +@^current page@> + +Utter chaos would reign if the user kept changing page specifications +while a page is being constructed, so the page builder keeps the pertinent +specifications frozen as soon as the page receives its first box or +insertion. The global variable |page_contents| is |empty| when the +current page contains only mark nodes and content-less whatsit nodes; it +is |inserts_only| if the page contains only insertion nodes in addition to +marks and whatsits. Glue nodes, kern nodes, and penalty nodes are +discarded until a box or rule node appears, at which time |page_contents| +changes to |box_there|. As soon as |page_contents| becomes non-|empty|, +the current |vsize| and |max_depth| are squirreled away into |page_goal| +and |page_max_depth|; the latter values will be used until the page has +been forwarded to the user's output routine. The \.{\\topskip} adjustment +is made when |page_contents| changes to |box_there|. + +Although |page_goal| starts out equal to |vsize|, it is decreased by the +scaled natural height-plus-depth of the insertions considered so far, and by +the \.{\\skip} corrections for those insertions. Therefore it represents +the size into which the non-inserted material should fit, assuming that +all insertions in the current page have been made. + +The global variables |best_page_break| and |least_page_cost| correspond +respectively to the local variables |best_place| and |least_cost| in the +|vert_break| routine that we have already studied; i.e., they record the +location and value of the best place currently known for breaking the +current page. The value of |page_goal| at the time of the best break is +stored in |best_size|. + +@c +halfword page_tail; /* the final node on the current page */ +int page_contents; /* what is on the current page so far? */ +scaled page_max_depth; /* maximum box depth on page being built */ +halfword best_page_break; /* break here to get the best page known so far */ +int least_page_cost; /* the score for this currently best page */ +scaled best_size; /* its |page_goal| */ + + +@ The page builder has another data structure to keep track of insertions. +This is a list of four-word nodes, starting and ending at |page_ins_head|. +That is, the first element of the list is node |r@t$_1$@>=vlink(page_ins_head)|; +node $r_j$ is followed by |r@t$_{j+1}$@>=vlink(r@t$_j$@>)|; and if there are +|n| items we have |r@t$_{n+1}$@>=page_ins_head|. The |subtype| field of +each node in this list refers to an insertion number; for example, `\.{\\insert +250}' would correspond to a node whose |subtype| is |qi(250)| +(the same as the |subtype| field of the relevant |ins_node|). These |subtype| +fields are in increasing order, and |subtype(page_ins_head)=65535|, so +|page_ins_head| serves as a convenient sentinel +at the end of the list. A record is present for each insertion number that +appears in the current page. + +The |type| field in these nodes distinguishes two possibilities that +might occur as we look ahead before deciding on the optimum page break. +If |type(r)=inserting_node|, then |height(r)| contains the total of the +height-plus-depth dimensions of the box and all its inserts seen so far. + |type(r)=split_up_node|, then no more insertions will be made into this box, +because at least one previous insertion was too big to fit on the current +page; |broken_ptr(r)| points to the node where that insertion will be +split, if \TeX\ decides to split it, |broken_ins(r)| points to the +insertion node that was tentatively split, and |height(r)| includes also the +natural height plus depth of the part that would be split off. + +In both cases, |last_ins_ptr(r)| points to the last |ins_node| +encountered for box |qo(subtype(r))| that would be at least partially +inserted on the next page; and |best_ins_ptr(r)| points to the last +such |ins_node| that should actually be inserted, to get the page with +minimum badness among all page breaks considered so far. We have +|best_ins_ptr(r)=null| if and only if no insertion for this box should +be made to produce this optimum page. + + + +@ Pages are built by appending nodes to the current list in \TeX's +vertical mode, which is at the outermost level of the semantic nest. This +vlist is split into two parts; the ``current page'' that we have been +talking so much about already, and the ``contribution list'' that receives +new nodes as they are created. The current page contains everything that +the page builder has accounted for in its data structures, as described +above, while the contribution list contains other things that have been +generated by other parts of \TeX\ but have not yet been +seen by the page builder. +The contribution list starts at |vlink(contrib_head)|, and it ends at the +current node in \TeX's vertical mode. + +When \TeX\ has appended new material in vertical mode, it calls the procedure +|build_page|, which tries to catch up by moving nodes from the contribution +list to the current page. This procedure will succeed in its goal of +emptying the contribution list, unless a page break is discovered, i.e., +unless the current page has grown to the point where the optimum next +page break has been determined. In the latter case, the nodes after the +optimum break will go back onto the contribution list, and control will +effectively pass to the user's output routine. + +We make |type(page_head)=glue_node|, so that an initial glue node on +the current page will not be considered a valid breakpoint. + +@c +void initialize_buildpage(void) +{ + subtype(page_ins_head) = 65535; + type(page_ins_head) = split_up_node; + vlink(page_ins_head) = page_ins_head; + + type(page_head) = glue_node; + subtype(page_head) = normal; +} + + +@ An array |page_so_far| records the heights and depths of everything +on the current page. This array contains six |scaled| numbers, like the +similar arrays already considered in |line_break| and |vert_break|; and it +also contains |page_goal| and |page_depth|, since these values are +all accessible to the user via |set_page_dimen| commands. The +value of |page_so_far[1]| is also called |page_total|. The stretch +and shrink components of the \.{\\skip} corrections for each insertion are +included in |page_so_far|, but the natural space components of these +corrections are not, since they have been subtracted from |page_goal|. + +The variable |page_depth| records the depth of the current page; it has been +adjusted so that it is at most |page_max_depth|. The variable +|last_glue| points to the glue specification of the most recent node +contributed from the contribution list, if this was a glue node; otherwise +|last_glue=max_halfword|. (If the contribution list is nonempty, +however, the value of |last_glue| is not necessarily accurate.) +The variables |last_penalty|, |last_kern|, and |last_node_type| +are similar. And +finally, |insert_penalties| holds the sum of the penalties associated with +all split and floating insertions. + +@c +scaled page_so_far[8]; /* height and glue of the current page */ +halfword last_glue; /* used to implement \.{\\lastskip} */ +int last_penalty; /* used to implement \.{\\lastpenalty} */ +scaled last_kern; /* used to implement \.{\\lastkern} */ +int last_node_type; /* used to implement \.{\\lastnodetype} */ +int insert_penalties; /* sum of the penalties for held-over insertions */ + +#define print_plus(A,B) do { \ + if (page_so_far[(A)]!=0) { \ + tprint(" plus "); \ + print_scaled(page_so_far[(A)]); \ + tprint((B)); \ + } \ + } while (0) + +void print_totals(void) +{ + print_scaled(page_total); + print_plus(2, ""); + print_plus(3, "fil"); + print_plus(4, "fill"); + print_plus(5, "filll"); + if (page_shrink != 0) { + tprint(" minus "); + print_scaled(page_shrink); + } +} + + +@ Here is a procedure that is called when the |page_contents| is changing +from |empty| to |inserts_only| or |box_there|. + +@c +#define do_all_six(A) A(1);A(2);A(3);A(4);A(5);A(6);A(7) +#define set_page_so_far_zero(A) page_so_far[(A)]=0 + +void freeze_page_specs(int s) +{ + page_contents = s; + page_goal = vsize; + page_max_depth = max_depth; + page_depth = 0; + do_all_six(set_page_so_far_zero); + least_page_cost = awful_bad; + if (int_par(tracing_pages_code) > 0) { + begin_diagnostic(); + tprint_nl("%% goal height="); + print_scaled(page_goal); + tprint(", max depth="); + print_scaled(page_max_depth); + end_diagnostic(false); + } +} + + +@ The global variable |output_active| is true during the time the +user's output routine is driving \TeX. + +@c +boolean output_active; /* are we in the midst of an output routine? */ + + +@ The page builder is ready to start a fresh page if we initialize +the following state variables. (However, the page insertion list is initialized +elsewhere.) + +@c +void start_new_page(void) +{ + page_contents = empty; + page_tail = page_head; + vlink(page_head) = null; + last_glue = max_halfword; + last_penalty = 0; + last_kern = 0; + last_node_type = -1; + page_depth = 0; + page_max_depth = 0; +} + + +@ At certain times box \.{\\outputbox} is supposed to be void (i.e., |null|), +or an insertion box is supposed to be ready to accept a vertical list. +If not, an error message is printed, and the following subroutine +flushes the unwanted contents, reporting them to the user. + +@c +static void box_error(int n) +{ + error(); + begin_diagnostic(); + tprint_nl("The following box has been deleted:"); + show_box(box(n)); + end_diagnostic(true); + flush_node_list(box(n)); + box(n) = null; +} + + +@ The following procedure guarantees that a given box register +does not contain an \.{\\hbox}. + +@c +static void ensure_vbox(int n) +{ + halfword p; /* the box register contents */ + p = box(n); + if (p != null && type(p) == hlist_node) { + print_err("Insertions can only be added to a vbox"); + help3("Tut tut: You're trying to \\insert into a", + "\\box register that now contains an \\hbox.", + "Proceed, and I'll discard its present contents."); + box_error(n); + } +} + + +@ \TeX\ is not always in vertical mode at the time |build_page| +is called; the current mode reflects what \TeX\ should return to, after +the contribution list has been emptied. A call on |build_page| should +be immediately followed by `|goto big_switch|', which is \TeX's central +control point. + +@c +void build_page(void) +{ /* append contributions to the current page */ + halfword p; /* the node being appended */ + halfword q, r; /* nodes being examined */ + int b, c; /* badness and cost of current page */ + int pi; /* penalty to be added to the badness */ + int n; /* insertion box number */ + scaled delta, h, w; /* sizes used for insertion calculations */ + pi = 0; + if ((vlink(contrib_head) == null) || output_active) + return; + do { + CONTINUE: + p = vlink(contrib_head); + /* Update the values of |last_glue|, |last_penalty|, and |last_kern| */ + if (last_glue != max_halfword) { + delete_glue_ref(last_glue); + last_glue = max_halfword; + } + last_penalty = 0; + last_kern = 0; + last_node_type = type(p) + 1; + if (type(p) == glue_node) { + last_glue = glue_ptr(p); + add_glue_ref(last_glue); + } else if (type(p) == penalty_node) { + last_penalty = penalty(p); + } else if (type(p) == kern_node) { + last_kern = width(p); + } + + /* Move node |p| to the current page; if it is time for a page break, + put the nodes following the break back onto the contribution list, + and |return| to the users output routine if there is one */ + + /* The code here is an example of a many-way switch into routines that + merge together in different places. Some people call this unstructured + programming, but the author doesn't see much wrong with it, as long as + the various labels have a well-understood meaning. + */ + /* If the current page is empty and node |p| is to be deleted, |goto done1|; + otherwise use node |p| to update the state of the current page; + if this node is an insertion, |goto contribute|; otherwise if this node + is not a legal breakpoint, |goto contribute| or |update_heights|; + otherwise set |pi| to the penalty associated with this breakpoint */ + /* The title of this section is already so long, it seems best to avoid + making it more accurate but still longer, by mentioning the fact that a + kern node at the end of the contribution list will not be contributed until + we know its successor. */ + switch (type(p)) { + case hlist_node: + case vlist_node: + case rule_node: + if (page_contents < box_there) { + /* Initialize the current page, insert the \.{\\topskip} glue + ahead of |p|, and |goto continue| */ + if (page_contents == empty) + freeze_page_specs(box_there); + else + page_contents = box_there; + q = new_skip_param(top_skip_code); /* now |temp_ptr=glue_ptr(q)| */ + if ((type(p) == hlist_node) && is_mirrored(body_direction)) { + if (width(temp_ptr) > depth(p)) + width(temp_ptr) = width(temp_ptr) - depth(p); + else + width(temp_ptr) = 0; + } else { + if (width(temp_ptr) > height(p)) + width(temp_ptr) = width(temp_ptr) - height(p); + else + width(temp_ptr) = 0; + } + vlink(q) = p; + vlink(contrib_head) = q; + goto CONTINUE; + + } else { + /* Prepare to move a box or rule node to the current page, + then |goto contribute| */ + if ((type(p) == hlist_node) && is_mirrored(body_direction)) { + page_total = page_total + page_depth + depth(p); + page_depth = height(p); + } else { + page_total = page_total + page_depth + height(p); + page_depth = depth(p); + } + goto CONTRIBUTE; + + } + break; + case whatsit_node: + /* Prepare to move whatsit |p| to the current page, + then |goto contribute| */ + if ((subtype(p) == pdf_refxform_node) + || (subtype(p) == pdf_refximage_node)) { + page_total = page_total + page_depth + height(p); + page_depth = depth(p); + } + goto CONTRIBUTE; + + break; + case glue_node: + if (page_contents < box_there) + goto DONE1; + else if (precedes_break(page_tail)) + pi = 0; + else + goto UPDATE_HEIGHTS; + break; + case kern_node: + if (page_contents < box_there) + goto DONE1; + else if (vlink(p) == null) + goto EXIT; + else if (type(vlink(p)) == glue_node) + pi = 0; + else + goto UPDATE_HEIGHTS; + break; + case penalty_node: + if (page_contents < box_there) + goto DONE1; + else + pi = penalty(p); + break; + case mark_node: + goto CONTRIBUTE; + break; + case ins_node: + /* Append an insertion to the current page and |goto contribute| */ + if (page_contents == empty) + freeze_page_specs(inserts_only); + n = subtype(p); + r = page_ins_head; + while (n >= subtype(vlink(r))) + r = vlink(r); + if (subtype(r) != n) { + /* Create a page insertion node with |subtype(r)=qi(n)|, and + include the glue correction for box |n| in the + current page state */ + /* We take note of the value of \.{\\skip} |n| and the height plus depth + of \.{\\box}~|n| only when the first \.{\\insert}~|n| node is + encountered for a new page. A user who changes the contents of \.{\\box}~|n| + after that first \.{\\insert}~|n| had better be either extremely careful + or extremely lucky, or both. */ + + q = new_node(inserting_node, n); + vlink(q) = vlink(r); + vlink(r) = q; + r = q; + ensure_vbox(n); + if (box(n) == null) + height(r) = 0; + else + height(r) = height(box(n)) + depth(box(n)); + best_ins_ptr(r) = null; + q = skip(n); + if (count(n) == 1000) + h = height(r); + else + h = x_over_n(height(r), 1000) * count(n); + page_goal = page_goal - h - width(q); + if (stretch_order(q) > 1) + page_so_far[1 + stretch_order(q)] = + page_so_far[1 + stretch_order(q)] + stretch(q); + else + page_so_far[2 + stretch_order(q)] = + page_so_far[2 + stretch_order(q)] + stretch(q); + page_shrink = page_shrink + shrink(q); + if ((shrink_order(q) != normal) && (shrink(q) != 0)) { + print_err("Infinite glue shrinkage inserted from \\skip"); + print_int(n); + help3 + ("The correction glue for page breaking with insertions", + "must have finite shrinkability. But you may proceed,", + "since the offensive shrinkability has been made finite."); + error(); + } + + } + if (type(r) == split_up_node) { + insert_penalties = insert_penalties + float_cost(p); + } else { + last_ins_ptr(r) = p; + delta = page_goal - page_total - page_depth + page_shrink; + /* this much room is left if we shrink the maximum */ + if (count(n) == 1000) + h = height(p); + else + h = x_over_n(height(p), 1000) * count(n); /* this much room is needed */ + if (((h <= 0) || (h <= delta)) + && (height(p) + height(r) <= dimen(n))) { + page_goal = page_goal - h; + height(r) = height(r) + height(p); + } else { + /* Find the best way to split the insertion, and change + |type(r)| to |split_up_node| */ + /* Here is the code that will split a long footnote between pages, in an + emergency. The current situation deserves to be recapitulated: Node |p| + is an insertion into box |n|; the insertion will not fit, in its entirety, + either because it would make the total contents of box |n| greater than + \.{\\dimen} |n|, or because it would make the incremental amount of growth + |h| greater than the available space |delta|, or both. (This amount |h| has + been weighted by the insertion scaling factor, i.e., by \.{\\count} |n| + over 1000.) Now we will choose the best way to break the vlist of the + insertion, using the same criteria as in the \.{\\vsplit} operation. + */ + if (count(n) <= 0) { + w = max_dimen; + } else { + w = page_goal - page_total - page_depth; + if (count(n) != 1000) + w = x_over_n(w, count(n)) * 1000; + } + if (w > dimen(n) - height(r)) + w = dimen(n) - height(r); + q = vert_break(ins_ptr(p), w, depth(p)); + height(r) = height(r) + best_height_plus_depth; + if (int_par(tracing_pages_code) > 0) { + /* Display the insertion split cost */ + begin_diagnostic(); + tprint_nl("% split"); + print_int(n); + tprint(" to "); + print_scaled(w); + print_char(','); + print_scaled(best_height_plus_depth); + tprint(" p="); + if (q == null) + print_int(eject_penalty); + else if (type(q) == penalty_node) + print_int(penalty(q)); + else + print_char('0'); + end_diagnostic(false); + + } + if (count(n) != 1000) + best_height_plus_depth = + x_over_n(best_height_plus_depth, 1000) * count(n); + page_goal = page_goal - best_height_plus_depth; + type(r) = split_up_node; + broken_ptr(r) = q; + broken_ins(r) = p; + if (q == null) + insert_penalties = insert_penalties + eject_penalty; + else if (type(q) == penalty_node) + insert_penalties = insert_penalties + penalty(q); + } + } + goto CONTRIBUTE; + + break; + default: + confusion("page"); + break; + } + + /* Check if node |p| is a new champion breakpoint; then if it is time for + a page break, prepare for output, and either fire up the users + output routine and |return| or ship out the page and |goto done| */ + + if (pi < inf_penalty) { + /* Compute the badness, |b|, of the current page, + using |awful_bad| if the box is too full */ + if (page_total < page_goal) { + if ((page_so_far[3] != 0) || (page_so_far[4] != 0) || + (page_so_far[5] != 0)) + b = 0; + else + b = badness(page_goal - page_total, page_so_far[2]); + } else if (page_total - page_goal > page_shrink) { + b = awful_bad; + } else { + b = badness(page_total - page_goal, page_shrink); + } + + if (b < awful_bad) { + if (pi <= eject_penalty) + c = pi; + else if (b < inf_bad) + c = b + pi + insert_penalties; + else + c = deplorable; + } else { + c = b; + } + if (insert_penalties >= 10000) + c = awful_bad; + if (int_par(tracing_pages_code) > 0) { + /* Display the page break cost */ + begin_diagnostic(); + tprint_nl("%"); + tprint(" t="); + print_totals(); + tprint(" g="); + print_scaled(page_goal); + tprint(" b="); + if (b == awful_bad) + print_char('*'); + else + print_int(b); + tprint(" p="); + print_int(pi); + tprint(" c="); + if (c == awful_bad) + print_char('*'); + else + print_int(c); + if (c <= least_page_cost) + print_char('#'); + end_diagnostic(false); + + } + if (c <= least_page_cost) { + best_page_break = p; + best_size = page_goal; + least_page_cost = c; + r = vlink(page_ins_head); + while (r != page_ins_head) { + best_ins_ptr(r) = last_ins_ptr(r); + r = vlink(r); + } + } + if ((c == awful_bad) || (pi <= eject_penalty)) { + fire_up(p); /* output the current page at the best place */ + if (output_active) + goto EXIT; /* user's output routine will act */ + goto DONE; /* the page has been shipped out by default output routine */ + } + } + + if ((type(p) < glue_node) || (type(p) > kern_node)) + goto CONTRIBUTE; + + UPDATE_HEIGHTS: /* go here to record glue in the |active_height| table */ + + /* Update the current page measurements with respect to the + glue or kern specified by node~|p| */ + if (type(p) == kern_node) { + q = p; + } else { + q = glue_ptr(p); + if (stretch_order(q) > 1) + page_so_far[1 + stretch_order(q)] = + page_so_far[1 + stretch_order(q)] + stretch(q); + else + page_so_far[2 + stretch_order(q)] = + page_so_far[2 + stretch_order(q)] + stretch(q); + page_shrink = page_shrink + shrink(q); + if ((shrink_order(q) != normal) && (shrink(q) != 0)) { + print_err("Infinite glue shrinkage found on current page"); + help4("The page about to be output 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(); + r = new_spec(q); + shrink_order(r) = normal; + delete_glue_ref(q); + glue_ptr(p) = r; + q = r; + } + } + page_total = page_total + page_depth + width(q); + page_depth = 0; + + CONTRIBUTE: /* go here to link a node into the current page */ + + /* Make sure that |page_max_depth| is not exceeded */ + if (page_depth > page_max_depth) { + page_total = page_total + page_depth - page_max_depth; + page_depth = page_max_depth; + } + + /* Link node |p| into the current page and |goto done| */ + vlink(page_tail) = p; + page_tail = p; + vlink(contrib_head) = vlink(p); + vlink(p) = null; + goto DONE; + DONE1: + /* Recycle node |p| */ + vlink(contrib_head) = vlink(p); + vlink(p) = null; + if (int_par(saving_vdiscards_code) > 0) { + if (page_disc == null) + page_disc = p; + else + vlink(tail_page_disc) = p; + tail_page_disc = p; + } else { + flush_node_list(p); + } + DONE: + ; + } while (vlink(contrib_head) != null); + /* Make the contribution list empty by setting its tail to |contrib_head| */ + contrib_tail = contrib_head; + EXIT: + ; +} + +@ When the page builder has looked at as much material as could appear before +the next page break, it makes its decision. The break that gave minimum +badness will be used to put a completed ``page'' into box \.{\\outputbox}, with insertions +appended to their other boxes. + +We also set the values of |top_mark|, |first_mark|, and |bot_mark|. The +program uses the fact that |bot_mark(x)<>null| implies |first_mark(x)<>null|; +it also knows that |bot_mark(x)=null| implies |top_mark(x)=first_mark(x)=null|. + +The |fire_up| subroutine prepares to output the current page at the best +place; then it fires up the user's output routine, if there is one, +or it simply ships out the page. There is one parameter, |c|, which represents +the node that was being contributed to the page when the decision to +force an output was made. + +@c +void fire_up(halfword c) +{ + halfword p, q, r, s; /* nodes being examined and/or changed */ + halfword prev_p; /* predecessor of |p| */ + int n; /* insertion box number */ + boolean wait; /* should the present insertion be held over? */ + int save_vbadness; /* saved value of |vbadness| */ + scaled save_vfuzz; /* saved value of |vfuzz| */ + halfword save_split_top_skip; /* saved value of |split_top_skip| */ + halfword i; /* for looping through the marks */ + + /* Set the value of |output_penalty| */ + if (type(best_page_break) == penalty_node) { + geq_word_define(int_base + output_penalty_code, + penalty(best_page_break)); + penalty(best_page_break) = inf_penalty; + } else { + geq_word_define(int_base + output_penalty_code, inf_penalty); + } + + for (i = 0; i <= biggest_used_mark; i++) { + if (bot_mark(i) != null) { + if (top_mark(i) != null) + delete_token_ref(top_mark(i)); + set_top_mark(i, bot_mark(i)); + add_token_ref(top_mark(i)); + delete_first_mark(i); + } + } + /* Put the optimal current page into box |output_box|, update |first_mark| and + |bot_mark|, append insertions to their boxes, and put the + remaining nodes back on the contribution list; */ + + /* As the page is finally being prepared for output, + pointer |p| runs through the vlist, with |prev_p| trailing behind; + pointer |q| is the tail of a list of insertions that + are being held over for a subsequent page. */ + + if (c == best_page_break) + best_page_break = null; /* |c| not yet linked in */ + /* Ensure that box |output_box| is empty before output */ + if (box(output_box) != null) { + print_err("\\box"); + print_int(output_box); + tprint(" is not void"); + help2("You shouldn't use \\box\\outputbox except in \\output routines.", + "Proceed, and I'll discard its present contents."); + box_error(output_box); + } + + insert_penalties = 0; /* this will count the number of insertions held over */ + save_split_top_skip = split_top_skip; + if (holding_inserts <= 0) { + /* Prepare all the boxes involved in insertions to act as queues */ + /* If many insertions are supposed to go into the same box, we want to know + the position of the last node in that box, so that we don't need to waste time + when linking further information into it. The |last_ins_ptr| fields of the + page insertion nodes are therefore used for this purpose during the + packaging phase. */ + + r = vlink(page_ins_head); + while (r != page_ins_head) { + if (best_ins_ptr(r) != null) { + n = subtype(r); + ensure_vbox(n); + if (box(n) == null) + box(n) = new_null_box(); + p = box(n) + list_offset; + while (vlink(p) != null) + p = vlink(p); + last_ins_ptr(r) = p; + } + r = vlink(r); + } + + } + q = hold_head; + vlink(q) = null; + prev_p = page_head; + p = vlink(prev_p); + while (p != best_page_break) { + if (type(p) == ins_node) { + if (holding_inserts <= 0) { + /* Either insert the material specified by node |p| into the + appropriate box, or hold it for the next page; + also delete node |p| from the current page */ + /* We will set |best_ins_ptr:=null| and package the box corresponding to + insertion node~|r|, just after making the final insertion into that box. + If this final insertion is `|split_up_node|', the remainder after splitting + and pruning (if any) will be carried over to the next page. */ + r = vlink(page_ins_head); + while (subtype(r) != subtype(p)) + r = vlink(r); + if (best_ins_ptr(r) == null) { + wait = true; + } else { + wait = false; + s = last_ins_ptr(r); + vlink(s) = ins_ptr(p); + if (best_ins_ptr(r) == p) { + /* Wrap up the box specified by node |r|, splitting node |p| if + called for; set |wait:=true| if node |p| holds a remainder after + splitting */ + if (type(r) == split_up_node) { + if ((broken_ins(r) == p) && (broken_ptr(r) != null)) { + while (vlink(s) != broken_ptr(r)) + s = vlink(s); + vlink(s) = null; + split_top_skip = split_top_ptr(p); + ins_ptr(p) = + prune_page_top(broken_ptr(r), false); + if (ins_ptr(p) != null) { + temp_ptr = + vpack(ins_ptr(p), 0, additional, -1); + height(p) = + height(temp_ptr) + depth(temp_ptr); + list_ptr(temp_ptr) = null; + flush_node(temp_ptr); + wait = true; + } + } + } + best_ins_ptr(r) = null; + n = subtype(r); + temp_ptr = list_ptr(box(n)); + list_ptr(box(n)) = null; + flush_node(box(n)); + box(n) = vpack(temp_ptr, 0, additional, body_direction); + + } else { + while (vlink(s) != null) + s = vlink(s); + last_ins_ptr(r) = s; + } + } + /* Either append the insertion node |p| after node |q|, and remove it + from the current page, or delete |node(p)| */ + vlink(prev_p) = vlink(p); + vlink(p) = null; + if (wait) { + vlink(q) = p; + q = p; + incr(insert_penalties); + } else { + ins_ptr(p) = null; + flush_node(p); + } + p = prev_p; + + } + } else if (type(p) == mark_node) { + /* Update the values of |first_mark| and |bot_mark| */ + if (first_mark(mark_class(p)) == null) { + set_first_mark(mark_class(p), mark_ptr(p)); + add_token_ref(first_mark(mark_class(p))); + } + if (bot_mark(mark_class(p)) != null) + delete_token_ref(bot_mark(mark_class(p))); + set_bot_mark(mark_class(p), mark_ptr(p)); + add_token_ref(bot_mark(mark_class(p))); + + } + prev_p = p; + p = vlink(prev_p); + } + split_top_skip = save_split_top_skip; + /* Break the current page at node |p|, put it in box~|output_box|, + and put the remaining nodes on the contribution list */ + /* When the following code is executed, the current page runs from node + |vlink(page_head)| to node |prev_p|, and the nodes from |p| to |page_tail| + are to be placed back at the front of the contribution list. Furthermore + the heldover insertions appear in a list from |vlink(hold_head)| to |q|; we + will put them into the current page list for safekeeping while the user's + output routine is active. We might have |q=hold_head|; and |p=null| if + and only if |prev_p=page_tail|. Error messages are suppressed within + |vpackage|, since the box might appear to be overfull or underfull simply + because the stretch and shrink from the \.{\\skip} registers for inserts + are not actually present in the box. */ + + if (p != null) { + if (vlink(contrib_head) == null) { + contrib_tail = page_tail; + } + vlink(page_tail) = vlink(contrib_head); + vlink(contrib_head) = p; + vlink(prev_p) = null; + } + save_vbadness = vbadness; + vbadness = inf_bad; + save_vfuzz = vfuzz; + vfuzz = max_dimen; /* inhibit error messages */ + box(output_box) = + filtered_vpackage(vlink(page_head), best_size, exactly, page_max_depth, + output_group, body_direction); + vbadness = save_vbadness; + vfuzz = save_vfuzz; + if (last_glue != max_halfword) + delete_glue_ref(last_glue); + /* Start a new current page */ + start_new_page(); /* this sets |last_glue:=max_halfword| */ + if (q != hold_head) { + vlink(page_head) = vlink(hold_head); + page_tail = q; + } + + /* Delete the page-insertion nodes */ + r = vlink(page_ins_head); + while (r != page_ins_head) { + q = vlink(r); + flush_node(r); + r = q; + } + vlink(page_ins_head) = page_ins_head; + + for (i = 0; i <= biggest_used_mark; i++) { + if ((top_mark(i) != null) && (first_mark(i) == null)) { + set_first_mark(i, top_mark(i)); + add_token_ref(top_mark(i)); + } + } + if (output_routine != null) { + if (dead_cycles >= max_dead_cycles) { + /* Explain that too many dead cycles have occurred in a row */ + print_err("Output loop---"); + print_int(dead_cycles); + tprint(" consecutive dead cycles"); + help3("I've concluded that your \\output is awry; it never does a", + "\\shipout, so I'm shipping \\box\\outputbox out myself. Next time", + "increase \\maxdeadcycles if you want me to be more patient!"); + error(); + + } else { + /* Fire up the users output routine and |return| */ + output_active = true; + incr(dead_cycles); + push_nest(); + mode = -vmode; + prev_depth = pdf_ignored_dimen; + mode_line = -line; + begin_token_list(output_routine, output_text); + new_save_level(output_group); + normal_paragraph(); + scan_left_brace(); + return; + + } + } + /* Perform the default output routine */ + /* The list of heldover insertions, running from |vlink(page_head)| to + |page_tail|, must be moved to the contribution list when the user has + specified no output routine. */ + if (vlink(page_head) != null) { + if (vlink(contrib_head) == null) { + contrib_tail = page_tail; + } else { + vlink(page_tail) = vlink(contrib_head); + } + vlink(contrib_head) = vlink(page_head); + vlink(page_head) = null; + page_tail = page_head; + } + flush_node_list(page_disc); + page_disc = null; + ship_out(static_pdf, box(output_box), true); + box(output_box) = null; +} + + +@ When the user's output routine finishes, it has constructed a vlist +in internal vertical mode, and \TeX\ will do the following: + +@c +void resume_after_output(void) +{ + if ((iloc != null) + || ((token_type != output_text) && (token_type != backed_up))) { + /* Recover from an unbalanced output routine */ + print_err("Unbalanced output routine"); + help2("Your sneaky output routine has problematic {'s and/or }'s.", + "I can't handle that very well; good luck."); + error(); + do { + get_token(); + } while (iloc != null); + /* loops forever if reading from a file, since |null=min_halfword<=0| */ + + } + end_token_list(); /* conserve stack space in case more outputs are triggered */ + end_graf(bottom_level); + unsave(); + output_active = false; + insert_penalties = 0; + /* Ensure that box |output_box| is empty after output */ + if (box(output_box) != null) { + print_err("Output routine didn't use all of \\box"); + print_int(output_box); + help3("Your \\output commands should empty \\box\\outputbox,", + "e.g., by saying `\\shipout\\box\\outputbox'.", + "Proceed; I'll discard its present contents."); + box_error(output_box); + } + + if (tail != head) { /* current list goes after heldover insertions */ + vlink(page_tail) = vlink(head); + page_tail = tail; + } + if (vlink(page_head) != null) { /* and both go before heldover contributions */ + if (vlink(contrib_head) == null) + contrib_tail = page_tail; + vlink(page_tail) = vlink(contrib_head); + vlink(contrib_head) = vlink(page_head); + vlink(page_head) = null; + page_tail = page_head; + } + flush_node_list(page_disc); + page_disc = null; + pop_nest(); + lua_node_filter_s(buildpage_filter_callback, "after_output"); + build_page(); + +} |