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        self.figures.taint("runs the output routine");
723        let bpb = self.page.best_break;
724        if self.diagnostics.is_some() {
725            let s = self.page.best_so_far;
726            let forced = !bpb.is_null()
727                && self.nodes.id(bpb) == id::PENALTY
728                && self.nodes.penalty(bpb) <= EJECT_PENALTY;
729            self.page.last_break = Some(crate::pagenote::PageBreakInfo {
730                page: self.eqtb.count[0].val,
731                goal: s[0],
732                total: s[1],
733                stretch: s[2],
734                infinite: self.page.best_infinite,
735                forced,
736                moved: if forced { None } else { self.first_moved(bpb) },
737            });
738        }
739        // Set the value of \outputpenalty.
740        if !bpb.is_null() && self.nodes.id(bpb) == id::PENALTY {
741            let pen = self.nodes.penalty(bpb);
742            self.geq_define(Loc::IntPar(ip::OUTPUTPENALTY), Val::Int(i64::from(pen)));
743            if let NodeData::Penalty { penalty } = self.nodes.data_mut(bpb) {
744                *penalty = INF_PENALTY;
745            }
746        } else {
747            self.geq_define(
748                Loc::IntPar(ip::OUTPUTPENALTY),
749                Val::Int(i64::from(INF_PENALTY)),
750            );
751        }
752        for m in self.marks.classes.values_mut() {
753            if m[mark_code::BOT].is_some() {
754                m[mark_code::TOP] = m[mark_code::BOT].clone();
755                m[mark_code::FIRST] = None;
756            }
757        }
758        if c == self.page.best_break {
759            self.page.best_break = NodeRef::NULL;
760        }
761        let best_break = self.page.best_break;
762        // Ensure that box 255 is empty before output.
763        let obox = self.output_box_num() as u32;
764        if !self.eqtb.boxes[obox as usize].val.is_null() {
765            self.print_err("");
766            self.print_esc_str("box");
767            self.printer.print_int(i64::from(obox));
768            self.printer.print_str(" is not void");
769            self.help(&[
770                "You shouldn't use \\box255 except in \\output routines.",
771                "Proceed, and I'll discard its present contents.",
772            ]);
773            self.box_error(obox);
774        }
775        self.insert_penalties = 0;
776        let save_split_top_skip = self.eqtb.glue_par[gp::SPLITTOPSKIP as usize].val;
777        let holding = self.eqtb.int(ip::HOLDINGINSERTS) > 0;
778        if !holding {
779            // Prepare all the boxes involved in insertions to act as
780            // queues.
781            for i in 0..self.page.ins.len() {
782                if !self.page.ins[i].best_ins_ptr.is_null() {
783                    let n = self.page.ins[i].n;
784                    self.ensure_vbox(n);
785                    if self.eqtb.boxes[n as usize].val.is_null() {
786                        let b = self.nodes.new_null_box(false);
787                        self.eqtb.boxes[n as usize].val = b;
788                    }
789                    let b = self.eqtb.boxes[n as usize].val;
790                    let l = self.nodes.list_ptr(b);
791                    self.page.ins[i].last_ins_ptr = self.nodes.tail_of(l);
792                }
793            }
794        }
795        // Held-over insertions are collected after `hold_head`.
796        let hold_head = self.nodes.new_temp();
797        let mut q = hold_head;
798        let mut prev_p = self.page.page_head;
799        let mut p = self.nodes.next(prev_p);
800        while p != best_break && !p.is_null() {
801            let t = self.nodes.id(p);
802            if t == id::INS {
803                if !holding {
804                    let n = u32::from(self.nodes.subtype(p));
805                    let Some(ri) = self.page.ins.iter().position(|r| r.n == n) else {
806                        prev_p = p;
807                        p = self.nodes.next(prev_p);
808                        continue;
809                    };
810                    let mut wait;
811                    if self.page.ins[ri].best_ins_ptr.is_null() {
812                        wait = true;
813                    } else {
814                        wait = false;
815                        let bx = self.eqtb.boxes[n as usize].val;
816                        let start = self.page.ins[ri].last_ins_ptr;
817                        let il = self.nodes.list_ptr(p);
818                        if start.is_null() {
819                            self.nodes.set_list_ptr(bx, il);
820                        } else {
821                            self.nodes.link(start, il);
822                        }
823                        if self.page.ins[ri].best_ins_ptr == p {
824                            // Wrap up the box, splitting node p if called
825                            // for.
826                            let r = self.page.ins[ri];
827                            if r.split_up && r.broken_ins == p && !r.broken_ptr.is_null() {
828                                if start.is_null() && self.nodes.list_ptr(bx) == r.broken_ptr {
829                                    self.nodes.set_list_ptr(bx, NodeRef::NULL);
830                                } else {
831                                    let mut s = if start.is_null() {
832                                        self.nodes.list_ptr(bx)
833                                    } else {
834                                        start
835                                    };
836                                    while !s.is_null() && self.nodes.next(s) != r.broken_ptr {
837                                        s = self.nodes.next(s);
838                                    }
839                                    if !s.is_null() {
840                                        self.nodes.set_next(s, NodeRef::NULL);
841                                    }
842                                }
843                                let sts = match self.nodes.data(p) {
844                                    NodeData::Ins { spec, .. } => *spec,
845                                    _ => Glue::ZERO,
846                                };
847                                self.eqtb.glue_par[gp::SPLITTOPSKIP as usize].val = sts;
848                                let rest = self.prune_page_top(r.broken_ptr, false);
849                                self.nodes.set_list_ptr(p, rest);
850                                if !rest.is_null() {
851                                    let tmp = self.vpack(rest, PackSpec::NATURAL);
852                                    let hh = self.node_height(tmp) + self.node_depth(tmp);
853                                    self.nodes.set_list_ptr(tmp, NodeRef::NULL);
854                                    self.nodes.free(tmp);
855                                    if let NodeData::Ins { height, .. } = self.nodes.data_mut(p) {
856                                        *height = hh;
857                                    }
858                                    wait = true;
859                                }
860                            }
861                            self.page.ins[ri].best_ins_ptr = NodeRef::NULL;
862                            let l = self.nodes.list_ptr(bx);
863                            self.nodes.set_list_ptr(bx, NodeRef::NULL);
864                            self.nodes.free(bx);
865                            let nb = self.vpack(l, PackSpec::NATURAL);
866                            self.eqtb.boxes[n as usize].val = nb;
867                        } else {
868                            let l = self.nodes.list_ptr(bx);
869                            self.page.ins[ri].last_ins_ptr = self.nodes.tail_of(l);
870                        }
871                    }
872                    // Either append the insertion node p after node q, or
873                    // delete it.
874                    let nx = self.nodes.next(p);
875                    self.nodes.set_next(prev_p, nx);
876                    if !nx.is_null() {
877                        self.nodes.set_prev(nx, prev_p);
878                    }
879                    self.nodes.set_next(p, NodeRef::NULL);
880                    if wait {
881                        self.nodes.link(q, p);
882                        q = p;
883                        self.insert_penalties += 1;
884                    } else {
885                        self.nodes.set_list_ptr(p, NodeRef::NULL);
886                        self.nodes.free(p);
887                    }
888                    p = prev_p;
889                }
890            } else if let NodeData::Mark { class, mark } = self.nodes.data(p) {
891                let (cl, m) = (*class, mark.clone());
892                let e = self.marks.classes.entry(cl).or_default();
893                if e[mark_code::FIRST].is_none() {
894                    e[mark_code::FIRST] = Some(m.clone());
895                }
896                e[mark_code::BOT] = Some(m);
897            }
898            prev_p = p;
899            p = self.nodes.next(prev_p);
900        }
901        self.eqtb.glue_par[gp::SPLITTOPSKIP as usize].val = save_split_top_skip;
902        // Break the current page at node p, put it in box 255, and put the
903        // remaining nodes on the contribution list.
904        let ch = self.page.contrib_head;
905        if !p.is_null() {
906            if self.nodes.next(ch).is_null() {
907                let pt = self.page.page_tail;
908                self.set_contrib_tail(pt);
909            }
910            let pt = self.page.page_tail;
911            let cn = self.nodes.next(ch);
912            self.nodes.set_next(pt, cn);
913            if !cn.is_null() {
914                self.nodes.set_prev(cn, pt);
915            }
916            self.nodes.link(ch, p);
917            self.nodes.set_next(prev_p, NodeRef::NULL);
918        }
919        let save_vbadness = self.eqtb.int_par[ip::VBADNESS as usize].val;
920        let save_vfuzz = self.eqtb.dimen_par[dp::VFUZZ as usize].val;
921        self.eqtb.int_par[ip::VBADNESS as usize].val = INF_BAD;
922        self.eqtb.dimen_par[dp::VFUZZ as usize].val = retex_arith::MAX_DIMEN;
923        let first = self.nodes.next(self.page.page_head);
924        let b = self.vpackage(
925            first,
926            PackSpec::Exactly(self.page.best_size),
927            self.page.max_depth,
928        );
929        self.eqtb.boxes[obox as usize].val = b;
930        self.eqtb.int_par[ip::VBADNESS as usize].val = save_vbadness;
931        self.eqtb.dimen_par[dp::VFUZZ as usize].val = save_vfuzz;
932        self.start_new_page();
933        if q != hold_head {
934            let h = self.nodes.next(hold_head);
935            self.nodes.link(self.page.page_head, h);
936            self.page.page_tail = q;
937        }
938        self.nodes.free(hold_head);
939        self.page.ins.clear();
940        for m in self.marks.classes.values_mut() {
941            if m[mark_code::TOP].is_some() && m[mark_code::FIRST].is_none() {
942                m[mark_code::FIRST] = m[mark_code::TOP].clone();
943            }
944        }
945        let output = self
946            .eqtb
947            .toks_param(crate::primitives_gen::assign_toks::OUTPUT)
948            .clone();
949        if !output.is_empty() {
950            if self.dead_cycles >= self.eqtb.int(ip::MAXDEADCYCLES) {
951                self.print_err("Output loop---");
952                self.printer.print_int(i64::from(self.dead_cycles));
953                self.printer.print_str(" consecutive dead cycles");
954                self.help(&[
955                    "I've concluded that your \\output is awry; it never does a",
956                    "\\shipout, so I'm shipping \\box255 out myself. Next time",
957                    "increase \\maxdeadcycles if you want me to be more patient!",
958                ]);
959                self.error();
960            } else {
961                // Fire up the user's output routine.
962                self.ts.output_active = true;
963                self.dead_cycles += 1;
964                self.push_nest(Mode::InternalVertical);
965                self.cur_list_mut().prev_depth = IGNORE_DEPTH;
966                let line = self.input.line();
967                self.cur_list_mut().mode_line = -line;
968                self.begin_token_list(output, TokKind::OutputText);
969                self.new_save_level(GroupCode::Output);
970                self.normal_paragraph();
971                self.scan_left_brace();
972                return;
973            }
974        }
975        // Perform the default output routine.
976        let ph = self.page.page_head;
977        let first = self.nodes.next(ph);
978        if !first.is_null() {
979            if self.nodes.next(ch).is_null() {
980                let pt = self.page.page_tail;
981                self.set_contrib_tail(pt);
982            } else {
983                let pt = self.page.page_tail;
984                let cn = self.nodes.next(ch);
985                self.nodes.link(pt, cn);
986            }
987            self.nodes.link(ch, first);
988            self.nodes.set_next(ph, NodeRef::NULL);
989            self.page.page_tail = ph;
990        }
991        let pd = std::mem::take(&mut self.ts.disc_ptr[2]);
992        self.nodes.flush_list(pd);
993        let b = self.eqtb.boxes[obox as usize].val;
994        self.eqtb.boxes[obox as usize].val = NodeRef::NULL;
995        if !b.is_null() {
996            self.ship_out(b);
997        }
998    }
999
1000    /// Resumes the page builder after the output routine (§1026).
1001    pub fn resume_output(&mut self) {
1002        let ok = matches!(
1003            self.input.stack.last(),
1004            Some(Level::Tokens(t)) if t.loc >= t.list.len()
1005                && (t.kind == TokKind::OutputText || t.kind == TokKind::BackedUp)
1006        );
1007        if !ok {
1008            self.print_err("Unbalanced output routine");
1009            self.help(&[
1010                "Your sneaky output routine has problematic {'s and/or }'s.",
1011                "I can't handle that very well; good luck.",
1012            ]);
1013            self.error();
1014            let mut guard = 0;
1015            loop {
1016                self.get_token();
1017                guard += 1;
1018                let done = matches!(
1019                    self.input.stack.last(),
1020                    Some(Level::Tokens(t)) if t.loc >= t.list.len()
1021                );
1022                if done || guard > 1_000_000 {
1023                    break;
1024                }
1025            }
1026        }
1027        self.end_token_list();
1028        self.end_graf();
1029        self.unsave();
1030        self.ts.output_active = false;
1031        self.insert_penalties = 0;
1032        // Ensure that box 255 is empty after output.
1033        let obox = self.output_box_num() as u32;
1034        if !self.eqtb.boxes[obox as usize].val.is_null() {
1035            self.print_err("Output routine didn't use all of ");
1036            self.print_esc_str("box");
1037            self.printer.print_int(i64::from(obox));
1038            self.help(&[
1039                "Your \\output commands should empty \\box255,",
1040                "e.g., by saying `\\shipout\\box255'.",
1041                "Proceed; I'll discard its present contents.",
1042            ]);
1043            self.box_error(obox);
1044        }
1045        let (head, tail) = (self.cur_list().head, self.cur_list().tail);
1046        if tail != head {
1047            let pt = self.page.page_tail;
1048            let first = self.nodes.next(head);
1049            self.nodes.link(pt, first);
1050            self.page.page_tail = tail;
1051            self.nodes.set_next(head, NodeRef::NULL);
1052        }
1053        let ph = self.page.page_head;
1054        let ch = self.page.contrib_head;
1055        let first = self.nodes.next(ph);
1056        if !first.is_null() {
1057            if self.nodes.next(ch).is_null() {
1058                let pt = self.page.page_tail;
1059                self.set_contrib_tail(pt);
1060            }
1061            let pt = self.page.page_tail;
1062            let cn = self.nodes.next(ch);
1063            self.nodes.set_next(pt, cn);
1064            if !cn.is_null() {
1065                self.nodes.set_prev(cn, pt);
1066            }
1067            self.nodes.link(ch, first);
1068            self.nodes.set_next(ph, NodeRef::NULL);
1069            self.page.page_tail = ph;
1070        }
1071        let pd = std::mem::take(&mut self.ts.disc_ptr[2]);
1072        self.nodes.flush_list(pd);
1073        self.pop_nest();
1074        self.build_page_from("after_output");
1075    }
1076}
1077
1078/// `x_over_n` returning only the quotient (0 on overflow).
1079fn x_over_n(x: i32, n: i32) -> i32 {
1080    retex_arith::x_over_n(x, n).map_or(0, |d| d.quotient)
1081}