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    /// Width, height and depth of a node in a discretionary text.
456    fn simple_dimensions(&self, p: NodeRef) -> (i32, i32, i32) {
457        match self.nodes.data(p) {
458            NodeData::Glyph(_) => self.glyph_dimensions(p),
459            NodeData::Box(b) => (b.width, b.height - b.shift, b.depth + b.shift),
460            NodeData::Rule {
461                width,
462                height,
463                depth,
464                ..
465            } => (*width, *height, *depth),
466            NodeData::Kern {
467                kern,
468                expansion_factor,
469            } => (*kern + *expansion_factor, 0, 0),
470            _ => (0, 0, 0),
471        }
472    }
473
474    fn box_mut(&mut self, r: NodeRef) -> &mut BoxData {
475        match self.nodes.data_mut(r) {
476            NodeData::Box(b) => b,
477            _ => unreachable!("box_mut on a non-box node"),
478        }
479    }
480
481    /// Prints the location and the box for an over/underfull hbox message.
482    fn finish_hbox_diagnostic(&mut self, r: NodeRef) {
483        if self.ts.output_active {
484            self.printer
485                .print_str(") has occurred while \\output is active");
486        } else {
487            let pbl = self.ts.pack_begin_line;
488            if pbl != 0 {
489                if pbl > 0 {
490                    self.printer.print_str(") in paragraph at lines ");
491                } else {
492                    self.printer.print_str(") in alignment at lines ");
493                }
494                self.printer.print_int(i64::from(pbl.abs()));
495                self.printer.print_str("--");
496            } else {
497                self.printer.print_str(") detected at line ");
498            }
499            self.printer.print_int(i64::from(self.input.line()));
500        }
501        let at = (self.ts.pack_begin_line != 0).then(|| self.ts.pack_begin_line.unsigned_abs());
502        self.diag_capture_warning(at);
503        if self.diagnostics.is_some() {
504            let facts = self.hbox_facts(r);
505            if let Some(d) = self.diagnostics.as_mut().and_then(|l| l.last_mut()) {
506                d.facts = facts;
507            }
508        }
509        self.printer.print_ln();
510        self.ts.font_in_short_display = 0;
511        let l = self.nodes.list_ptr(r);
512        self.short_display(l);
513        self.printer.print_ln();
514        self.begin_diagnostic();
515        self.show_box(r);
516        self.end_diagnostic(true);
517    }
518
519    /// Packs a vlist with unlimited depth (`vpack`).
520    pub fn vpack(&mut self, p: NodeRef, spec: PackSpec) -> NodeRef {
521        self.vpackage(p, spec, retex_arith::MAX_DIMEN)
522    }
523
524    /// Packs the vlist `p` into a vbox with maximum depth `l`
525    /// (`vpackage`).
526    pub fn vpackage(&mut self, p: NodeRef, spec: PackSpec, l: i32) -> NodeRef {
527        self.last_badness = 0;
528        let r = self.new_null_box_node(false);
529        self.take_pack_dir(r);
530        let (mut w, mut d, mut x) = (0i32, 0i32, 0i32);
531        let mut tot = Totals::default();
532        let mut q = p;
533        while !q.is_null() {
534            match self.nodes.data(q) {
535                NodeData::Box(b) => {
536                    x += d + b.height;
537                    d = b.depth;
538                    if b.width + b.shift > w {
539                        w = b.width + b.shift;
540                    }
541                }
542                NodeData::Unset { b, .. } => {
543                    x += d + b.height;
544                    d = b.depth;
545                    if b.width > w {
546                        w = b.width;
547                    }
548                }
549                NodeData::Rule {
550                    width,
551                    height,
552                    depth,
553                    ..
554                } => {
555                    x += d + *height;
556                    d = *depth;
557                    if *width > w {
558                        w = *width;
559                    }
560                }
561                NodeData::Glue { spec, leader } => {
562                    x += d;
563                    d = 0;
564                    x += spec.width;
565                    tot.add(spec);
566                    let leader = *leader;
567                    if self.nodes.subtype(q) >= glue_sub::A_LEADERS && !leader.is_null() {
568                        let lw = self.nodes.width(leader);
569                        if lw > w {
570                            w = lw;
571                        }
572                    }
573                }
574                NodeData::Kern { kern, .. } => {
575                    x += d + *kern;
576                    d = 0;
577                }
578                // Observed: LuaTeX stops at a glyph in a vertical list
579                // (Lua code can give `node.vpack` one).
580                NodeData::Glyph(_) => self.confusion("vpack"),
581                _ => {}
582            }
583            q = self.nodes.next(q);
584        }
585        {
586            let b = self.box_mut(r);
587            b.width = w;
588            b.head = p;
589        }
590        if d > l {
591            x += d - l;
592            self.box_mut(r).depth = l;
593        } else {
594            self.box_mut(r).depth = d;
595        }
596        let h = match spec {
597            PackSpec::Exactly(h) => h,
598            PackSpec::Additional(h) => x + h,
599        };
600        self.box_mut(r).height = h;
601        self.ts.last_totals = (tot.stretch, tot.shrink);
602        let x = h - x;
603        let quality = self.has_callback("vpack_quality");
604        let mut incident: Option<(&str, i32)> = None;
605        let report = if x == 0 {
606            false
607        } else if x > 0 {
608            let o = tot.stretch_order();
609            let ts = tot.stretch[o as usize];
610            {
611                let b = self.box_mut(r);
612                b.glue_order = o;
613                b.glue_sign = 1;
614                if ts != 0 {
615                    b.glue_set = crate::nodes::glue_ratio(x, ts);
616                } else {
617                    b.glue_sign = 0;
618                    b.glue_set = 0.0;
619                }
620            }
621            if o == 0 && !p.is_null() {
622                self.last_badness = retex_arith::badness(x, tot.stretch[0]);
623                if self.last_badness > self.eqtb.int(ip::VBADNESS) && quality {
624                    let kind = if self.last_badness > 100 {
625                        "underfull"
626                    } else {
627                        "loose"
628                    };
629                    incident = Some((kind, self.last_badness));
630                    false
631                } else if self.last_badness > self.eqtb.int(ip::VBADNESS) {
632                    self.printer.print_ln();
633                    if self.last_badness > 100 {
634                        self.diag_capture_start();
635                        self.printer.print_nl("Underfull");
636                    } else {
637                        self.printer.print_nl("Loose");
638                    }
639                    self.printer.print_str(" \\vbox (badness ");
640                    self.printer.print_int(i64::from(self.last_badness));
641                    true
642                } else {
643                    false
644                }
645            } else {
646                false
647            }
648        } else {
649            let o = tot.shrink_order();
650            let ts = tot.shrink[o as usize];
651            {
652                let b = self.box_mut(r);
653                b.glue_order = o;
654                b.glue_sign = 2;
655                if ts != 0 {
656                    b.glue_set = crate::nodes::glue_ratio(-x, ts);
657                } else {
658                    b.glue_sign = 0;
659                    b.glue_set = 0.0;
660                }
661            }
662            if ts < -x && o == 0 && !p.is_null() {
663                self.last_badness = 1_000_000;
664                self.box_mut(r).glue_set = 1.0;
665                let excess = -x - tot.shrink[0];
666                if (excess > self.eqtb.dimen(dp::VFUZZ) || self.eqtb.int(ip::VBADNESS) < 100)
667                    && quality
668                {
669                    incident = Some(("overfull", excess));
670                    false
671                } else if excess > self.eqtb.dimen(dp::VFUZZ) || self.eqtb.int(ip::VBADNESS) < 100 {
672                    self.printer.print_ln();
673                    self.diag_capture_start();
674                    self.printer.print_nl("Overfull \\vbox (");
675                    self.printer.print_scaled(excess);
676                    self.printer.print_str("pt too high");
677                    true
678                } else {
679                    false
680                }
681            } else if o == 0 && !p.is_null() {
682                self.last_badness = retex_arith::badness(-x, tot.shrink[0]);
683                if self.last_badness > self.eqtb.int(ip::VBADNESS) && quality {
684                    incident = Some(("tight", self.last_badness));
685                    false
686                } else if self.last_badness > self.eqtb.int(ip::VBADNESS) {
687                    self.printer.print_ln();
688                    self.diag_capture_start();
689                    self.printer.print_nl("Tight \\vbox (badness ");
690                    self.printer.print_int(i64::from(self.last_badness));
691                    true
692                } else {
693                    false
694                }
695            } else {
696                false
697            }
698        };
699        if let Some((kind, detail)) = incident {
700            // The callback's return value is not used (observed).
701            self.call_pack_quality("vpack_quality", kind, detail, r);
702        }
703        if report {
704            if self.ts.output_active {
705                self.printer
706                    .print_str(") has occurred while \\output is active");
707            } else {
708                let pbl = self.ts.pack_begin_line;
709                if pbl != 0 {
710                    self.printer.print_str(") in alignment at lines ");
711                    self.printer.print_int(i64::from(pbl.abs()));
712                    self.printer.print_str("--");
713                } else {
714                    self.printer.print_str(") detected at line ");
715                }
716                self.printer.print_int(i64::from(self.input.line()));
717            }
718            let at = (self.ts.pack_begin_line != 0).then(|| self.ts.pack_begin_line.unsigned_abs());
719            self.diag_capture_warning(at);
720            // An underfull page: explain why (see `pagenote`).
721            if self.ts.output_active
722                && self.last_badness > 100
723                && self.last_badness < 1_000_000
724                && let Some((message, notes, help)) = self.underfull_page_note(self.last_badness)
725                && let Some(d) = self.diagnostics.as_mut().and_then(|l| l.last_mut())
726                && d.message.starts_with("Underfull \\vbox")
727            {
728                d.message = message;
729                d.notes = notes;
730                d.help = help;
731            }
732            if !self.ts.output_active {
733                self.printer.print_ln();
734            }
735            self.begin_diagnostic();
736            self.show_box(r);
737            self.end_diagnostic(true);
738        }
739        r
740    }
741}
742
743/// The running dimension of rules (`null_flag`).
744const NULL_RULE: i32 = crate::typeset::NULL_FLAG;