/* tectonic/xetex-pagebuilder.c: the page builder Copyright 2017-2018 The Tectonic Project Licensed under the MIT License. */ /* Customizations for Tectonic: * * In semantic pagination mode, we don't run the pagebuilder routine. We just * directly invoke the shipout code, which in turn writes out the output vlist * without worrying about pages. We also behave as if holding_inserts is * always true: inserts are kept in the page vlist rather than being * processed. */ #include "xetex-core.h" #include "xetex-xetexd.h" static int32_t best_page_break; static scaled_t best_size; static int32_t least_page_cost; static scaled_t page_max_depth; /* XXX other variables belong here but pop up all over the code */ void initialize_pagebuilder_variables(void) { page_max_depth = 0; } static void freeze_page_specs(small_number s) { page_contents = s; page_so_far[0] = DIMENPAR(vsize); page_max_depth = DIMENPAR(max_depth); page_so_far[7] = 0; page_so_far[1] = 0; page_so_far[2] = 0; page_so_far[3] = 0; page_so_far[4] = 0; page_so_far[5] = 0; page_so_far[6] = 0; least_page_cost = MAX_HALFWORD; } static void ensure_vbox(eight_bits n) { int32_t p = BOX_REG(n); if (p == TEX_NULL) return; if (NODE_type(p) != HLIST_NODE) return; error_here_with_diagnostic("Insertions can only be added to a vbox"); capture_to_diagnostic(NULL); help_ptr = 3; help_line[2] = "Tut tut: You're trying to \\insert into a"; help_line[1] = "\\box register that now contains an \\hbox."; help_line[0] = "Proceed, and I'll discard its present contents."; box_error(n); } /*1047: "The fire_up subroutine prepares to output the curent page at the best * place; then it fires up the user's output routine, if there is one, or it * simple 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." */ static void fire_up(int32_t c) { int32_t p, q, r, s; int32_t prev_p; unsigned char n; bool wait; int32_t save_vbadness; scaled_t save_vfuzz; int32_t save_split_top_skip; bool process_inserts; /*1048: "Set the value of output_penalty" */ if (NODE_type(best_page_break) == PENALTY_NODE) { geq_word_define(INT_BASE + INT_PAR__output_penalty, PENALTY_NODE_penalty(best_page_break)); PENALTY_NODE_penalty(best_page_break) = INF_PENALTY; } else { geq_word_define(INT_BASE + INT_PAR__output_penalty, INF_PENALTY); } /* ... resuming 1047 ... "We set the values of top_mark, first_mark, and * bot_mark. The program uses the fact that `bot_mark != null` implies * `first_mark != null`; it also knows that `bot_mark == null` implies * `top_mark = first_mark = null`." The do_marks() call basically does the * same thing as the code immediately below it, but for all "mark classes" * beyond the default one -- a "mark class" being a concept introduced in * e-TeX. */ if (sa_root[MARK_VAL] != TEX_NULL) { if (do_marks(FIRE_UP_INIT, 0, sa_root[MARK_VAL])) sa_root[MARK_VAL] = TEX_NULL; } if (cur_mark[BOT_MARK_CODE] != TEX_NULL) { if (cur_mark[TOP_MARK_CODE] != TEX_NULL) delete_token_ref(cur_mark[TOP_MARK_CODE]); cur_mark[TOP_MARK_CODE] = cur_mark[BOT_MARK_CODE]; TOKEN_LIST_ref_count(cur_mark[TOP_MARK_CODE])++; delete_token_ref(cur_mark[FIRST_MARK_CODE]); cur_mark[FIRST_MARK_CODE] = TEX_NULL; } /*1049: "Put the optimal current page into box 255, update first_mark and * bot_mark, append insertions to their boxes, and put the remaining nodes * back on the contribution list." */ if (c == best_page_break) best_page_break = TEX_NULL; /* "c not yet linked in" */ if (BOX_REG(255) != TEX_NULL) { /*1050:*/ error_here_with_diagnostic(""); print_esc_cstr("box"); print_cstr("255 is not void"); capture_to_diagnostic(NULL); help_ptr = 2; help_line[1] = "You shouldn't use \\box255 except in \\output routines."; help_line[0] = "Proceed, and I'll discard its present contents."; box_error(255); } insert_penalties = 0; /* "this will count the number of insertions held over" */ save_split_top_skip = GLUEPAR(split_top_skip); /* Tectonic: in semantic pagination mode, we act as if holding_inserts is * always active. */ process_inserts = (INTPAR(holding_inserts) <= 0) && !semantic_pagination_enabled; if (process_inserts) { /*1053: "Prepare all the boxes involved in insertions to act as * queues". Namely: for each insert being tracked, set the * `last_ins_ptr` field of its data structure to the last node in its * associated vlist. If holding_inserts is positive, the inserts are * just kept in the page vlist without any processing, I believe with * the expectation that the output routine will do something clever * with them. */ r = LLIST_link(PAGE_INS_HEAD); while (r != PAGE_INS_HEAD) { if (PAGE_INS_NODE_best_ins_ptr(r) != TEX_NULL) { n = NODE_subtype(r); ensure_vbox(n); if (BOX_REG(n) == TEX_NULL) BOX_REG(n) = new_null_box(); p = BOX_REG(n) + 5; /* 5 = list_offset, "position of the list inside the box" */ while (LLIST_link(p) != TEX_NULL) p = LLIST_link(p); PAGE_INS_NODE_last_ins_ptr(r) = p; } r = LLIST_link(r); } } q = HOLD_HEAD; LLIST_link(q) = TEX_NULL; prev_p = PAGE_HEAD; p = LLIST_link(prev_p); while (p != best_page_break) { if (NODE_type(p) == INS_NODE) { if (process_inserts) { /*1055: "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." */ r = LLIST_link(PAGE_INS_HEAD); while (NODE_subtype(r) != NODE_subtype(p)) r = LLIST_link(r); if (PAGE_INS_NODE_best_ins_ptr(r) == TEX_NULL) { wait = true; } else { wait = false; s = PAGE_INS_NODE_last_ins_ptr(r); LLIST_link(s) = INSERTION_NODE_ins_ptr(p); if (PAGE_INS_NODE_best_ins_ptr(r) == p) { /*1056: "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 (NODE_type(r) == SPLIT_UP) { if (PAGE_INS_NODE_broken_ins(r) == p && PAGE_INS_NODE_broken_ptr(r) != TEX_NULL) { while (LLIST_link(s) != PAGE_INS_NODE_broken_ptr(r)) s = LLIST_link(s); LLIST_link(s) = TEX_NULL; GLUEPAR(split_top_skip) = INSERTION_NODE_split_top_ptr(p); INSERTION_NODE_ins_ptr(p) = prune_page_top(PAGE_INS_NODE_broken_ptr(r), false); if (INSERTION_NODE_ins_ptr(p) != TEX_NULL) { temp_ptr = vpackage(INSERTION_NODE_ins_ptr(p), 0, ADDITIONAL, MAX_HALFWORD); BOX_height(p) = BOX_height(temp_ptr) + BOX_depth(temp_ptr); free_node(temp_ptr, BOX_NODE_SIZE); wait = true; } } } PAGE_INS_NODE_best_ins_ptr(r) = TEX_NULL; n = NODE_subtype(r); temp_ptr = BOX_list_ptr(BOX_REG(n)); free_node(BOX_REG(n), BOX_NODE_SIZE); BOX_REG(n) = vpackage(temp_ptr, 0, ADDITIONAL, MAX_HALFWORD); } else { while (LLIST_link(s) != TEX_NULL) s = LLIST_link(s); PAGE_INS_NODE_last_ins_ptr(r) = s; } } /*1057: "Either append the insertion node p after node q, and * remove it from the current page, or delete node(p)" */ LLIST_link(prev_p) = LLIST_link(p); LLIST_link(p) = TEX_NULL; if (wait) { LLIST_link(q) = p; q = p; insert_penalties++; } else { delete_glue_ref(INSERTION_NODE_split_top_ptr(p)); free_node(p, INS_NODE_SIZE); } p = prev_p; /*:1057 */ } } else if (NODE_type(p) == MARK_NODE) { if (MARK_NODE_class(p) != 0) { /*1618: "Update the current marks" */ find_sa_element(MARK_VAL, MARK_NODE_class(p), true); if (ETEX_MARK_sa_first_mark(cur_ptr) == TEX_NULL) { ETEX_MARK_sa_first_mark(cur_ptr) = MARK_NODE_ptr(p); TOKEN_LIST_ref_count(MARK_NODE_ptr(p))++; } if (ETEX_MARK_sa_bot_mark(cur_ptr) != TEX_NULL) delete_token_ref(ETEX_MARK_sa_bot_mark(cur_ptr)); ETEX_MARK_sa_bot_mark(cur_ptr) = MARK_NODE_ptr(p); TOKEN_LIST_ref_count(MARK_NODE_ptr(p))++; } else { /*1051: "Update the values of first_mark and bot_mark" */ if (cur_mark[FIRST_MARK_CODE] == TEX_NULL) { cur_mark[FIRST_MARK_CODE] = MARK_NODE_ptr(p); TOKEN_LIST_ref_count(cur_mark[FIRST_MARK_CODE])++; } if (cur_mark[BOT_MARK_CODE] != TEX_NULL) delete_token_ref(cur_mark[BOT_MARK_CODE]); cur_mark[BOT_MARK_CODE] = MARK_NODE_ptr(p); TOKEN_LIST_ref_count(cur_mark[BOT_MARK_CODE])++; } } prev_p = p; p = LLIST_link(prev_p); } GLUEPAR(split_top_skip) = save_split_top_skip; /*1052: "Break the current page at node p, put it in box 255, and put the * remaining nodes on the contribution list". */ if (p != TEX_NULL) { if (LLIST_link(CONTRIB_HEAD) == TEX_NULL) { if (nest_ptr == 0) cur_list.tail = page_tail; else nest[0].tail = page_tail; } LLIST_link(page_tail) = LLIST_link(CONTRIB_HEAD); LLIST_link(CONTRIB_HEAD) = p; LLIST_link(prev_p) = TEX_NULL; } /* Temporarily futz some variables to inhibit error messages */ save_vbadness = INTPAR(vbadness); INTPAR(vbadness) = INF_BAD; save_vfuzz = DIMENPAR(vfuzz); DIMENPAR(vfuzz) = MAX_HALFWORD; BOX_REG(255) = vpackage(LLIST_link(PAGE_HEAD), best_size, EXACTLY, page_max_depth); INTPAR(vbadness) = save_vbadness; DIMENPAR(vfuzz) = save_vfuzz; if (last_glue != MAX_HALFWORD) delete_glue_ref(last_glue); /*1026: "Start a new current page" */ page_contents = EMPTY; page_tail = PAGE_HEAD; LLIST_link(PAGE_HEAD) = TEX_NULL; last_glue = MAX_HALFWORD; last_penalty = 0; last_kern = 0; last_node_type = -1; page_so_far[7] = 0; page_max_depth = 0; if (q != HOLD_HEAD) { LLIST_link(PAGE_HEAD) = LLIST_link(HOLD_HEAD); page_tail = q; } /*1054: "Delete the page-insertion nodes" */ r = LLIST_link(PAGE_INS_HEAD); while (r != PAGE_INS_HEAD) { q = LLIST_link(r); free_node(r, PAGE_INS_NODE_SIZE); r = q; } LLIST_link(PAGE_INS_HEAD) = PAGE_INS_HEAD; /* ... resuming 1047 ... */ if (sa_root[MARK_VAL] != TEX_NULL) { if (do_marks(FIRE_UP_DONE, 0, sa_root[MARK_VAL])) sa_root[MARK_VAL] = TEX_NULL; } if (cur_mark[TOP_MARK_CODE] != TEX_NULL && cur_mark[FIRST_MARK_CODE] == TEX_NULL) { cur_mark[FIRST_MARK_CODE] = cur_mark[TOP_MARK_CODE]; TOKEN_LIST_ref_count(cur_mark[TOP_MARK_CODE])++; } if (LOCAL(output_routine) != TEX_NULL) { if (dead_cycles >= INTPAR(max_dead_cycles)) { /*1059: "Explain that too many dead cycles have happened in a row." */ error_here_with_diagnostic("Output loop---"); print_int(dead_cycles); print_cstr(" consecutive dead cycles"); capture_to_diagnostic(NULL); help_ptr = 3; help_line[2] = "I've concluded that your \\output is awry; it never does a"; help_line[1] = "\\shipout, so I'm shipping \\box255 out myself. Next time"; help_line[0] = "increase \\maxdeadcycles if you want me to be more patient!"; error(); } else { /*1060: "Fire up the user's output routine and return" */ output_active = true; dead_cycles++; push_nest(); cur_list.mode = -VMODE; cur_list.aux.b32.s1 = IGNORE_DEPTH; /* this is `prev_depth` */ cur_list.mode_line = -line; begin_token_list(LOCAL(output_routine), OUTPUT_TEXT); new_save_level(OUTPUT_GROUP); normal_paragraph(); scan_left_brace(); return; } } /*1058: "Perform the default output routine." */ if (LLIST_link(PAGE_HEAD) != TEX_NULL) { if (LLIST_link(CONTRIB_HEAD) == TEX_NULL) { if (nest_ptr == 0) cur_list.tail = page_tail; else nest[0].tail = page_tail; } else { LLIST_link(page_tail) = LLIST_link(CONTRIB_HEAD); } LLIST_link(CONTRIB_HEAD) = LLIST_link(PAGE_HEAD); LLIST_link(PAGE_HEAD) = TEX_NULL; page_tail = PAGE_HEAD; } flush_node_list(disc_ptr[LAST_BOX_CODE]); disc_ptr[LAST_BOX_CODE] = TEX_NULL; ship_out(BOX_REG(255)); BOX_REG(255) = TEX_NULL; } /*1029: "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." ... "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 immediate followed by `goto big_switch`, which * is TeX's central control point." */ #define AWFUL_BAD MAX_HALFWORD /* XXX redundant with xetex-linebreak.c */ void build_page(void) { int32_t p; int32_t q, r; int32_t b, c; int32_t pi; unsigned char /*biggest_reg */ n; scaled_t delta, h, w; if (LLIST_link(CONTRIB_HEAD) == TEX_NULL || output_active) return; do { p = LLIST_link(CONTRIB_HEAD); /*1031: "Update the values of last_glue, last_penalty, and last_kern" */ if (last_glue != MAX_HALFWORD) delete_glue_ref(last_glue); last_penalty = 0; last_kern = 0; last_node_type = NODE_type(p) + 1; if (NODE_type(p) == GLUE_NODE) { last_glue = GLUE_NODE_glue_ptr(p); GLUE_SPEC_ref_count(last_glue)++; } else { last_glue = MAX_HALFWORD; if (NODE_type(p) == PENALTY_NODE) last_penalty = PENALTY_NODE_penalty(p); else if (NODE_type(p) == KERN_NODE) last_kern = BOX_width(p); } /*1032: "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 user's 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." */ /* 1035: "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 (NODE_type(p)) { case HLIST_NODE: case VLIST_NODE: case RULE_NODE: if (page_contents < BOX_THERE) { /*1036: "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(GLUE_PAR__top_skip); /* "now temp_ptr = glue_ptr(q) */ if (BOX_width(temp_ptr) > BOX_height(p)) BOX_width(temp_ptr) -= BOX_height(p); else BOX_width(temp_ptr) = 0; LLIST_link(q) = p; LLIST_link(CONTRIB_HEAD) = q; continue; } else { /*1037: "Prepare to move a box or rule node to the current * page, then goto contribute." */ page_so_far[1] += page_so_far[7] + BOX_height(p); page_so_far[7] = BOX_depth(p); goto contribute; } break; case WHATSIT_NODE: /*1401: "Prepare to move whatsit p to the current page, then goto contribute" */ if (NODE_subtype(p) == PIC_NODE || NODE_subtype(p) == PDF_NODE) { page_so_far[1] += page_so_far[7] + BOX_height(p); page_so_far[7] = BOX_depth(p); } goto contribute; break; case GLUE_NODE: if (page_contents < BOX_THERE) goto done1; else if (is_non_discardable_node(page_tail)) pi = 0; else goto update_heights; break; case KERN_NODE: if (page_contents < BOX_THERE) goto done1; else if (LLIST_link(p) == TEX_NULL) return; else if (NODE_type(LLIST_link(p)) == GLUE_NODE) pi = 0; else goto update_heights; break; case PENALTY_NODE: if (page_contents < BOX_THERE) goto done1; else pi = PENALTY_NODE_penalty(p); break; case MARK_NODE: goto contribute; break; case INS_NODE: /*1043: "Append an insertion to the current page and goto contribute" */ if (page_contents == EMPTY) freeze_page_specs(INSERTS_ONLY); n = NODE_subtype(p); r = PAGE_INS_HEAD; while (n >= NODE_subtype(LLIST_link(r))) r = LLIST_link(r); if (NODE_subtype(r) != n) { /*1044: "Create a page insertion node with subtype(r) = n, and * include the glue correction for box `n` in the current page * state" */ q = get_node(PAGE_INS_NODE_SIZE); LLIST_link(q) = LLIST_link(r); LLIST_link(r) = q; r = q; NODE_subtype(r) = n; NODE_type(r) = INSERTING; ensure_vbox(n); if (BOX_REG(n) == TEX_NULL) BOX_height(r) = 0; else BOX_height(r) = BOX_height(BOX_REG(n)) + BOX_depth(BOX_REG(n)); PAGE_INS_NODE_best_ins_ptr(r) = TEX_NULL; q = SKIP_REG(n); if (COUNT_REG(n) == 1000) h = BOX_height(r); else h = x_over_n(BOX_height(r), 1000) * COUNT_REG(n); page_so_far[0] -= h + BOX_width(q); page_so_far[2 + GLUE_SPEC_stretch_order(q)] += GLUE_SPEC_stretch(q); page_so_far[6] += GLUE_SPEC_shrink(q); if (GLUE_SPEC_shrink_order(q) != NORMAL && GLUE_SPEC_shrink(q) != 0) { error_here_with_diagnostic("Infinite glue shrinkage inserted from "); print_esc_cstr("skip"); print_int(n); capture_to_diagnostic(NULL); help_ptr = 3; help_line[2] = "The correction glue for page breaking with insertions"; help_line[1] = "must have finite shrinkability. But you may proceed,"; help_line[0] = "since the offensive shrinkability has been made finite."; error(); } } if (NODE_type(r) == SPLIT_UP) { insert_penalties += INSERTION_NODE_float_cost(p); } else { PAGE_INS_NODE_last_ins_ptr(r) = p; delta = page_so_far[0] - page_so_far[1] - page_so_far[7] + page_so_far[6]; if (COUNT_REG(n) == 1000) h = BOX_height(p); else h = x_over_n(BOX_height(p), 1000) * COUNT_REG(n); if ((h <= 0 || h <= delta) && BOX_height(p) + BOX_height(r) <= SCALED_REG(n)) { page_so_far[0] -= h; BOX_height(r) += BOX_height(p); } else { /*1045: "Find the best way to split the insertion, and * change type(r) to split_up." ... "Here is code that * will split a long footnote between pages, in an * emergency ... 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 then `\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_REG(n) <= 0) w = MAX_HALFWORD; else { w = page_so_far[0] - page_so_far[1] - page_so_far[7]; if (COUNT_REG(n) != 1000) w = x_over_n(w, COUNT_REG(n)) * 1000; } if (w > SCALED_REG(n) - BOX_height(r)) w = SCALED_REG(n) - BOX_height(r); q = vert_break(INSERTION_NODE_ins_ptr(p), w, BOX_depth(p)); BOX_height(r) += best_height_plus_depth; if (COUNT_REG(n) != 1000) best_height_plus_depth = x_over_n(best_height_plus_depth, 1000) * COUNT_REG(n); page_so_far[0] -= best_height_plus_depth; NODE_type(r) = SPLIT_UP; PAGE_INS_NODE_broken_ptr(r) = q; PAGE_INS_NODE_broken_ins(r) = p; if (q == TEX_NULL) insert_penalties += EJECT_PENALTY; else if (NODE_type(q) == PENALTY_NODE) insert_penalties += PENALTY_NODE_penalty(q); } } goto contribute; break; /* end of INS_NODE */ default: confusion("page"); break; } /*1040: "Check if node p is the new champion breakpoint; then if it is * time for a page break, prepare for output, and either fire up the * user's output routine and return or ship out the page and goto * done." We reach this point when p is a glue, kern, or penalty, and * there's already content on the page -- so this might be a place to * break the page. */ if (pi < INF_PENALTY) { /*1042: "Compute the badness b of the current page, using * awful_bad if the box is too full." */ if (page_so_far[1] < page_so_far[0]) { if (page_so_far[3] != 0 || page_so_far[4] != 0 || page_so_far[5] != 0) b = 0; else b = badness(page_so_far[0] - page_so_far[1], page_so_far[2]); } else if (page_so_far[1] - page_so_far[0] > page_so_far[6]) { b = AWFUL_BAD; } else { b = badness(page_so_far[1] - page_so_far[0], page_so_far[6]); } if (b < AWFUL_BAD) { if (pi <= EJECT_PENALTY) c = pi; else if (b < INF_BAD) c = b + pi + insert_penalties; else c = 100000L; /* DEPLORABLE */ } else { c = b; } if (insert_penalties >= 10000) c = MAX_HALFWORD; if (c <= least_page_cost) { best_page_break = p; best_size = page_so_far[0]; least_page_cost = c; r = LLIST_link(PAGE_INS_HEAD); while (r != PAGE_INS_HEAD) { PAGE_INS_NODE_best_ins_ptr(r) = PAGE_INS_NODE_last_ins_ptr(r); r = LLIST_link(r); } } if (c == AWFUL_BAD || pi <= EJECT_PENALTY) { fire_up(p); if (output_active) /* "user's output routine will act" */ return; goto done; /* "the page has been shipped out by the default output routine" */ } } /* ... resuming 1032 ... I believe the "goto" here can only be * triggered if p is a penalty node, and we decided not to break. */ if (NODE_type(p) < GLUE_NODE || NODE_type(p) > KERN_NODE) goto contribute; update_heights: /*1039: "Update the current page measurements with respect to the glue or kern * specified by node p" */ if (NODE_type(p) == KERN_NODE) { q = p; } else { q = GLUE_NODE_glue_ptr(p); page_so_far[2 + GLUE_SPEC_stretch_order(q)] += GLUE_SPEC_stretch(q); page_so_far[6] += GLUE_SPEC_shrink(q); if (GLUE_SPEC_shrink_order(q) != NORMAL && GLUE_SPEC_shrink(q) != 0) { error_here_with_diagnostic("Infinite glue shrinkage found on current page"); capture_to_diagnostic(NULL); help_ptr = 4; help_line[3] = "The page about to be output contains some infinitely"; help_line[2] = "shrinkable glue, e.g., `\\vss' or `\\vskip 0pt minus 1fil'."; help_line[1] = "Such glue doesn't belong there; but you can safely proceed,"; help_line[0] = "since the offensive shrinkability has been made finite."; error(); r = new_spec(q); GLUE_SPEC_shrink_order(r) = NORMAL; delete_glue_ref(q); GLUE_NODE_glue_ptr(p) = r; q = r; } } page_so_far[1] += page_so_far[7] + BOX_width(q); page_so_far[7] = 0; contribute: /*1038: "Make sure that page_max_depth is not exceeded." */ if (page_so_far[7] > page_max_depth) { page_so_far[1] += page_so_far[7] - page_max_depth; page_so_far[7] = page_max_depth; } /*1033: "Link node p into the current page and goto done." */ LLIST_link(page_tail) = p; page_tail = p; LLIST_link(CONTRIB_HEAD) = LLIST_link(p); LLIST_link(p) = TEX_NULL; goto done; done1: /*1034: "Recycle node p". This codepath is triggered if we encountered * something nonprinting (glue, kern, penalty) and there aren't any * yes-printing boxes at the top of the page yet. When that happens, * we just discard the nonprinting node. */ LLIST_link(CONTRIB_HEAD) = LLIST_link(p); LLIST_link(p) = TEX_NULL; if (INTPAR(saving_vdiscards) <= 0) { flush_node_list(p); } else { /* `disc_ptr[LAST_BOX_CODE]` is `tail_page_disc`, the last item * removed by the page builder. `disc_ptr[LAST_BOX_CODE]` is * `page_disc`, the first item removed by the page builder. * `disc_ptr[VSPLIT_CODE]` is `split_disc`, the first item removed * by \vsplit. */ if (disc_ptr[LAST_BOX_CODE] == TEX_NULL) disc_ptr[LAST_BOX_CODE] = p; else LLIST_link(disc_ptr[COPY_CODE]) = p; disc_ptr[COPY_CODE] = p; } done: ; } while (LLIST_link(CONTRIB_HEAD) != TEX_NULL); if (nest_ptr == 0) cur_list.tail = CONTRIB_HEAD; /* "vertical mode" */ else nest[0].tail = CONTRIB_HEAD; /* "other modes" */ }