Skip to main content

retex_engine/
pack.rs

1//! Packaging: `hpack` and `vpackage` (*TeX: The Program* §644–§679).
2//!
3//! The over/underfull box messages follow TeX82; LuaTeX's box display adds
4//! the box direction (see [`crate::showbox`]).
5
6use crate::Engine;
7use crate::build::PackSpec;
8use crate::nodes::{BoxData, NodeData, NodeRef, glue_sub, id};
9use crate::primitives_gen::{assign_dimen as dp, assign_int as ip};
10
11/// Totals of stretch and shrink by glue order (`total_stretch`,
12/// `total_shrink`).
13#[derive(Default, Clone, Copy)]
14struct Totals {
15    stretch: [i32; 5],
16    shrink: [i32; 5],
17}
18
19impl Totals {
20    #[inline]
21    fn add(&mut self, g: &crate::glue::Glue) {
22        let so = (g.stretch_order as usize).min(4);
23        let ho = (g.shrink_order as usize).min(4);
24        self.stretch[so] = self.stretch[so].wrapping_add(g.stretch);
25        self.shrink[ho] = self.shrink[ho].wrapping_add(g.shrink);
26    }
27
28    fn stretch_order(&self) -> u8 {
29        (1..5).rev().find(|&o| self.stretch[o] != 0).unwrap_or(0) as u8
30    }
31
32    fn shrink_order(&self) -> u8 {
33        (1..5).rev().find(|&o| self.shrink[o] != 0).unwrap_or(0) as u8
34    }
35}
36
37impl Engine {
38    /// With `\tracinglostchars` 4 or more (LaTeX's setting), reports glyph
39    /// `p` if its font lacks its character, as LuaTeX does each time it
40    /// measures the glyph (when packing a box and in each line-breaking
41    /// pass): `Missing character: There is no ペ (U+30DA) in font cmr10!`
42    /// on its own line, whatever `\tracingonline` (observed).
43    #[inline]
44    pub fn check_lost_char(&mut self, p: NodeRef) {
45        if self.eqtb.int(ip::TRACINGLOSTCHARS) < 4 {
46            return;
47        }
48        let NodeData::Glyph(g) = self.nodes.data(p) else {
49            return;
50        };
51        let (f, c) = (g.font, g.chr);
52        if self.fonts.char_metrics(f, c).is_some() {
53            return;
54        }
55        self.printer.print_nl("Missing character: There is no ");
56        self.printer.print_char(c);
57        self.printer.print_str(&format!(" (U+{c:04X}) in font "));
58        let name = self.font(f).name.clone();
59        self.printer.print_bytes(&name);
60        self.printer.print_char('!' as u32);
61        self.printer.print_nl("");
62    }
63
64    /// Reports character `c` of font `f`, which the font lacks, when it is
65    /// shipped out (observed): with `\tracinglostchars` 1 in the log only
66    /// (unless `\tracingonline`), from 2 on the terminal too, and with 3
67    /// as an error (context, no help).
68    pub fn shipout_lost_char(&mut self, f: u32, c: u32) {
69        let level = self.eqtb.int(ip::TRACINGLOSTCHARS);
70        if level <= 0 {
71            return;
72        }
73        if level == 1 {
74            self.begin_diagnostic();
75        }
76        self.printer.print_nl("Missing character: There is no ");
77        self.printer.print_char(c);
78        self.printer.print_str(&format!(" (U+{c:04X}) in font "));
79        let name = self.font(f).name.clone();
80        self.printer.print_bytes(&name);
81        self.printer.print_char('!' as u32);
82        if level == 1 {
83            self.end_diagnostic(false);
84        } else {
85            self.printer.print_nl("");
86        }
87        if level == 3 {
88            self.help(&[]);
89            self.error();
90        }
91    }
92
93    /// The dimensions (width, height, depth) of a glyph node. Observed:
94    /// the `yoffset` raises or lowers the height, but only a raised glyph
95    /// has a smaller depth; a lowered one keeps its depth.
96    #[inline]
97    pub fn glyph_dimensions(&self, p: NodeRef) -> (i32, i32, i32) {
98        match self.nodes.data(p) {
99            NodeData::Glyph(g) => match self.fonts.char_metrics(g.font, g.chr) {
100                Some(m) => (
101                    crate::expansion::expanded_width(m.width, g.expansion_factor),
102                    m.height + g.yoffset,
103                    m.depth - g.yoffset.max(0),
104                ),
105                None => (0, 0, 0),
106            },
107            _ => (0, 0, 0),
108        }
109    }
110
111    /// Gives box `r` the direction in [`Engine::pack_dir`], if any.
112    fn take_pack_dir(&mut self, r: NodeRef) {
113        let dir = std::mem::replace(&mut self.pack_dir, -1);
114        if dir >= 0
115            && let Some(b) = self.nodes.box_data_mut(r)
116        {
117            b.dir = dir;
118        }
119    }
120
121    /// Packs the hlist `p` into an hbox (`hpack`).
122    pub fn hpack(&mut self, p: NodeRef, spec: PackSpec) -> NodeRef {
123        self.fonts.sync_dense();
124        self.last_badness = 0;
125        let r = self.new_null_box_node(true);
126        self.take_pack_dir(r);
127        let (mut h, mut d, mut x) = (0i32, 0i32, 0i32);
128        let mut tot = Totals::default();
129        // `q` trails behind `p`; a temporary head holds the list so that
130        // nodes can be moved to the adjustment lists.
131        let head = self.nodes.new_temp();
132        self.nodes.link(head, p);
133        let mut q = head;
134        let mut p = p;
135        while !p.is_null() {
136            let next = self.nodes.next(p);
137            match self.nodes.data(p) {
138                NodeData::Glyph(_) => {
139                    self.check_lost_char(p);
140                    let (w, gh, gd) = self.glyph_dimensions(p);
141                    x += w;
142                    if gh > h {
143                        h = gh;
144                    }
145                    if gd > d {
146                        d = gd;
147                    }
148                }
149                NodeData::Box(b) => {
150                    x += b.width;
151                    let s = b.shift;
152                    if b.height - s > h {
153                        h = b.height - s;
154                    }
155                    if b.depth + s > d {
156                        d = b.depth + s;
157                    }
158                }
159                NodeData::Unset { b, .. } => {
160                    x += b.width;
161                    if b.height > h {
162                        h = b.height;
163                    }
164                    if b.depth > d {
165                        d = b.depth;
166                    }
167                }
168                NodeData::Rule {
169                    width,
170                    height,
171                    depth,
172                    ..
173                } => {
174                    x += *width;
175                    if *height > h {
176                        h = *height;
177                    }
178                    if *depth > d {
179                        d = *depth;
180                    }
181                }
182                NodeData::Ins { .. } | NodeData::Mark { .. } | NodeData::Adjust { .. } => {
183                    if let Some(at) = self.ts.adjust_tail {
184                        // Transfer node p to the adjustment list.
185                        self.nodes.set_next(q, next);
186                        if !next.is_null() {
187                            self.nodes.set_prev(next, q);
188                        }
189                        let is_pre = self.nodes.id(p) == id::ADJUST && self.nodes.subtype(p) == 1;
190                        if self.nodes.id(p) == id::ADJUST {
191                            let l = self.nodes.list_ptr(p);
192                            self.nodes.set_list_ptr(p, NodeRef::NULL);
193                            self.nodes.free(p);
194                            if !l.is_null() {
195                                let target = if is_pre {
196                                    self.ts.pre_adjust_tail.unwrap_or(at)
197                                } else {
198                                    at
199                                };
200                                self.nodes.link(target, l);
201                                let t = self.nodes.tail_of(l);
202                                if is_pre && self.ts.pre_adjust_tail.is_some() {
203                                    self.ts.pre_adjust_tail = Some(t);
204                                } else {
205                                    self.ts.adjust_tail = Some(t);
206                                }
207                            }
208                        } else {
209                            self.nodes.link(at, p);
210                            self.nodes.set_next(p, NodeRef::NULL);
211                            self.ts.adjust_tail = Some(p);
212                        }
213                        p = next;
214                        continue;
215                    }
216                }
217                NodeData::Glue { spec, leader } => {
218                    x += spec.width;
219                    tot.add(spec);
220                    let (leader, sub) = (*leader, self.nodes.subtype(p));
221                    if sub >= glue_sub::A_LEADERS && !leader.is_null() {
222                        let (lh, ld) = (self.node_height(leader), self.node_depth(leader));
223                        if lh > h {
224                            h = lh;
225                        }
226                        if ld > d {
227                            d = ld;
228                        }
229                    }
230                }
231                NodeData::Kern {
232                    kern,
233                    expansion_factor,
234                } => x += *kern + *expansion_factor,
235                NodeData::MarginKern { width, .. } => x += *width,
236                NodeData::Math { surround, glue } => {
237                    if glue.width != 0 || glue.stretch != 0 || glue.shrink != 0 {
238                        x += glue.width;
239                        let g = *glue;
240                        tot.add(&g);
241                    } else {
242                        x += *surround;
243                    }
244                }
245                NodeData::Disc { replace, .. } => {
246                    // The replacement text is typeset when there is no break.
247                    let mut s = *replace;
248                    while !s.is_null() {
249                        self.check_lost_char(s);
250                        let (w, sh, sd) = self.simple_dimensions(s);
251                        x += w;
252                        if sh > h {
253                            h = sh;
254                        }
255                        if sd > d {
256                            d = sd;
257                        }
258                        s = self.nodes.next(s);
259                    }
260                }
261                _ => {}
262            }
263            q = p;
264            p = next;
265        }
266        if let Some(at) = self.ts.adjust_tail {
267            self.nodes.set_next(at, NodeRef::NULL);
268        }
269        if let Some(at) = self.ts.pre_adjust_tail {
270            self.nodes.set_next(at, NodeRef::NULL);
271        }
272        let first = self.nodes.next(head);
273        self.nodes.free(head);
274        if !first.is_null() {
275            self.nodes.set_prev(first, NodeRef::NULL);
276        }
277        let w = match spec {
278            PackSpec::Exactly(w) => w,
279            PackSpec::Additional(w) => x + w,
280        };
281        {
282            let b = self.box_mut(r);
283            b.head = first;
284            b.height = h;
285            b.depth = d;
286            b.width = w;
287        }
288        self.ts.last_totals = (tot.stretch, tot.shrink);
289        let x = w - x;
290        // With the hpack_quality callback, it gets the incident instead
291        // of the warning and the overfull rule.
292        let quality = self.has_callback("hpack_quality");
293        let mut incident: Option<(&str, i32)> = None;
294        let report: Option<()> = if x == 0 {
295            None
296        } else if x > 0 {
297            let o = tot.stretch_order();
298            let ts = tot.stretch[o as usize];
299            {
300                let b = self.box_mut(r);
301                b.glue_order = o;
302                b.glue_sign = 1;
303                if ts != 0 {
304                    b.glue_set = crate::nodes::glue_ratio(x, ts);
305                } else {
306                    b.glue_sign = 0;
307                    b.glue_set = 0.0;
308                }
309            }
310            if o == 0 && !first.is_null() {
311                self.last_badness = retex_arith::badness(x, tot.stretch[0]);
312                if self.last_badness > self.eqtb.int(ip::HBADNESS) && quality {
313                    let kind = if self.last_badness > 100 {
314                        "underfull"
315                    } else {
316                        "loose"
317                    };
318                    incident = Some((kind, self.last_badness));
319                    None
320                } else if self.last_badness > self.eqtb.int(ip::HBADNESS) {
321                    self.printer.print_ln();
322                    if self.last_badness > 100 {
323                        self.diag_capture_start();
324                        self.printer.print_nl("Underfull");
325                    } else {
326                        self.printer.print_nl("Loose");
327                    }
328                    self.printer.print_str(" \\hbox (badness ");
329                    self.printer.print_int(i64::from(self.last_badness));
330                    Some(())
331                } else {
332                    None
333                }
334            } else {
335                None
336            }
337        } else {
338            let o = tot.shrink_order();
339            let ts = tot.shrink[o as usize];
340            {
341                let b = self.box_mut(r);
342                b.glue_order = o;
343                b.glue_sign = 2;
344                if ts != 0 {
345                    b.glue_set = crate::nodes::glue_ratio(-x, ts);
346                } else {
347                    b.glue_sign = 0;
348                    b.glue_set = 0.0;
349                }
350            }
351            if ts < -x && o == 0 && !first.is_null() {
352                self.last_badness = 1_000_000;
353                self.box_mut(r).glue_set = 1.0;
354                let excess = -x - tot.shrink[0];
355                let hfuzz = self.eqtb.dimen(dp::HFUZZ);
356                if (excess > hfuzz || self.eqtb.int(ip::HBADNESS) < 100) && quality {
357                    incident = Some(("overfull", excess));
358                    None
359                } else if excess > hfuzz || self.eqtb.int(ip::HBADNESS) < 100 {
360                    let ofr = self.eqtb.dimen(dp::OVERFULLRULE);
361                    if ofr > 0 && excess > hfuzz {
362                        let rule = self.nodes.new_rule(ofr, NULL_RULE, NULL_RULE);
363                        if first.is_null() {
364                            self.box_mut(r).head = rule;
365                        } else {
366                            let t = self.nodes.tail_of(first);
367                            self.nodes.link(t, rule);
368                        }
369                    }
370                    self.printer.print_ln();
371                    self.diag_capture_start();
372                    self.printer.print_nl("Overfull \\hbox (");
373                    self.printer.print_scaled(excess);
374                    self.printer.print_str("pt too wide");
375                    Some(())
376                } else {
377                    None
378                }
379            } else if o == 0 && !first.is_null() {
380                self.last_badness = retex_arith::badness(-x, tot.shrink[0]);
381                if self.last_badness > self.eqtb.int(ip::HBADNESS) && quality {
382                    incident = Some(("tight", self.last_badness));
383                    None
384                } else if self.last_badness > self.eqtb.int(ip::HBADNESS) {
385                    self.printer.print_ln();
386                    self.diag_capture_start();
387                    self.printer.print_nl("Tight \\hbox (badness ");
388                    self.printer.print_int(i64::from(self.last_badness));
389                    Some(())
390                } else {
391                    None
392                }
393            } else {
394                None
395            }
396        };
397        if report.is_some() {
398            self.finish_hbox_diagnostic(r);
399        }
400        if let Some((kind, detail)) = incident {
401            // Observed: a node returned goes at the end of the box's list.
402            let p = self.call_pack_quality("hpack_quality", kind, detail, r);
403            if !p.is_null() {
404                let head = self.nodes.list_ptr(r);
405                if head.is_null() {
406                    self.box_mut(r).head = p;
407                } else {
408                    let t = self.nodes.tail_of(head);
409                    self.nodes.link(t, p);
410                }
411            }
412        }
413        r
414    }
415
416    /// Calls `hpack_quality` or `vpack_quality` for box `r` (observed:
417    /// with the box, `\pack_begin_line` and the current line).
418    fn call_pack_quality(&mut self, cb: &str, kind: &str, detail: i32, r: NodeRef) -> NodeRef {
419        let (first, last) = (self.ts.pack_begin_line, self.input.line());
420        match self.lua.clone() {
421            Some(h) => h.pack_quality(self, cb, kind, detail, r, first, last),
422            None => NodeRef::NULL,
423        }
424    }
425
426    /// The natural width, height and depth of the hlist from `p` up to
427    /// `stop` (exclusive), as `hpack` measures it (LuaTeX's
428    /// `node.dimensions`).
429    pub fn natural_dimensions(&self, mut p: NodeRef, stop: NodeRef) -> (i32, i32, i32) {
430        let (mut w, mut h, mut d) = (0i32, 0i32, 0i32);
431        while !p.is_null() && p != stop {
432            let (pw, ph, pd) = match self.nodes.data(p) {
433                NodeData::Glyph(_) => self.glyph_dimensions(p),
434                NodeData::Box(b) => (b.width, b.height - b.shift, b.depth + b.shift),
435                NodeData::Rule {
436                    width,
437                    height,
438                    depth,
439                    ..
440                } => (*width, *height, *depth),
441                NodeData::Disc { replace, .. } => {
442                    let r = *replace;
443                    self.natural_dimensions(r, NodeRef::NULL)
444                }
445                _ => (self.nodes.width(p), 0, 0),
446            };
447            w += pw;
448            h = h.max(ph);
449            d = d.max(pd);
450            p = self.nodes.next(p);
451        }
452        (w, h, d)
453    }
454
455    /// Like [`natural_dimensions`](Self::natural_dimensions), with the
456    /// glue set as in a box with `glue_set`, `sign` (1 stretching, 2
457    /// shrinking) and `order` (`node.dimensions` with glue settings).
458    /// Observed: the glue widths are rounded as TeX outputs a box
459    /// (§625), from the running total of the stretch or shrink.
460    pub fn set_dimensions(
461        &self,
462        mut p: NodeRef,
463        stop: NodeRef,
464        glue_set: f64,
465        sign: i32,
466        order: i32,
467    ) -> (i32, i32, i32) {
468        let (mut w, mut h, mut d) = (0i64, 0i32, 0i32);
469        let (mut cur_glue, mut cur_g) = (0f64, 0i64);
470        while !p.is_null() && p != stop {
471            if let NodeData::Glue { spec, .. } = self.nodes.data(p) {
472                let mut wd = i64::from(spec.width) - cur_g;
473                let set = match sign {
474                    1 if i32::from(spec.stretch_order) == order => {
475                        cur_glue += f64::from(spec.stretch);
476                        true
477                    }
478                    2 if i32::from(spec.shrink_order) == order => {
479                        cur_glue -= f64::from(spec.shrink);
480                        true
481                    }
482                    _ => false,
483                };
484                if set {
485                    cur_g = (glue_set * cur_glue).clamp(-1e9, 1e9).round() as i64;
486                }
487                wd += cur_g;
488                w += wd;
489            } else {
490                let (pw, ph, pd) = self.natural_dimensions(p, self.nodes.next(p));
491                w += i64::from(pw);
492                h = h.max(ph);
493                d = d.max(pd);
494            }
495            p = self.nodes.next(p);
496        }
497        (w as i32, h, d)
498    }
499
500    /// Width, height and depth of a node in a discretionary text.
501    fn simple_dimensions(&self, p: NodeRef) -> (i32, i32, i32) {
502        match self.nodes.data(p) {
503            NodeData::Glyph(_) => self.glyph_dimensions(p),
504            NodeData::Box(b) => (b.width, b.height - b.shift, b.depth + b.shift),
505            NodeData::Rule {
506                width,
507                height,
508                depth,
509                ..
510            } => (*width, *height, *depth),
511            NodeData::Kern {
512                kern,
513                expansion_factor,
514            } => (*kern + *expansion_factor, 0, 0),
515            _ => (0, 0, 0),
516        }
517    }
518
519    fn box_mut(&mut self, r: NodeRef) -> &mut BoxData {
520        match self.nodes.data_mut(r) {
521            NodeData::Box(b) => b,
522            _ => unreachable!("box_mut on a non-box node"),
523        }
524    }
525
526    /// Prints the location and the box for an over/underfull hbox message.
527    fn finish_hbox_diagnostic(&mut self, r: NodeRef) {
528        if self.ts.output_active {
529            self.printer
530                .print_str(") has occurred while \\output is active");
531        } else {
532            let pbl = self.ts.pack_begin_line;
533            if pbl != 0 {
534                if pbl > 0 {
535                    self.printer.print_str(") in paragraph at lines ");
536                } else {
537                    self.printer.print_str(") in alignment at lines ");
538                }
539                self.printer.print_int(i64::from(pbl.abs()));
540                self.printer.print_str("--");
541            } else {
542                self.printer.print_str(") detected at line ");
543            }
544            self.printer.print_int(i64::from(self.input.line()));
545        }
546        let at = (self.ts.pack_begin_line != 0).then(|| self.ts.pack_begin_line.unsigned_abs());
547        self.diag_capture_warning(at);
548        if self.diagnostics.is_some() {
549            let facts = self.hbox_facts(r);
550            if let Some(d) = self.diagnostics.as_mut().and_then(|l| l.last_mut()) {
551                d.facts = facts;
552            }
553        }
554        self.printer.print_ln();
555        self.ts.font_in_short_display = 0;
556        let l = self.nodes.list_ptr(r);
557        self.short_display(l);
558        self.printer.print_ln();
559        self.begin_diagnostic();
560        self.show_box(r);
561        self.end_diagnostic(true);
562    }
563
564    /// Packs a vlist with unlimited depth (`vpack`).
565    pub fn vpack(&mut self, p: NodeRef, spec: PackSpec) -> NodeRef {
566        self.vpackage(p, spec, retex_arith::MAX_DIMEN)
567    }
568
569    /// Packs the vlist `p` into a vbox with maximum depth `l`
570    /// (`vpackage`).
571    pub fn vpackage(&mut self, p: NodeRef, spec: PackSpec, l: i32) -> NodeRef {
572        self.last_badness = 0;
573        let r = self.new_null_box_node(false);
574        self.take_pack_dir(r);
575        let (mut w, mut d, mut x) = (0i32, 0i32, 0i32);
576        let mut tot = Totals::default();
577        let mut q = p;
578        while !q.is_null() {
579            match self.nodes.data(q) {
580                NodeData::Box(b) => {
581                    x += d + b.height;
582                    d = b.depth;
583                    if b.width + b.shift > w {
584                        w = b.width + b.shift;
585                    }
586                }
587                NodeData::Unset { b, .. } => {
588                    x += d + b.height;
589                    d = b.depth;
590                    if b.width > w {
591                        w = b.width;
592                    }
593                }
594                NodeData::Rule {
595                    width,
596                    height,
597                    depth,
598                    ..
599                } => {
600                    x += d + *height;
601                    d = *depth;
602                    if *width > w {
603                        w = *width;
604                    }
605                }
606                NodeData::Glue { spec, leader } => {
607                    x += d;
608                    d = 0;
609                    x += spec.width;
610                    tot.add(spec);
611                    let leader = *leader;
612                    if self.nodes.subtype(q) >= glue_sub::A_LEADERS && !leader.is_null() {
613                        let lw = self.nodes.width(leader);
614                        if lw > w {
615                            w = lw;
616                        }
617                    }
618                }
619                NodeData::Kern { kern, .. } => {
620                    x += d + *kern;
621                    d = 0;
622                }
623                // Observed: LuaTeX stops at a glyph in a vertical list
624                // (Lua code can give `node.vpack` one).
625                NodeData::Glyph(_) => self.confusion("vpack"),
626                _ => {}
627            }
628            q = self.nodes.next(q);
629        }
630        {
631            let b = self.box_mut(r);
632            b.width = w;
633            b.head = p;
634        }
635        if d > l {
636            x += d - l;
637            self.box_mut(r).depth = l;
638        } else {
639            self.box_mut(r).depth = d;
640        }
641        let h = match spec {
642            PackSpec::Exactly(h) => h,
643            PackSpec::Additional(h) => x + h,
644        };
645        self.box_mut(r).height = h;
646        self.ts.last_totals = (tot.stretch, tot.shrink);
647        let x = h - x;
648        let quality = self.has_callback("vpack_quality");
649        let mut incident: Option<(&str, i32)> = None;
650        let report = if x == 0 {
651            false
652        } else if x > 0 {
653            let o = tot.stretch_order();
654            let ts = tot.stretch[o as usize];
655            {
656                let b = self.box_mut(r);
657                b.glue_order = o;
658                b.glue_sign = 1;
659                if ts != 0 {
660                    b.glue_set = crate::nodes::glue_ratio(x, ts);
661                } else {
662                    b.glue_sign = 0;
663                    b.glue_set = 0.0;
664                }
665            }
666            if o == 0 && !p.is_null() {
667                self.last_badness = retex_arith::badness(x, tot.stretch[0]);
668                if self.last_badness > self.eqtb.int(ip::VBADNESS) && quality {
669                    let kind = if self.last_badness > 100 {
670                        "underfull"
671                    } else {
672                        "loose"
673                    };
674                    incident = Some((kind, self.last_badness));
675                    false
676                } else if self.last_badness > self.eqtb.int(ip::VBADNESS) {
677                    self.printer.print_ln();
678                    if self.last_badness > 100 {
679                        self.diag_capture_start();
680                        self.printer.print_nl("Underfull");
681                    } else {
682                        self.printer.print_nl("Loose");
683                    }
684                    self.printer.print_str(" \\vbox (badness ");
685                    self.printer.print_int(i64::from(self.last_badness));
686                    true
687                } else {
688                    false
689                }
690            } else {
691                false
692            }
693        } else {
694            let o = tot.shrink_order();
695            let ts = tot.shrink[o as usize];
696            {
697                let b = self.box_mut(r);
698                b.glue_order = o;
699                b.glue_sign = 2;
700                if ts != 0 {
701                    b.glue_set = crate::nodes::glue_ratio(-x, ts);
702                } else {
703                    b.glue_sign = 0;
704                    b.glue_set = 0.0;
705                }
706            }
707            if ts < -x && o == 0 && !p.is_null() {
708                self.last_badness = 1_000_000;
709                self.box_mut(r).glue_set = 1.0;
710                let excess = -x - tot.shrink[0];
711                if (excess > self.eqtb.dimen(dp::VFUZZ) || self.eqtb.int(ip::VBADNESS) < 100)
712                    && quality
713                {
714                    incident = Some(("overfull", excess));
715                    false
716                } else if excess > self.eqtb.dimen(dp::VFUZZ) || self.eqtb.int(ip::VBADNESS) < 100 {
717                    self.printer.print_ln();
718                    self.diag_capture_start();
719                    self.printer.print_nl("Overfull \\vbox (");
720                    self.printer.print_scaled(excess);
721                    self.printer.print_str("pt too high");
722                    true
723                } else {
724                    false
725                }
726            } else if o == 0 && !p.is_null() {
727                self.last_badness = retex_arith::badness(-x, tot.shrink[0]);
728                if self.last_badness > self.eqtb.int(ip::VBADNESS) && quality {
729                    incident = Some(("tight", self.last_badness));
730                    false
731                } else if self.last_badness > self.eqtb.int(ip::VBADNESS) {
732                    self.printer.print_ln();
733                    self.diag_capture_start();
734                    self.printer.print_nl("Tight \\vbox (badness ");
735                    self.printer.print_int(i64::from(self.last_badness));
736                    true
737                } else {
738                    false
739                }
740            } else {
741                false
742            }
743        };
744        if let Some((kind, detail)) = incident {
745            // The callback's return value is not used (observed).
746            self.call_pack_quality("vpack_quality", kind, detail, r);
747        }
748        if report {
749            if self.ts.output_active {
750                self.printer
751                    .print_str(") has occurred while \\output is active");
752            } else {
753                let pbl = self.ts.pack_begin_line;
754                if pbl != 0 {
755                    self.printer.print_str(") in alignment at lines ");
756                    self.printer.print_int(i64::from(pbl.abs()));
757                    self.printer.print_str("--");
758                } else {
759                    self.printer.print_str(") detected at line ");
760                }
761                self.printer.print_int(i64::from(self.input.line()));
762            }
763            let at = (self.ts.pack_begin_line != 0).then(|| self.ts.pack_begin_line.unsigned_abs());
764            self.diag_capture_warning(at);
765            // An underfull page: explain why (see `pagenote`).
766            if self.ts.output_active
767                && self.last_badness > 100
768                && self.last_badness < 1_000_000
769                && let Some((message, notes, help)) = self.underfull_page_note(self.last_badness)
770                && let Some(d) = self.diagnostics.as_mut().and_then(|l| l.last_mut())
771                && d.message.starts_with("Underfull \\vbox")
772            {
773                d.message = message;
774                d.notes = notes;
775                d.help = help;
776            }
777            if !self.ts.output_active {
778                self.printer.print_ln();
779            }
780            self.begin_diagnostic();
781            self.show_box(r);
782            self.end_diagnostic(true);
783        }
784        r
785    }
786}
787
788/// The running dimension of rules (`null_flag`).
789const NULL_RULE: i32 = crate::typeset::NULL_FLAG;