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    /// The dimensions (width, height, depth) of a glyph node.
39    #[inline]
40    pub fn glyph_dimensions(&self, p: NodeRef) -> (i32, i32, i32) {
41        match self.nodes.data(p) {
42            NodeData::Glyph(g) => match self.fonts.char_metrics(g.font, g.chr) {
43                Some(m) => (
44                    crate::expansion::expanded_width(m.width, g.expansion_factor),
45                    m.height + g.yoffset,
46                    m.depth - g.yoffset,
47                ),
48                None => (0, 0, 0),
49            },
50            _ => (0, 0, 0),
51        }
52    }
53
54    /// Gives box `r` the direction in [`Engine::pack_dir`], if any.
55    fn take_pack_dir(&mut self, r: NodeRef) {
56        let dir = std::mem::replace(&mut self.pack_dir, -1);
57        if dir >= 0
58            && let Some(b) = self.nodes.box_data_mut(r)
59        {
60            b.dir = dir;
61        }
62    }
63
64    /// Packs the hlist `p` into an hbox (`hpack`).
65    pub fn hpack(&mut self, p: NodeRef, spec: PackSpec) -> NodeRef {
66        self.fonts.sync_dense();
67        self.last_badness = 0;
68        let r = self.nodes.new_null_box(true);
69        self.take_pack_dir(r);
70        let (mut h, mut d, mut x) = (0i32, 0i32, 0i32);
71        let mut tot = Totals::default();
72        // `q` trails behind `p`; a temporary head holds the list so that
73        // nodes can be moved to the adjustment lists.
74        let head = self.nodes.new_temp();
75        self.nodes.link(head, p);
76        let mut q = head;
77        let mut p = p;
78        while !p.is_null() {
79            let next = self.nodes.next(p);
80            match self.nodes.data(p) {
81                NodeData::Glyph(_) => {
82                    let (w, gh, gd) = self.glyph_dimensions(p);
83                    x += w;
84                    if gh > h {
85                        h = gh;
86                    }
87                    if gd > d {
88                        d = gd;
89                    }
90                }
91                NodeData::Box(b) => {
92                    x += b.width;
93                    let s = b.shift;
94                    if b.height - s > h {
95                        h = b.height - s;
96                    }
97                    if b.depth + s > d {
98                        d = b.depth + s;
99                    }
100                }
101                NodeData::Unset { b, .. } => {
102                    x += b.width;
103                    if b.height > h {
104                        h = b.height;
105                    }
106                    if b.depth > d {
107                        d = b.depth;
108                    }
109                }
110                NodeData::Rule {
111                    width,
112                    height,
113                    depth,
114                    ..
115                } => {
116                    x += *width;
117                    if *height > h {
118                        h = *height;
119                    }
120                    if *depth > d {
121                        d = *depth;
122                    }
123                }
124                NodeData::Ins { .. } | NodeData::Mark { .. } | NodeData::Adjust { .. } => {
125                    if let Some(at) = self.ts.adjust_tail {
126                        // Transfer node p to the adjustment list.
127                        self.nodes.set_next(q, next);
128                        if !next.is_null() {
129                            self.nodes.set_prev(next, q);
130                        }
131                        let is_pre = self.nodes.id(p) == id::ADJUST && self.nodes.subtype(p) == 1;
132                        if self.nodes.id(p) == id::ADJUST {
133                            let l = self.nodes.list_ptr(p);
134                            self.nodes.set_list_ptr(p, NodeRef::NULL);
135                            self.nodes.free(p);
136                            if !l.is_null() {
137                                let target = if is_pre {
138                                    self.ts.pre_adjust_tail.unwrap_or(at)
139                                } else {
140                                    at
141                                };
142                                self.nodes.link(target, l);
143                                let t = self.nodes.tail_of(l);
144                                if is_pre && self.ts.pre_adjust_tail.is_some() {
145                                    self.ts.pre_adjust_tail = Some(t);
146                                } else {
147                                    self.ts.adjust_tail = Some(t);
148                                }
149                            }
150                        } else {
151                            self.nodes.link(at, p);
152                            self.nodes.set_next(p, NodeRef::NULL);
153                            self.ts.adjust_tail = Some(p);
154                        }
155                        p = next;
156                        continue;
157                    }
158                }
159                NodeData::Glue { spec, leader } => {
160                    x += spec.width;
161                    tot.add(spec);
162                    let (leader, sub) = (*leader, self.nodes.subtype(p));
163                    if sub >= glue_sub::A_LEADERS && !leader.is_null() {
164                        let (lh, ld) = (self.node_height(leader), self.node_depth(leader));
165                        if lh > h {
166                            h = lh;
167                        }
168                        if ld > d {
169                            d = ld;
170                        }
171                    }
172                }
173                NodeData::Kern {
174                    kern,
175                    expansion_factor,
176                } => x += *kern + *expansion_factor,
177                NodeData::MarginKern { width, .. } => x += *width,
178                NodeData::Math { surround, glue } => {
179                    if glue.width != 0 || glue.stretch != 0 || glue.shrink != 0 {
180                        x += glue.width;
181                        let g = *glue;
182                        tot.add(&g);
183                    } else {
184                        x += *surround;
185                    }
186                }
187                NodeData::Disc { replace, .. } => {
188                    // The replacement text is typeset when there is no break.
189                    let mut s = *replace;
190                    while !s.is_null() {
191                        let (w, sh, sd) = self.simple_dimensions(s);
192                        x += w;
193                        if sh > h {
194                            h = sh;
195                        }
196                        if sd > d {
197                            d = sd;
198                        }
199                        s = self.nodes.next(s);
200                    }
201                }
202                _ => {}
203            }
204            q = p;
205            p = next;
206        }
207        if let Some(at) = self.ts.adjust_tail {
208            self.nodes.set_next(at, NodeRef::NULL);
209        }
210        if let Some(at) = self.ts.pre_adjust_tail {
211            self.nodes.set_next(at, NodeRef::NULL);
212        }
213        let first = self.nodes.next(head);
214        self.nodes.free(head);
215        if !first.is_null() {
216            self.nodes.set_prev(first, NodeRef::NULL);
217        }
218        let w = match spec {
219            PackSpec::Exactly(w) => w,
220            PackSpec::Additional(w) => x + w,
221        };
222        {
223            let b = self.box_mut(r);
224            b.head = first;
225            b.height = h;
226            b.depth = d;
227            b.width = w;
228        }
229        self.ts.last_totals = (tot.stretch, tot.shrink);
230        let x = w - x;
231        let report: Option<()> = if x == 0 {
232            None
233        } else if x > 0 {
234            let o = tot.stretch_order();
235            let ts = tot.stretch[o as usize];
236            {
237                let b = self.box_mut(r);
238                b.glue_order = o;
239                b.glue_sign = 1;
240                if ts != 0 {
241                    b.glue_set = crate::nodes::glue_ratio(x, ts);
242                } else {
243                    b.glue_sign = 0;
244                    b.glue_set = 0.0;
245                }
246            }
247            if o == 0 && !first.is_null() {
248                self.last_badness = retex_arith::badness(x, tot.stretch[0]);
249                if self.last_badness > self.eqtb.int(ip::HBADNESS) {
250                    self.printer.print_ln();
251                    if self.last_badness > 100 {
252                        self.diag_capture_start();
253                        self.printer.print_nl("Underfull");
254                    } else {
255                        self.printer.print_nl("Loose");
256                    }
257                    self.printer.print_str(" \\hbox (badness ");
258                    self.printer.print_int(i64::from(self.last_badness));
259                    Some(())
260                } else {
261                    None
262                }
263            } else {
264                None
265            }
266        } else {
267            let o = tot.shrink_order();
268            let ts = tot.shrink[o as usize];
269            {
270                let b = self.box_mut(r);
271                b.glue_order = o;
272                b.glue_sign = 2;
273                if ts != 0 {
274                    b.glue_set = crate::nodes::glue_ratio(-x, ts);
275                } else {
276                    b.glue_sign = 0;
277                    b.glue_set = 0.0;
278                }
279            }
280            if ts < -x && o == 0 && !first.is_null() {
281                self.last_badness = 1_000_000;
282                self.box_mut(r).glue_set = 1.0;
283                let excess = -x - tot.shrink[0];
284                let hfuzz = self.eqtb.dimen(dp::HFUZZ);
285                if excess > hfuzz || self.eqtb.int(ip::HBADNESS) < 100 {
286                    let ofr = self.eqtb.dimen(dp::OVERFULLRULE);
287                    if ofr > 0 && excess > hfuzz {
288                        let rule = self.nodes.new_rule(ofr, NULL_RULE, NULL_RULE);
289                        if first.is_null() {
290                            self.box_mut(r).head = rule;
291                        } else {
292                            let t = self.nodes.tail_of(first);
293                            self.nodes.link(t, rule);
294                        }
295                    }
296                    self.printer.print_ln();
297                    self.diag_capture_start();
298                    self.printer.print_nl("Overfull \\hbox (");
299                    self.printer.print_scaled(excess);
300                    self.printer.print_str("pt too wide");
301                    Some(())
302                } else {
303                    None
304                }
305            } else if o == 0 && !first.is_null() {
306                self.last_badness = retex_arith::badness(-x, tot.shrink[0]);
307                if self.last_badness > self.eqtb.int(ip::HBADNESS) {
308                    self.printer.print_ln();
309                    self.diag_capture_start();
310                    self.printer.print_nl("Tight \\hbox (badness ");
311                    self.printer.print_int(i64::from(self.last_badness));
312                    Some(())
313                } else {
314                    None
315                }
316            } else {
317                None
318            }
319        };
320        if report.is_some() {
321            self.finish_hbox_diagnostic(r);
322        }
323        r
324    }
325
326    /// The natural width, height and depth of the hlist from `p` up to
327    /// `stop` (exclusive), as `hpack` measures it (LuaTeX's
328    /// `node.dimensions`).
329    pub fn natural_dimensions(&self, mut p: NodeRef, stop: NodeRef) -> (i32, i32, i32) {
330        let (mut w, mut h, mut d) = (0i32, 0i32, 0i32);
331        while !p.is_null() && p != stop {
332            let (pw, ph, pd) = match self.nodes.data(p) {
333                NodeData::Glyph(_) => self.glyph_dimensions(p),
334                NodeData::Box(b) => (b.width, b.height - b.shift, b.depth + b.shift),
335                NodeData::Rule {
336                    width,
337                    height,
338                    depth,
339                    ..
340                } => (*width, *height, *depth),
341                NodeData::Disc { replace, .. } => {
342                    let r = *replace;
343                    self.natural_dimensions(r, NodeRef::NULL)
344                }
345                _ => (self.nodes.width(p), 0, 0),
346            };
347            w += pw;
348            h = h.max(ph);
349            d = d.max(pd);
350            p = self.nodes.next(p);
351        }
352        (w, h, d)
353    }
354
355    /// Width, height and depth of a node in a discretionary text.
356    fn simple_dimensions(&self, p: NodeRef) -> (i32, i32, i32) {
357        match self.nodes.data(p) {
358            NodeData::Glyph(_) => self.glyph_dimensions(p),
359            NodeData::Box(b) => (b.width, b.height - b.shift, b.depth + b.shift),
360            NodeData::Rule {
361                width,
362                height,
363                depth,
364                ..
365            } => (*width, *height, *depth),
366            NodeData::Kern {
367                kern,
368                expansion_factor,
369            } => (*kern + *expansion_factor, 0, 0),
370            _ => (0, 0, 0),
371        }
372    }
373
374    fn box_mut(&mut self, r: NodeRef) -> &mut BoxData {
375        match self.nodes.data_mut(r) {
376            NodeData::Box(b) => b,
377            _ => unreachable!("box_mut on a non-box node"),
378        }
379    }
380
381    /// Prints the location and the box for an over/underfull hbox message.
382    fn finish_hbox_diagnostic(&mut self, r: NodeRef) {
383        if self.ts.output_active {
384            self.printer
385                .print_str(") has occurred while \\output is active");
386        } else {
387            let pbl = self.ts.pack_begin_line;
388            if pbl != 0 {
389                if pbl > 0 {
390                    self.printer.print_str(") in paragraph at lines ");
391                } else {
392                    self.printer.print_str(") in alignment at lines ");
393                }
394                self.printer.print_int(i64::from(pbl.abs()));
395                self.printer.print_str("--");
396            } else {
397                self.printer.print_str(") detected at line ");
398            }
399            self.printer.print_int(i64::from(self.input.line()));
400        }
401        let at = (self.ts.pack_begin_line != 0).then(|| self.ts.pack_begin_line.unsigned_abs());
402        self.diag_capture_warning(at);
403        self.printer.print_ln();
404        self.ts.font_in_short_display = 0;
405        let l = self.nodes.list_ptr(r);
406        self.short_display(l);
407        self.printer.print_ln();
408        self.begin_diagnostic();
409        self.show_box(r);
410        self.end_diagnostic(true);
411    }
412
413    /// Packs a vlist with unlimited depth (`vpack`).
414    pub fn vpack(&mut self, p: NodeRef, spec: PackSpec) -> NodeRef {
415        self.vpackage(p, spec, retex_arith::MAX_DIMEN)
416    }
417
418    /// Packs the vlist `p` into a vbox with maximum depth `l`
419    /// (`vpackage`).
420    pub fn vpackage(&mut self, p: NodeRef, spec: PackSpec, l: i32) -> NodeRef {
421        self.last_badness = 0;
422        let r = self.nodes.new_null_box(false);
423        self.take_pack_dir(r);
424        let (mut w, mut d, mut x) = (0i32, 0i32, 0i32);
425        let mut tot = Totals::default();
426        let mut q = p;
427        while !q.is_null() {
428            match self.nodes.data(q) {
429                NodeData::Box(b) => {
430                    x += d + b.height;
431                    d = b.depth;
432                    if b.width + b.shift > w {
433                        w = b.width + b.shift;
434                    }
435                }
436                NodeData::Unset { b, .. } => {
437                    x += d + b.height;
438                    d = b.depth;
439                    if b.width > w {
440                        w = b.width;
441                    }
442                }
443                NodeData::Rule {
444                    width,
445                    height,
446                    depth,
447                    ..
448                } => {
449                    x += d + *height;
450                    d = *depth;
451                    if *width > w {
452                        w = *width;
453                    }
454                }
455                NodeData::Glue { spec, leader } => {
456                    x += d;
457                    d = 0;
458                    x += spec.width;
459                    tot.add(spec);
460                    let leader = *leader;
461                    if self.nodes.subtype(q) >= glue_sub::A_LEADERS && !leader.is_null() {
462                        let lw = self.nodes.width(leader);
463                        if lw > w {
464                            w = lw;
465                        }
466                    }
467                }
468                NodeData::Kern { kern, .. } => {
469                    x += d + *kern;
470                    d = 0;
471                }
472                _ => {}
473            }
474            q = self.nodes.next(q);
475        }
476        {
477            let b = self.box_mut(r);
478            b.width = w;
479            b.head = p;
480        }
481        if d > l {
482            x += d - l;
483            self.box_mut(r).depth = l;
484        } else {
485            self.box_mut(r).depth = d;
486        }
487        let h = match spec {
488            PackSpec::Exactly(h) => h,
489            PackSpec::Additional(h) => x + h,
490        };
491        self.box_mut(r).height = h;
492        self.ts.last_totals = (tot.stretch, tot.shrink);
493        let x = h - x;
494        let report = if x == 0 {
495            false
496        } else if x > 0 {
497            let o = tot.stretch_order();
498            let ts = tot.stretch[o as usize];
499            {
500                let b = self.box_mut(r);
501                b.glue_order = o;
502                b.glue_sign = 1;
503                if ts != 0 {
504                    b.glue_set = crate::nodes::glue_ratio(x, ts);
505                } else {
506                    b.glue_sign = 0;
507                    b.glue_set = 0.0;
508                }
509            }
510            if o == 0 && !p.is_null() {
511                self.last_badness = retex_arith::badness(x, tot.stretch[0]);
512                if self.last_badness > self.eqtb.int(ip::VBADNESS) {
513                    self.printer.print_ln();
514                    if self.last_badness > 100 {
515                        self.diag_capture_start();
516                        self.printer.print_nl("Underfull");
517                    } else {
518                        self.printer.print_nl("Loose");
519                    }
520                    self.printer.print_str(" \\vbox (badness ");
521                    self.printer.print_int(i64::from(self.last_badness));
522                    true
523                } else {
524                    false
525                }
526            } else {
527                false
528            }
529        } else {
530            let o = tot.shrink_order();
531            let ts = tot.shrink[o as usize];
532            {
533                let b = self.box_mut(r);
534                b.glue_order = o;
535                b.glue_sign = 2;
536                if ts != 0 {
537                    b.glue_set = crate::nodes::glue_ratio(-x, ts);
538                } else {
539                    b.glue_sign = 0;
540                    b.glue_set = 0.0;
541                }
542            }
543            if ts < -x && o == 0 && !p.is_null() {
544                self.last_badness = 1_000_000;
545                self.box_mut(r).glue_set = 1.0;
546                let excess = -x - tot.shrink[0];
547                if excess > self.eqtb.dimen(dp::VFUZZ) || self.eqtb.int(ip::VBADNESS) < 100 {
548                    self.printer.print_ln();
549                    self.diag_capture_start();
550                    self.printer.print_nl("Overfull \\vbox (");
551                    self.printer.print_scaled(excess);
552                    self.printer.print_str("pt too high");
553                    true
554                } else {
555                    false
556                }
557            } else if o == 0 && !p.is_null() {
558                self.last_badness = retex_arith::badness(-x, tot.shrink[0]);
559                if self.last_badness > self.eqtb.int(ip::VBADNESS) {
560                    self.printer.print_ln();
561                    self.diag_capture_start();
562                    self.printer.print_nl("Tight \\vbox (badness ");
563                    self.printer.print_int(i64::from(self.last_badness));
564                    true
565                } else {
566                    false
567                }
568            } else {
569                false
570            }
571        };
572        if report {
573            if self.ts.output_active {
574                self.printer
575                    .print_str(") has occurred while \\output is active");
576            } else {
577                let pbl = self.ts.pack_begin_line;
578                if pbl != 0 {
579                    self.printer.print_str(") in alignment at lines ");
580                    self.printer.print_int(i64::from(pbl.abs()));
581                    self.printer.print_str("--");
582                } else {
583                    self.printer.print_str(") detected at line ");
584                }
585                self.printer.print_int(i64::from(self.input.line()));
586            }
587            let at = (self.ts.pack_begin_line != 0).then(|| self.ts.pack_begin_line.unsigned_abs());
588            self.diag_capture_warning(at);
589            if !self.ts.output_active {
590                self.printer.print_ln();
591            }
592            self.begin_diagnostic();
593            self.show_box(r);
594            self.end_diagnostic(true);
595        }
596        r
597    }
598}
599
600/// The running dimension of rules (`null_flag`).
601const NULL_RULE: i32 = crate::typeset::NULL_FLAG;