Skip to main content

tex_engine/tex/nodes/
boxes.rs

1/*! [`TeXBox`]es */
2
3use crate::engine::filesystem::SourceRef;
4use crate::engine::stomach::methods::ParLineSpec;
5use crate::engine::EngineTypes;
6use crate::tex::nodes::horizontal::{HNode, HorizontalNodeListType};
7use crate::tex::nodes::math::{
8    MathAtom, MathClass, MathKernel, MathNode, MathNucleus, UnresolvedMathFontStyle,
9};
10use crate::tex::nodes::vertical::{VNode, VerticalNodeListType};
11use crate::tex::nodes::{display_do_indent, BoxTarget, NodeList, NodeTrait, NodeType};
12use crate::tex::numerics::Skip;
13use crate::tex::numerics::TeXDimen;
14use std::fmt::{Display, Formatter};
15
16#[cfg(feature = "multithreaded")]
17type Once<A> = std::sync::OnceLock<A>;
18#[cfg(not(feature = "multithreaded"))]
19type Once<A> = std::cell::OnceCell<A>;
20
21/// The type of a box, e.g. `\hbox` or `\vbox`.
22#[derive(Clone, Copy, Eq, PartialEq, Debug)]
23pub enum BoxType {
24    /// A horizontal box, e.g. `\hbox`.
25    Horizontal,
26    /// A vertical box, e.g. `\vbox`.
27    Vertical,
28}
29impl BoxType {
30    /// Horizontal -> Vertical, Vertical -> Horizontal
31    pub fn other(&self) -> Self {
32        match self {
33            BoxType::Horizontal => BoxType::Vertical,
34            BoxType::Vertical => BoxType::Horizontal,
35        }
36    }
37}
38impl Display for BoxType {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        match self {
41            BoxType::Horizontal => write!(f, "hbox"),
42            BoxType::Vertical => write!(f, "vbox"),
43            //BoxType::InlineMath | BoxType::DisplayMath => write!(f, "math shift")
44        }
45    }
46}
47
48/// The "scaling factor" of a box, e.g. `\hbox to 50pt` or `\hbox spread 10pt`.
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum ToOrSpread<D: TeXDimen> {
51    /// unscaled
52    None,
53    /// e.g. `\hbox to 50pt`
54    To(D),
55    /// e.g. `\hbox spread 10pt`
56    Spread(D),
57}
58
59/// "Metadata" of a horizontal box
60#[derive(Debug, Clone, PartialEq, Eq)]
61pub enum HBoxInfo<ET: EngineTypes> {
62    /// A "normal" `\hbox`
63    HBox {
64        /// scaling factor
65        scaled: ToOrSpread<ET::Dim>,
66        /// width, if assigned via e.g. `\wd0=50pt`
67        assigned_width: Option<ET::Dim>,
68        /// height, if assigned via e.g. `\ht0=50pt`
69        assigned_height: Option<ET::Dim>,
70        /// depth, if assigned via e.g. `\dp0=50pt`
71        assigned_depth: Option<ET::Dim>,
72        /// horizontal movement, if moved via e.g. `\moveleft 50pt` (or `\moveright`)
73        moved_left: Option<ET::Dim>,
74        /// vertical movement, if raised via e.g. `\raise 50pt` (or `\lower`)
75        raised: Option<ET::Dim>,
76        /// computed width by summing the widths of all children
77        computed_width: Once<ET::Dim>,
78        /// computed height by taking the maximum height of all children
79        computed_height: Once<ET::Dim>,
80        /// computed depth by taking the maximum depth of all children
81        computed_depth: Once<ET::Dim>,
82    },
83    /// A line in a paragraph
84    ParLine {
85        /// The paragraph line specification used to determine the break point of this line
86        spec: ParLineSpec<ET>,
87        /// Whether this line was forcefully ended via `\penalty-10000`
88        ends_with_line_break: bool,
89        /// The height of the line as computed via the maximum height of all children
90        inner_height: ET::Dim,
91        /// The depth of the line as computed via the maximum depth of all children
92        inner_depth: ET::Dim,
93    },
94    /// A row in an `\halign`; should only contain [HBoxInfo::HAlignCell]s
95    HAlignRow,
96    /// A cell in an `\halign`
97    HAlignCell {
98        /// The width of the cell, as computed by comparing all cells in the same columng (not yet implemented)
99        to: Option<ET::Dim>,
100        /// The computed width of the cell as the sum of the widths of the children
101        computed_width: Once<ET::Dim>,
102        /// The computed height of the cell as the maximum height of the children
103        computed_height: Once<ET::Dim>,
104        /// The computed depth of the cell as the maximum depth of the children
105        computed_depth: Once<ET::Dim>,
106        /// The number of *additional* columns this cell spans (i.e. by default 0)
107        spans: u8,
108    },
109    /// A paragraph indent box
110    ParIndent(ET::Dim),
111}
112impl<ET: EngineTypes> Display for HBoxInfo<ET> {
113    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
114        use HBoxInfo::*;
115        match self {
116            HBox { .. } => write!(f, "hbox"),
117            ParLine { .. } => write!(f, "parline"),
118            ParIndent(_) => write!(f, "parindent"),
119            HAlignRow => write!(f, "halignrow"),
120            HAlignCell { .. } => write!(f, "haligncell"),
121        }
122    }
123}
124impl<ET: EngineTypes> HBoxInfo<ET> {
125    /// Create a new `\hbox` box info with the given scaling factor
126    pub fn new_box(scaled: ToOrSpread<ET::Dim>) -> Self {
127        HBoxInfo::HBox {
128            scaled,
129            assigned_width: None,
130            assigned_height: None,
131            assigned_depth: None,
132            moved_left: None,
133            raised: None,
134            computed_width: Once::new(),
135            computed_height: Once::new(),
136            computed_depth: Once::new(),
137        }
138    }
139    /// Convert this to a simple `\hbox` box info
140    pub fn to_hbox(&mut self) {
141        match self {
142            HBoxInfo::HBox { .. } => (),
143            HBoxInfo::ParLine { spec, .. } => {
144                *self = HBoxInfo::new_box(ToOrSpread::To(spec.target))
145            }
146            HBoxInfo::ParIndent(d) => *self = HBoxInfo::new_box(ToOrSpread::To(*d)),
147            HBoxInfo::HAlignRow => *self = HBoxInfo::new_box(ToOrSpread::None),
148            HBoxInfo::HAlignCell { to, .. } => {
149                *self = HBoxInfo::new_box(match to {
150                    None => ToOrSpread::None,
151                    Some(to) => ToOrSpread::To(*to),
152                })
153            }
154        }
155    }
156
157    /// Create a new `\halign` cell box info with the given number of column spans (default 0)
158    pub fn new_cell(spans: u8) -> Self {
159        HBoxInfo::HAlignCell {
160            to: None,
161            computed_width: Once::new(),
162            computed_height: Once::new(),
163            computed_depth: Once::new(),
164            spans,
165        }
166    }
167    /// Turns this box info into the corresponding [`NodeList`]
168    pub fn open_list(self, start: SourceRef<ET>) -> NodeList<ET> {
169        match self {
170            HBoxInfo::HBox { .. } => NodeList::Horizontal {
171                tp: HorizontalNodeListType::Box(self, start, BoxTarget::none()),
172                children: vec![],
173            },
174            HBoxInfo::ParLine { .. } => NodeList::Horizontal {
175                tp: HorizontalNodeListType::Paragraph(start),
176                children: vec![],
177            },
178            HBoxInfo::HAlignRow => NodeList::Horizontal {
179                tp: HorizontalNodeListType::HAlignRow(start),
180                children: vec![],
181            },
182            HBoxInfo::HAlignCell { .. } => NodeList::Horizontal {
183                tp: HorizontalNodeListType::HAlignCell(start, 0),
184                children: vec![],
185            },
186            HBoxInfo::ParIndent(_) => unreachable!(),
187        }
188    }
189
190    fn height_inner(v: &[HNode<ET>]) -> ET::Dim {
191        v.iter().map(|c| c.height()).max().unwrap_or_default()
192    }
193
194    fn depth_inner(v: &[HNode<ET>]) -> ET::Dim {
195        v.iter().map(|c| c.depth()).max().unwrap_or_default()
196    }
197
198    fn width_inner(v: &[HNode<ET>]) -> ET::Dim {
199        v.iter().map(|c| c.width()).sum()
200    }
201
202    fn get_height(&self, v: &[HNode<ET>]) -> ET::Dim {
203        match self {
204            HBoxInfo::HBox {
205                assigned_height,
206                computed_height,
207                ..
208            } => assigned_height
209                .unwrap_or_else(|| *computed_height.get_or_init(|| Self::height_inner(v))),
210            HBoxInfo::ParLine { inner_height, .. } => *inner_height,
211            HBoxInfo::HAlignRow => Self::height_inner(v),
212            HBoxInfo::HAlignCell { .. } => Self::height_inner(v),
213            HBoxInfo::ParIndent(_) => ET::Dim::default(),
214        }
215    }
216    fn get_width(&self, v: &[HNode<ET>]) -> ET::Dim {
217        match self {
218            HBoxInfo::HBox {
219                assigned_width,
220                computed_width,
221                ..
222            } => assigned_width
223                .unwrap_or_else(|| *computed_width.get_or_init(|| Self::width_inner(v))),
224            HBoxInfo::ParLine { spec, .. } => {
225                spec.leftskip.base + spec.rightskip.base + spec.target
226            }
227            HBoxInfo::HAlignRow => Self::width_inner(v),
228            HBoxInfo::HAlignCell {
229                to, computed_width, ..
230            } => to.unwrap_or_else(|| *computed_width.get_or_init(|| Self::width_inner(v))),
231            HBoxInfo::ParIndent(d) => *d,
232        }
233    }
234    fn get_depth(&self, v: &[HNode<ET>]) -> ET::Dim {
235        match self {
236            HBoxInfo::HBox {
237                assigned_depth,
238                computed_depth,
239                ..
240            } => assigned_depth
241                .unwrap_or_else(|| *computed_depth.get_or_init(|| Self::depth_inner(v))),
242            HBoxInfo::ParLine { inner_depth, .. } => *inner_depth,
243            HBoxInfo::HAlignRow => Self::depth_inner(v),
244            HBoxInfo::HAlignCell { .. } => Self::depth_inner(v),
245            HBoxInfo::ParIndent(_) => ET::Dim::default(),
246        }
247    }
248
249    /// Raise this box by the given amount (i.e. `\raise` or `\lower`)
250    pub fn raise(&mut self, d: ET::Dim) {
251        self.to_hbox();
252        match self {
253            HBoxInfo::HBox { ref mut raised, .. } => *raised = Some(d),
254            _ => unreachable!(),
255        }
256    }
257    /// Move this box left by the given amount (i.e. `\moveleft` or `\moveright`)
258    pub fn move_left(&mut self, d: ET::Dim) {
259        self.to_hbox();
260        match self {
261            HBoxInfo::HBox {
262                ref mut moved_left, ..
263            } => *moved_left = Some(d),
264            _ => unreachable!(),
265        }
266    }
267}
268
269/// "Metadata" of a vertical box
270#[derive(Debug, Clone, PartialEq, Eq)]
271pub enum VBoxInfo<ET: EngineTypes> {
272    /// A "normal" `\vbox`
273    VBox {
274        /// scaling factor
275        scaled: ToOrSpread<ET::Dim>,
276        /// width, if assigned via e.g. `\wd0=50pt`
277        assigned_width: Option<ET::Dim>,
278        /// height, if assigned via e.g. `\ht0=50pt`
279        assigned_height: Option<ET::Dim>,
280        /// depth, if assigned via e.g. `\dp0=50pt`
281        assigned_depth: Option<ET::Dim>,
282        /// horizontal movement, if moved via e.g. `\moveleft 50pt` (or `\moveright`)
283        moved_left: Option<ET::Dim>,
284        /// vertical movement, if raised via e.g. `\raise 50pt` (or `\lower`)
285        raised: Option<ET::Dim>,
286        /// computed width by taking the maximum width of all children
287        computed_width: Once<ET::Dim>,
288        /// computed height by summing the heights and depths of all children (except for the last)
289        computed_height: Once<ET::Dim>,
290        /// computed depth by taking the depth of the last child box
291        computed_depth: Once<ET::Dim>,
292    },
293    /// A `\vtop` box
294    VTop {
295        /// scaling factor
296        scaled: ToOrSpread<ET::Dim>,
297        /// width, if assigned via e.g. `\wd0=50pt`
298        assigned_width: Option<ET::Dim>,
299        /// height, if assigned via e.g. `\ht0=50pt`
300        assigned_height: Option<ET::Dim>,
301        /// depth, if assigned via e.g. `\dp0=50pt`
302        assigned_depth: Option<ET::Dim>,
303        /// horizontal movement, if moved via e.g. `\moveleft 50pt` (or `\moveright`)
304        moved_left: Option<ET::Dim>,
305        /// vertical movement, if raised via e.g. `\raise 50pt` (or `\lower`)
306        raised: Option<ET::Dim>,
307        /// computed width by taking the maximum width of all children
308        computed_width: Once<ET::Dim>,
309        /// computed height by taking the height of the first child box
310        computed_height: Once<ET::Dim>,
311        /// computed depth by taking the height + depth of all children minus the computed height
312        computed_depth: Once<ET::Dim>,
313    },
314    /// A column in a `\valign`; should only contain [VBoxInfo::VAlignCell]s
315    VAlignColumn,
316    /// A cell in a `\valign`
317    VAlignCell {
318        /// The height of the cell, as computed by comparing all cells in the same row (not yet implemented)
319        to: Option<ET::Dim>,
320        /// The number of *additional* rows this cell spans (i.e. by default 0)
321        spans: u8,
322    },
323}
324impl<ET: EngineTypes> Display for VBoxInfo<ET> {
325    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
326        use VBoxInfo::*;
327        match self {
328            VBox { .. } => write!(f, "vbox"),
329            VTop { .. } => write!(f, "vtop"),
330            VAlignColumn => write!(f, "valignrow"),
331            VAlignCell { .. } => write!(f, "valigncell"),
332        }
333    }
334}
335
336impl<ET: EngineTypes> VBoxInfo<ET> {
337    /// Create a new `\vbox` box info with the given scaling factor
338    pub fn new_box(scaled: ToOrSpread<ET::Dim>) -> Self {
339        VBoxInfo::VBox {
340            scaled,
341            assigned_width: None,
342            assigned_height: None,
343            assigned_depth: None,
344            moved_left: None,
345            raised: None,
346            computed_width: Once::new(),
347            computed_height: Once::new(),
348            computed_depth: Once::new(),
349        }
350    }
351
352    /// Convert this box to a `\vbox` (or `\vtop` if it already is a `\vtop`)
353    pub fn to_vbox(&mut self) {
354        match self {
355            VBoxInfo::VTop { .. } | VBoxInfo::VBox { .. } => (),
356            VBoxInfo::VAlignColumn => *self = VBoxInfo::new_box(ToOrSpread::None),
357            VBoxInfo::VAlignCell { to, .. } => {
358                *self = VBoxInfo::new_box(match to {
359                    None => ToOrSpread::None,
360                    Some(d) => ToOrSpread::To(*d),
361                })
362            }
363        }
364    }
365
366    /// Create a new `\vtop` box info with the given scaling factor
367    pub fn new_top(scaled: ToOrSpread<ET::Dim>) -> Self {
368        VBoxInfo::VTop {
369            scaled,
370            assigned_width: None,
371            assigned_height: None,
372            assigned_depth: None,
373            moved_left: None,
374            raised: None,
375            computed_width: Once::new(),
376            computed_height: Once::new(),
377            computed_depth: Once::new(),
378        }
379    }
380    /// Turns this box info into the corresponding [`NodeList`]
381    pub fn open_list(self, start: SourceRef<ET>) -> NodeList<ET> {
382        match self {
383            VBoxInfo::VBox { .. } => NodeList::Vertical {
384                tp: VerticalNodeListType::Box(self, start, BoxTarget::none()),
385                children: vec![],
386            },
387            VBoxInfo::VTop { .. } => NodeList::Vertical {
388                tp: VerticalNodeListType::Box(self, start, BoxTarget::none()),
389                children: vec![],
390            },
391            VBoxInfo::VAlignColumn => NodeList::Vertical {
392                tp: VerticalNodeListType::VAlignColumn(start),
393                children: vec![],
394            },
395            VBoxInfo::VAlignCell { .. } => NodeList::Vertical {
396                tp: VerticalNodeListType::VAlignCell(start, 0),
397                children: vec![],
398            },
399        }
400    }
401    /// Clone this box info for use in a split box (i.e. `\vsplit`)
402    pub fn clone_for_split(&mut self) -> Self {
403        match self {
404            VBoxInfo::VBox {
405                scaled,
406                assigned_width,
407                assigned_height,
408                assigned_depth,
409                moved_left,
410                raised,
411                ..
412            } => {
413                *assigned_height = None;
414                *assigned_depth = None;
415                *scaled = ToOrSpread::None;
416                VBoxInfo::VBox {
417                    scaled: ToOrSpread::None,
418                    assigned_width: *assigned_width,
419                    assigned_height: None,
420                    assigned_depth: None,
421                    moved_left: *moved_left,
422                    raised: *raised,
423                    computed_width: Once::new(),
424                    computed_height: Once::new(),
425                    computed_depth: Once::new(),
426                }
427            }
428            VBoxInfo::VTop {
429                scaled,
430                assigned_width,
431                assigned_height,
432                assigned_depth,
433                moved_left,
434                raised,
435                ..
436            } => {
437                *assigned_height = None;
438                *assigned_depth = None;
439                *scaled = ToOrSpread::None;
440                VBoxInfo::VBox {
441                    scaled: ToOrSpread::None,
442                    assigned_width: *assigned_width,
443                    assigned_height: None,
444                    assigned_depth: None,
445                    moved_left: *moved_left,
446                    raised: *raised,
447                    computed_width: Once::new(),
448                    computed_height: Once::new(),
449                    computed_depth: Once::new(),
450                }
451            }
452            _ => unreachable!(),
453        }
454    }
455
456    fn height_inner(v: &[VNode<ET>]) -> ET::Dim {
457        v.iter().map(|c| c.height() + c.depth()).sum::<ET::Dim>() + -Self::depth_inner(v)
458    }
459
460    fn depth_inner(v: &[VNode<ET>]) -> ET::Dim {
461        for c in v.iter().rev() {
462            if !c.opaque() {
463                return c.depth();
464            }
465        }
466        ET::Dim::default()
467    }
468
469    fn width_inner(v: &[VNode<ET>]) -> ET::Dim {
470        v.iter().map(|c| c.width()).max().unwrap_or_default()
471    }
472
473    fn get_height(&self, v: &[VNode<ET>]) -> ET::Dim {
474        match self {
475            VBoxInfo::VAlignColumn => Self::height_inner(v),
476            VBoxInfo::VAlignCell { to, .. } => to.unwrap_or_else(|| Self::height_inner(v)),
477            VBoxInfo::VBox {
478                assigned_height,
479                computed_height,
480                ..
481            } => assigned_height
482                .unwrap_or_else(|| *computed_height.get_or_init(|| Self::height_inner(v))),
483            VBoxInfo::VTop {
484                assigned_height,
485                computed_height,
486                ..
487            } => assigned_height.unwrap_or_else(|| {
488                *computed_height.get_or_init(|| match v.first() {
489                    Some(c @ VNode::Box(..)) => c.height(),
490                    _ => ET::Dim::default(),
491                })
492            }),
493        }
494    }
495    fn get_width(&self, v: &[VNode<ET>]) -> ET::Dim {
496        match self {
497            VBoxInfo::VAlignColumn => Self::width_inner(v),
498            VBoxInfo::VAlignCell { .. } => Self::width_inner(v),
499            VBoxInfo::VBox {
500                assigned_width,
501                computed_width,
502                ..
503            } => assigned_width
504                .unwrap_or_else(|| *computed_width.get_or_init(|| Self::width_inner(v))),
505            VBoxInfo::VTop {
506                assigned_width,
507                computed_width,
508                ..
509            } => assigned_width
510                .unwrap_or_else(|| *computed_width.get_or_init(|| Self::width_inner(v))),
511        }
512    }
513    fn get_depth(&self, v: &[VNode<ET>]) -> ET::Dim {
514        match self {
515            VBoxInfo::VAlignColumn => Self::depth_inner(v),
516            VBoxInfo::VAlignCell { .. } => Self::depth_inner(v),
517            VBoxInfo::VBox {
518                assigned_depth,
519                computed_depth,
520                ..
521            } => assigned_depth
522                .unwrap_or_else(|| *computed_depth.get_or_init(|| Self::depth_inner(v))),
523            VBoxInfo::VTop {
524                assigned_depth,
525                computed_depth,
526                ..
527            } => assigned_depth.unwrap_or_else(|| {
528                *computed_depth.get_or_init(|| {
529                    let x = match v.first() {
530                        Some(c @ VNode::Box(..)) => c.height(),
531                        _ => ET::Dim::default(),
532                    };
533                    let h = Self::height_inner(v);
534                    let d = Self::depth_inner(v);
535                    h + d - x
536                })
537            }),
538        }
539    }
540
541    /// Raise this box by the given amount (i.e. `\raise` or `\lower`)
542    pub fn raise(&mut self, d: ET::Dim) {
543        match self {
544            VBoxInfo::VBox { ref mut raised, .. } => *raised = Some(d),
545            VBoxInfo::VTop { ref mut raised, .. } => *raised = Some(d),
546            _ => {
547                self.to_vbox();
548                let VBoxInfo::VBox { raised, .. } = self else {
549                    unreachable!()
550                };
551                *raised = Some(d)
552            }
553        }
554    }
555    /// Move this box left by the given amount (i.e. `\moveleft` or `\moveright`)
556    pub fn move_left(&mut self, d: ET::Dim) {
557        match self {
558            VBoxInfo::VBox {
559                ref mut moved_left, ..
560            } => *moved_left = Some(d),
561            VBoxInfo::VTop {
562                ref mut moved_left, ..
563            } => *moved_left = Some(d),
564            _ => {
565                self.to_vbox();
566                let VBoxInfo::VBox { moved_left, .. } = self else {
567                    unreachable!()
568                };
569                *moved_left = Some(d)
570            }
571        }
572    }
573}
574
575/// "Metadata" of a box
576#[derive(Debug, Clone, PartialEq, Eq)]
577pub enum BoxInfo<ET: EngineTypes> {
578    H(HBoxInfo<ET>),
579    V(VBoxInfo<ET>),
580}
581impl<ET: EngineTypes> BoxInfo<ET> {
582    /// Turns this box info into the corresponding [`NodeList`]
583    pub fn open_list(self, start: SourceRef<ET>) -> NodeList<ET> {
584        match self {
585            BoxInfo::H(h) => h.open_list(start),
586            BoxInfo::V(v) => v.open_list(start),
587        }
588    }
589    /// Move this box left by the given amount (i.e. `\moveleft` or `\moveright`)
590    pub fn move_left(&mut self, d: ET::Dim) {
591        match self {
592            BoxInfo::H(h) => h.move_left(d),
593            BoxInfo::V(v) => v.move_left(d),
594        }
595    }
596    /// Raise this box by the given amount (i.e. `\raise` or `\lower`)
597    pub fn raise(&mut self, d: ET::Dim) {
598        match self {
599            BoxInfo::H(h) => h.raise(d),
600            BoxInfo::V(v) => v.raise(d),
601        }
602    }
603
604    pub fn assigned_height(&self) -> Option<ET::Dim> {
605        match self {
606            BoxInfo::H(h) => h.assigned_height(),
607            BoxInfo::V(v) => v.assigned_height(),
608        }
609    }
610    pub fn assigned_width(&self) -> Option<ET::Dim> {
611        match self {
612            BoxInfo::H(h) => h.assigned_width(),
613            BoxInfo::V(v) => v.assigned_width(),
614        }
615    }
616    pub fn assigned_depth(&self) -> Option<ET::Dim> {
617        match self {
618            BoxInfo::H(h) => h.assigned_depth(),
619            BoxInfo::V(v) => v.assigned_depth(),
620        }
621    }
622    pub fn computed_height(&self) -> Option<ET::Dim> {
623        match self {
624            BoxInfo::H(h) => h.computed_height(),
625            BoxInfo::V(v) => v.computed_height(),
626        }
627    }
628    pub fn computed_width(&self) -> Option<ET::Dim> {
629        match self {
630            BoxInfo::H(h) => h.computed_width(),
631            BoxInfo::V(v) => v.computed_width(),
632        }
633    }
634    pub fn computed_depth(&self) -> Option<ET::Dim> {
635        match self {
636            BoxInfo::H(h) => h.computed_depth(),
637            BoxInfo::V(v) => v.computed_depth(),
638        }
639    }
640    pub fn to_or_scaled(&self) -> Option<ToOrSpread<ET::Dim>> {
641        match self {
642            BoxInfo::H(HBoxInfo::HBox { scaled, .. }) => Some(*scaled),
643            BoxInfo::V(VBoxInfo::VBox { scaled, .. }) => Some(*scaled),
644            BoxInfo::V(VBoxInfo::VTop { scaled, .. }) => Some(*scaled),
645            _ => None,
646        }
647    }
648    pub fn raised(&self) -> Option<ET::Dim> {
649        match self {
650            BoxInfo::H(HBoxInfo::HBox { raised, .. }) => *raised,
651            BoxInfo::V(VBoxInfo::VBox { raised, .. }) => *raised,
652            BoxInfo::V(VBoxInfo::VTop { raised, .. }) => *raised,
653            _ => None,
654        }
655    }
656    pub fn moved_left(&self) -> Option<ET::Dim> {
657        match self {
658            BoxInfo::H(HBoxInfo::HBox { moved_left, .. }) => *moved_left,
659            BoxInfo::V(VBoxInfo::VBox { moved_left, .. }) => *moved_left,
660            BoxInfo::V(VBoxInfo::VTop { moved_left, .. }) => *moved_left,
661            _ => None,
662        }
663    }
664}
665impl<ET: EngineTypes> VBoxInfo<ET> {
666    pub fn assigned_height(&self) -> Option<ET::Dim> {
667        match self {
668            VBoxInfo::VBox {
669                assigned_height, ..
670            } => *assigned_height,
671            VBoxInfo::VTop {
672                assigned_height, ..
673            } => *assigned_height,
674            _ => None,
675        }
676    }
677    pub fn assigned_width(&self) -> Option<ET::Dim> {
678        match self {
679            VBoxInfo::VBox { assigned_width, .. } => *assigned_width,
680            VBoxInfo::VTop { assigned_width, .. } => *assigned_width,
681            _ => None,
682        }
683    }
684    pub fn assigned_depth(&self) -> Option<ET::Dim> {
685        match self {
686            VBoxInfo::VBox { assigned_depth, .. } => *assigned_depth,
687            VBoxInfo::VTop { assigned_depth, .. } => *assigned_depth,
688            _ => None,
689        }
690    }
691    pub fn computed_height(&self) -> Option<ET::Dim> {
692        match self {
693            VBoxInfo::VBox {
694                computed_height, ..
695            } => computed_height.get().copied(),
696            VBoxInfo::VTop {
697                computed_height, ..
698            } => computed_height.get().copied(),
699            _ => None,
700        }
701    }
702    pub fn computed_width(&self) -> Option<ET::Dim> {
703        match self {
704            VBoxInfo::VBox { computed_width, .. } => computed_width.get().copied(),
705            VBoxInfo::VTop { computed_width, .. } => computed_width.get().copied(),
706            _ => None,
707        }
708    }
709    pub fn computed_depth(&self) -> Option<ET::Dim> {
710        match self {
711            VBoxInfo::VBox { computed_depth, .. } => computed_depth.get().copied(),
712            VBoxInfo::VTop { computed_depth, .. } => computed_depth.get().copied(),
713            _ => None,
714        }
715    }
716    pub fn raised(&self) -> Option<ET::Dim> {
717        match self {
718            VBoxInfo::VBox { raised, .. } => *raised,
719            VBoxInfo::VTop { raised, .. } => *raised,
720            _ => None,
721        }
722    }
723    pub fn moved_left(&self) -> Option<ET::Dim> {
724        match self {
725            VBoxInfo::VBox { moved_left, .. } => *moved_left,
726            VBoxInfo::VTop { moved_left, .. } => *moved_left,
727            _ => None,
728        }
729    }
730    pub fn to_or_scaled(&self) -> Option<ToOrSpread<ET::Dim>> {
731        match self {
732            VBoxInfo::VBox { scaled, .. } => Some(*scaled),
733            VBoxInfo::VTop { scaled, .. } => Some(*scaled),
734            _ => None,
735        }
736    }
737}
738impl<ET: EngineTypes> HBoxInfo<ET> {
739    pub fn assigned_height(&self) -> Option<ET::Dim> {
740        match self {
741            HBoxInfo::HBox {
742                assigned_height, ..
743            } => *assigned_height,
744            _ => None,
745        }
746    }
747    pub fn assigned_width(&self) -> Option<ET::Dim> {
748        match self {
749            HBoxInfo::HBox { assigned_width, .. } => *assigned_width,
750            _ => None,
751        }
752    }
753    pub fn assigned_depth(&self) -> Option<ET::Dim> {
754        match self {
755            HBoxInfo::HBox { assigned_depth, .. } => *assigned_depth,
756            _ => None,
757        }
758    }
759    pub fn computed_height(&self) -> Option<ET::Dim> {
760        match self {
761            HBoxInfo::HBox {
762                computed_height, ..
763            } => computed_height.get().copied(),
764            _ => None,
765        }
766    }
767    pub fn computed_width(&self) -> Option<ET::Dim> {
768        match self {
769            HBoxInfo::HBox { computed_width, .. } => computed_width.get().copied(),
770            _ => None,
771        }
772    }
773    pub fn computed_depth(&self) -> Option<ET::Dim> {
774        match self {
775            HBoxInfo::HBox { computed_depth, .. } => computed_depth.get().copied(),
776            _ => None,
777        }
778    }
779    pub fn raised(&self) -> Option<ET::Dim> {
780        match self {
781            HBoxInfo::HBox { raised, .. } => *raised,
782            _ => None,
783        }
784    }
785    pub fn moved_left(&self) -> Option<ET::Dim> {
786        match self {
787            HBoxInfo::HBox { moved_left, .. } => *moved_left,
788            _ => None,
789        }
790    }
791    pub fn to_or_scaled(&self) -> Option<ToOrSpread<ET::Dim>> {
792        match self {
793            HBoxInfo::HBox { scaled, .. } => Some(*scaled),
794            _ => None,
795        }
796    }
797}
798
799/// A box, i.e. the result of e.g. `\hbox`, `\vbox`, `\vtop`,
800/// a line of a paragraph, a row/cell in an `\halign`...
801#[derive(Debug, Clone)]
802pub enum TeXBox<ET: EngineTypes> {
803    /// A vertial box
804    V {
805        /// The box info, containing "metadata" about the box
806        info: VBoxInfo<ET>,
807        /// The nodes in this box
808        children: Box<[VNode<ET>]>,
809        /// The source reference of the start of this box
810        start: SourceRef<ET>,
811        /// The source reference of the end of this box
812        end: SourceRef<ET>,
813    },
814    /// A horizontal box
815    H {
816        /// The box info, containing "metadata" about the box
817        info: HBoxInfo<ET>,
818        /// The nodes in this box
819        children: Box<[HNode<ET>]>,
820        /// The source reference of the start of this box
821        start: SourceRef<ET>,
822        /// The source reference of the end of this box
823        end: SourceRef<ET>,
824        /// The vertical skip before this box, if any, as computed based on `\prevdepth`
825        preskip: Option<Skip<ET::Dim>>,
826    },
827}
828
829impl<ET: EngineTypes> TeXBox<ET> {
830    pub fn to_math(self) -> MathNode<ET, UnresolvedMathFontStyle<ET>> {
831        MathNode::Atom(MathAtom {
832            nucleus: MathNucleus::Simple {
833                kernel: MathKernel::Box(self),
834                limits: None,
835                cls: MathClass::Ord,
836            },
837            sub: None,
838            sup: None,
839        })
840    }
841    /// Whether this box is empty
842    pub fn is_empty(&self) -> bool {
843        match self {
844            TeXBox::H { children, .. } => children.is_empty(),
845            TeXBox::V { children, .. } => children.is_empty(),
846        }
847    }
848    /// Assigns the given height to this box (as e.g. `\ht0=50pt`)
849    pub fn assign_height(&mut self, h: ET::Dim) {
850        match self {
851            TeXBox::H {
852                info:
853                    HBoxInfo::HBox {
854                        ref mut assigned_height,
855                        ..
856                    },
857                ..
858            } => *assigned_height = Some(h),
859            TeXBox::V {
860                info:
861                    VBoxInfo::VBox {
862                        ref mut assigned_height,
863                        ..
864                    },
865                ..
866            } => *assigned_height = Some(h),
867            TeXBox::V {
868                info:
869                    VBoxInfo::VTop {
870                        ref mut assigned_height,
871                        ..
872                    },
873                ..
874            } => *assigned_height = Some(h),
875            _ => (),
876        }
877    }
878    /// The assigned height of this box, if any (i.e. the result of `\ht0=...`)
879    pub fn assigned_height(&self) -> Option<ET::Dim> {
880        match self {
881            TeXBox::H {
882                info: HBoxInfo::HBox {
883                    assigned_height, ..
884                },
885                ..
886            } => *assigned_height,
887            TeXBox::V {
888                info: VBoxInfo::VBox {
889                    assigned_height, ..
890                },
891                ..
892            } => *assigned_height,
893            TeXBox::V {
894                info: VBoxInfo::VTop {
895                    assigned_height, ..
896                },
897                ..
898            } => *assigned_height,
899            _ => None,
900        }
901    }
902    /// Assigns the given width to this box (as e.g. `\wd0=50pt`)
903    pub fn assign_width(&mut self, w: ET::Dim) {
904        match self {
905            TeXBox::H {
906                info:
907                    HBoxInfo::HBox {
908                        ref mut assigned_width,
909                        ..
910                    },
911                ..
912            } => *assigned_width = Some(w),
913            TeXBox::V {
914                info:
915                    VBoxInfo::VBox {
916                        ref mut assigned_width,
917                        ..
918                    },
919                ..
920            } => *assigned_width = Some(w),
921            TeXBox::V {
922                info:
923                    VBoxInfo::VTop {
924                        ref mut assigned_width,
925                        ..
926                    },
927                ..
928            } => *assigned_width = Some(w),
929            _ => (),
930        }
931    }
932    /// The assigned width of this box, if any (i.e. the result of `\wd0=...`)
933    pub fn assigned_width(&self) -> Option<ET::Dim> {
934        match self {
935            TeXBox::H {
936                info: HBoxInfo::HBox { assigned_width, .. },
937                ..
938            } => *assigned_width,
939            TeXBox::V {
940                info: VBoxInfo::VBox { assigned_width, .. },
941                ..
942            } => *assigned_width,
943            TeXBox::V {
944                info: VBoxInfo::VTop { assigned_width, .. },
945                ..
946            } => *assigned_width,
947            _ => None,
948        }
949    }
950    /// Assigns the given depth to this box (as e.g. `\dp0=50pt`)
951    pub fn assign_depth(&mut self, d: ET::Dim) {
952        match self {
953            TeXBox::H {
954                info:
955                    HBoxInfo::HBox {
956                        ref mut assigned_depth,
957                        ..
958                    },
959                ..
960            } => *assigned_depth = Some(d),
961            TeXBox::V {
962                info:
963                    VBoxInfo::VBox {
964                        ref mut assigned_depth,
965                        ..
966                    },
967                ..
968            } => *assigned_depth = Some(d),
969            TeXBox::V {
970                info:
971                    VBoxInfo::VTop {
972                        ref mut assigned_depth,
973                        ..
974                    },
975                ..
976            } => *assigned_depth = Some(d),
977            _ => (),
978        }
979    }
980    /// The assigned depth of this box, if any (i.e. the result of `\dp0=...`)
981    pub fn assigned_depth(&self) -> Option<ET::Dim> {
982        match self {
983            TeXBox::H {
984                info: HBoxInfo::HBox { assigned_depth, .. },
985                ..
986            } => *assigned_depth,
987            TeXBox::V {
988                info: VBoxInfo::VBox { assigned_depth, .. },
989                ..
990            } => *assigned_depth,
991            TeXBox::V {
992                info: VBoxInfo::VTop { assigned_depth, .. },
993                ..
994            } => *assigned_depth,
995            _ => None,
996        }
997    }
998    /// The "scaling factor" of this box, i.e. the `to` or `spread` in e.g. `\hbox to 50pt` or `\hbox spread 10pt`
999    pub fn to_or_scaled(&self) -> ToOrSpread<ET::Dim> {
1000        match self {
1001            TeXBox::H {
1002                info: HBoxInfo::HBox { scaled, .. },
1003                ..
1004            } => *scaled,
1005            TeXBox::V {
1006                info: VBoxInfo::VBox { scaled, .. },
1007                ..
1008            } => *scaled,
1009            TeXBox::V {
1010                info: VBoxInfo::VTop { scaled, .. },
1011                ..
1012            } => *scaled,
1013            _ => ToOrSpread::None,
1014        }
1015    }
1016}
1017
1018impl<ET: EngineTypes> NodeTrait<ET> for TeXBox<ET> {
1019    fn display_fmt(&self, indent: usize, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1020        display_do_indent(indent, f)?;
1021        match self {
1022            TeXBox::H { info, children, .. } => {
1023                write!(f, "<hbox:{}>", info)?;
1024                for c in children.iter() {
1025                    c.display_fmt(indent + 2, f)?;
1026                }
1027                display_do_indent(indent, f)?;
1028                write!(f, "</hbox:{}>", info)
1029            }
1030            TeXBox::V { info, children, .. } => {
1031                write!(f, "<vbox:{}>", info)?;
1032                for c in children.iter() {
1033                    c.display_fmt(indent + 2, f)?;
1034                }
1035                display_do_indent(indent, f)?;
1036                write!(f, "</vbox:{}>", info)
1037            }
1038        }
1039    }
1040
1041    fn height(&self) -> ET::Dim {
1042        match self {
1043            TeXBox::H {
1044                info,
1045                children,
1046                preskip,
1047                ..
1048            } => info.get_height(children) + preskip.map(|s| s.base).unwrap_or_default(),
1049            TeXBox::V { info, children, .. } => info.get_height(children),
1050        }
1051    }
1052
1053    fn width(&self) -> ET::Dim {
1054        match self {
1055            TeXBox::H { info, children, .. } => info.get_width(children),
1056            TeXBox::V { info, children, .. } => info.get_width(children),
1057        }
1058    }
1059
1060    fn depth(&self) -> ET::Dim {
1061        match self {
1062            TeXBox::H { info, children, .. } => info.get_depth(children),
1063            TeXBox::V { info, children, .. } => info.get_depth(children),
1064        }
1065    }
1066    fn nodetype(&self) -> NodeType {
1067        match self {
1068            TeXBox::H { .. } => NodeType::HList,
1069            TeXBox::V { .. } => NodeType::VList,
1070        }
1071    }
1072    fn sourceref(&self) -> Option<(&SourceRef<ET>, &SourceRef<ET>)> {
1073        match self {
1074            TeXBox::H { start, end, .. } => Some((start, end)),
1075            TeXBox::V { start, end, .. } => Some((start, end)),
1076        }
1077    }
1078}