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                id::MARK | id::WHATSIT | id::BOUNDARY => Act::Contribute,
431                // Observed: other nodes (Lua code can put a glyph in a
432                // vertical list) end the run.
433                _ => self.luatex_fatal(
434                    "pagebuilder",
435                    &format!("invalid node of type {t} in vertical mode"),
436                ),
437            };
438            let mut update = matches!(act, Act::UpdateHeights);
439            match act {
440                Act::Recycle => {
441                    let n = self.nodes.next(p);
442                    self.nodes.set_next(ch, n);
443                    self.nodes.set_next(p, NodeRef::NULL);
444                    if self.eqtb.int(ip::SAVINGVDISCARDS) > 0 {
445                        if self.ts.disc_ptr[2].is_null() {
446                            self.ts.disc_ptr[2] = p;
447                        } else {
448                            let t = self.ts.disc_ptr[1];
449                            self.nodes.link(t, p);
450                        }
451                        self.ts.disc_ptr[1] = p;
452                    } else {
453                        self.nodes.flush_list(p);
454                    }
455                    continue;
456                }
457                Act::Break(pi) => {
458                    if pi < INF_PENALTY {
459                        let b = self.page_badness();
460                        let ip_ = self.insert_penalties;
461                        let mut c = if b < AWFUL_BAD {
462                            if pi <= EJECT_PENALTY {
463                                pi
464                            } else if b < INF_BAD {
465                                b + pi + ip_
466                            } else {
467                                DEPLORABLE
468                            }
469                        } else {
470                            b
471                        };
472                        if ip_ >= 10000 {
473                            c = AWFUL_BAD;
474                        }
475                        if self.eqtb.int(ip::TRACINGPAGES) > 0 {
476                            self.begin_diagnostic();
477                            self.printer.print_nl("%");
478                            self.printer.print_str(" t=");
479                            self.print_totals();
480                            self.printer.print_str(" g=");
481                            self.printer.print_scaled(self.page.so_far[0]);
482                            self.printer.print_str(" b=");
483                            if b == AWFUL_BAD {
484                                self.printer.print_char('*' as u32);
485                            } else {
486                                self.printer.print_int(i64::from(b));
487                            }
488                            self.printer.print_str(" p=");
489                            self.printer.print_int(i64::from(pi));
490                            self.printer.print_str(" c=");
491                            if c == AWFUL_BAD {
492                                self.printer.print_char('*' as u32);
493                            } else {
494                                self.printer.print_int(i64::from(c));
495                            }
496                            if c <= self.page.least_cost {
497                                self.printer.print_char('#' as u32);
498                            }
499                            self.end_diagnostic(false);
500                        }
501                        if c <= self.page.least_cost {
502                            self.page.best_break = p;
503                            self.page.best_size = self.page.so_far[0];
504                            self.page.best_so_far = self.page.so_far;
505                            self.page.best_infinite = self.page.fi_stretch != 0
506                                || self.page.so_far[3..6].iter().any(|&s| s != 0);
507                            self.page.least_cost = c;
508                            for r in self.page.ins.iter_mut() {
509                                r.best_ins_ptr = r.last_ins_ptr;
510                            }
511                        }
512                        if c == AWFUL_BAD || pi <= EJECT_PENALTY {
513                            self.fire_up(p);
514                            if self.ts.output_active {
515                                return;
516                            }
517                            // The page has been shipped out by the default
518                            // output routine.
519                            continue;
520                        }
521                    }
522                    if t == id::GLUE || t == id::KERN {
523                        update = true;
524                    }
525                }
526                _ => {}
527            }
528            if update {
529                if t == id::KERN {
530                    self.page.so_far[1] += self.page.so_far[7] + self.nodes.width(p);
531                } else {
532                    let q = self.nodes.glue_spec(p);
533                    *self.page.stretch_mut(q.stretch_order) += q.stretch;
534                    self.page.so_far[6] += q.shrink;
535                    if q.shrink_order != 0 && q.shrink != 0 {
536                        self.print_err("Infinite glue shrinkage found on current page");
537                        self.help(&[
538                            "The page about to be output contains some infinitely",
539                            "shrinkable glue, e.g., `\\vss' or `\\vskip 0pt minus 1fil'.",
540                            "Such glue doesn't belong there; but you can safely proceed,",
541                            "since the offensive shrinkability has been made finite.",
542                        ]);
543                        self.error();
544                        if let NodeData::Glue { spec, .. } = self.nodes.data_mut(p) {
545                            spec.shrink_order = 0;
546                        }
547                    }
548                    self.page.so_far[1] += self.page.so_far[7] + q.width;
549                }
550                self.page.so_far[7] = 0;
551            }
552            // contribute: make sure page_max_depth is not exceeded.
553            if self.page.so_far[7] > self.page.max_depth {
554                self.page.so_far[1] += self.page.so_far[7] - self.page.max_depth;
555                self.page.so_far[7] = self.page.max_depth;
556            }
557            // Link node p into the current page.
558            let n = self.nodes.next(p);
559            let pt = self.page.page_tail;
560            self.nodes.link(pt, p);
561            self.page.page_tail = p;
562            self.nodes.set_next(ch, n);
563            if !n.is_null() {
564                self.nodes.set_prev(n, ch);
565            }
566            self.nodes.set_next(p, NodeRef::NULL);
567        }
568        // Make the contribution list empty.
569        self.set_contrib_tail(ch);
570    }
571
572    /// The badness of the current page.
573    fn page_badness(&self) -> i32 {
574        let s = &self.page.so_far;
575        let (goal, total) = (s[0], s[1]);
576        if total < goal {
577            if self.page.fi_stretch != 0 || s[3] != 0 || s[4] != 0 || s[5] != 0 {
578                0
579            } else {
580                retex_arith::badness(goal - total, s[2])
581            }
582        } else if total - goal > s[6] {
583            AWFUL_BAD
584        } else {
585            retex_arith::badness(total - goal, s[6])
586        }
587    }
588
589    /// Appends an insertion to the current page (§1008).
590    fn append_insertion(&mut self, p: NodeRef) {
591        if self.page.contents == PageContents::Empty {
592            self.freeze_page_specs(PageContents::InsertsOnly);
593        }
594        let n = u32::from(self.nodes.subtype(p));
595        let (pheight, pdepth, pcost, plist) = match self.nodes.data(p) {
596            NodeData::Ins {
597                height,
598                depth,
599                cost,
600                head,
601                ..
602            } => (*height, *depth, *cost, *head),
603            _ => return,
604        };
605        let idx = match self.page.ins.iter().position(|r| r.n >= n) {
606            Some(i) if self.page.ins[i].n == n => i,
607            pos => {
608                // Create a page insertion record.
609                let i = pos.unwrap_or(self.page.ins.len());
610                self.ensure_vbox(n);
611                let b = self.eqtb.boxes[n as usize].val;
612                let height = if b.is_null() {
613                    0
614                } else {
615                    self.node_height(b) + self.node_depth(b)
616                };
617                self.page.ins.insert(
618                    i,
619                    PageIns {
620                        n,
621                        split_up: false,
622                        height,
623                        broken_ptr: NodeRef::NULL,
624                        broken_ins: NodeRef::NULL,
625                        last_ins_ptr: NodeRef::NULL,
626                        best_ins_ptr: NodeRef::NULL,
627                    },
628                );
629                let q = self.eqtb.skip[n as usize].val;
630                let c = self.eqtb.count[n as usize].val;
631                let h = if c == 1000 {
632                    height
633                } else {
634                    x_over_n(height, 1000).wrapping_mul(c)
635                };
636                self.page.so_far[0] = self.page.so_far[0] - h - q.width;
637                *self.page.stretch_mut(q.stretch_order) += q.stretch;
638                self.page.so_far[6] += q.shrink;
639                if q.shrink_order != 0 && q.shrink != 0 {
640                    self.print_err("Infinite glue shrinkage inserted from ");
641                    self.print_esc_str("skip");
642                    self.printer.print_int(i64::from(n));
643                    self.help(&[
644                        "The correction glue for page breaking with insertions",
645                        "must have finite shrinkability. But you may proceed,",
646                        "since the offensive shrinkability has been made finite.",
647                    ]);
648                    self.error();
649                }
650                i
651            }
652        };
653        if self.page.ins[idx].split_up {
654            self.insert_penalties += pcost;
655            return;
656        }
657        self.page.ins[idx].last_ins_ptr = p;
658        let s = &self.page.so_far;
659        let delta = s[0] - s[1] - s[7] + s[6];
660        let c = self.eqtb.count[n as usize].val;
661        let h = if c == 1000 {
662            pheight
663        } else {
664            x_over_n(pheight, 1000).wrapping_mul(c)
665        };
666        let dimen_n = self.eqtb.dimen[n as usize].val;
667        if (h <= 0 || h <= delta) && pheight + self.page.ins[idx].height <= dimen_n {
668            self.page.so_far[0] -= h;
669            self.page.ins[idx].height += pheight;
670        } else {
671            // Find the best way to split the insertion.
672            let mut w = if c <= 0 {
673                retex_arith::MAX_DIMEN
674            } else {
675                let mut w = self.page.so_far[0] - self.page.so_far[1] - self.page.so_far[7];
676                if c != 1000 {
677                    w = x_over_n(w, c).wrapping_mul(1000);
678                }
679                w
680            };
681            if w > dimen_n - self.page.ins[idx].height {
682                w = dimen_n - self.page.ins[idx].height;
683            }
684            let (q, mut bhpd) = self.vert_break(plist, w, pdepth);
685            self.page.ins[idx].height += bhpd;
686            if self.eqtb.int(ip::TRACINGPAGES) > 0 {
687                self.begin_diagnostic();
688                self.printer.print_nl("% split");
689                self.printer.print_int(i64::from(n));
690                self.printer.print_str(" to ");
691                self.printer.print_scaled(w);
692                self.printer.print_char(',' as u32);
693                self.printer.print_scaled(bhpd);
694                self.printer.print_str(" p=");
695                if q.is_null() {
696                    self.printer.print_int(i64::from(EJECT_PENALTY));
697                } else if self.nodes.id(q) == id::PENALTY {
698                    self.printer.print_int(i64::from(self.nodes.penalty(q)));
699                } else {
700                    self.printer.print_char('0' as u32);
701                }
702                self.end_diagnostic(false);
703            }
704            if c != 1000 {
705                bhpd = x_over_n(bhpd, 1000).wrapping_mul(c);
706            }
707            self.page.so_far[0] -= bhpd;
708            let r = &mut self.page.ins[idx];
709            r.split_up = true;
710            r.broken_ptr = q;
711            r.broken_ins = p;
712            if q.is_null() {
713                self.insert_penalties += EJECT_PENALTY;
714            } else if self.nodes.id(q) == id::PENALTY {
715                self.insert_penalties += self.nodes.penalty(q);
716            }
717        }
718    }
719
720    /// Outputs the current page at the best place (`fire_up`).
721    fn fire_up(&mut self, c: NodeRef) {
722        let bpb = self.page.best_break;
723        if self.diagnostics.is_some() {
724            let s = self.page.best_so_far;
725            let forced = !bpb.is_null()
726                && self.nodes.id(bpb) == id::PENALTY
727                && self.nodes.penalty(bpb) <= EJECT_PENALTY;
728            self.page.last_break = Some(crate::pagenote::PageBreakInfo {
729                page: self.eqtb.count[0].val,
730                goal: s[0],
731                total: s[1],
732                stretch: s[2],
733                infinite: self.page.best_infinite,
734                forced,
735                moved: if forced { None } else { self.first_moved(bpb) },
736            });
737        }
738        // Set the value of \outputpenalty.
739        if !bpb.is_null() && self.nodes.id(bpb) == id::PENALTY {
740            let pen = self.nodes.penalty(bpb);
741            self.geq_define(Loc::IntPar(ip::OUTPUTPENALTY), Val::Int(i64::from(pen)));
742            if let NodeData::Penalty { penalty } = self.nodes.data_mut(bpb) {
743                *penalty = INF_PENALTY;
744            }
745        } else {
746            self.geq_define(
747                Loc::IntPar(ip::OUTPUTPENALTY),
748                Val::Int(i64::from(INF_PENALTY)),
749            );
750        }
751        for m in self.marks.classes.values_mut() {
752            if m[mark_code::BOT].is_some() {
753                m[mark_code::TOP] = m[mark_code::BOT].clone();
754                m[mark_code::FIRST] = None;
755            }
756        }
757        if c == self.page.best_break {
758            self.page.best_break = NodeRef::NULL;
759        }
760        let best_break = self.page.best_break;
761        // Ensure that box 255 is empty before output.
762        let obox = self.output_box_num() as u32;
763        if !self.eqtb.boxes[obox as usize].val.is_null() {
764            self.print_err("");
765            self.print_esc_str("box");
766            self.printer.print_int(i64::from(obox));
767            self.printer.print_str(" is not void");
768            self.help(&[
769                "You shouldn't use \\box255 except in \\output routines.",
770                "Proceed, and I'll discard its present contents.",
771            ]);
772            self.box_error(obox);
773        }
774        self.insert_penalties = 0;
775        let save_split_top_skip = self.eqtb.glue_par[gp::SPLITTOPSKIP as usize].val;
776        let holding = self.eqtb.int(ip::HOLDINGINSERTS) > 0;
777        if !holding {
778            // Prepare all the boxes involved in insertions to act as
779            // queues.
780            for i in 0..self.page.ins.len() {
781                if !self.page.ins[i].best_ins_ptr.is_null() {
782                    let n = self.page.ins[i].n;
783                    self.ensure_vbox(n);
784                    if self.eqtb.boxes[n as usize].val.is_null() {
785                        let b = self.nodes.new_null_box(false);
786                        self.eqtb.boxes[n as usize].val = b;
787                    }
788                    let b = self.eqtb.boxes[n as usize].val;
789                    let l = self.nodes.list_ptr(b);
790                    self.page.ins[i].last_ins_ptr = self.nodes.tail_of(l);
791                }
792            }
793        }
794        // Held-over insertions are collected after `hold_head`.
795        let hold_head = self.nodes.new_temp();
796        let mut q = hold_head;
797        let mut prev_p = self.page.page_head;
798        let mut p = self.nodes.next(prev_p);
799        while p != best_break && !p.is_null() {
800            let t = self.nodes.id(p);
801            if t == id::INS {
802                if !holding {
803                    let n = u32::from(self.nodes.subtype(p));
804                    let Some(ri) = self.page.ins.iter().position(|r| r.n == n) else {
805                        prev_p = p;
806                        p = self.nodes.next(prev_p);
807                        continue;
808                    };
809                    let mut wait;
810                    if self.page.ins[ri].best_ins_ptr.is_null() {
811                        wait = true;
812                    } else {
813                        wait = false;
814                        let bx = self.eqtb.boxes[n as usize].val;
815                        let start = self.page.ins[ri].last_ins_ptr;
816                        let il = self.nodes.list_ptr(p);
817                        if start.is_null() {
818                            self.nodes.set_list_ptr(bx, il);
819                        } else {
820                            self.nodes.link(start, il);
821                        }
822                        if self.page.ins[ri].best_ins_ptr == p {
823                            // Wrap up the box, splitting node p if called
824                            // for.
825                            let r = self.page.ins[ri];
826                            if r.split_up && r.broken_ins == p && !r.broken_ptr.is_null() {
827                                if start.is_null() && self.nodes.list_ptr(bx) == r.broken_ptr {
828                                    self.nodes.set_list_ptr(bx, NodeRef::NULL);
829                                } else {
830                                    let mut s = if start.is_null() {
831                                        self.nodes.list_ptr(bx)
832                                    } else {
833                                        start
834                                    };
835                                    while !s.is_null() && self.nodes.next(s) != r.broken_ptr {
836                                        s = self.nodes.next(s);
837                                    }
838                                    if !s.is_null() {
839                                        self.nodes.set_next(s, NodeRef::NULL);
840                                    }
841                                }
842                                let sts = match self.nodes.data(p) {
843                                    NodeData::Ins { spec, .. } => *spec,
844                                    _ => Glue::ZERO,
845                                };
846                                self.eqtb.glue_par[gp::SPLITTOPSKIP as usize].val = sts;
847                                let rest = self.prune_page_top(r.broken_ptr, false);
848                                self.nodes.set_list_ptr(p, rest);
849                                if !rest.is_null() {
850                                    let tmp = self.vpack(rest, PackSpec::NATURAL);
851                                    let hh = self.node_height(tmp) + self.node_depth(tmp);
852                                    self.nodes.set_list_ptr(tmp, NodeRef::NULL);
853                                    self.nodes.free(tmp);
854                                    if let NodeData::Ins { height, .. } = self.nodes.data_mut(p) {
855                                        *height = hh;
856                                    }
857                                    wait = true;
858                                }
859                            }
860                            self.page.ins[ri].best_ins_ptr = NodeRef::NULL;
861                            let l = self.nodes.list_ptr(bx);
862                            self.nodes.set_list_ptr(bx, NodeRef::NULL);
863                            self.nodes.free(bx);
864                            let nb = self.vpack(l, PackSpec::NATURAL);
865                            self.eqtb.boxes[n as usize].val = nb;
866                        } else {
867                            let l = self.nodes.list_ptr(bx);
868                            self.page.ins[ri].last_ins_ptr = self.nodes.tail_of(l);
869                        }
870                    }
871                    // Either append the insertion node p after node q, or
872                    // delete it.
873                    let nx = self.nodes.next(p);
874                    self.nodes.set_next(prev_p, nx);
875                    if !nx.is_null() {
876                        self.nodes.set_prev(nx, prev_p);
877                    }
878                    self.nodes.set_next(p, NodeRef::NULL);
879                    if wait {
880                        self.nodes.link(q, p);
881                        q = p;
882                        self.insert_penalties += 1;
883                    } else {
884                        self.nodes.set_list_ptr(p, NodeRef::NULL);
885                        self.nodes.free(p);
886                    }
887                    p = prev_p;
888                }
889            } else if let NodeData::Mark { class, mark } = self.nodes.data(p) {
890                let (cl, m) = (*class, mark.clone());
891                let e = self.marks.classes.entry(cl).or_default();
892                if e[mark_code::FIRST].is_none() {
893                    e[mark_code::FIRST] = Some(m.clone());
894                }
895                e[mark_code::BOT] = Some(m);
896            }
897            prev_p = p;
898            p = self.nodes.next(prev_p);
899        }
900        self.eqtb.glue_par[gp::SPLITTOPSKIP as usize].val = save_split_top_skip;
901        // Break the current page at node p, put it in box 255, and put the
902        // remaining nodes on the contribution list.
903        let ch = self.page.contrib_head;
904        if !p.is_null() {
905            if self.nodes.next(ch).is_null() {
906                let pt = self.page.page_tail;
907                self.set_contrib_tail(pt);
908            }
909            let pt = self.page.page_tail;
910            let cn = self.nodes.next(ch);
911            self.nodes.set_next(pt, cn);
912            if !cn.is_null() {
913                self.nodes.set_prev(cn, pt);
914            }
915            self.nodes.link(ch, p);
916            self.nodes.set_next(prev_p, NodeRef::NULL);
917        }
918        let save_vbadness = self.eqtb.int_par[ip::VBADNESS as usize].val;
919        let save_vfuzz = self.eqtb.dimen_par[dp::VFUZZ as usize].val;
920        self.eqtb.int_par[ip::VBADNESS as usize].val = INF_BAD;
921        self.eqtb.dimen_par[dp::VFUZZ as usize].val = retex_arith::MAX_DIMEN;
922        let first = self.nodes.next(self.page.page_head);
923        let b = self.vpackage(
924            first,
925            PackSpec::Exactly(self.page.best_size),
926            self.page.max_depth,
927        );
928        self.eqtb.boxes[obox as usize].val = b;
929        self.eqtb.int_par[ip::VBADNESS as usize].val = save_vbadness;
930        self.eqtb.dimen_par[dp::VFUZZ as usize].val = save_vfuzz;
931        self.start_new_page();
932        if q != hold_head {
933            let h = self.nodes.next(hold_head);
934            self.nodes.link(self.page.page_head, h);
935            self.page.page_tail = q;
936        }
937        self.nodes.free(hold_head);
938        self.page.ins.clear();
939        for m in self.marks.classes.values_mut() {
940            if m[mark_code::TOP].is_some() && m[mark_code::FIRST].is_none() {
941                m[mark_code::FIRST] = m[mark_code::TOP].clone();
942            }
943        }
944        let output = self
945            .eqtb
946            .toks_param(crate::primitives_gen::assign_toks::OUTPUT)
947            .clone();
948        if !output.is_empty() {
949            if self.dead_cycles >= self.eqtb.int(ip::MAXDEADCYCLES) {
950                self.print_err("Output loop---");
951                self.printer.print_int(i64::from(self.dead_cycles));
952                self.printer.print_str(" consecutive dead cycles");
953                self.help(&[
954                    "I've concluded that your \\output is awry; it never does a",
955                    "\\shipout, so I'm shipping \\box255 out myself. Next time",
956                    "increase \\maxdeadcycles if you want me to be more patient!",
957                ]);
958                self.error();
959            } else {
960                // Fire up the user's output routine.
961                self.ts.output_active = true;
962                self.dead_cycles += 1;
963                self.push_nest(Mode::InternalVertical);
964                self.cur_list_mut().prev_depth = IGNORE_DEPTH;
965                let line = self.input.line();
966                self.cur_list_mut().mode_line = -line;
967                self.begin_token_list(output, TokKind::OutputText);
968                self.new_save_level(GroupCode::Output);
969                self.normal_paragraph();
970                self.scan_left_brace();
971                return;
972            }
973        }
974        // Perform the default output routine.
975        let ph = self.page.page_head;
976        let first = self.nodes.next(ph);
977        if !first.is_null() {
978            if self.nodes.next(ch).is_null() {
979                let pt = self.page.page_tail;
980                self.set_contrib_tail(pt);
981            } else {
982                let pt = self.page.page_tail;
983                let cn = self.nodes.next(ch);
984                self.nodes.link(pt, cn);
985            }
986            self.nodes.link(ch, first);
987            self.nodes.set_next(ph, NodeRef::NULL);
988            self.page.page_tail = ph;
989        }
990        let pd = std::mem::take(&mut self.ts.disc_ptr[2]);
991        self.nodes.flush_list(pd);
992        let b = self.eqtb.boxes[obox as usize].val;
993        self.eqtb.boxes[obox as usize].val = NodeRef::NULL;
994        if !b.is_null() {
995            self.ship_out(b);
996        }
997    }
998
999    /// Resumes the page builder after the output routine (§1026).
1000    pub fn resume_output(&mut self) {
1001        let ok = matches!(
1002            self.input.stack.last(),
1003            Some(Level::Tokens(t)) if t.loc >= t.list.len()
1004                && (t.kind == TokKind::OutputText || t.kind == TokKind::BackedUp)
1005        );
1006        if !ok {
1007            self.print_err("Unbalanced output routine");
1008            self.help(&[
1009                "Your sneaky output routine has problematic {'s and/or }'s.",
1010                "I can't handle that very well; good luck.",
1011            ]);
1012            self.error();
1013            let mut guard = 0;
1014            loop {
1015                self.get_token();
1016                guard += 1;
1017                let done = matches!(
1018                    self.input.stack.last(),
1019                    Some(Level::Tokens(t)) if t.loc >= t.list.len()
1020                );
1021                if done || guard > 1_000_000 {
1022                    break;
1023                }
1024            }
1025        }
1026        self.end_token_list();
1027        self.end_graf();
1028        self.unsave();
1029        self.ts.output_active = false;
1030        self.insert_penalties = 0;
1031        // Ensure that box 255 is empty after output.
1032        let obox = self.output_box_num() as u32;
1033        if !self.eqtb.boxes[obox as usize].val.is_null() {
1034            self.print_err("Output routine didn't use all of ");
1035            self.print_esc_str("box");
1036            self.printer.print_int(i64::from(obox));
1037            self.help(&[
1038                "Your \\output commands should empty \\box255,",
1039                "e.g., by saying `\\shipout\\box255'.",
1040                "Proceed; I'll discard its present contents.",
1041            ]);
1042            self.box_error(obox);
1043        }
1044        let (head, tail) = (self.cur_list().head, self.cur_list().tail);
1045        if tail != head {
1046            let pt = self.page.page_tail;
1047            let first = self.nodes.next(head);
1048            self.nodes.link(pt, first);
1049            self.page.page_tail = tail;
1050            self.nodes.set_next(head, NodeRef::NULL);
1051        }
1052        let ph = self.page.page_head;
1053        let ch = self.page.contrib_head;
1054        let first = self.nodes.next(ph);
1055        if !first.is_null() {
1056            if self.nodes.next(ch).is_null() {
1057                let pt = self.page.page_tail;
1058                self.set_contrib_tail(pt);
1059            }
1060            let pt = self.page.page_tail;
1061            let cn = self.nodes.next(ch);
1062            self.nodes.set_next(pt, cn);
1063            if !cn.is_null() {
1064                self.nodes.set_prev(cn, pt);
1065            }
1066            self.nodes.link(ch, first);
1067            self.nodes.set_next(ph, NodeRef::NULL);
1068            self.page.page_tail = ph;
1069        }
1070        let pd = std::mem::take(&mut self.ts.disc_ptr[2]);
1071        self.nodes.flush_list(pd);
1072        self.pop_nest();
1073        self.build_page_from("after_output");
1074    }
1075}
1076
1077/// `x_over_n` returning only the quotient (0 on overflow).
1078fn x_over_n(x: i32, n: i32) -> i32 {
1079    retex_arith::x_over_n(x, n).map_or(0, |d| d.quotient)
1080}