Skip to main content

retex_engine/
page.rs

1//! Breaking vertical lists into pages: `\vsplit`, the page builder, the
2//! output routine and `\shipout` (*TeX: The Program* §967–§1028 and
3//! §638, with e-TeX's mark classes and saved discards).
4//!
5//! `\shipout` calls the backend through [`crate::typeset::ShipoutHook`];
6//! without a hook the built-in DVI writer is used, which is what LuaTeX
7//! does in IniTeX mode (`\outputmode=0`).
8
9use crate::Engine;
10use crate::build::PackSpec;
11use crate::eqtb::{Loc, Val};
12use crate::glue::Glue;
13use crate::input::{Level, TokKind};
14use crate::maincontrol::{IGNORE_DEPTH, Mode};
15use crate::nodes::{NodeData, NodeRef, glue_sub, id};
16use crate::primitives_gen::{assign_dimen as dp, assign_glue as gp, assign_int as ip};
17use crate::save::GroupCode;
18use crate::typeset::{
19    AWFUL_BAD, DEPLORABLE, EJECT_PENALTY, INF_BAD, INF_PENALTY, PageContents, PageIns, mark_code,
20};
21
22/// TeX's `precedes_break`: whether a glue node after `p` is a legal
23/// breakpoint (LuaTeX node numbering, `type(p) < math_node`).
24#[inline]
25pub fn precedes_break(t: u8) -> bool {
26    t < id::MATH
27}
28
29impl Engine {
30    // ----- pruning and vertical breaking ---------------------------------
31
32    /// Removes glue, kerns and penalties before the first box or rule and
33    /// inserts `\splittopskip` glue (`prune_page_top`). With `save`, the
34    /// removed items go to `\splitdiscards`.
35    pub fn prune_page_top(&mut self, p: NodeRef, save: bool) -> NodeRef {
36        let head = self.nodes.new_temp();
37        self.nodes.link(head, p);
38        let mut prev_p = head;
39        let mut p = p;
40        let mut r = NodeRef::NULL;
41        while !p.is_null() {
42            match self.nodes.id(p) {
43                id::HLIST | id::VLIST | id::RULE => {
44                    let mut g = self.eqtb.glue(gp::SPLITTOPSKIP);
45                    let h = self.node_height(p);
46                    g.width = if g.width > h { g.width - h } else { 0 };
47                    let q = self.new_glue_node(g, glue_sub::SPLIT_TOP_SKIP);
48                    self.nodes.link(prev_p, q);
49                    self.nodes.link(q, p);
50                    break;
51                }
52                id::GLUE | id::KERN | id::PENALTY => {
53                    let q = p;
54                    p = self.nodes.next(q);
55                    self.nodes.set_next(q, NodeRef::NULL);
56                    self.nodes.set_next(prev_p, p);
57                    if !p.is_null() {
58                        self.nodes.set_prev(p, prev_p);
59                    }
60                    if save {
61                        if self.ts.disc_ptr[3].is_null() {
62                            self.ts.disc_ptr[3] = q;
63                        } else {
64                            self.nodes.link(r, q);
65                        }
66                        r = q;
67                    } else {
68                        self.nodes.flush_list(q);
69                    }
70                }
71                _ => {
72                    prev_p = p;
73                    p = self.nodes.next(prev_p);
74                }
75            }
76        }
77        let first = self.nodes.next(head);
78        self.nodes.free(head);
79        if !first.is_null() {
80            self.nodes.set_prev(first, NodeRef::NULL);
81        }
82        first
83    }
84
85    /// Finds the best place to break a vlist for height `h` and maximum
86    /// depth `d` (`vert_break`); returns the break node (null for the end)
87    /// and `best_height_plus_depth`.
88    pub fn vert_break(&mut self, p: NodeRef, h: i32, d: i32) -> (NodeRef, i32) {
89        let mut prev_p = p;
90        let mut p = p;
91        let mut least_cost = AWFUL_BAD;
92        let mut best_place = NodeRef::NULL;
93        let mut best_hpd = 0;
94        let mut cur_height = 0i32;
95        let mut stretch = [0i32; 5];
96        let mut shrink = 0i32;
97        let mut prev_dp = 0i32;
98        loop {
99            // Determine whether p is a legal breakpoint and its penalty.
100            let mut pi: Option<i32> = None;
101            let mut update = false;
102            if p.is_null() {
103                pi = Some(EJECT_PENALTY);
104            } else {
105                match self.nodes.id(p) {
106                    id::HLIST | id::VLIST | id::RULE => {
107                        cur_height += prev_dp + self.node_height(p);
108                        prev_dp = self.node_depth(p);
109                    }
110                    id::GLUE => {
111                        if precedes_break(self.nodes.id(prev_p)) {
112                            pi = Some(0);
113                        } else {
114                            update = true;
115                        }
116                    }
117                    id::KERN => {
118                        let n = self.nodes.next(p);
119                        let t = if n.is_null() {
120                            id::PENALTY
121                        } else {
122                            self.nodes.id(n)
123                        };
124                        if t == id::GLUE {
125                            pi = Some(0);
126                        } else {
127                            update = true;
128                        }
129                    }
130                    id::PENALTY => pi = Some(self.nodes.penalty(p)),
131                    _ => {}
132                }
133            }
134            if let Some(pi) = pi {
135                if pi < INF_PENALTY {
136                    let b = if cur_height < h {
137                        if stretch[1..].iter().any(|&s| s != 0) {
138                            0
139                        } else {
140                            retex_arith::badness(h - cur_height, stretch[0])
141                        }
142                    } else if cur_height - h > shrink {
143                        AWFUL_BAD
144                    } else {
145                        retex_arith::badness(cur_height - h, shrink)
146                    };
147                    let b = if b < AWFUL_BAD {
148                        if pi <= EJECT_PENALTY {
149                            pi
150                        } else if b < INF_BAD {
151                            b + pi
152                        } else {
153                            DEPLORABLE
154                        }
155                    } else {
156                        b
157                    };
158                    if b <= least_cost {
159                        best_place = p;
160                        least_cost = b;
161                        best_hpd = cur_height + prev_dp;
162                    }
163                    if b == AWFUL_BAD || pi <= EJECT_PENALTY {
164                        return (best_place, best_hpd);
165                    }
166                }
167                let t = self.nodes.id(p);
168                if t == id::GLUE || t == id::KERN {
169                    update = true;
170                }
171            }
172            if update {
173                if self.nodes.id(p) == id::KERN {
174                    cur_height += prev_dp + self.nodes.width(p);
175                } else {
176                    let mut q = self.nodes.glue_spec(p);
177                    stretch[(q.stretch_order as usize).min(4)] += q.stretch;
178                    shrink += q.shrink;
179                    if q.shrink_order != 0 && q.shrink != 0 {
180                        const MSG: &str = "Infinite glue shrinkage found in box being split";
181                        if self.eqtb.int(ip::IGNOREPRIMITIVEERROR) & 1 != 0 {
182                            // LuaTeX's \ignoreprimitiveerror bit 1 turns
183                            // this error (only) into a log line.
184                            self.printer.print_nl("ignored error ");
185                            self.printer.print_str(MSG);
186                        } else {
187                            self.print_err(MSG);
188                            self.help(&[
189                                "The box you are \\vsplitting contains some infinitely",
190                                "shrinkable glue, e.g., `\\vss' or `\\vskip 0pt minus 1fil'.",
191                                "Such glue doesn't belong there; but you can safely proceed,",
192                                "since the offensive shrinkability has been made finite.",
193                            ]);
194                            self.error();
195                        }
196                        q.shrink_order = 0;
197                        if let NodeData::Glue { spec, .. } = self.nodes.data_mut(p) {
198                            spec.shrink_order = 0;
199                        }
200                    }
201                    cur_height += prev_dp + q.width;
202                }
203                prev_dp = 0;
204            }
205            if prev_dp > d {
206                cur_height = cur_height + prev_dp - d;
207                prev_dp = d;
208            }
209            prev_p = p;
210            p = self.nodes.next(prev_p);
211        }
212    }
213
214    /// `\vsplit n to h` (`vsplit`).
215    pub fn vsplit(&mut self, n: u32, h: i32) -> NodeRef {
216        let v = self.eqtb.boxes[n as usize].val;
217        let sd = std::mem::take(&mut self.ts.disc_ptr[3]);
218        self.nodes.flush_list(sd);
219        for m in self.marks.classes.values_mut() {
220            m[mark_code::SPLIT_FIRST] = None;
221            m[mark_code::SPLIT_BOT] = None;
222        }
223        if v.is_null() {
224            return NodeRef::NULL;
225        }
226        if self.nodes.id(v) != id::VLIST {
227            self.print_err("");
228            self.print_esc_str("vsplit");
229            self.printer.print_str(" needs a ");
230            self.print_esc_str("vbox");
231            self.help(&[
232                "The box you are trying to split is an \\hbox.",
233                "i can't split such a box, so I''ll leave it alone.",
234            ]);
235            self.error();
236            return NodeRef::NULL;
237        }
238        let smd = self.eqtb.dimen(dp::SPLITMAXDEPTH);
239        let list = self.nodes.list_ptr(v);
240        let (q, _) = self.vert_break(list, h, smd);
241        // Look at the marks before the break and cut the list there.
242        let mut p = list;
243        if p == q {
244            self.nodes.set_list_ptr(v, NodeRef::NULL);
245        } else {
246            loop {
247                if let NodeData::Mark { class, mark } = self.nodes.data(p) {
248                    let (c, m) = (*class, mark.clone());
249                    let e = self.marks.classes.entry(c).or_default();
250                    if e[mark_code::SPLIT_FIRST].is_none() {
251                        e[mark_code::SPLIT_FIRST] = Some(m.clone());
252                    }
253                    e[mark_code::SPLIT_BOT] = Some(m);
254                }
255                let nx = self.nodes.next(p);
256                if nx == q {
257                    self.nodes.set_next(p, NodeRef::NULL);
258                    break;
259                }
260                p = nx;
261            }
262        }
263        let save = self.eqtb.int(ip::SAVINGVDISCARDS) > 0;
264        let q = self.prune_page_top(q, save);
265        let p = self.nodes.list_ptr(v);
266        self.nodes.set_list_ptr(v, NodeRef::NULL);
267        self.nodes.free(v);
268        self.eqtb.boxes[n as usize].val = if q.is_null() {
269            NodeRef::NULL
270        } else {
271            self.vpack(q, PackSpec::NATURAL)
272        };
273        self.vpackage(p, PackSpec::Exactly(h), smd)
274    }
275
276    // ----- the page builder ----------------------------------------------
277
278    /// Prints a box being deleted and voids the register (`box_error`).
279    fn box_error(&mut self, n: u32) {
280        self.error();
281        self.begin_diagnostic();
282        self.printer.print_nl("The following box has been deleted:");
283        let b = self.eqtb.boxes[n as usize].val;
284        self.show_box(b);
285        self.end_diagnostic(true);
286        self.nodes.flush_list(b);
287        self.eqtb.boxes[n as usize].val = NodeRef::NULL;
288    }
289
290    /// Makes sure box `n` is not an hbox (`ensure_vbox`).
291    fn ensure_vbox(&mut self, n: u32) {
292        let p = self.eqtb.boxes[n as usize].val;
293        if !p.is_null() && self.nodes.id(p) == id::HLIST {
294            self.print_err("Insertions can only be added to a vbox");
295            self.help(&[
296                "Tut tut: You're trying to \\insert into a",
297                "\\box register that now contains an \\hbox.",
298                "Proceed, and I'll discard its present contents.",
299            ]);
300            self.box_error(n);
301        }
302    }
303
304    /// Freezes `\vsize` and `\maxdepth` for the page (`freeze_page_specs`).
305    fn freeze_page_specs(&mut self, s: PageContents) {
306        self.page.contents = s;
307        self.page.so_far = [0; 8];
308        self.page.fi_stretch = 0;
309        self.page.so_far[0] = self.eqtb.dimen(dp::VSIZE);
310        self.page.max_depth = self.eqtb.dimen(dp::MAXDEPTH);
311        self.page.least_cost = AWFUL_BAD;
312        if self.eqtb.int(ip::TRACINGPAGES) > 0 {
313            self.begin_diagnostic();
314            self.printer.print_nl("%% goal height=");
315            self.printer.print_scaled(self.page.so_far[0]);
316            self.printer.print_str(", max depth=");
317            self.printer.print_scaled(self.page.max_depth);
318            self.end_diagnostic(false);
319        }
320    }
321
322    /// Starts a fresh current page.
323    fn start_new_page(&mut self) {
324        self.page.contents = PageContents::Empty;
325        self.page.page_tail = self.page.page_head;
326        self.nodes.set_next(self.page.page_head, NodeRef::NULL);
327        self.page.last_glue = None;
328        self.page.last_penalty = 0;
329        self.page.last_kern = 0;
330        self.page.last_node_type = -1;
331        self.page.so_far[7] = 0;
332        self.page.max_depth = 0;
333    }
334
335    /// Sets the tail of the contribution list.
336    #[inline]
337    fn set_contrib_tail(&mut self, t: NodeRef) {
338        self.nest[0].tail = t;
339    }
340
341    /// Appends contributions to the current page (`build_page`).
342    pub fn build_page(&mut self) {
343        let ch = self.page.contrib_head;
344        if self.nodes.next(ch).is_null() || self.ts.output_active {
345            return;
346        }
347        loop {
348            let p = self.nodes.next(ch);
349            if p.is_null() {
350                break;
351            }
352            // Update last_glue, last_penalty, last_kern, last_node_type.
353            self.page.last_penalty = 0;
354            self.page.last_kern = 0;
355            let t = self.nodes.id(p);
356            self.page.last_node_type = crate::typeset::etex_node_type(t, self.nodes.subtype(p));
357            if t == id::GLUE {
358                self.page.last_glue = Some(self.nodes.glue_spec(p));
359            } else {
360                self.page.last_glue = None;
361                if t == id::PENALTY {
362                    self.page.last_penalty = self.nodes.penalty(p);
363                } else if t == id::KERN {
364                    self.page.last_kern = self.nodes.width(p);
365                }
366            }
367            // Move node p to the current page.
368            enum Act {
369                Contribute,
370                UpdateHeights,
371                Recycle,
372                Break(i32),
373            }
374            let contents = self.page.contents;
375            let act = match t {
376                id::HLIST | id::VLIST | id::RULE => {
377                    if contents < PageContents::BoxThere {
378                        // Initialize the page and insert \topskip glue.
379                        if contents == PageContents::Empty {
380                            self.freeze_page_specs(PageContents::BoxThere);
381                        } else {
382                            self.page.contents = PageContents::BoxThere;
383                        }
384                        let mut g = self.eqtb.glue(gp::TOPSKIP);
385                        let h = self.node_height(p);
386                        g.width = if g.width > h { g.width - h } else { 0 };
387                        let q = self.new_glue_node(g, glue_sub::TOP_SKIP);
388                        self.nodes.link(q, p);
389                        self.nodes.link(ch, q);
390                        continue;
391                    }
392                    self.page.so_far[1] += self.page.so_far[7] + self.node_height(p);
393                    self.page.so_far[7] = self.node_depth(p);
394                    Act::Contribute
395                }
396                id::GLUE => {
397                    if contents < PageContents::BoxThere {
398                        Act::Recycle
399                    } else if precedes_break(self.nodes.id(self.page.page_tail)) {
400                        Act::Break(0)
401                    } else {
402                        Act::UpdateHeights
403                    }
404                }
405                id::KERN => {
406                    if contents < PageContents::BoxThere {
407                        Act::Recycle
408                    } else {
409                        let n = self.nodes.next(p);
410                        if n.is_null() {
411                            return;
412                        } else if self.nodes.id(n) == id::GLUE {
413                            Act::Break(0)
414                        } else {
415                            Act::UpdateHeights
416                        }
417                    }
418                }
419                id::PENALTY => {
420                    if contents < PageContents::BoxThere {
421                        Act::Recycle
422                    } else {
423                        Act::Break(self.nodes.penalty(p))
424                    }
425                }
426                id::INS => {
427                    self.append_insertion(p);
428                    Act::Contribute
429                }
430                _ => Act::Contribute,
431            };
432            let mut update = matches!(act, Act::UpdateHeights);
433            match act {
434                Act::Recycle => {
435                    let n = self.nodes.next(p);
436                    self.nodes.set_next(ch, n);
437                    self.nodes.set_next(p, NodeRef::NULL);
438                    if self.eqtb.int(ip::SAVINGVDISCARDS) > 0 {
439                        if self.ts.disc_ptr[2].is_null() {
440                            self.ts.disc_ptr[2] = p;
441                        } else {
442                            let t = self.ts.disc_ptr[1];
443                            self.nodes.link(t, p);
444                        }
445                        self.ts.disc_ptr[1] = p;
446                    } else {
447                        self.nodes.flush_list(p);
448                    }
449                    continue;
450                }
451                Act::Break(pi) => {
452                    if pi < INF_PENALTY {
453                        let b = self.page_badness();
454                        let ip_ = self.insert_penalties;
455                        let mut c = if b < AWFUL_BAD {
456                            if pi <= EJECT_PENALTY {
457                                pi
458                            } else if b < INF_BAD {
459                                b + pi + ip_
460                            } else {
461                                DEPLORABLE
462                            }
463                        } else {
464                            b
465                        };
466                        if ip_ >= 10000 {
467                            c = AWFUL_BAD;
468                        }
469                        if self.eqtb.int(ip::TRACINGPAGES) > 0 {
470                            self.begin_diagnostic();
471                            self.printer.print_nl("%");
472                            self.printer.print_str(" t=");
473                            self.print_totals();
474                            self.printer.print_str(" g=");
475                            self.printer.print_scaled(self.page.so_far[0]);
476                            self.printer.print_str(" b=");
477                            if b == AWFUL_BAD {
478                                self.printer.print_char('*' as u32);
479                            } else {
480                                self.printer.print_int(i64::from(b));
481                            }
482                            self.printer.print_str(" p=");
483                            self.printer.print_int(i64::from(pi));
484                            self.printer.print_str(" c=");
485                            if c == AWFUL_BAD {
486                                self.printer.print_char('*' as u32);
487                            } else {
488                                self.printer.print_int(i64::from(c));
489                            }
490                            if c <= self.page.least_cost {
491                                self.printer.print_char('#' as u32);
492                            }
493                            self.end_diagnostic(false);
494                        }
495                        if c <= self.page.least_cost {
496                            self.page.best_break = p;
497                            self.page.best_size = self.page.so_far[0];
498                            self.page.best_so_far = self.page.so_far;
499                            self.page.best_infinite = self.page.fi_stretch != 0
500                                || self.page.so_far[3..6].iter().any(|&s| s != 0);
501                            self.page.least_cost = c;
502                            for r in self.page.ins.iter_mut() {
503                                r.best_ins_ptr = r.last_ins_ptr;
504                            }
505                        }
506                        if c == AWFUL_BAD || pi <= EJECT_PENALTY {
507                            self.fire_up(p);
508                            if self.ts.output_active {
509                                return;
510                            }
511                            // The page has been shipped out by the default
512                            // output routine.
513                            continue;
514                        }
515                    }
516                    if t == id::GLUE || t == id::KERN {
517                        update = true;
518                    }
519                }
520                _ => {}
521            }
522            if update {
523                if t == id::KERN {
524                    self.page.so_far[1] += self.page.so_far[7] + self.nodes.width(p);
525                } else {
526                    let q = self.nodes.glue_spec(p);
527                    *self.page.stretch_mut(q.stretch_order) += q.stretch;
528                    self.page.so_far[6] += q.shrink;
529                    if q.shrink_order != 0 && q.shrink != 0 {
530                        self.print_err("Infinite glue shrinkage found on current page");
531                        self.help(&[
532                            "The page about to be output contains some infinitely",
533                            "shrinkable glue, e.g., `\\vss' or `\\vskip 0pt minus 1fil'.",
534                            "Such glue doesn't belong there; but you can safely proceed,",
535                            "since the offensive shrinkability has been made finite.",
536                        ]);
537                        self.error();
538                        if let NodeData::Glue { spec, .. } = self.nodes.data_mut(p) {
539                            spec.shrink_order = 0;
540                        }
541                    }
542                    self.page.so_far[1] += self.page.so_far[7] + q.width;
543                }
544                self.page.so_far[7] = 0;
545            }
546            // contribute: make sure page_max_depth is not exceeded.
547            if self.page.so_far[7] > self.page.max_depth {
548                self.page.so_far[1] += self.page.so_far[7] - self.page.max_depth;
549                self.page.so_far[7] = self.page.max_depth;
550            }
551            // Link node p into the current page.
552            let n = self.nodes.next(p);
553            let pt = self.page.page_tail;
554            self.nodes.link(pt, p);
555            self.page.page_tail = p;
556            self.nodes.set_next(ch, n);
557            if !n.is_null() {
558                self.nodes.set_prev(n, ch);
559            }
560            self.nodes.set_next(p, NodeRef::NULL);
561        }
562        // Make the contribution list empty.
563        self.set_contrib_tail(ch);
564    }
565
566    /// The badness of the current page.
567    fn page_badness(&self) -> i32 {
568        let s = &self.page.so_far;
569        let (goal, total) = (s[0], s[1]);
570        if total < goal {
571            if self.page.fi_stretch != 0 || s[3] != 0 || s[4] != 0 || s[5] != 0 {
572                0
573            } else {
574                retex_arith::badness(goal - total, s[2])
575            }
576        } else if total - goal > s[6] {
577            AWFUL_BAD
578        } else {
579            retex_arith::badness(total - goal, s[6])
580        }
581    }
582
583    /// Appends an insertion to the current page (§1008).
584    fn append_insertion(&mut self, p: NodeRef) {
585        if self.page.contents == PageContents::Empty {
586            self.freeze_page_specs(PageContents::InsertsOnly);
587        }
588        let n = u32::from(self.nodes.subtype(p));
589        let (pheight, pdepth, pcost, plist) = match self.nodes.data(p) {
590            NodeData::Ins {
591                height,
592                depth,
593                cost,
594                head,
595                ..
596            } => (*height, *depth, *cost, *head),
597            _ => return,
598        };
599        let idx = match self.page.ins.iter().position(|r| r.n >= n) {
600            Some(i) if self.page.ins[i].n == n => i,
601            pos => {
602                // Create a page insertion record.
603                let i = pos.unwrap_or(self.page.ins.len());
604                self.ensure_vbox(n);
605                let b = self.eqtb.boxes[n as usize].val;
606                let height = if b.is_null() {
607                    0
608                } else {
609                    self.node_height(b) + self.node_depth(b)
610                };
611                self.page.ins.insert(
612                    i,
613                    PageIns {
614                        n,
615                        split_up: false,
616                        height,
617                        broken_ptr: NodeRef::NULL,
618                        broken_ins: NodeRef::NULL,
619                        last_ins_ptr: NodeRef::NULL,
620                        best_ins_ptr: NodeRef::NULL,
621                    },
622                );
623                let q = self.eqtb.skip[n as usize].val;
624                let c = self.eqtb.count[n as usize].val;
625                let h = if c == 1000 {
626                    height
627                } else {
628                    x_over_n(height, 1000).wrapping_mul(c)
629                };
630                self.page.so_far[0] = self.page.so_far[0] - h - q.width;
631                *self.page.stretch_mut(q.stretch_order) += q.stretch;
632                self.page.so_far[6] += q.shrink;
633                if q.shrink_order != 0 && q.shrink != 0 {
634                    self.print_err("Infinite glue shrinkage inserted from ");
635                    self.print_esc_str("skip");
636                    self.printer.print_int(i64::from(n));
637                    self.help(&[
638                        "The correction glue for page breaking with insertions",
639                        "must have finite shrinkability. But you may proceed,",
640                        "since the offensive shrinkability has been made finite.",
641                    ]);
642                    self.error();
643                }
644                i
645            }
646        };
647        if self.page.ins[idx].split_up {
648            self.insert_penalties += pcost;
649            return;
650        }
651        self.page.ins[idx].last_ins_ptr = p;
652        let s = &self.page.so_far;
653        let delta = s[0] - s[1] - s[7] + s[6];
654        let c = self.eqtb.count[n as usize].val;
655        let h = if c == 1000 {
656            pheight
657        } else {
658            x_over_n(pheight, 1000).wrapping_mul(c)
659        };
660        let dimen_n = self.eqtb.dimen[n as usize].val;
661        if (h <= 0 || h <= delta) && pheight + self.page.ins[idx].height <= dimen_n {
662            self.page.so_far[0] -= h;
663            self.page.ins[idx].height += pheight;
664        } else {
665            // Find the best way to split the insertion.
666            let mut w = if c <= 0 {
667                retex_arith::MAX_DIMEN
668            } else {
669                let mut w = self.page.so_far[0] - self.page.so_far[1] - self.page.so_far[7];
670                if c != 1000 {
671                    w = x_over_n(w, c).wrapping_mul(1000);
672                }
673                w
674            };
675            if w > dimen_n - self.page.ins[idx].height {
676                w = dimen_n - self.page.ins[idx].height;
677            }
678            let (q, mut bhpd) = self.vert_break(plist, w, pdepth);
679            self.page.ins[idx].height += bhpd;
680            if self.eqtb.int(ip::TRACINGPAGES) > 0 {
681                self.begin_diagnostic();
682                self.printer.print_nl("% split");
683                self.printer.print_int(i64::from(n));
684                self.printer.print_str(" to ");
685                self.printer.print_scaled(w);
686                self.printer.print_char(',' as u32);
687                self.printer.print_scaled(bhpd);
688                self.printer.print_str(" p=");
689                if q.is_null() {
690                    self.printer.print_int(i64::from(EJECT_PENALTY));
691                } else if self.nodes.id(q) == id::PENALTY {
692                    self.printer.print_int(i64::from(self.nodes.penalty(q)));
693                } else {
694                    self.printer.print_char('0' as u32);
695                }
696                self.end_diagnostic(false);
697            }
698            if c != 1000 {
699                bhpd = x_over_n(bhpd, 1000).wrapping_mul(c);
700            }
701            self.page.so_far[0] -= bhpd;
702            let r = &mut self.page.ins[idx];
703            r.split_up = true;
704            r.broken_ptr = q;
705            r.broken_ins = p;
706            if q.is_null() {
707                self.insert_penalties += EJECT_PENALTY;
708            } else if self.nodes.id(q) == id::PENALTY {
709                self.insert_penalties += self.nodes.penalty(q);
710            }
711        }
712    }
713
714    /// Outputs the current page at the best place (`fire_up`).
715    fn fire_up(&mut self, c: NodeRef) {
716        let bpb = self.page.best_break;
717        if self.diagnostics.is_some() {
718            let s = self.page.best_so_far;
719            let forced = !bpb.is_null()
720                && self.nodes.id(bpb) == id::PENALTY
721                && self.nodes.penalty(bpb) <= EJECT_PENALTY;
722            self.page.last_break = Some(crate::pagenote::PageBreakInfo {
723                page: self.eqtb.count[0].val,
724                goal: s[0],
725                total: s[1],
726                stretch: s[2],
727                infinite: self.page.best_infinite,
728                forced,
729                moved: if forced { None } else { self.first_moved(bpb) },
730            });
731        }
732        // Set the value of \outputpenalty.
733        if !bpb.is_null() && self.nodes.id(bpb) == id::PENALTY {
734            let pen = self.nodes.penalty(bpb);
735            self.geq_define(Loc::IntPar(ip::OUTPUTPENALTY), Val::Int(i64::from(pen)));
736            if let NodeData::Penalty { penalty } = self.nodes.data_mut(bpb) {
737                *penalty = INF_PENALTY;
738            }
739        } else {
740            self.geq_define(
741                Loc::IntPar(ip::OUTPUTPENALTY),
742                Val::Int(i64::from(INF_PENALTY)),
743            );
744        }
745        for m in self.marks.classes.values_mut() {
746            if m[mark_code::BOT].is_some() {
747                m[mark_code::TOP] = m[mark_code::BOT].clone();
748                m[mark_code::FIRST] = None;
749            }
750        }
751        if c == self.page.best_break {
752            self.page.best_break = NodeRef::NULL;
753        }
754        let best_break = self.page.best_break;
755        // Ensure that box 255 is empty before output.
756        let obox = self.output_box_num() as u32;
757        if !self.eqtb.boxes[obox as usize].val.is_null() {
758            self.print_err("");
759            self.print_esc_str("box");
760            self.printer.print_int(i64::from(obox));
761            self.printer.print_str(" is not void");
762            self.help(&[
763                "You shouldn't use \\box255 except in \\output routines.",
764                "Proceed, and I'll discard its present contents.",
765            ]);
766            self.box_error(obox);
767        }
768        self.insert_penalties = 0;
769        let save_split_top_skip = self.eqtb.glue_par[gp::SPLITTOPSKIP as usize].val;
770        let holding = self.eqtb.int(ip::HOLDINGINSERTS) > 0;
771        if !holding {
772            // Prepare all the boxes involved in insertions to act as
773            // queues.
774            for i in 0..self.page.ins.len() {
775                if !self.page.ins[i].best_ins_ptr.is_null() {
776                    let n = self.page.ins[i].n;
777                    self.ensure_vbox(n);
778                    if self.eqtb.boxes[n as usize].val.is_null() {
779                        let b = self.nodes.new_null_box(false);
780                        self.eqtb.boxes[n as usize].val = b;
781                    }
782                    let b = self.eqtb.boxes[n as usize].val;
783                    let l = self.nodes.list_ptr(b);
784                    self.page.ins[i].last_ins_ptr = self.nodes.tail_of(l);
785                }
786            }
787        }
788        // Held-over insertions are collected after `hold_head`.
789        let hold_head = self.nodes.new_temp();
790        let mut q = hold_head;
791        let mut prev_p = self.page.page_head;
792        let mut p = self.nodes.next(prev_p);
793        while p != best_break && !p.is_null() {
794            let t = self.nodes.id(p);
795            if t == id::INS {
796                if !holding {
797                    let n = u32::from(self.nodes.subtype(p));
798                    let Some(ri) = self.page.ins.iter().position(|r| r.n == n) else {
799                        prev_p = p;
800                        p = self.nodes.next(prev_p);
801                        continue;
802                    };
803                    let mut wait;
804                    if self.page.ins[ri].best_ins_ptr.is_null() {
805                        wait = true;
806                    } else {
807                        wait = false;
808                        let bx = self.eqtb.boxes[n as usize].val;
809                        let start = self.page.ins[ri].last_ins_ptr;
810                        let il = self.nodes.list_ptr(p);
811                        if start.is_null() {
812                            self.nodes.set_list_ptr(bx, il);
813                        } else {
814                            self.nodes.link(start, il);
815                        }
816                        if self.page.ins[ri].best_ins_ptr == p {
817                            // Wrap up the box, splitting node p if called
818                            // for.
819                            let r = self.page.ins[ri];
820                            if r.split_up && r.broken_ins == p && !r.broken_ptr.is_null() {
821                                if start.is_null() && self.nodes.list_ptr(bx) == r.broken_ptr {
822                                    self.nodes.set_list_ptr(bx, NodeRef::NULL);
823                                } else {
824                                    let mut s = if start.is_null() {
825                                        self.nodes.list_ptr(bx)
826                                    } else {
827                                        start
828                                    };
829                                    while !s.is_null() && self.nodes.next(s) != r.broken_ptr {
830                                        s = self.nodes.next(s);
831                                    }
832                                    if !s.is_null() {
833                                        self.nodes.set_next(s, NodeRef::NULL);
834                                    }
835                                }
836                                let sts = match self.nodes.data(p) {
837                                    NodeData::Ins { spec, .. } => *spec,
838                                    _ => Glue::ZERO,
839                                };
840                                self.eqtb.glue_par[gp::SPLITTOPSKIP as usize].val = sts;
841                                let rest = self.prune_page_top(r.broken_ptr, false);
842                                self.nodes.set_list_ptr(p, rest);
843                                if !rest.is_null() {
844                                    let tmp = self.vpack(rest, PackSpec::NATURAL);
845                                    let hh = self.node_height(tmp) + self.node_depth(tmp);
846                                    self.nodes.set_list_ptr(tmp, NodeRef::NULL);
847                                    self.nodes.free(tmp);
848                                    if let NodeData::Ins { height, .. } = self.nodes.data_mut(p) {
849                                        *height = hh;
850                                    }
851                                    wait = true;
852                                }
853                            }
854                            self.page.ins[ri].best_ins_ptr = NodeRef::NULL;
855                            let l = self.nodes.list_ptr(bx);
856                            self.nodes.set_list_ptr(bx, NodeRef::NULL);
857                            self.nodes.free(bx);
858                            let nb = self.vpack(l, PackSpec::NATURAL);
859                            self.eqtb.boxes[n as usize].val = nb;
860                        } else {
861                            let l = self.nodes.list_ptr(bx);
862                            self.page.ins[ri].last_ins_ptr = self.nodes.tail_of(l);
863                        }
864                    }
865                    // Either append the insertion node p after node q, or
866                    // delete it.
867                    let nx = self.nodes.next(p);
868                    self.nodes.set_next(prev_p, nx);
869                    if !nx.is_null() {
870                        self.nodes.set_prev(nx, prev_p);
871                    }
872                    self.nodes.set_next(p, NodeRef::NULL);
873                    if wait {
874                        self.nodes.link(q, p);
875                        q = p;
876                        self.insert_penalties += 1;
877                    } else {
878                        self.nodes.set_list_ptr(p, NodeRef::NULL);
879                        self.nodes.free(p);
880                    }
881                    p = prev_p;
882                }
883            } else if let NodeData::Mark { class, mark } = self.nodes.data(p) {
884                let (cl, m) = (*class, mark.clone());
885                let e = self.marks.classes.entry(cl).or_default();
886                if e[mark_code::FIRST].is_none() {
887                    e[mark_code::FIRST] = Some(m.clone());
888                }
889                e[mark_code::BOT] = Some(m);
890            }
891            prev_p = p;
892            p = self.nodes.next(prev_p);
893        }
894        self.eqtb.glue_par[gp::SPLITTOPSKIP as usize].val = save_split_top_skip;
895        // Break the current page at node p, put it in box 255, and put the
896        // remaining nodes on the contribution list.
897        let ch = self.page.contrib_head;
898        if !p.is_null() {
899            if self.nodes.next(ch).is_null() {
900                let pt = self.page.page_tail;
901                self.set_contrib_tail(pt);
902            }
903            let pt = self.page.page_tail;
904            let cn = self.nodes.next(ch);
905            self.nodes.set_next(pt, cn);
906            if !cn.is_null() {
907                self.nodes.set_prev(cn, pt);
908            }
909            self.nodes.link(ch, p);
910            self.nodes.set_next(prev_p, NodeRef::NULL);
911        }
912        let save_vbadness = self.eqtb.int_par[ip::VBADNESS as usize].val;
913        let save_vfuzz = self.eqtb.dimen_par[dp::VFUZZ as usize].val;
914        self.eqtb.int_par[ip::VBADNESS as usize].val = INF_BAD;
915        self.eqtb.dimen_par[dp::VFUZZ as usize].val = retex_arith::MAX_DIMEN;
916        let first = self.nodes.next(self.page.page_head);
917        let b = self.vpackage(
918            first,
919            PackSpec::Exactly(self.page.best_size),
920            self.page.max_depth,
921        );
922        self.eqtb.boxes[obox as usize].val = b;
923        self.eqtb.int_par[ip::VBADNESS as usize].val = save_vbadness;
924        self.eqtb.dimen_par[dp::VFUZZ as usize].val = save_vfuzz;
925        self.start_new_page();
926        if q != hold_head {
927            let h = self.nodes.next(hold_head);
928            self.nodes.link(self.page.page_head, h);
929            self.page.page_tail = q;
930        }
931        self.nodes.free(hold_head);
932        self.page.ins.clear();
933        for m in self.marks.classes.values_mut() {
934            if m[mark_code::TOP].is_some() && m[mark_code::FIRST].is_none() {
935                m[mark_code::FIRST] = m[mark_code::TOP].clone();
936            }
937        }
938        let output = self
939            .eqtb
940            .toks_param(crate::primitives_gen::assign_toks::OUTPUT)
941            .clone();
942        if !output.is_empty() {
943            if self.dead_cycles >= self.eqtb.int(ip::MAXDEADCYCLES) {
944                self.print_err("Output loop---");
945                self.printer.print_int(i64::from(self.dead_cycles));
946                self.printer.print_str(" consecutive dead cycles");
947                self.help(&[
948                    "I've concluded that your \\output is awry; it never does a",
949                    "\\shipout, so I'm shipping \\box255 out myself. Next time",
950                    "increase \\maxdeadcycles if you want me to be more patient!",
951                ]);
952                self.error();
953            } else {
954                // Fire up the user's output routine.
955                self.ts.output_active = true;
956                self.dead_cycles += 1;
957                self.push_nest(Mode::InternalVertical);
958                self.cur_list_mut().prev_depth = IGNORE_DEPTH;
959                let line = self.input.line();
960                self.cur_list_mut().mode_line = -line;
961                self.begin_token_list(output, TokKind::OutputText);
962                self.new_save_level(GroupCode::Output);
963                self.normal_paragraph();
964                self.scan_left_brace();
965                return;
966            }
967        }
968        // Perform the default output routine.
969        let ph = self.page.page_head;
970        let first = self.nodes.next(ph);
971        if !first.is_null() {
972            if self.nodes.next(ch).is_null() {
973                let pt = self.page.page_tail;
974                self.set_contrib_tail(pt);
975            } else {
976                let pt = self.page.page_tail;
977                let cn = self.nodes.next(ch);
978                self.nodes.link(pt, cn);
979            }
980            self.nodes.link(ch, first);
981            self.nodes.set_next(ph, NodeRef::NULL);
982            self.page.page_tail = ph;
983        }
984        let pd = std::mem::take(&mut self.ts.disc_ptr[2]);
985        self.nodes.flush_list(pd);
986        let b = self.eqtb.boxes[obox as usize].val;
987        self.eqtb.boxes[obox as usize].val = NodeRef::NULL;
988        if !b.is_null() {
989            self.ship_out(b);
990        }
991    }
992
993    /// Resumes the page builder after the output routine (§1026).
994    pub fn resume_output(&mut self) {
995        let ok = matches!(
996            self.input.stack.last(),
997            Some(Level::Tokens(t)) if t.loc >= t.list.len()
998                && (t.kind == TokKind::OutputText || t.kind == TokKind::BackedUp)
999        );
1000        if !ok {
1001            self.print_err("Unbalanced output routine");
1002            self.help(&[
1003                "Your sneaky output routine has problematic {'s and/or }'s.",
1004                "I can't handle that very well; good luck.",
1005            ]);
1006            self.error();
1007            let mut guard = 0;
1008            loop {
1009                self.get_token();
1010                guard += 1;
1011                let done = matches!(
1012                    self.input.stack.last(),
1013                    Some(Level::Tokens(t)) if t.loc >= t.list.len()
1014                );
1015                if done || guard > 1_000_000 {
1016                    break;
1017                }
1018            }
1019        }
1020        self.end_token_list();
1021        self.end_graf();
1022        self.unsave();
1023        self.ts.output_active = false;
1024        self.insert_penalties = 0;
1025        // Ensure that box 255 is empty after output.
1026        let obox = self.output_box_num() as u32;
1027        if !self.eqtb.boxes[obox as usize].val.is_null() {
1028            self.print_err("Output routine didn't use all of ");
1029            self.print_esc_str("box");
1030            self.printer.print_int(i64::from(obox));
1031            self.help(&[
1032                "Your \\output commands should empty \\box255,",
1033                "e.g., by saying `\\shipout\\box255'.",
1034                "Proceed; I'll discard its present contents.",
1035            ]);
1036            self.box_error(obox);
1037        }
1038        let (head, tail) = (self.cur_list().head, self.cur_list().tail);
1039        if tail != head {
1040            let pt = self.page.page_tail;
1041            let first = self.nodes.next(head);
1042            self.nodes.link(pt, first);
1043            self.page.page_tail = tail;
1044            self.nodes.set_next(head, NodeRef::NULL);
1045        }
1046        let ph = self.page.page_head;
1047        let ch = self.page.contrib_head;
1048        let first = self.nodes.next(ph);
1049        if !first.is_null() {
1050            if self.nodes.next(ch).is_null() {
1051                let pt = self.page.page_tail;
1052                self.set_contrib_tail(pt);
1053            }
1054            let pt = self.page.page_tail;
1055            let cn = self.nodes.next(ch);
1056            self.nodes.set_next(pt, cn);
1057            if !cn.is_null() {
1058                self.nodes.set_prev(cn, pt);
1059            }
1060            self.nodes.link(ch, first);
1061            self.nodes.set_next(ph, NodeRef::NULL);
1062            self.page.page_tail = ph;
1063        }
1064        let pd = std::mem::take(&mut self.ts.disc_ptr[2]);
1065        self.nodes.flush_list(pd);
1066        self.pop_nest();
1067        self.build_page();
1068    }
1069}
1070
1071/// `x_over_n` returning only the quotient (0 on overflow).
1072fn x_over_n(x: i32, n: i32) -> i32 {
1073    retex_arith::x_over_n(x, n).map_or(0, |d| d.quotient)
1074}