texbuildpage.c /size: 73 Kb    last modification: 2025-02-21 11:03
1/*
2    See license.txt in the root of this project.
3*/
4
5# include "luametatex.h"
6
7/*tex
8
9    When \TEX\ appends new material to its main vlist in vertical mode, it uses a method something
10    like |vsplit| to decide where a page ends, except that the calculations are done \quote {on
11    line} as new items come in. The main complication in this process is that insertions must be
12    put into their boxes and removed from the vlist, in a more-or-less optimum manner.
13
14    We shall use the term \quote {current page} for that part of the main vlist that is being
15    considered as a candidate for being broken off and sent to the user's output routine. The
16    current page starts at |node_next(page_head)|, and it ends at |page_tail|. We have |page_head =
17    page_tail| if this list is empty.
18
19    Utter chaos would reign if the user kept changing page specifications while a page is being
20    constructed, so the page builder keeps the pertinent specifications frozen as soon as the page
21    receives its first box or insertion. The global variable |page_contents| is |empty| when the
22    current page contains only mark nodes and content-less whatsit nodes; it is |inserts_only|
23    if the page contains only insertion nodes in addition to marks and whatsits. Glue nodes, kern
24    nodes, and penalty nodes are discarded until a box or rule node appears, at which time
25    |page_contents| changes to |box_there|. As soon as |page_contents| becomes non-|empty|, the
26    current |vsize| and |max_depth| are squirreled away into |page_goal| and |page_max_depth|; the
27    latter values will be used until the page has been forwarded to the user's output routine. The
28    |\topskip| adjustment is made when |page_contents| changes to |box_there|.
29
30    Although |page_goal| starts out equal to |vsize|, it is decreased by the scaled natural
31    height-plus-depth of the insertions considered so far, and by the |\skip| corrections for
32    those insertions. Therefore it represents the size into which the non-inserted material
33    should fit, assuming that all insertions in the current page have been made.
34
35    The global variables |best_page_break| and |least_page_cost| correspond respectively to the
36    local variables |best_place| and |least_cost| in the |vert_break| routine that we have already
37    studied; i.e., they record the location and value of the best place currently known for
38    breaking the current page. The value of |page_goal| at the time of the best break is stored in
39    |best_size|.
40
41*/
42
43page_builder_state_info lmt_page_builder_state = {
44    .page_tail        = null,
45    .contents         = 0,
46    .max_depth        = 0,
47    .best_break       = null,
48    .least_cost       = 0,
49    .best_size        = 0,
50    .goal             = 0,
51    .vsize            = 0,
52    .total            = 0,
53    .depth            = 0,
54    .excess           = 0,
55    .page_so_far      = { 0 },
56    .insert_penalties = 0,
57    .insert_heights   = 0,
58    .last_glue        = max_halfword,
59    .last_penalty     = 0,
60    .last_kern        = 0,
61    .last_node_type   = unknown_node_type,
62    .last_node_subtype= unknown_node_subtype,
63    .last_extra_used  = 0,
64    .last_boundary    = 0,
65    .output_active    = 0,
66    .dead_cycles      = 0,
67    .current_state    = 0 
68};
69
70/*tex
71    Because we have an extra order (fi) we need to intercept it in the case of the page builder 
72    although we could decide to support it here too. 
73*/
74
75# define page_stretched(order) lmt_page_builder_state.page_so_far[page_stretch_state+order] 
76
77static void tex_aux_fire_up (halfword c);
78
79/*tex
80
81    The page builder has another data structure to keep track of insertions. This is a list of
82    four-word nodes, starting and ending at |page_insert_head|. That is, the first element of the
83    list is node |t$_1$ = node_next(page_insert_head)|; node $r_j$ is followed by |t$_{j+1}$ =
84    node_next(t$_j$)|; and if there are |n| items we have |$_{n+1}$ >= page_insert_head|. The
85    |subtype| field of each node in this list refers to an insertion number; for example, |\insert
86    250| would correspond to a node whose |subtype| is |qi(250)| (the same as the |subtype| field
87    of the relevant |insert_node|). These |subtype| fields are in increasing order, and |subtype
88    (page_insert_head) = 65535|, so |page_insert_head| serves as a convenient sentinel at the end
89    of the list. A record is present for each insertion number that appears in the current page.
90
91    The |type| field in these nodes distinguishes two possibilities that might occur as we look
92    ahead before deciding on the optimum page break. If |type(r) = inserting_node|, then |height(r)|
93    contains the total of the height-plus-depth dimensions of the box and all its inserts seen so
94    far. If |type(r) = split_up_node|, then no more insertions will be made into this box, because at
95    least one previous insertion was too big to fit on the current page; |broken_ptr(r)| points to
96    the node where that insertion will be split, if \TEX\ decides to split it, |broken_insert(r)|
97    points to the insertion node that was tentatively split, and |height(r)| includes also the
98    natural height plus depth of the part that would be split off.
99
100    In both cases, |last_insert(r)| points to the last |insert_node| encountered for box
101    |qo(subtype(r))| that would be at least partially inserted on the next page; and
102    |best_insert(r)| points to the last such |insert_node| that should actually be inserted, to get
103    the page with minimum badness among all page breaks considered so far. We have |best_insert
104    (r) = null| if and only if no insertion for this box should be made to produce this optimum page.
105
106    Pages are built by appending nodes to the current list in \TEX's vertical mode, which is at the
107    outermost level of the semantic nest. This vlist is split into two parts; the \quote {current
108    page} that we have been talking so much about already, and the |quote {contribution list} that
109    receives new nodes as they are created. The current page contains everything that the page
110    builder has accounted for in its data structures, as described above, while the contribution
111    list contains other things that have been generated by other parts of \TEX\ but have not yet
112    been seen by the page builder. The contribution list starts at |vlink (contribute_head)|, and it
113    ends at the current node in \TEX's vertical mode.
114
115    When \TEX\ has appended new material in vertical mode, it calls the procedure |build_page|,
116    which tries to catch up by moving nodes from the contribution list to the current page. This
117    procedure will succeed in its goal of emptying the contribution list, unless a page break is
118    discovered, i.e., unless the current page has grown to the point where the optimum next page
119    break has been determined. In the latter case, the nodes after the optimum break will go back
120    onto the contribution list, and control will effectively pass to the user's output routine.
121
122    We make |type (page_head) = glue_node|, so that an initial glue node on the current page will
123    not be considered a valid breakpoint. We keep this old tex trickery of cheating with node types
124    but have to make sure that the size is valid to do so (and we have different sizes!).
125
126*/
127
128void tex_initialize_pagestate(void)
129{
130    lmt_page_builder_state.page_tail = page_head;
131    lmt_page_builder_state.contents = contribute_nothing;
132    lmt_page_builder_state.max_depth = 0;
133    lmt_page_builder_state.best_break = null;
134    lmt_page_builder_state.least_cost = 0;
135    lmt_page_builder_state.best_size = 0;
136    lmt_page_builder_state.goal = 0;
137    lmt_page_builder_state.vsize = 0;
138    lmt_page_builder_state.total = 0;
139    lmt_page_builder_state.depth = 0;
140    for (int i = page_initial_state; i <= page_shrink_state; i++) { 
141        lmt_page_builder_state.page_so_far[i] = 0;
142    } 
143    lmt_page_builder_state.insert_penalties = 0;
144    lmt_page_builder_state.insert_heights = 0;
145    lmt_page_builder_state.last_glue = max_halfword;
146    lmt_page_builder_state.last_penalty = 0;
147    lmt_page_builder_state.last_kern = 0;
148    lmt_page_builder_state.last_extra_used = 0;
149    lmt_page_builder_state.last_boundary = 0;
150    lmt_page_builder_state.last_node_type = unknown_node_type;
151    lmt_page_builder_state.last_node_subtype = unknown_node_subtype;
152    lmt_page_builder_state.output_active = 0;
153    lmt_page_builder_state.dead_cycles = 0;
154    lmt_page_builder_state.current_state = 0;
155}
156
157void tex_initialize_buildpage(void)
158{
159    node_type(page_insert_head) = split_node;
160    node_subtype(page_insert_head) = insert_split_subtype;
161    insert_index(page_insert_head) = 65535;          /*tex some signal */
162    node_next(page_insert_head) = page_insert_head;
163    node_type(page_head) = glue_node;                /*tex brr, a temp node has a different size than a glue node */
164    node_subtype(page_head) = page_glue;             /*tex basically: unset */
165}
166
167/*tex
168
169    An array |page_so_far| records the heights and depths of everything on the current page. This
170    array contains six |scaled| numbers, like the similar arrays already considered in |line_break|
171    and |vert_break|; and it also contains |page_goal| and |page_depth|, since these values are all
172    accessible to the user via |set_page_dimension| commands. The value of |page_so_far[1]| is also
173    called |page_total|. The stretch and shrink components of the |\skip| corrections for each
174    insertion are included in |page_so_far|, but the natural space components of these corrections
175    are not, since they have been subtracted from |page_goal|.
176
177    The variable |page_depth| records the depth of the current page; it has been adjusted so that it
178    is at most |page_max_depth|. The variable |last_glue| points to the glue specification of the
179    most recent node contributed from the contribution list, if this was a glue node; otherwise
180    |last_glue = max_halfword|. (If the contribution list is nonempty, however, the value of
181    |last_glue| is not necessarily accurate.) The variables |last_penalty|, |last_kern|, and
182    |last_node_type| are similar. And finally, |insert_penalties| holds the sum of the penalties
183    associated with all split and floating insertions.
184
185    Here is a procedure that is called when the |page_contents| is changing from |empty| to
186    |inserts_only| or |box_there|.
187
188*/
189
190void tex_additional_page_skip(void)
191{
192    if (page_total > 0 && additional_page_skip_par) {
193        page_stretch += glue_stretch(additional_page_skip_par);
194        page_shrink += glue_shrink(additional_page_skip_par);
195        page_total += glue_amount(additional_page_skip_par);
196        update_tex_additional_page_skip(null);
197    }
198}
199
200static void tex_aux_save_best_page_specs(void)
201{
202    page_last_depth = page_depth;
203    page_last_height = page_total;
204    page_last_stretch = page_stretch;
205    page_last_fistretch = page_fistretch;
206    page_last_filstretch = page_filstretch;  
207    page_last_fillstretch = page_fillstretch; 
208    page_last_filllstretch = page_filllstretch;
209    page_last_shrink = page_shrink;
210}
211
212static void tex_aux_freeze_page_specs(int s)
213{
214    lmt_page_builder_state.contents = s;
215    lmt_page_builder_state.max_depth = max_depth_par;
216    lmt_page_builder_state.least_cost = awful_bad;
217 /* page_builder_state.insert_heights = 0; */ /* up to the user */
218    for (int i = page_initial_state; i <= page_shrink_state; i++) { 
219        lmt_page_builder_state.page_so_far[i] = 0;
220        lmt_page_builder_state.page_last_so_far[i] = 0;
221    } 
222    page_goal = vsize_par;
223    page_vsize = vsize_par;
224    page_depth = 0;
225    page_total = 0;
226    page_excess = 0;
227    page_except = 0;
228    page_last_depth = 0;
229    page_last_height = 0;
230    if (initial_page_skip_par) {
231        page_stretch = glue_stretch(initial_page_skip_par);
232        page_shrink = glue_shrink(initial_page_skip_par);
233        page_total = glue_amount(initial_page_skip_par);
234    }
235    if (additional_page_skip_par) {
236        page_stretch += glue_stretch(additional_page_skip_par);
237        page_shrink += glue_shrink(additional_page_skip_par);
238        page_total += glue_amount(additional_page_skip_par);
239        update_tex_additional_page_skip(null);
240    }
241    if (tracing_pages_par > 0) {
242        tex_begin_diagnostic();
243        tex_print_format(
244            "[page: frozen state, goal %p, maxdepth %p, contribution %s, insertheights %p]",
245            page_goal,
246            lmt_page_builder_state.max_depth,
247            lmt_interface.page_contribute_values[s].name,
248            lmt_page_builder_state.insert_heights
249        );
250        tex_end_diagnostic();
251    }
252}
253
254static void update_page_goal(halfword index, scaled total, scaled delta)
255{
256    page_goal -= delta;
257    lmt_page_builder_state.insert_heights += total;
258    if (lmt_page_builder_state.insert_heights > max_dimension) {
259        lmt_page_builder_state.insert_heights = max_dimension;
260    }
261    if (tracing_inserts_par > 0) {
262        tex_begin_diagnostic();
263        tex_print_format(
264            "[page: update page goal for insert, index %i, total %p, insertheights %p, vsize %p, delta %p, goal %p]",
265            index, total, lmt_page_builder_state.insert_heights, page_vsize, delta, page_goal
266        );
267        tex_end_diagnostic();
268    }
269}
270
271/*tex
272
273    The global variable |output_active| is true during the time the user's output routine is
274    driving \TEX. The page builder is ready to start a fresh page if we initialize the following
275    state variables. (However, the page insertion list is initialized elsewhere.)
276
277*/
278
279static void tex_aux_start_new_page(void)
280{
281    lmt_page_builder_state.contents = contribute_nothing;
282    lmt_page_builder_state.page_tail = page_head;
283    node_next(page_head) = null;
284    lmt_page_builder_state.last_glue = max_halfword;
285    lmt_page_builder_state.last_penalty = 0;
286    lmt_page_builder_state.last_kern = 0;
287    lmt_page_builder_state.last_boundary = 0;
288    lmt_page_builder_state.last_node_type = unknown_node_type;
289    lmt_page_builder_state.last_node_subtype = unknown_node_subtype;
290    page_depth = 0;
291    lmt_page_builder_state.max_depth = 0;
292}
293
294/*tex
295
296    At certain times box |\outputbox| is supposed to be void (i.e., |null|), or an insertion box is
297    supposed to be ready to accept a vertical list. If not, an error message is printed, and the
298    following subroutine flushes the unwanted contents, reporting them to the user.
299
300*/
301
302static halfword tex_aux_delete_box_content(int n, const char *what, int where)
303{
304    if (tracing_pages_par > 0) {
305        tex_begin_diagnostic();
306        tex_print_format("[page: deleting %s box, case %i]", what, where);
307        tex_show_box(n);
308        tex_end_diagnostic();
309    }
310    tex_flush_node_list(n);
311    return null;
312}
313
314/*tex
315
316    The following procedure guarantees that an insert box is not an |\hbox|. A user can actually
317    mess with this box, unless we decide to come up with a dedicated data structure for it.
318
319*/
320
321static int tex_aux_valid_insert_content(halfword content)
322{
323    if (content && node_type(content) == hlist_node) {
324        /*tex It's not always a box so we need to adapt this message some day. */
325        tex_handle_error(
326            normal_error_type,
327            "Insertions can only be added to a vbox",
328            "Tut tut: You're trying to \\insert into a \\box register that now contains an\n"
329            "\\hbox. Proceed, and I'll discard its present contents."
330        );
331        return 0;
332    } else {
333        return 1;
334    }
335}
336
337/*tex
338
339    \TEX\ is not always in vertical mode at the time |build_page| is called; the current mode
340    reflects what \TEX\ should return to, after the contribution list has been emptied. A call on
341    |build_page| should be immediately followed by |goto big_switch|, which is \TEX's central
342    control point.
343
344    Append contributions to the current page.
345
346*/
347
348static void tex_aux_display_page_break_cost(halfword badness, halfword penalty, halfword cost, int moveon, int fireup)
349{
350    tex_begin_diagnostic();
351    tex_print_format("[page: break, total %P, goal %p, badness %B, penalty %i, cost %B%s, moveon %s, fireup %s]",
352        page_total, page_stretch, page_fistretch, page_filstretch, page_fillstretch, page_filllstretch, page_shrink,
353        page_goal, badness, penalty, cost, cost < lmt_page_builder_state.least_cost ? "#" : "",
354        moveon ? "yes" : "no", fireup ? "yes" : "no"
355    );
356    tex_end_diagnostic();
357}
358
359static void tex_aux_display_insertion_split_cost(halfword index, scaled height, halfword penalty)
360{
361    /*tex Display the insertion split cost. */
362    tex_begin_diagnostic();
363    tex_print_format("[page: split insert %i: height %p, depth %p, penalty %i]",
364        index, height, lmt_packaging_state.best_height_plus_depth, penalty
365    );
366    tex_end_diagnostic();
367}
368
369static halfword tex_aux_page_badness(scaled goal)
370{
371    if (page_total + page_except < goal) {
372        if (page_fistretch || page_filstretch || page_fillstretch || page_filllstretch) {
373            return 0;
374        } else {
375            return tex_badness(goal - page_total, page_stretch);
376        }
377    } else if (page_total + page_except - goal > page_shrink) {
378        return awful_bad;
379    } else {
380        return tex_badness(page_total + page_except - goal, page_shrink);
381    }
382}
383
384static inline halfword tex_aux_page_costs(halfword badness, halfword penalty)
385{
386    if (lmt_page_builder_state.insert_penalties >= infinite_penalty) {
387        return awful_bad;
388    } else if (badness >= awful_bad) {
389        return badness; /* trigger fireup */
390    } else if (penalty <= eject_penalty) {
391        return penalty; /* trigger fireup */
392    } else if (badness < infinite_bad) {
393        return badness + penalty + lmt_page_builder_state.insert_penalties;
394    } else {
395        return deplorable;
396    }
397}
398
399static halfword tex_aux_insert_topskip(halfword height, int contribution)
400{
401    if (lmt_page_builder_state.contents != contribute_nothing) {
402        lmt_page_builder_state.contents = contribution;
403    } else {
404        tex_aux_freeze_page_specs(contribution);
405    }
406    {
407        halfword glue = tex_new_param_glue_node(tex_glue_is_zero(initial_top_skip_par) ? top_skip_code : initial_top_skip_code, top_skip_glue);
408        if (glue_amount(glue) > height) {
409            glue_amount(glue) -= height;
410        } else {
411            glue_amount(glue) = 0;
412        }
413        return glue;
414    }
415}
416
417/*tex
418    Append an insertion to the current page and |goto contribute|. The insertion number (index) is 
419    registered in the subtype (not any more for a while).
420*/
421
422static void tex_aux_append_insert(halfword current)
423{
424    halfword index = insert_index(current); /* initially 65K */
425    halfword location = page_insert_head;
426    halfword multiplier = tex_get_insert_multiplier(index);
427    halfword content = tex_get_insert_content(index);
428    scaled limit = tex_get_insert_limit(index);
429    int slot = 1;
430    if (lmt_page_builder_state.contents == contribute_nothing) {
431        tex_aux_freeze_page_specs(contribute_insert);
432    }
433    while (index >= insert_index(node_next(location))) {
434        location = node_next(location);
435        slot += 1 ;
436    }
437    if (insert_index(location) != index) {
438        /*tex
439
440            Create a page insertion node with |subtype(r) = qi(n)|, and include the glue correction 
441            for box |n| in the current page state. We take note of the value of |\skip| |n| and the
442            height plus depth of |\box| |n| only when the first |\insert n| node is encountered for 
443            a new page. A user who changes the contents of |\box| |n| after that first |\insert n| 
444            had better be either extremely careful or extremely lucky, or both.
445
446            We need to handle this too:
447
448            [content]
449            [max(space shared,space n)]
450            [class n]
451            .........
452            [space m]
453            [class m]
454
455            For now a callback can deal with this but maybe we need to have a more advanced 
456            mechanism for this (and more control over inserts in general).
457
458        */
459        halfword splitnode = tex_new_node(split_node, normal_split_subtype);
460        scaled advance = 0;
461        halfword distance = lmt_get_insert_distance(index, slot); /*tex Callback: we get a copy! */
462        split_insert_index(splitnode) = index;
463        tex_try_couple_nodes(splitnode, node_next(location));
464        tex_couple_nodes(location, splitnode);
465        location = splitnode;
466        if (! tex_aux_valid_insert_content(content)) {
467            content = tex_aux_delete_box_content(content, "insert", 1);
468            tex_set_insert_content(index, content);
469        };
470        if (content) {
471            box_height(location) = box_total(content);
472        } else {
473            box_height(location) = 0;
474        }
475        split_best_insert(location) = null;
476        if (multiplier == scaling_factor) {
477            advance = box_height(location);
478        } else {
479            advance = tex_x_over_n(box_height(location), scaling_factor) * multiplier;
480        }
481        advance += glue_amount(distance);
482        update_page_goal(index, 0, advance); /*tex Here gets no height added! */
483        page_stretched(glue_stretch_order(distance)) += glue_stretch(distance);
484        page_shrink += glue_shrink(distance);
485        if (glue_shrink_order(distance) != normal_glue_order && glue_shrink(distance)) {
486            tex_handle_error(
487                normal_error_type,
488                "Infinite glue shrinkage inserted from \\skip%i",
489                index,
490                "The correction glue for page breaking with insertions must have finite\n"
491                "shrinkability. But you may proceed, since the offensive shrinkability has been\n"
492                "made finite."
493            );
494        }
495        tex_flush_node(distance);
496    }
497    /*tex I really need to check this logic with the original \LUATEX\ code. */
498    if (node_type(location) == split_node && node_subtype(location) == insert_split_subtype) {
499        lmt_page_builder_state.insert_penalties += insert_float_cost(current);
500    } else {
501        scaled delta = page_goal - page_total - page_depth + page_shrink;
502        scaled needed = insert_total_height(current);
503        split_last_insert(location) = current;
504        /*tex This much room is left if we shrink the maximum. */
505        if (multiplier != scaling_factor) {
506            /*tex This much room is needed. */
507            needed = tex_x_over_n(needed, scaling_factor) * multiplier;
508        }
509        if ((needed <= 0 || needed <= delta) && (insert_total_height(current) + box_height(location) <= limit)) {
510            update_page_goal(index, insert_total_height(current), needed);
511            box_height(location) += insert_total_height(current);
512        } else {
513            /*tex
514
515                Find the best way to split the insertion, and change |subtype(r)| to 
516                |split_up_inserting_code|.
517
518                Here is the code that will split a long footnote between pages, in an emergency. 
519                The current situation deserves to be recapitulated: Node |p| is an insertion 
520                into box |n|; the insertion will not fit, in its entirety, either because it
521                would make the total contents of box |n| greater than |\dimen| |n|, or because 
522                it would make the incremental amount of growth |h| greater than the available 
523                space |delta|, or both. (This amount |h| has been weighted by the insertion 
524                scaling factor, i.e., by |\count| |n| over 1000.) Now we will choose the best
525                way to break the vlist of the insertion, using the same criteria as in the 
526                |\vsplit| operation.
527
528            */
529            scaled height;
530            halfword breaknode, penalty;
531            if (multiplier <= 0) {
532                height = max_dimension;
533            } else {
534                height = page_goal - page_total - page_depth;
535                if (multiplier != scaling_factor) {
536                    height = tex_x_over_n(height, multiplier) * scaling_factor;
537                }
538            }
539            if (height > limit - box_height(location)) {
540                height = limit - box_height(location);
541            }
542            breaknode = tex_vert_break(insert_list(current), height, insert_max_depth(current), 0, 0);
543            box_height(location) += lmt_packaging_state.best_height_plus_depth;
544            penalty = breaknode ? (node_type(breaknode) == penalty_node ? penalty_amount(breaknode) : 0) : eject_penalty;
545            if (tracing_pages_par > 0) {
546                tex_aux_display_insertion_split_cost(index, height, penalty);
547            }
548            if (multiplier != scaling_factor) {
549                lmt_packaging_state.best_height_plus_depth = tex_x_over_n(lmt_packaging_state.best_height_plus_depth, scaling_factor) * multiplier;
550            }
551            update_page_goal(index, lmt_packaging_state.best_height_plus_depth, lmt_packaging_state.best_height_plus_depth);
552            node_subtype(location) = insert_split_subtype;
553            split_broken(location) = breaknode;
554            split_broken_insert(location) = current;
555            lmt_page_builder_state.insert_penalties += penalty;
556        }
557    }
558}
559
560static inline int tex_aux_get_penalty_option(halfword current) 
561{
562    while (1) { 
563        current = node_prev(current);
564        if (current && current != contribute_head) {
565            switch (node_type(current)) {
566                case glue_node: 
567                    break;
568                case penalty_node: 
569                    if (penalty_amount(current) >= infinite_penalty) {
570                        if (tex_has_penalty_option(current, penalty_option_widowed)) {
571                            if (tracing_pages_par > 1) {
572                                tex_begin_diagnostic();
573                                tex_print_format("[page: widowed]");
574                                tex_end_diagnostic();
575                            }
576                            return penalty_option_widowed;
577                        } else if (tex_has_penalty_option(current, penalty_option_clubbed)) {
578                            if (tracing_pages_par > 1) {
579                                tex_begin_diagnostic();
580                                tex_print_format("[page: clubbed]");
581                                tex_end_diagnostic();
582                            }
583                            return penalty_option_clubbed;
584                        }            
585                    }
586                    return 0;
587                default:
588                    return 0;
589            }
590        } else { 
591            return 0;
592        }
593    }
594 // return 0;
595}
596
597static inline int tex_aux_get_last_penalty(halfword current) 
598{
599    return node_type(current) == penalty_node 
600        ? penalty_options(current) & (penalty_option_widow | penalty_option_club | penalty_option_broken | penalty_option_shaping)
601        : 0;
602}
603
604static void tex_aux_initialize_show_build_node(int callback_id)
605{
606    lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "d->", initialize_show_build_context);
607}
608
609static void tex_aux_step_show_build_node(int callback_id, halfword current)
610{
611    lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "dNdd->", step_show_build_context, 
612        current,
613        page_goal,
614        page_total
615    );
616}
617
618static void tex_aux_check_show_build_node(int callback_id, halfword current, int badness, int costs, int penalty, int *moveon, int *fireup)
619{
620    lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "dNbbddd->bb", check_show_build_context, 
621        current,
622        *moveon, 
623        *fireup,
624        badness, 
625        costs, 
626        penalty, 
627        moveon, 
628        fireup
629    );
630}
631
632static void tex_aux_skip_show_build_node(int callback_id, halfword current)
633{
634    (void) current;
635    lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "dN->", skip_show_build_context);
636}
637
638static void tex_aux_move_show_build_node(int callback_id, halfword current)
639{
640    lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "dNddddb->", move_show_build_context, 
641        current,
642        page_last_height,
643        page_last_depth,
644        page_last_stretch,
645        page_last_shrink,
646        (page_last_fistretch || page_last_filstretch || page_last_fillstretch || page_last_filllstretch) ? 1 : 0
647    );
648}
649
650static void tex_aux_fireup_show_build_node(int callback_id, halfword current)
651{
652    lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "dN->", fireup_show_build_context,
653        current 
654    );
655}
656
657static void tex_aux_wrapup_show_build_node(int callback_id)
658{
659    lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "d->", wrapup_show_build_context);
660}
661
662static void tex_aux_show_loner_penalty(int callback_id, halfword options, scaled penalty)
663{
664    lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "dd->", options, penalty);
665}
666
667static int tex_aux_topskip_restart(halfword current, int where, scaled height, scaled depth, scaled exdepth, int discard, int tracing)
668{
669    if (lmt_page_builder_state.contents < contribute_box) {
670        /*tex
671            Initialize the current page, insert the |\topskip| glue ahead of |p|, and |goto 
672            continue|.
673        */
674        if (discard) { 
675            return 2;
676        } else { 
677            halfword gluenode = tex_aux_insert_topskip(height, where);
678            tex_attach_attribute_list_copy(gluenode, current);
679            tex_couple_nodes(gluenode, current);
680            tex_couple_nodes(contribute_head, gluenode);
681            if (tracing > 1) {
682                tex_begin_diagnostic();
683                tex_print_format("[page: initialize, topskip at %s]", where == contribute_box ? "box" : "rule");
684                tex_end_diagnostic();
685            }
686        }
687        return 1;
688    } else {
689        /*tex Move a box to the current page, then |goto contribute|. */
690        page_total += page_depth + height;
691        page_depth = depth;
692        page_except -= height;
693        page_except -= depth;
694        if (exdepth > page_except) {
695            page_except = exdepth;
696        }
697        if (page_except < 0) {
698            page_except = 0; 
699        }
700        return 0;
701    }
702}
703
704static int tex_aux_migrating_restart(halfword current, int tracing)
705{
706    if (auto_migrating_mode_permitted(auto_migration_mode_par, auto_migrate_post)) {
707        halfword head = box_post_migrated(current);
708        if (head) {
709            halfword tail = tex_tail_of_node_list(head);
710            if (tracing > 1 || tracing_adjusts_par > 1) {
711                tex_begin_diagnostic();
712                tex_print_format("[adjust: post, mvl]");
713                tex_print_node_list(head, "post", show_box_depth_par, show_box_breadth_par);
714                tex_end_diagnostic();
715            }   
716            if (node_next(current)) {
717                tex_couple_nodes(tail, node_next(current));
718            } else {
719                contribute_tail = tail;
720            }
721            tex_couple_nodes(current, head);
722            box_post_migrated(current) = null;
723        }
724    }
725    if (auto_migrating_mode_permitted(auto_migration_mode_par, auto_migrate_pre)) {
726        halfword head = box_pre_migrated(current);
727        if (head) {
728            halfword tail = tex_tail_of_node_list(head);
729            if (tracing > 1 || tracing_adjusts_par > 1) {
730                tex_begin_diagnostic();
731                tex_print_format("[adjust: pre, mvl]");
732                tex_print_node_list(head, "pre", show_box_depth_par, show_box_breadth_par);
733                tex_end_diagnostic();
734            }
735            tex_couple_nodes(tail, current);
736            tex_couple_nodes(contribute_head, current);
737            // if (contribute_head == contribute_tail) {
738            //     contribute_tail = tail; 
739            // }
740            box_pre_migrated(current) = null;
741            return 1;
742        }
743    }
744    return 0;
745}
746
747/*tex
748    We just triggered the pagebuilder for which we needed a contribution. We fake a zero penalty 
749    so that all gets processed. The main rationale is that we get a better indication of what we 
750    do. Of course a callback can remove this node  so that it is never seen. Triggering from the 
751    callback is not doable.
752*/
753
754static halfword tex_aux_process_boundary(halfword current)
755{
756    halfword penaltynode = tex_new_node(penalty_node, user_penalty_subtype);
757    /* todo: copy attributes */
758    tex_page_boundary_message("processed as penalty", 0);
759    tex_try_couple_nodes(node_prev(current), penaltynode);
760    tex_try_couple_nodes(penaltynode, node_next(current));
761    tex_flush_node(current);
762    penalty_amount(penaltynode) = boundary_data(current);
763    current = penaltynode;
764    node_next(contribute_head) = current;
765    return current;
766}
767
768static void tex_aux_reconsider_goal(halfword current, halfword *badness, halfword *costs, halfword *penalty, int tracing)
769{
770    (void) current;
771    if (*badness >= awful_bad && page_extra_goal_par) {
772        switch (tex_aux_get_penalty_option(lmt_page_builder_state.page_tail)) {
773            case penalty_option_widowed: 
774                if (page_total <= (page_goal + page_extra_goal_par)) {
775                    halfword extrabadness = tex_aux_page_badness(page_goal + page_extra_goal_par);
776                    halfword extracosts = tex_aux_page_costs(extrabadness, *penalty);
777                    if (tracing > 0) {
778                        tex_begin_diagnostic();
779                        tex_print_format(
780                            "[page: extra check, total %P, goal %p, extragoal %p, badness %B, costs %i, extrabadness %B, extracosts %i]",
781                            page_total, page_stretch, page_filstretch, page_filstretch, page_fillstretch, page_filllstretch, page_shrink,
782                            page_goal, page_extra_goal_par,
783                            *badness, *costs, extrabadness, extracosts
784                        );
785                        tex_end_diagnostic();
786                    }
787                    /* we need to append etc. so an extra loop cycle */
788                    *badness = extrabadness; 
789                    *costs = extracosts;
790                    /* */
791                    lmt_page_builder_state.last_extra_used = 1;
792                    if (tracing > 1) {
793                        tex_begin_diagnostic();
794                        tex_print_format("[page: widowed]");
795                        tex_end_diagnostic();
796                    }
797                }
798                break;
799            case penalty_option_clubbed: 
800                if (page_total >= (page_goal - page_extra_goal_par)) {
801                    /* we force a flush and no extra loop cycle */
802                    *penalty = eject_penalty;
803                    /* */
804                    if (tracing > 1) {
805                        tex_begin_diagnostic();
806                        tex_print_format("[page: clubbed]");
807                        tex_end_diagnostic();
808                    }
809                }
810                break;
811        }
812    }
813}
814
815static void tex_aux_contribute_glue(halfword current)
816{
817    page_stretched(glue_stretch_order(current)) += glue_stretch(current);
818    page_shrink += glue_shrink(current);
819    if (glue_shrink_order(current) != normal_glue_order && glue_shrink(current)) {
820        tex_handle_error(
821            normal_error_type,
822            "Infinite glue shrinkage found on current page",
823            "The page about to be output contains some infinitely shrinkable glue, e.g.,\n"
824            "'\\vss' or '\\vskip 0pt minus 1fil'. Such glue doesn't belong there; but you can\n"
825            "safely proceed, since the offensive shrinkability has been made finite."
826        );
827        tex_reset_glue_to_zero(current);
828        glue_shrink_order(current) = normal_glue_order;
829    }
830}
831
832/*tex
833    Maybe: migrate everything beforehand is somewhat nicer when we use the builder for multicolumns
834    balancing etc. 
835*/
836
837static inline halfword tex_aux_used_penalty(halfword p)
838{
839    if (double_penalty_mode_par && tex_has_penalty_option(p, penalty_option_double)) { 
840        tex_add_penalty_option(p, penalty_option_double_used);
841        return penalty_tnuoma(p);
842    } else { 
843        tex_remove_penalty_option(p, penalty_option_double_used);
844        return penalty_amount(p);
845    }
846}
847
848static void tex_process_mvl(halfword context, halfword boundary)
849{
850    if (! lmt_page_builder_state.output_active) {
851        lmt_page_filter_callback(context, boundary);
852    }        
853    if (node_next(contribute_head) && ! lmt_page_builder_state.output_active) {
854        /*tex The (upcoming) penalty to be added to the badness: */
855        halfword penalty = 0;
856        int callback_id = lmt_callback_defined(show_build_callback);
857        int tracing = tracing_pages_par;
858        if (callback_id) { 
859            tex_aux_initialize_show_build_node(callback_id);
860        }
861        do {
862            /*tex 
863                We update the values of |\lastskip|, |\lastpenalty|, |\lastkern| and 
864                |\lastboundary| as we go.
865            */
866            halfword current = node_next(contribute_head);
867            halfword type = node_type(current);
868            quarterword subtype = node_subtype(current);
869            if (callback_id) { 
870                tex_aux_step_show_build_node(callback_id, current);
871            }
872            if (lmt_page_builder_state.last_glue != max_halfword) {
873                tex_flush_node(lmt_page_builder_state.last_glue);
874                lmt_page_builder_state.last_glue = max_halfword;
875            }
876            lmt_page_builder_state.last_penalty = 0;
877            lmt_page_builder_state.last_kern = 0;
878            lmt_page_builder_state.last_boundary = 0;
879            lmt_page_builder_state.last_node_type = type;
880            lmt_page_builder_state.last_node_subtype = subtype;
881            lmt_page_builder_state.last_extra_used = 0;
882            /*tex
883
884                Move node |p| to the current page; if it is time for a page break, put the nodes
885                following the break back onto the contribution list, and |return| to the users
886                output routine if there is one.
887
888                The code here is an example of a many-way switch into routines that merge together
889                in different places. Some people call this unstructured programming, but the author
890                doesn't see much wrong with it, as long as the various labels have a well-understood
891                meaning.
892
893                If the current page is empty and node |p| is to be deleted, |goto done1|; otherwise
894                use node |p| to update the state of the current page; if this node is an insertion,
895                |goto contribute|; otherwise if this node is not a legal breakpoint,
896                |goto contribute| or |update_heights|; otherwise set |pi| to the penalty associated
897                with this breakpoint.
898
899                The title of this section is already so long, it seems best to avoid making it more
900                accurate but still longer, by mentioning the fact that a kern node at the end of
901                the contribution list will not be contributed until we know its successor.
902
903            */
904            switch (type) {
905                case hlist_node:
906                case vlist_node:
907                    if (tex_aux_migrating_restart(current, tracing)) {
908                        continue;
909                    } else { 
910                        switch (tex_aux_topskip_restart(current, contribute_box, box_height(current), box_depth(current), box_exdepth(current), (box_options(current) & box_option_discardable), tracing)) {
911                            case 1: 
912                                continue;
913                            case 2: 
914                                /* todo: remove */
915                                box_height(current) = 0;
916                                box_depth(current) = 0;
917                                continue;
918                            default: 
919                                goto CONTRIBUTE;
920                        }
921                    }
922                case rule_node:
923                    switch (tex_aux_topskip_restart(current, contribute_rule, rule_height(current), rule_depth(current), 0, (rule_options(current) & rule_option_discardable), tracing)) {
924                        case 1: 
925                            continue;
926                        case 2: 
927                            /* todo: remove */
928                            rule_height(current) = 0;
929                            rule_depth(current) = 0;
930                            continue;
931                        default: 
932                            goto CONTRIBUTE;
933                    }
934                case boundary_node:
935                    if (subtype == page_boundary) {
936                        lmt_page_builder_state.last_boundary = boundary_data(current);
937                    }
938                    if (lmt_page_builder_state.contents < contribute_box) {
939                        goto DISCARD;
940                    } else if (subtype == page_boundary) {
941                        current = tex_aux_process_boundary(current);
942                        penalty = 0;
943                        break;
944                    } else {
945                        goto DISCARD;
946                    }
947                case whatsit_node:
948                    goto CONTRIBUTE;
949                case glue_node:
950                    lmt_page_builder_state.last_glue = tex_new_glue_node(current, subtype);
951                    if (lmt_page_builder_state.contents < contribute_box) {
952                        goto DISCARD;
953                    } else if (precedes_break(lmt_page_builder_state.page_tail)) {
954                        penalty = 0;
955                        break;
956                    } else {
957                        goto UPDATEHEIGHTS;
958                    }
959                case kern_node:
960                    lmt_page_builder_state.last_kern = kern_amount(current);
961                    if (lmt_page_builder_state.contents < contribute_box) {
962                        goto DISCARD;
963                    } else if (! node_next(current)) {
964                        return;
965                    } else if (node_type(node_next(current)) == glue_node) {
966                        penalty = 0;
967                        break;
968                    } else {
969                        goto UPDATEHEIGHTS;
970                    }
971                case penalty_node:
972                    lmt_page_builder_state.last_penalty = penalty_amount(current);
973                    if (lmt_page_builder_state.contents < contribute_box) {
974                        goto DISCARD;
975                    } else {
976                        penalty = tex_aux_used_penalty(current);
977                        break;
978                    }
979                case mark_node:
980                    goto CONTRIBUTE;
981                case insert_node:
982                    tex_aux_append_insert(current);
983                    goto CONTRIBUTE;
984                default:
985                    tex_handle_error(
986                        // normal_error_type,
987                        succumb_error_type,
988                        "Invalid %N node in pagebuilder",
989                        current,
990                        NULL
991                    );
992                    goto DISCARD;
993                    // tex_formatted_error("pagebuilder", "invalid %N node in vertical mode", current);
994                    break;
995            }
996            /*tex
997                Check if node |p| is a new champion breakpoint; then if it is time for a page break,
998                prepare for output, and either fire up the users output routine and |return| or
999                ship out the page and |goto done|.
1000            */
1001            if (tracing > 1) {
1002                tex_begin_diagnostic();
1003                tex_print_format("[page: compute: %N, %d, penalty %i]", current, current, penalty);
1004                tex_end_diagnostic();
1005            }
1006            if (penalty < infinite_penalty) {
1007                /*tex
1008                    Compute the badness, |b|, of the current page, using |awful_bad| if the box is
1009                    too full. The |c| variable holds the costs.
1010                */
1011                halfword badness = tex_aux_page_badness(page_goal);
1012                halfword costs = tex_aux_page_costs(badness, penalty);
1013                lmt_page_builder_state.last_extra_used = 0;
1014                if (tracing > 1) {
1015                    tex_begin_diagnostic();
1016                    tex_print_format("[page: calculate, %N, %d, total %P, goal %p, badness %B, costs %i]", 
1017                        current, current, 
1018                        page_total, page_stretch, page_fistretch, page_filstretch, page_fillstretch, page_filllstretch, page_shrink,
1019                        page_goal,
1020                        badness, costs
1021                    );
1022                    tex_end_diagnostic();
1023                }
1024                tex_aux_reconsider_goal(current, &badness, &costs, &penalty, tracing);
1025                {
1026                    int moveon = costs <= lmt_page_builder_state.least_cost;
1027                    int fireup = costs == awful_bad || penalty <= eject_penalty;
1028                    if (callback_id) { 
1029                        tex_aux_check_show_build_node(callback_id, current, badness, lmt_page_builder_state.last_penalty, costs, &moveon, &fireup);
1030                    }
1031                    if (tracing > 0) {
1032                        tex_aux_display_page_break_cost(badness, penalty, costs, moveon, fireup);
1033                    }
1034                    if (moveon) {
1035                        halfword insert = node_next(page_insert_head);
1036                        lmt_page_builder_state.best_break = current;
1037                        lmt_page_builder_state.best_size = page_goal;
1038                        lmt_page_builder_state.insert_penalties = 0;
1039                        lmt_page_builder_state.least_cost = costs;
1040                        while (insert != page_insert_head) {
1041                            split_best_insert(insert) = split_last_insert(insert);
1042                            insert = node_next(insert);
1043                        }
1044                        tex_aux_save_best_page_specs();
1045                        if (callback_id) { 
1046                            tex_aux_move_show_build_node(callback_id, current);
1047                        }
1048                    }
1049                    if (fireup) {
1050                        if (tracing > 1) {
1051                            tex_begin_diagnostic();
1052                            tex_print_format("[page: fireup: %N, %d]", current, current);
1053                            tex_end_diagnostic();
1054                        }
1055                        if (callback_id) { 
1056                            tex_aux_fireup_show_build_node(callback_id, current);
1057                        }
1058                        /*tex Output the current page at the best place. */
1059                        tex_aux_fire_up(current);
1060                        if (lmt_page_builder_state.output_active) {
1061                            /*tex User's output routine will act. */
1062                            if (callback_id) { 
1063                                tex_aux_wrapup_show_build_node(callback_id);
1064                            }
1065                            return;
1066                        } else {
1067                            /*tex The page has been shipped out by default output routine. */
1068                            continue;
1069                        }
1070                    }
1071                }
1072            } else {
1073                if (callback_id) { 
1074                    tex_aux_skip_show_build_node(callback_id, current);
1075                }
1076            }
1077            UPDATEHEIGHTS:
1078            /*tex
1079                Go here to record glue in the |active_height| table. Update the current page
1080                measurements with respect to the glue or kern specified by node~|p|.
1081            */
1082            if (tracing > 1) {
1083                tex_begin_diagnostic();
1084                tex_print_format("[page: update, %N, %d]", current, current);
1085                tex_end_diagnostic();
1086            }
1087            switch(node_type(current)) {
1088                case kern_node:
1089                    if (page_except) { 
1090                        page_except -= kern_amount(current);
1091                        if (page_except < 0) {
1092                            page_except = 0;
1093                        }
1094                    }
1095                    page_total += page_depth + kern_amount(current);
1096                    page_depth = 0;
1097                    goto APPEND;
1098                case glue_node:
1099                    if (page_except) { 
1100                        glue_stretch(current) = 0; /* why not also shrink etc */
1101                        page_except -= glue_amount(current);
1102                        if (page_except < 0) {
1103                            page_except = 0;
1104                        }
1105                    }
1106                    tex_aux_contribute_glue(current);
1107                    page_total += page_depth + glue_amount(current);
1108                    page_depth = 0;
1109                    goto APPEND;
1110            }
1111            CONTRIBUTE:
1112            /*tex
1113                Go here to link a node into the current page. Make sure that |page_max_depth| is
1114                not exceeded.
1115            */
1116            if (tracing > 1) {
1117                tex_begin_diagnostic();
1118                tex_print_format("[page: contribute, %N, %d]", current, current);
1119                tex_end_diagnostic();
1120            }
1121            if (page_depth > lmt_page_builder_state.max_depth) {
1122                page_total += page_depth - lmt_page_builder_state.max_depth;
1123                page_depth = lmt_page_builder_state.max_depth;
1124            }
1125            APPEND:
1126            if (tracing > 1) {
1127                tex_begin_diagnostic();
1128                tex_print_format("[page: append, %N, %d]", current, current);
1129                tex_end_diagnostic();
1130            }
1131            /*tex Link node |p| into the current page and |goto done|. We assume a positive depth. */
1132            tex_couple_nodes(lmt_page_builder_state.page_tail, current);
1133            lmt_page_builder_state.page_tail = current;
1134            tex_try_couple_nodes(contribute_head, node_next(current));
1135            node_next(current) = null;
1136            continue; // or: break; 
1137            DISCARD:
1138            if (tracing > 1) {
1139                tex_begin_diagnostic();
1140                tex_print_format("[page: discard, %N, %d]", current, current);
1141                tex_end_diagnostic();
1142            }
1143            /*tex Recycle node |p|. */
1144            tex_try_couple_nodes(contribute_head, node_next(current));
1145            node_next(current) = null;
1146            if (saving_vdiscards_par > 0) {
1147                if (lmt_packaging_state.page_discards_head) {
1148                    tex_couple_nodes(lmt_packaging_state.page_discards_tail, current);
1149                } else {
1150                    lmt_packaging_state.page_discards_head = current;
1151                }
1152                lmt_packaging_state.page_discards_tail = current;
1153            } else {
1154                tex_flush_node_list(current);
1155            }
1156        } while (node_next(contribute_head));
1157        /*tex Make the contribution list empty by setting its tail to |contribute_head|. */
1158        contribute_tail = contribute_head;
1159    }
1160}
1161
1162void tex_build_page(halfword context, halfword boundary)
1163{
1164    if (! tex_appended_mvl(context, boundary)) {
1165        tex_process_mvl(context, boundary);
1166    }
1167}
1168
1169/*tex
1170
1171    When the page builder has looked at as much material as could appear before the next page break,
1172    it makes its decision. The break that gave minimum badness will be used to put a completed page
1173    into box |\outputbox|, with insertions appended to their other boxes.
1174
1175    We also set the values of |top_mark|, |first_mark|, and |bot_mark|. The program uses the fact
1176    that |bot_mark(x) <> null| implies |first_mark(x) <> null|; it also knows that |bot_mark(x) =
1177    null| implies |top_mark(x) = first_mark(x) = null|.
1178
1179    The |fire_up| subroutine prepares to output the current page at the best place; then it fires
1180    up the user's output routine, if there is one, or it simply ships out the page. There is one
1181    parameter, |c|, which represents the node that was being contributed to the page when the
1182    decision to force an output was made.
1183
1184*/
1185
1186static void tex_aux_fire_up(halfword c)
1187{
1188    /*tex nodes being examined and/or changed */
1189    halfword current, previous, lastinsert;
1190    /*tex Set the value of |output_penalty|. */
1191    if (node_type(lmt_page_builder_state.best_break) == penalty_node) {
1192        update_tex_output_penalty(penalty_amount(lmt_page_builder_state.best_break));
1193        if (tracing_loners_par) { 
1194            int callback_id = lmt_callback_defined(show_loners_callback);
1195            // this one should return the str 
1196            halfword n = tex_aux_get_last_penalty(lmt_page_builder_state.best_break);
1197            if (n) {
1198                halfword penalty = tex_aux_used_penalty(lmt_page_builder_state.best_break);
1199                if (callback_id) { 
1200                    tex_aux_show_loner_penalty(callback_id, penalty_options(lmt_page_builder_state.best_break), penalty);
1201                } else { 
1202                    unsigned char state[5] = { '.', '.', '.', '.', '\0' };
1203                    if (tex_has_penalty_option(lmt_page_builder_state.best_break, penalty_option_widow  )) { state[0] = 'W'; }
1204                    if (tex_has_penalty_option(lmt_page_builder_state.best_break, penalty_option_club   )) { state[1] = 'C'; }
1205                    if (tex_has_penalty_option(lmt_page_builder_state.best_break, penalty_option_broken )) { state[2] = 'B'; }
1206                    if (tex_has_penalty_option(lmt_page_builder_state.best_break, penalty_option_shaping)) { state[3] = 'S'; }
1207                    tex_begin_diagnostic();
1208                    tex_print_format("[page: loner: %s penalty %i]", state, penalty);
1209                    tex_end_diagnostic();
1210                }
1211            }
1212        }
1213        penalty_amount(lmt_page_builder_state.best_break) = infinite_penalty;
1214        penalty_tnuoma(lmt_page_builder_state.best_break) = infinite_penalty;
1215    } else {
1216        update_tex_output_penalty(infinite_penalty);
1217    }
1218    tex_update_top_marks();
1219    /*tex
1220        Put the optimal current page into box |output_box|, update |first_mark| and |bot_mark|,
1221        append insertions to their boxes, and put the remaining nodes back on the contribution
1222        list.
1223
1224        As the page is finally being prepared for output, pointer |p| runs through the vlist, with
1225        |prev_p| trailing behind; pointer |q| is the tail of a list of insertions that are being
1226        held over for a subsequent page.
1227    */
1228    if (c == lmt_page_builder_state.best_break) {
1229        /*tex |c| not yet linked in */
1230        lmt_page_builder_state.best_break = null;
1231    }
1232    /*tex Ensure that box |output_box| is empty before output. */
1233    if (box_register(output_box_par)) {
1234        if (! ((no_output_box_error_par > 2) || (no_output_box_error_par & 1))) { 
1235            tex_handle_error(
1236                normal_error_type,
1237                "\\box%i is not void",
1238                output_box_par,
1239                "You shouldn't use \\box\\outputbox except in \\output routines. Proceed, and I'll\n"
1240                "discard its present contents."
1241            );
1242        }
1243        box_register(output_box_par) = tex_aux_delete_box_content(box_register(output_box_par), "output", 1);
1244    }
1245    /*
1246    {
1247        int callback_id = lmt_callback_defined(fire_up_output_callback);
1248        if (callback_id != 0) {
1249            halfword insert = node_next(page_insert_head);
1250            lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "->");
1251        }
1252    }
1253    */
1254    /*tex This will count the number of insertions held over. */
1255    {
1256        halfword save_split_top_skip = split_top_skip_par;
1257        lmt_page_builder_state.insert_penalties = 0;
1258        if (holding_inserts_par <= 0) {
1259            /*tex
1260                Prepare all the boxes involved in insertions to act as queues. If many insertions are
1261                supposed to go into the same box, we want to know the position of the last node in that
1262                box, so that we don't need to waste time when linking further information into it. The
1263                |last_insert| fields of the page insertion nodes are therefore used for this purpose
1264                during the packaging phase.
1265
1266                This is tricky: |last_insert| directly points to a \quote {address} in the node list,
1267                that is: the row where |list_ptr| sits. The |raw_list_ptr| macro is just an offset to
1268                the base index of the node. Then |node_next| will start out there and follow the list.
1269                So, |last_insert| kind of points to a subnode (as in disc nodes) of size 1.
1270
1271                    last_insert => [shift][list]
1272
1273                which fakes:
1274
1275                    last_insert => [type|subtype][next] => [real node with next]
1276
1277                and with shift being zero this (when it would be queried) will be seen as a hlist node
1278                of type zero with subtype zero, but ... it is not really such a node which means that
1279                other properties are not valid! Normally this is ok, because \TEX\ only follows this
1280                list and never looks at the parent. But, when accessing from \LUA\ this is asking for
1281                troubles. However, as all happens in the page builder, we don't really expose this and
1282                if we would (somehow, e.g. via a callback) then for sure we would need to make sure
1283                that the node |last_insert(r)| points to is made into a new kind of node: one with
1284                size 1 and type |fake_node| or so, just to be sure (so that at the \LUA\ end no
1285                properties can be asked).
1286
1287                Of course I can be wrong here and changing the approach would involve patching some
1288                code that I don't want to touch. I need a test case for \quote {following the chain}.
1289            */
1290            halfword insert = node_next(page_insert_head);
1291            while (insert != page_insert_head) {
1292                if (split_best_insert(insert)) {
1293                    halfword index = insert_index(insert);
1294                    halfword content = tex_get_insert_content(index);
1295                    if (! tex_aux_valid_insert_content(content)) {
1296                        content = tex_aux_delete_box_content(content, "insert", 2);
1297                    }
1298                    if (! content) {
1299                        /*tex
1300                            So we package the content in a box. Originally this is a hlist which
1301                            is somewhat strange because we're operating in vmode. The box is still
1302                            empty!
1303                        */
1304                        content = tex_new_null_box_node(vlist_node, insert_result_list);
1305                        tex_set_insert_content(index, content);
1306                    }
1307                    /*tex
1308                        We locate the place where we can add. We have an (unpackaged) list here so we
1309                        need to go to the end. Here we have this sort of hackery |box(n) + 5 == row of
1310                        list ptr, a fake node of size 1| trick.
1311                    */
1312                    split_last_insert(insert) = tex_tail_of_node_list(insert_first_box(content));
1313                }
1314                insert = node_next(insert);
1315            }
1316        }
1317        previous = page_head;
1318        current = node_next(previous);
1319        lastinsert = hold_head;
1320        node_next(lastinsert) = null;
1321        while (current != lmt_page_builder_state.best_break) {
1322            switch (node_type(current)) {
1323                case insert_node:
1324                    if (holding_inserts_par <= 0) {
1325                        /*tex
1326                            Either insert the material specified by node |p| into the appropriate box, or
1327                            hold it for the next page; also delete node |p| from the current page.
1328
1329                            We will set |best_insert := null| and package the box corresponding to
1330                            insertion node |r|, just after making the final insertion into that box. If
1331                            this final insertion is |split_up_node|, the remainder after splitting and
1332                            pruning (if any) will be carried over to the next page.
1333                        */
1334                        /*tex should the present insertion be held over? */
1335                        int wait = 0;
1336                        halfword insert = node_next(page_insert_head);
1337                        while (insert_index(insert) != insert_index(current)) {
1338                            insert = node_next(insert);
1339                        }
1340                        if (split_best_insert(insert)) {
1341                            halfword split = split_last_insert(insert);
1342                            tex_try_couple_nodes(split, insert_list(current));
1343                            if (split_best_insert(insert) == current) {
1344                                /*tex
1345                                    Wrap up the box specified by node |r|, splitting node |p| if called
1346                                    for and set |wait| if node |p| holds a remainder after splitting.
1347                                */
1348                                if (node_type(insert) == split_node && node_subtype(insert) == insert_split_subtype && (split_broken_insert(insert) == current) && split_broken(insert)) {
1349                                    while (node_next(split) != split_broken(insert)) {
1350                                        split = node_next(split);
1351                                    }
1352                                    node_next(split) = null;
1353                                    split_top_skip_par = insert_split_top(current); /*tex The old value is saved and restored. */
1354                                    insert_list(current) = tex_prune_page_top(split_broken(insert), 0);
1355                                    if (insert_list(current)) {
1356                                        /*tex
1357                                            We only determine the total height of the list stored in
1358                                            the insert node.
1359                                         */
1360                                        halfword list = insert_list(current);
1361                                        halfword result = tex_vpack(list, 0, packing_additional, max_dimension, direction_unknown, holding_none_option, NULL);
1362                                        insert_total_height(current) = box_total(result);
1363                                        box_list(result) = null;
1364                                        tex_flush_node(result);
1365                                        wait = 1;
1366                                    }
1367                                }
1368                                split_best_insert(insert) = null;
1369                                {
1370                                    /*tex
1371                                        We need this juggling in order to also set the old school box
1372                                        when we're in traditional mode.
1373                                    */
1374                                    halfword index = insert_index(insert);
1375                                    halfword content = tex_get_insert_content(index);
1376                                    halfword list = box_list(content);
1377                                    halfword result = tex_vpack(list, 0, packing_additional, max_dimension, dir_lefttoright, holding_none_option, NULL);
1378                                    tex_set_insert_content(index, result);
1379                                    box_list(content) = null;
1380                                    tex_flush_node(content);
1381                                }
1382                            } else {
1383                                split_last_insert(insert) = tex_tail_of_node_list(split);
1384                            }
1385                        } else {
1386                            wait = 1;
1387                        }
1388                        /*tex
1389                            Either append the insertion node |p| after node |q|, and remove it from the
1390                            current page, or delete |node(p)|.
1391                        */
1392                        tex_try_couple_nodes(previous, node_next(current));
1393                        node_next(current) = null;
1394                        if (wait) {
1395                            tex_couple_nodes(lastinsert, current);
1396                            lastinsert = current;
1397                            ++lmt_page_builder_state.insert_penalties; /* todo: use a proper variable name instead */
1398                        } else {
1399                            insert_list(current) = null;
1400                            tex_flush_node(current);
1401                        }
1402                        current = previous;
1403                    }
1404                    break;
1405                case mark_node:
1406                    tex_update_first_and_bot_mark(current);
1407                    break;
1408            }
1409            previous = current;
1410            current = node_next(current);
1411        }
1412        split_top_skip_par = save_split_top_skip;
1413    }
1414    /*tex
1415        Break the current page at node |p|, put it in box~|output_box|, and put the remaining nodes
1416        on the contribution list.
1417
1418        When the following code is executed, the current page runs from node |vlink (page_head)| to
1419        node |prev_p|, and the nodes from |p| to |page_tail| are to be placed back at the front of
1420        the contribution list. Furthermore the heldover insertions appear in a list from |vlink
1421        (hold_head)| to |q|; we will put them into the current page list for safekeeping while the
1422        user's output routine is active. We might have |q = hold_head|; and |p = null| if and only
1423        if |prev_p = page_tail|. Error messages are suppressed within |vpackage|, since the box
1424        might appear to be overfull or underfull simply because the stretch and shrink from the
1425        |\skip| registers for inserts are not actually present in the box.
1426    */
1427    if (current) {
1428        if (! node_next(contribute_head)) {
1429            contribute_tail = lmt_page_builder_state.page_tail;
1430        }
1431        tex_couple_nodes(lmt_page_builder_state.page_tail, node_next(contribute_head));
1432        tex_couple_nodes(contribute_head, current);
1433        node_next(previous) = null;
1434    }
1435    /*tex When we pack the box we inhibit error messages. */
1436    {
1437        halfword save_vbadness = vbadness_par;
1438        halfword save_vfuzz = vfuzz_par;
1439        vbadness_par = infinite_bad;
1440        vfuzz_par = max_dimension;
1441        tex_show_marks();
1442     // if (1) { 
1443            box_register(output_box_par) = tex_filtered_vpack(node_next(page_head), lmt_page_builder_state.best_size, packing_exactly, lmt_page_builder_state.max_depth, output_group, dir_lefttoright, 0, 0, 0, holding_none_option, &lmt_page_builder_state.excess);
1444     // } else { 
1445     //     /* maybe an option one day */
1446     //     box_register(output_box_par) = tex_filtered_vpack(node_next(page_head), 0, packing_additional, lmt_page_builder_state.max_depth, output_group, dir_lefttoright, 0, 0, 0, holding_none_option));
1447     // }
1448        vbadness_par = save_vbadness;
1449        vfuzz_par = save_vfuzz;
1450    }
1451    if (lmt_page_builder_state.last_glue != max_halfword) {
1452        tex_flush_node(lmt_page_builder_state.last_glue);
1453        lmt_page_builder_state.last_glue = max_halfword;
1454    }
1455    /*tex Start a new current page. This sets |last_glue := max_halfword|, well we already did that. */
1456    tex_aux_start_new_page(); 
1457    /*tex So depth is now forgotten .. hm. */
1458    if (lastinsert != hold_head) {
1459        node_next(page_head) = node_next(hold_head);
1460        lmt_page_builder_state.page_tail = lastinsert;
1461    }
1462    /*tex Delete the page-insertion nodes. */
1463    {
1464        halfword r = node_next(page_insert_head);
1465        while (r != page_insert_head) {
1466            lastinsert = node_next(r);
1467            tex_flush_node(r);
1468            r = lastinsert;
1469        }
1470    }
1471    node_next(page_insert_head) = page_insert_head;
1472    tex_update_first_marks();
1473    if (output_routine_par) {
1474        if (lmt_page_builder_state.dead_cycles >= max_dead_cycles_par) {
1475            /*tex Explain that too many dead cycles have occurred in a row. */
1476            tex_handle_error(
1477                normal_error_type,
1478                "Output loop --- %i consecutive dead cycles",
1479                lmt_page_builder_state.dead_cycles,
1480                "I've concluded that your \\output is awry; it never does a \\shipout, so I'm\n"
1481                "shipping \\box\\outputbox out myself. Next time increase \\maxdeadcycles if you\n"
1482                "want me to be more patient!"
1483            );
1484        } else {
1485            /*tex Fire up the users output routine and |return|. */
1486            lmt_page_builder_state.output_active = 1;
1487            ++lmt_page_builder_state.dead_cycles;
1488            tex_push_nest();
1489            cur_list.mode = internal_vmode;
1490            cur_list.prev_depth = ignore_depth_criterion_par;
1491            cur_list.mode_line = -lmt_input_state.input_line;
1492            tex_begin_token_list(output_routine_par, output_text);
1493            tex_new_save_level(output_group);
1494            tex_normal_paragraph(output_par_context);
1495            tex_scan_left_brace();
1496            return;
1497        }
1498    }
1499    /*tex
1500        Perform the default output routine. The list of heldover insertions, running from |vlink
1501        (page_head)| to |page_tail|, must be moved to the contribution list when the user has
1502        specified no output routine.
1503    */
1504
1505    /* todo: double link */
1506
1507    if (node_next(page_head)) {
1508        if (node_next(contribute_head)) {
1509            node_next(lmt_page_builder_state.page_tail) = node_next(contribute_head);
1510        }
1511        else {
1512            contribute_tail = lmt_page_builder_state.page_tail;
1513        }
1514        node_next(contribute_head) = node_next(page_head);
1515        node_next(page_head) = null;
1516        lmt_page_builder_state.page_tail = page_head;
1517    }
1518    if (lmt_packaging_state.page_discards_head) {
1519        tex_flush_node_list(lmt_packaging_state.page_discards_head);
1520        lmt_packaging_state.page_discards_head = null;
1521    }
1522    if (box_register(output_box_par)) {
1523        tex_flush_node_list(box_register(output_box_par));
1524        box_register(output_box_par) = null;
1525    }
1526}
1527
1528/*tex
1529
1530    When the user's output routine finishes, it has constructed a vlist in internal vertical mode,
1531    and \TEX\ will do the following:
1532
1533*/
1534
1535void tex_resume_after_output(void)
1536{
1537    if (lmt_input_state.cur_input.loc || ((lmt_input_state.cur_input.token_type != output_text) && (lmt_input_state.cur_input.token_type != backed_up_text))) {
1538        /*tex Let's just be fatal here. */
1539        tex_fatal_error("Unbalanced output routine");
1540     // /*tex Recover from an unbalanced output routine */
1541     // tex_handle_error(
1542     //     normal_error_type,
1543     //     "Unbalanced output routine",
1544     //     "Your sneaky output routine has problematic {'s and/or }'s. I can't handle that\n"
1545     //     "very well; good luck."
1546     // );
1547     // /*tex Loops forever if reading from a file, since |null = min_halfword <= 0|. */
1548     // do {
1549     //     tex_get_token();
1550     // } while (lmt_input_state.cur_input.loc);
1551    }
1552    /*tex Conserve stack space in case more outputs are triggered. */
1553    tex_end_token_list();
1554    tex_end_paragraph(bottom_level_group, output_par_context); /*tex No |wrapped_up_paragraph| here. */
1555    tex_unsave();
1556    lmt_page_builder_state.output_active = 0;
1557    lmt_page_builder_state.insert_penalties = 0;
1558    /*tex Ensure that box |output_box| is empty after output. */
1559    if (box_register(output_box_par)) {
1560        if (! ((no_output_box_error_par > 2) || (no_output_box_error_par & 2))) { 
1561            tex_handle_error(
1562                normal_error_type,
1563                "Output routine didn't use all of \\box%i", output_box_par,
1564                "Your \\output commands should empty \\box\\outputbox, e.g., by saying\n"
1565                "'\\shipout\\box\\outputbox'. Proceed; I'll discard its present contents."
1566            );
1567        }
1568        box_register(output_box_par) = tex_aux_delete_box_content(box_register(output_box_par), "output", 1);
1569    }
1570    if (lmt_insert_state.storing == insert_storage_delay && tex_insert_stored()) {
1571        if (tracing_inserts_par > 0) {
1572            tex_print_levels();
1573            tex_print_str(lmt_insert_state.head ? "<delaying inserts>" : "<no inserts to delay>");
1574            if (lmt_insert_state.head && tracing_inserts_par > 1) {
1575                tex_show_node_list(lmt_insert_state.head, max_integer, max_integer);
1576            }
1577        }
1578        tex_try_couple_nodes(lmt_page_builder_state.page_tail, lmt_insert_state.head);
1579        lmt_page_builder_state.page_tail = lmt_insert_state.tail;
1580        lmt_insert_state.head = null;
1581        lmt_insert_state.tail = null;
1582    }
1583    if (cur_list.tail != cur_list.head) {
1584        /*tex Current list goes after heldover insertions. */
1585        tex_try_couple_nodes(lmt_page_builder_state.page_tail, node_next(cur_list.head));
1586        lmt_page_builder_state.page_tail = cur_list.tail;
1587    }
1588    if (node_next(page_head)) {
1589        /* Both go before held over contributions. */
1590        if (! node_next(contribute_head)) {
1591            contribute_tail = lmt_page_builder_state.page_tail;
1592        }
1593        tex_try_couple_nodes(lmt_page_builder_state.page_tail, node_next(contribute_head));
1594        tex_try_couple_nodes(contribute_head, node_next(page_head));
1595        node_next(page_head) = null;
1596        lmt_page_builder_state.page_tail = page_head;
1597    }
1598    if (lmt_insert_state.storing == insert_storage_inject) {
1599        halfword h = node_next(contribute_head);
1600        while (h) {
1601            halfword n = node_next(h);
1602            if (node_type(h) == insert_node) {
1603                tex_try_couple_nodes(node_prev(h), n);
1604                tex_insert_restore(h);
1605            }
1606            h = n;
1607        }
1608        if (tracing_inserts_par > 0) {
1609            tex_print_levels();
1610            tex_print_str(lmt_insert_state.head ? "<storing inserts>" : "<no inserts to store>");
1611            if (lmt_insert_state.head && tracing_inserts_par > 1) {
1612                tex_show_node_list(lmt_insert_state.head, max_integer, max_integer);
1613            }
1614        }
1615    }
1616    lmt_insert_state.storing = insert_storage_ignore;
1617    tex_flush_node_list(lmt_packaging_state.page_discards_head);
1618    lmt_packaging_state.page_discards_head = null;
1619    tex_pop_nest();
1620    tex_build_page(after_output_page_context, 0);
1621}
1622