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