Skip to main content

tex_engine/tex/nodes/
math.rs

1/*! Nodes allowed in math mode */
2use crate::commands::primitives::PRIMITIVES;
3use crate::engine::filesystem::SourceRef;
4use crate::engine::fontsystem::Font;
5use crate::engine::state::State;
6use crate::engine::EngineTypes;
7use crate::tex::nodes::boxes::{TeXBox, ToOrSpread};
8use crate::tex::nodes::vertical::VNode;
9use crate::tex::nodes::{display_do_indent, Leaders, ListTarget, NodeTrait, NodeType, WhatsitNode};
10use crate::tex::numerics::NumSet;
11use crate::tex::numerics::{MuSkip, Skip, TeXDimen};
12use crate::tex::tokens::token_lists::TokenList;
13use either::Either;
14use std::cell::OnceCell;
15use std::fmt::{Debug, Formatter, Write};
16use std::marker::PhantomData;
17
18/// A math list node. Comes in two forms: an unresolved form, while the list is being
19/// constructed and the various font styles are not fixed yet (e.g. because
20/// an `\atop` comes later that shifts the font style), and a resolved form,
21/// where the fonts have been determined and are fixed.
22/// The parameter `S:`[`MathFontStyleT`] makes the distinction between the two forms.
23/// For `S=`[`MathFontStyle`] (the resolved form), this implements [`NodeTrait`].
24#[derive(Clone, Debug)]
25pub enum MathNode<ET: EngineTypes, S: MathFontStyleT<ET>> {
26    /// A math atom node (see [`MathAtom`])
27    Atom(MathAtom<ET, S>),
28    /// A penalty node, as produced by `\penalty`.
29    Penalty(i32),
30    /// A mark node, as produced by `\mark`.
31    Mark(usize, TokenList<ET::Token>),
32    /// A whatsit node, as produced by `\special`, `\write`, etc.
33    Whatsit(WhatsitNode<ET>),
34    /// A glue node, as produced by `\hskip`.
35    HSkip(Skip<ET::Dim>),
36    /// A glue node, as produced by `\mskip`. If resolved, `S` provides the adequate `em` value.
37    MSkip { skip: MuSkip<ET::MuDim>, style: S },
38    /// A glue node, as produced by `\hfil`.
39    HFil,
40    /// A glue node, as produced by `\hfill`.
41    HFill,
42    /// A glue node, as produced by `\hfilneg`.
43    HFilneg,
44    /// A glue node, as produced by `\hss`.
45    Hss,
46    /// A glue node, as produced by `\ `
47    Space,
48    /// A kern node, as produced by `\kern`.
49    HKern(ET::Dim),
50    /// A kern node, as produced by `\mkern`.
51    MKern { kern: ET::MuDim, style: S },
52    /// A leaders node, as produced by `\leaders` or `\cleaders` or `\xleaders`.
53    Leaders(Leaders<ET>),
54    /// A rule node, as produced by `\vrule`.
55    VRule {
56        /// The *provided* width of the rule.
57        width: Option<ET::Dim>,
58        /// The *provided* height of the rule.
59        height: Option<ET::Dim>,
60        /// The *provided* depth of the rule.
61        depth: Option<ET::Dim>,
62        /// The source reference for the start of the rule.
63        start: SourceRef<ET>,
64        /// The source reference for the end of the rule.
65        end: SourceRef<ET>,
66    },
67    /// A "generalized fraction", as produced by `\over`, `\atop`, `\above`,
68    /// `\abovewithdelims`, `\overwithdelims`, `\atopwithdelims`.
69    Over {
70        /// The source reference for the start of the node.
71        start: SourceRef<ET>,
72        /// The source reference for the end of the node.
73        end: SourceRef<ET>,
74        /// The numerator.
75        top: Box<[MathNode<ET, S>]>,
76        /// The optional separator height.
77        sep: Option<ET::Dim>,
78        /// The denominator.
79        bottom: Box<[MathNode<ET, S>]>,
80        /// The optional left delimiter.
81        left: Option<(ET::Char, S)>,
82        /// The optional right delimiter.
83        right: Option<(ET::Char, S)>,
84    },
85    /// A `\mathchoice` node; if resolved, this is just a wrapper around more math nodes.
86    Choice(S::Choice),
87    /// Markers for the various font styles, e.g. `\displaystyle`, `\textstyle`, etc.
88    Marker(S::Markers),
89    /// A custom node.
90    Custom(ET::CustomNode),
91}
92impl<ET: EngineTypes> MathNode<ET, UnresolvedMathFontStyle<ET>> {
93    /// The node type of this node (as in `\lastnodetype`).
94    pub fn nodetype(&self) -> NodeType {
95        match self {
96            MathNode::Penalty(_) => NodeType::Penalty,
97            MathNode::VRule { .. } => NodeType::Rule,
98            MathNode::HKern(_) | MathNode::MKern { .. } => NodeType::Kern,
99            MathNode::Mark(_, _) => NodeType::Mark,
100            MathNode::Whatsit(_) => NodeType::WhatsIt,
101            MathNode::HSkip(_)
102            | MathNode::MSkip { .. }
103            | MathNode::Space
104            | MathNode::HFil
105            | MathNode::HFill
106            | MathNode::HFilneg
107            | MathNode::Hss => NodeType::Glue,
108            MathNode::Leaders(_) => NodeType::Glue,
109            MathNode::Custom(n) => n.nodetype(),
110            _ => NodeType::Math,
111        }
112    }
113    /// See [`NodeTrait::opaque`].
114    pub fn opaque(&self) -> bool {
115        match self {
116            MathNode::Mark(_, _) => true,
117            MathNode::Custom(n) => n.opaque(),
118            _ => false,
119        }
120    }
121}
122impl<ET: EngineTypes> NodeTrait<ET> for MathNode<ET, MathFontStyle<ET>> {
123    fn display_fmt(&self, indent: usize, f: &mut Formatter<'_>) -> std::fmt::Result {
124        match self {
125            MathNode::Penalty(p) => {
126                display_do_indent(indent, f)?;
127                write!(f, "<penalty:{}>", p)
128            }
129            MathNode::Mark(i, _) => {
130                display_do_indent(indent, f)?;
131                write!(f, "<mark:{}>", i)
132            }
133            MathNode::Over {
134                top,
135                sep,
136                bottom,
137                left,
138                right,
139                ..
140            } => {
141                display_do_indent(indent, f)?;
142                write!(f, "<over")?;
143                if let Some(s) = sep {
144                    write!(f, " sep={}", s)?;
145                }
146                f.write_char('>')?;
147                if let Some((l, _)) = left {
148                    display_do_indent(indent + 2, f)?;
149                    write!(f, "<left-delim = {}/>", l)?;
150                }
151                display_do_indent(indent + 2, f)?;
152                f.write_str("<top>")?;
153                for c in top.iter() {
154                    c.display_fmt(indent + 4, f)?;
155                }
156                display_do_indent(indent + 2, f)?;
157                f.write_str("</top>")?;
158                display_do_indent(indent + 2, f)?;
159                f.write_str("<bottom>")?;
160                for c in bottom.iter() {
161                    c.display_fmt(indent + 4, f)?;
162                }
163                display_do_indent(indent + 2, f)?;
164                f.write_str("</bottom>")?;
165                if let Some((r, _)) = right {
166                    display_do_indent(indent + 2, f)?;
167                    write!(f, "<right-delim = {}/>", r)?;
168                }
169                display_do_indent(indent, f)?;
170                write!(f, "</over>")
171            }
172            MathNode::Marker(m) => m.display_fmt(indent, f),
173            MathNode::Choice(c) => c.display_fmt(indent, f),
174            MathNode::Leaders(l) => l.display_fmt(indent, f),
175            MathNode::VRule {
176                width,
177                height,
178                depth,
179                ..
180            } => {
181                write!(f, "<vrule")?;
182                if let Some(w) = width {
183                    write!(f, " width={}", w)?;
184                }
185                if let Some(h) = height {
186                    write!(f, " height={}", h)?;
187                }
188                if let Some(d) = depth {
189                    write!(f, " depth={}", d)?;
190                }
191                write!(f, ">")
192            }
193            MathNode::Whatsit(w) => {
194                display_do_indent(indent, f)?;
195                write!(f, "{:?}", w)
196            }
197            MathNode::HSkip(s) => write!(f, "<hskip:{}>", s),
198            MathNode::MSkip { skip, .. } => write!(f, "<mskip:{}>", skip),
199            MathNode::MKern { kern, .. } => write!(f, "<mkern:{}>", kern),
200            MathNode::HFil => write!(f, "<hfil>"),
201            MathNode::HFill => write!(f, "<hfill>"),
202            MathNode::HFilneg => write!(f, "<hfilneg>"),
203            MathNode::Hss => write!(f, "<hss>"),
204            MathNode::Space => write!(f, "<space>"),
205            MathNode::HKern(d) => write!(f, "<hkern:{}>", d),
206            MathNode::Custom(n) => n.display_fmt(indent, f),
207            MathNode::Atom(a) => a.display_fmt(indent, f),
208        }
209    }
210    fn height(&self) -> ET::Dim {
211        match self {
212            MathNode::VRule { height, .. } => height.unwrap_or_default(),
213            MathNode::Custom(n) => n.height(),
214            MathNode::Atom(a) => a.height(),
215            MathNode::Leaders(l) => l.height(),
216            MathNode::Choice(c) => c.height(),
217            MathNode::Over { top, sep, .. } => {
218                let mut inner = top
219                    .iter()
220                    .map(|c| c.height() + c.depth())
221                    .max()
222                    .unwrap_or_default();
223                match sep {
224                    None => (),
225                    Some(s) => inner = inner + s.scale_float(0.5) + ET::Dim::from_sp(65536 * 3), // TODO heuristic
226                }
227                inner
228            }
229            _ => ET::Dim::default(),
230        }
231    }
232    fn width(&self) -> ET::Dim {
233        match self {
234            MathNode::VRule { width, .. } => width.unwrap_or(ET::Dim::from_sp(26214)),
235            MathNode::Custom(n) => n.width(),
236            MathNode::HKern(d) => *d,
237            MathNode::MKern { kern, style } => ET::Num::mudim_to_dim(*kern, style.get_em()),
238            MathNode::MSkip { skip, style } => ET::Num::mudim_to_dim(skip.base, style.get_em()),
239            MathNode::HSkip(s) => s.base,
240            MathNode::Space => ET::Dim::from_sp(65536 * 5), // TODO heuristic; use spacefactor instead
241            MathNode::Leaders(l) => l.width(),
242            MathNode::Atom(a) => a.width(),
243            MathNode::Choice(c) => c.width(),
244            MathNode::Over { top, bottom, .. } => {
245                let top: ET::Dim = top.iter().map(|c| c.width()).sum();
246                let bot: ET::Dim = bottom.iter().map(|c| c.width()).sum();
247                top.max(bot)
248            }
249            _ => ET::Dim::default(),
250        }
251    }
252    fn depth(&self) -> ET::Dim {
253        match self {
254            MathNode::VRule { depth, .. } => depth.unwrap_or_default(),
255            MathNode::Custom(n) => n.depth(),
256            MathNode::Atom(a) => a.depth(),
257            MathNode::Leaders(l) => l.depth(),
258            MathNode::Choice(c) => c.depth(),
259            MathNode::Over { bottom, sep, .. } => {
260                let mut inner = bottom
261                    .iter()
262                    .map(|c| c.height() + c.depth())
263                    .max()
264                    .unwrap_or_default();
265                match sep {
266                    None => (),
267                    Some(s) => inner = inner + s.scale_float(0.5) + ET::Dim::from_sp(65536 * 3), // TODO heuristic
268                }
269                inner
270            }
271            _ => ET::Dim::default(),
272        }
273    }
274    fn nodetype(&self) -> NodeType {
275        match self {
276            MathNode::Penalty(_) => NodeType::Penalty,
277            MathNode::VRule { .. } => NodeType::Rule,
278            MathNode::HKern(_) | MathNode::MKern { .. } => NodeType::Kern,
279            MathNode::Mark(_, _) => NodeType::Mark,
280            MathNode::Whatsit(_) => NodeType::WhatsIt,
281            MathNode::HSkip(_)
282            | MathNode::MSkip { .. }
283            | MathNode::Space
284            | MathNode::HFil
285            | MathNode::HFill
286            | MathNode::HFilneg
287            | MathNode::Hss => NodeType::Glue,
288            MathNode::Leaders(_) => NodeType::Glue,
289            MathNode::Custom(n) => n.nodetype(),
290            _ => NodeType::Math,
291        }
292    }
293    fn opaque(&self) -> bool {
294        match self {
295            MathNode::Mark(_, _) => true,
296            MathNode::Custom(n) => n.opaque(),
297            _ => false,
298        }
299    }
300}
301
302/// One of the 8 styles of math formatting; The TeXBook
303/// calls these D,T,S,SS,D',T',S' and SS'.
304#[derive(Clone, Copy, Eq, PartialEq, Debug)]
305pub struct MathStyle {
306    /// Whether the style is cramped or not (i.e. the `'`
307    pub cramped: bool,
308    /// The style itself (D,T,S,SS)
309    pub style: MathStyleType,
310}
311impl MathStyle {
312    /// The math style to use for a superscript in this style
313    pub fn sup(self) -> Self {
314        match self.style {
315            MathStyleType::Text | MathStyleType::Display => MathStyle {
316                cramped: self.cramped,
317                style: MathStyleType::Script,
318            },
319            MathStyleType::Script | MathStyleType::ScriptScript => MathStyle {
320                cramped: self.cramped,
321                style: MathStyleType::ScriptScript,
322            },
323        }
324    }
325    /// The same math style, but cramped
326    pub fn cramp(mut self) -> Self {
327        self.cramped = true;
328        self
329    }
330    /// The math style to use for a subscript in this style
331    pub fn sub(self) -> Self {
332        self.sup().cramp()
333    }
334    /// The math style to use for the numerator of a generalized fraction in this style
335    pub fn numerator(self) -> Self {
336        match self.style {
337            MathStyleType::Display => MathStyle {
338                cramped: self.cramped,
339                style: MathStyleType::Text,
340            },
341            MathStyleType::Text => MathStyle {
342                cramped: self.cramped,
343                style: MathStyleType::Script,
344            },
345            MathStyleType::Script | MathStyleType::ScriptScript => MathStyle {
346                cramped: self.cramped,
347                style: MathStyleType::ScriptScript,
348            },
349        }
350    }
351    /// The math style to use for the denominator of a generalized fraction in this style
352    pub fn denominator(self) -> Self {
353        self.numerator().cramp()
354    }
355}
356
357/// The four base math formatting styles
358#[derive(Clone, Copy, Eq, PartialEq, Debug)]
359pub enum MathStyleType {
360    Display,
361    Text,
362    Script,
363    ScriptScript,
364}
365
366/// The 7 math classes
367#[derive(Debug, Clone, Copy, PartialEq, Eq)]
368pub enum MathClass {
369    /// Ordinary
370    Ord = 0,
371    /// Large operator
372    Op = 1,
373    /// Binary operator
374    Bin = 2,
375    /// Relation
376    Rel = 3,
377    /// Opening delimiter
378    Open = 4,
379    /// Closing delimiter
380    Close = 5,
381    /// Punctuation
382    Punct = 6,
383}
384impl From<u8> for MathClass {
385    fn from(v: u8) -> Self {
386        match v {
387            0 => MathClass::Ord,
388            1 => MathClass::Op,
389            2 => MathClass::Bin,
390            3 => MathClass::Rel,
391            4 => MathClass::Open,
392            5 => MathClass::Close,
393            6 => MathClass::Punct,
394            _ => panic!("Invalid math class {}", v),
395        }
396    }
397}
398
399/// This trait is implemented for exactly two types that indicate
400/// whether we are in the unresolved [`UnresolvedMathFontStyle`] or resolved
401/// ([`MathFontStyle`]) state.
402pub trait MathFontStyleT<ET: EngineTypes>: Clone + Debug {
403    /// The type of the choice node, which is either [`UnresolvedMathChoice`] or [`ResolvedChoice`].
404    type Choice: MathChoiceT<ET>;
405    /// The type of the markers, which is either [`UnresolvedMarkers`] or a dummy that never occurs.
406    type Markers: Clone + Debug + NodeTrait<ET>;
407}
408/// Unresolved math font style. This is the state while the math list is being constructed.
409/// Carries information about the family if relevant (or 0), to be picked
410/// when the math list is resolved.
411#[derive(Debug, Clone)]
412pub struct UnresolvedMathFontStyle<ET: EngineTypes>(u8, PhantomData<ET>);
413impl<ET: EngineTypes> MathFontStyleT<ET> for UnresolvedMathFontStyle<ET> {
414    type Choice = UnresolvedMathChoice<ET>;
415    type Markers = UnresolvedMarkers;
416    //fn get_font(&self) -> &ET::Font { &self.text_font }
417}
418impl<ET: EngineTypes> UnresolvedMathFontStyle<ET> {
419    /// Create a new unresolved math font style with the given family.
420    pub fn of_fam(fam: u8) -> Self {
421        Self(fam, PhantomData)
422    }
423    /// The family to use.
424    pub fn fam(&self) -> u8 {
425        self.0
426    }
427}
428
429/// A resolved math font style. This is the state after the math list has been constructed.
430/// Has a definite style and font.
431#[derive(Debug, Clone)]
432pub struct MathFontStyle<ET: EngineTypes> {
433    pub style: MathStyleType,
434    pub cramped: bool,
435    pub font: ET::Font,
436}
437impl<ET: EngineTypes> MathFontStyleT<ET> for MathFontStyle<ET> {
438    type Choice = ResolvedChoice<ET>;
439    type Markers = PhantomData<ET>;
440}
441impl<ET: EngineTypes> MathFontStyle<ET> {
442    /// The em value to use to compute widths (i.e. `\fontdimen6` of the font).
443    pub fn get_em(&self) -> ET::Dim {
444        self.font.get_dim(5)
445    }
446    /// The font to use for this style.
447    pub fn get_font(&self) -> &ET::Font {
448        &self.font
449    }
450}
451impl<ET: EngineTypes> NodeTrait<ET> for PhantomData<ET> {
452    fn display_fmt(&self, _indent: usize, _f: &mut Formatter<'_>) -> std::fmt::Result {
453        Ok(())
454    }
455    fn height(&self) -> ET::Dim {
456        ET::Dim::default()
457    }
458    fn width(&self) -> ET::Dim {
459        ET::Dim::default()
460    }
461    fn depth(&self) -> ET::Dim {
462        ET::Dim::default()
463    }
464    fn nodetype(&self) -> NodeType {
465        NodeType::Math
466    }
467}
468
469/// This trait is implemented for exactly two types that indicate
470/// whether we are in the unresolved [`UnresolvedMathFontStyle`] or resolved
471/// ([`MathFontStyle`]) state.
472pub trait MathChoiceT<ET: EngineTypes>: Clone + Debug {}
473
474/// A `\mathcoice` node, not yet resolved. When the current math list
475/// is closed, one of the four choices is picked, depending on the
476/// current style.
477#[derive(Clone, Debug)]
478pub struct UnresolvedMathChoice<ET: EngineTypes> {
479    pub display: Box<[MathNode<ET, UnresolvedMathFontStyle<ET>>]>,
480    pub text: Box<[MathNode<ET, UnresolvedMathFontStyle<ET>>]>,
481    pub script: Box<[MathNode<ET, UnresolvedMathFontStyle<ET>>]>,
482    pub scriptscript: Box<[MathNode<ET, UnresolvedMathFontStyle<ET>>]>,
483}
484impl<ET: EngineTypes> MathChoiceT<ET> for UnresolvedMathChoice<ET> {}
485
486/// A resolved `\mathchoice` node. This is the state after the math list has been constructed,
487/// at which point it is only a wrapper around a list of nodes.
488pub struct ResolvedChoice<ET: EngineTypes>(pub Box<[MathNode<ET, MathFontStyle<ET>>]>);
489impl<ET: EngineTypes> Debug for ResolvedChoice<ET> {
490    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
491        f.write_str("<resolved_choice>")?;
492        for c in self.0.iter() {
493            c.display_fmt(2, f)?;
494        }
495        f.write_str("</resolved_choice>")
496    }
497}
498impl<ET: EngineTypes> Clone for ResolvedChoice<ET> {
499    fn clone(&self) -> Self {
500        Self(self.0.clone())
501    }
502}
503impl<ET: EngineTypes> MathChoiceT<ET> for ResolvedChoice<ET> {}
504impl<ET: EngineTypes> ResolvedChoice<ET> {
505    /// See [`NodeTrait::display_fmt`]
506    pub fn display_fmt(&self, indent: usize, f: &mut Formatter<'_>) -> std::fmt::Result {
507        for c in self.0.iter() {
508            c.display_fmt(indent, f)?;
509        }
510        Ok(())
511    }
512    /// See [`NodeTrait::width`]
513    pub fn width(&self) -> ET::Dim {
514        self.0.iter().map(|c| c.width()).sum()
515    }
516    /// See [`NodeTrait::height`]
517    pub fn height(&self) -> ET::Dim {
518        self.0.iter().map(|c| c.height()).max().unwrap_or_default()
519    }
520    /// See [`NodeTrait::depth`]
521    pub fn depth(&self) -> ET::Dim {
522        self.0.iter().map(|c| c.depth()).max().unwrap_or_default()
523    }
524}
525
526/// Markers inserted by `\displaystyle`, `\textstyle`, `\scriptstyle` and `\scriptscriptstyle`.
527/// Only meaningful in unresolved mode, while the math list is open. When the list
528/// is closed, these are removed and used to determine the font style at that point.
529#[derive(Debug, Copy, Clone)]
530pub enum UnresolvedMarkers {
531    Display,
532    Text,
533    Script,
534    ScriptScript,
535}
536impl<ET: EngineTypes> NodeTrait<ET> for UnresolvedMarkers {
537    fn display_fmt(&self, _indent: usize, f: &mut Formatter<'_>) -> std::fmt::Result {
538        match self {
539            UnresolvedMarkers::Display => f.write_str("<display>"),
540            UnresolvedMarkers::Text => f.write_str("<text>"),
541            UnresolvedMarkers::Script => f.write_str("<script>"),
542            UnresolvedMarkers::ScriptScript => f.write_str("<scriptscript>"),
543        }
544    }
545    fn height(&self) -> ET::Dim {
546        ET::Dim::default()
547    }
548    fn width(&self) -> ET::Dim {
549        ET::Dim::default()
550    }
551    fn depth(&self) -> ET::Dim {
552        ET::Dim::default()
553    }
554    fn nodetype(&self) -> NodeType {
555        NodeType::Math
556    }
557}
558
559/// The most central kind of node in a math list. Consisting of a [nucleus](MathNucleus)
560/// with optional superscript and subscript math lists.
561#[derive(Clone, Debug)]
562pub struct MathAtom<ET: EngineTypes, S: MathFontStyleT<ET>> {
563    pub nucleus: MathNucleus<ET, S>,
564    pub sup: Option<Box<[MathNode<ET, S>]>>,
565    pub sub: Option<Box<[MathNode<ET, S>]>>,
566}
567impl<ET: EngineTypes, S: MathFontStyleT<ET>> MathAtom<ET, S> {
568    /// Create a new empty math atom.
569    pub fn empty() -> Self {
570        Self {
571            nucleus: MathNucleus::Simple {
572                cls: MathClass::Ord,
573                kernel: MathKernel::Empty,
574                limits: None,
575            },
576            sup: None,
577            sub: None,
578        }
579    }
580}
581impl<ET: EngineTypes> NodeTrait<ET> for MathAtom<ET, MathFontStyle<ET>> {
582    fn display_fmt(&self, indent: usize, f: &mut Formatter<'_>) -> std::fmt::Result {
583        display_do_indent(indent, f)?;
584        if self.sub.is_none() || self.sup.is_none() {
585            return self.nucleus.display_fmt(indent, f);
586        }
587        f.write_str("<atom>")?;
588        self.nucleus.display_fmt(indent + 2, f)?;
589        if let Some(sup) = &self.sup {
590            display_do_indent(indent + 2, f)?;
591            f.write_str("<sup>")?;
592            for c in sup.iter() {
593                c.display_fmt(indent + 4, f)?;
594            }
595            display_do_indent(indent + 2, f)?;
596            f.write_str("</sup>")?;
597        }
598        if let Some(sub) = &self.sub {
599            display_do_indent(indent + 2, f)?;
600            f.write_str("<sub>")?;
601            for c in sub.iter() {
602                c.display_fmt(indent + 4, f)?;
603            }
604            display_do_indent(indent + 2, f)?;
605            f.write_str("</sub>")?;
606        }
607        display_do_indent(indent, f)?;
608        f.write_str("</atom>")
609    }
610    fn height(&self) -> ET::Dim {
611        if self.sup.is_none() {
612            return self.nucleus.height();
613        }
614        let h = self.nucleus.height();
615        let limits = matches!(
616            self.nucleus,
617            MathNucleus::Simple {
618                cls: MathClass::Op,
619                limits: Some(true),
620                ..
621            }
622        );
623        let sup = self
624            .sup
625            .as_ref()
626            .unwrap()
627            .iter()
628            .map(|c| c.height() + c.depth())
629            .max()
630            .unwrap_or_default();
631        if limits {
632            h + sup + ET::Dim::from_sp(65536 * 3) // TODO heuristic
633        } else {
634            h + sup.scale_float(0.75) // TODO heuristic
635        }
636    }
637    fn width(&self) -> ET::Dim {
638        if self.sup.is_none() && self.sub.is_none() {
639            return self.nucleus.width();
640        }
641        let w = self.nucleus.width();
642        let sup = match self.sup {
643            Some(ref ls) => ls.iter().map(|c| c.width()).sum(),
644            _ => ET::Dim::default(),
645        };
646        let sub = match self.sub {
647            Some(ref ls) => ls.iter().map(|c| c.width()).sum(),
648            _ => ET::Dim::default(),
649        };
650        let limits = matches!(
651            self.nucleus,
652            MathNucleus::Simple {
653                cls: MathClass::Op,
654                limits: Some(true),
655                ..
656            }
657        );
658        if limits {
659            w.max(sup).max(sub)
660        } else {
661            w + sup.max(sub) + ET::Dim::from_sp(65536 * 3) // TODO heuristic
662        }
663    }
664    fn depth(&self) -> ET::Dim {
665        if self.sub.is_none() {
666            return self.nucleus.depth();
667        }
668        let h = self.nucleus.depth();
669        let limits = matches!(
670            self.nucleus,
671            MathNucleus::Simple {
672                cls: MathClass::Op,
673                limits: Some(true),
674                ..
675            }
676        );
677        let sub = self
678            .sub
679            .as_ref()
680            .unwrap()
681            .iter()
682            .map(|c| c.depth() + c.height())
683            .max()
684            .unwrap_or_default();
685        if limits {
686            h + sub + ET::Dim::from_sp(65536 * 3) // TODO heuristic
687        } else {
688            //println!("HERE: {self:?}\n{h}+{sub}*0.75={}",sub.scale_float(0.75));
689            h + sub.scale_float(0.75) // TODO heuristic
690        }
691    }
692
693    fn nodetype(&self) -> NodeType {
694        NodeType::Math
695    }
696}
697
698/// The nucleus of a [`MathAtom`]; a cohesive "unit" with optional sub/superscript.
699#[derive(Clone, Debug)]
700pub enum MathNucleus<ET: EngineTypes, S: MathFontStyleT<ET>> {
701    /// A simple nucleus, consisting of a [`MathKernel`] and a math class.
702    /// `limits` is `None` during construction of the list, and `Some(true)` or `Some(false)`
703    /// after the list has been closed, or if a large operator is followed by a `\limits` or `\nolimits`.
704    Simple {
705        cls: MathClass,
706        kernel: MathKernel<ET, S>,
707        limits: Option<bool>,
708    },
709    /// A `\mathinner` node, as produced by `{...}`
710    Inner(MathKernel<ET, S>),
711    /// A node produced by `\left...\right`.
712    LeftRight {
713        start: SourceRef<ET>,
714        left: Option<(ET::Char, S)>,
715        children: Box<[MathNode<ET, S>]>,
716        right: Option<(ET::Char, S)>,
717        end: SourceRef<ET>,
718    },
719    /// A node produced by `\middle`.
720    Middle(ET::Char, S),
721    /// A node produced by `\overline`.
722    Overline(MathKernel<ET, S>),
723    /// A node produced by `\underline`.
724    Underline(MathKernel<ET, S>),
725    /// A node produced by `\mathaccent`.
726    Accent {
727        accent: (ET::Char, S),
728        inner: Box<[MathNode<ET, S>]>,
729    },
730    /// A node produced by `\radical`.
731    Radical {
732        rad: (ET::Char, S),
733        inner: Box<[MathNode<ET, S>]>,
734    },
735    /// A node produced by `\vcenter`.
736    VCenter {
737        start: SourceRef<ET>,
738        end: SourceRef<ET>,
739        children: Box<[VNode<ET>]>,
740        scaled: ToOrSpread<ET::Dim>,
741    },
742}
743impl<ET: EngineTypes> NodeTrait<ET> for MathNucleus<ET, MathFontStyle<ET>> {
744    fn display_fmt(&self, indent: usize, f: &mut Formatter<'_>) -> std::fmt::Result {
745        display_do_indent(indent, f)?;
746        match self {
747            MathNucleus::Simple {
748                cls: MathClass::Op,
749                kernel,
750                limits,
751            } => {
752                write!(f, "<mathop limits={:?}>", limits)?;
753                kernel.display_fmt(indent + 2, f)?;
754                display_do_indent(indent, f)?;
755                f.write_str("</mathop>")
756            }
757            MathNucleus::Simple { cls, kernel, .. } => {
758                write!(f, "<math{:?}>", cls)?;
759                kernel.display_fmt(indent + 2, f)?;
760                display_do_indent(indent, f)?;
761                write!(f, "</math{:?}>", cls)
762            }
763            MathNucleus::Inner(k) => {
764                write!(f, "<mathinner>")?;
765                k.display_fmt(indent + 2, f)?;
766                display_do_indent(indent, f)?;
767                f.write_str("</mathinner>")
768            }
769            MathNucleus::LeftRight {
770                left,
771                right,
772                children,
773                ..
774            } => {
775                write!(f, "<leftright>")?;
776                if let Some((l, _)) = left {
777                    write!(f, "<left = {}/>", l)?;
778                }
779                for c in children.iter() {
780                    c.display_fmt(indent + 2, f)?;
781                }
782                if let Some((r, _)) = right {
783                    write!(f, "<right = {}/>", r)?;
784                }
785                display_do_indent(indent, f)?;
786                f.write_str("</leftright>")
787            }
788            MathNucleus::Middle(c, _) => {
789                write!(f, "<middle = {}/>", c)
790            }
791            MathNucleus::Overline(k) => {
792                write!(f, "<overline>")?;
793                k.display_fmt(indent + 2, f)?;
794                display_do_indent(indent, f)?;
795                f.write_str("</overline>")
796            }
797            MathNucleus::Underline(k) => {
798                write!(f, "<underline>")?;
799                k.display_fmt(indent + 2, f)?;
800                display_do_indent(indent, f)?;
801                f.write_str("</underline>")
802            }
803            MathNucleus::Accent { accent, inner } => {
804                write!(f, "<accent char=\"{}\">", accent.0)?;
805                for i in inner.iter() {
806                    i.display_fmt(indent + 2, f)?;
807                }
808                display_do_indent(indent, f)?;
809                f.write_str("</accent>")
810            }
811            MathNucleus::Radical { rad, inner } => {
812                write!(f, "<radical char=\"{}\">", rad.0)?;
813                for i in inner.iter() {
814                    i.display_fmt(indent + 2, f)?;
815                }
816                display_do_indent(indent, f)?;
817                f.write_str("</radical>")
818            }
819            MathNucleus::VCenter { children, .. } => {
820                write!(f, "<vcenter>")?;
821                for c in children.iter() {
822                    c.display_fmt(indent + 2, f)?;
823                }
824                display_do_indent(indent, f)?;
825                f.write_str("</vcenter>")
826            }
827        }
828    }
829    fn height(&self) -> ET::Dim {
830        match self {
831            MathNucleus::Simple {
832                cls: MathClass::Op,
833                kernel,
834                ..
835            } => (kernel.height() + kernel.depth()).scale_float(0.5),
836            MathNucleus::Simple { kernel, .. } => kernel.height(),
837            MathNucleus::Inner(k) => k.height(),
838            MathNucleus::LeftRight { children, .. } => children
839                .iter()
840                .map(|c| c.height())
841                .max()
842                .unwrap_or_default(),
843            MathNucleus::Overline(k) => k.height(),
844            MathNucleus::Underline(k) => k.height(),
845            MathNucleus::Middle(c, s) => s.get_font().get_ht(*c),
846            MathNucleus::Accent {
847                inner,
848                accent: (c, f),
849            } => {
850                inner.iter().map(|c| c.height()).max().unwrap_or_default()
851                    + f.get_font().get_ht(*c)
852                    + f.get_font().get_dp(*c)
853            }
854            MathNucleus::Radical { inner, rad: (c, f) } => {
855                inner.iter().map(|c| c.height()).max().unwrap_or_default() + f.get_font().get_ht(*c)
856            } // + \epsilon?
857            MathNucleus::VCenter { children, .. } => {
858                children
859                    .iter()
860                    .map(|c| c.height() + c.depth())
861                    .sum::<ET::Dim>()
862                    + -children
863                        .iter()
864                        .last()
865                        .map(|c| c.depth())
866                        .unwrap_or_default()
867            }
868        }
869    }
870    fn width(&self) -> ET::Dim {
871        match self {
872            MathNucleus::Simple { kernel, .. } => kernel.width() + ET::Dim::from_sp(65536 * 2), // heuristic adjustment
873            MathNucleus::Inner(k) => k.width(),
874            MathNucleus::LeftRight { children, .. } => {
875                children.iter().map(|c| c.width()).sum::<ET::Dim>() + ET::Dim::from_sp(65536 * 3)
876            } // heuristic adjustment
877            MathNucleus::Overline(k) => k.width(),
878            MathNucleus::Underline(k) => k.width(),
879            MathNucleus::Middle(c, s) => s.get_font().get_wd(*c),
880            MathNucleus::Accent { inner, .. } => inner.iter().map(|c| c.width()).sum(),
881            MathNucleus::Radical { inner, rad, .. } => {
882                inner.iter().map(|c| c.width()).sum::<ET::Dim>() + rad.1.get_font().get_wd(rad.0)
883            }
884            MathNucleus::VCenter { children, .. } => {
885                children.iter().map(|c| c.width()).max().unwrap_or_default()
886            }
887        }
888    }
889    fn depth(&self) -> ET::Dim {
890        match self {
891            MathNucleus::Simple {
892                cls: MathClass::Op,
893                kernel,
894                ..
895            } => (kernel.height() + kernel.depth()).scale_float(0.5),
896            MathNucleus::Simple { kernel, .. } => kernel.depth(),
897            MathNucleus::LeftRight { children, .. } => {
898                children.iter().map(|c| c.depth()).max().unwrap_or_default()
899            }
900            MathNucleus::Inner(k) => k.depth(),
901            MathNucleus::Overline(k) => k.depth(),
902            MathNucleus::Underline(k) => k.depth(),
903            MathNucleus::Middle(c, s) => s.get_font().get_dp(*c),
904            MathNucleus::Accent { inner, .. } => {
905                inner.iter().map(|c| c.depth()).max().unwrap_or_default()
906            }
907            MathNucleus::Radical { inner, .. } => {
908                inner.iter().map(|c| c.depth()).max().unwrap_or_default()
909            }
910            MathNucleus::VCenter { children, .. } => children
911                .iter()
912                .last()
913                .map(|c| c.depth())
914                .unwrap_or_default(),
915        }
916    }
917
918    fn nodetype(&self) -> NodeType {
919        NodeType::Math
920    }
921}
922
923/// The kernel of a [`MathNucleus`]; the actual content of the nucleus.
924#[derive(Clone, Debug, Default)]
925pub enum MathKernel<ET: EngineTypes, S: MathFontStyleT<ET>> {
926    /// empty
927    #[default]
928    Empty,
929    /// a single character
930    Char { char: ET::Char, style: S },
931    /// a box
932    Box(TeXBox<ET>),
933    /// a list of math nodes
934    List {
935        start: SourceRef<ET>,
936        children: Box<[MathNode<ET, S>]>,
937        end: SourceRef<ET>,
938    },
939}
940impl<ET: EngineTypes> NodeTrait<ET> for MathKernel<ET, MathFontStyle<ET>> {
941    fn display_fmt(&self, indent: usize, f: &mut Formatter<'_>) -> std::fmt::Result {
942        match self {
943            MathKernel::Empty => Ok(()),
944            MathKernel::Char { char, .. } => {
945                display_do_indent(indent, f)?;
946                write!(f, "<char:{}/>", char)
947            }
948            MathKernel::Box(b) => b.display_fmt(indent, f),
949            MathKernel::List { children, .. } => {
950                for c in children.iter() {
951                    c.display_fmt(indent + 2, f)?;
952                }
953                Ok(())
954            }
955        }
956    }
957    fn height(&self) -> ET::Dim {
958        match self {
959            MathKernel::Empty => ET::Dim::default(),
960            MathKernel::Char { style, char } => style.get_font().get_ht(*char),
961            MathKernel::Box(b) => b.height(),
962            MathKernel::List { children, .. } => children
963                .iter()
964                .map(|c| c.height())
965                .max()
966                .unwrap_or_default(),
967        }
968    }
969    fn width(&self) -> ET::Dim {
970        match self {
971            MathKernel::Empty => ET::Dim::default(),
972            MathKernel::Char { style, char } => style.get_font().get_wd(*char),
973            MathKernel::Box(b) => b.width(),
974            MathKernel::List { children, .. } => children.iter().map(|c| c.width()).sum(),
975        }
976    }
977    fn depth(&self) -> ET::Dim {
978        match self {
979            MathKernel::Empty => ET::Dim::default(),
980            MathKernel::Char { style, char } => style.get_font().get_dp(*char),
981            MathKernel::Box(b) => b.depth(),
982            MathKernel::List { children, .. } => {
983                children.iter().map(|c| c.depth()).max().unwrap_or_default()
984            }
985        }
986    }
987    fn nodetype(&self) -> NodeType {
988        match self {
989            MathKernel::Empty => NodeType::Math,
990            MathKernel::Char { .. } => NodeType::MathChar,
991            MathKernel::Box(b) => b.nodetype(),
992            MathKernel::List { .. } => NodeType::Math,
993        }
994    }
995}
996
997/// A resolved math group; the result of a math list.
998#[derive(Debug, Clone)]
999pub struct MathGroup<ET: EngineTypes> {
1000    /// If this is a display math group, the `\abovedisplayskip` and `\belowdisplayskip`
1001    /// values.
1002    pub display: Option<(Skip<ET::Dim>, Skip<ET::Dim>)>,
1003    /// The nodes in this group
1004    pub children: Box<[MathNode<ET, MathFontStyle<ET>>]>,
1005    /// The source reference of the `$` or `$$` that started this group.
1006    pub start: SourceRef<ET>,
1007    /// The source reference of the `$` or `$$` that ended this group.
1008    pub end: SourceRef<ET>,
1009    /// If the mathgroup contained an `\eqno` or `\leqno`,
1010    /// the nodes *following* that command
1011    pub eqno: Option<(EqNoPosition, Box<[MathNode<ET, MathFontStyle<ET>>]>)>,
1012    /// The computed width of this group - i.e. the sum of the widths of the children.
1013    pub computed_width: OnceCell<ET::Dim>,
1014    /// The computed height of this group - i.e. the maximum of the heights of the children.
1015    pub computed_height: OnceCell<ET::Dim>,
1016    /// The computed depth of this group - i.e. the maximum of the depths of the children.
1017    pub computed_depth: OnceCell<ET::Dim>,
1018}
1019impl<ET: EngineTypes> NodeTrait<ET> for MathGroup<ET> {
1020    fn display_fmt(&self, indent: usize, f: &mut Formatter<'_>) -> std::fmt::Result {
1021        display_do_indent(indent, f)?;
1022        write!(
1023            f,
1024            "<{}math>",
1025            if self.display.is_some() {
1026                "display"
1027            } else {
1028                ""
1029            }
1030        )?;
1031        for c in self.children.iter() {
1032            c.display_fmt(indent + 2, f)?;
1033        }
1034        display_do_indent(indent, f)?;
1035        write!(
1036            f,
1037            "</{}math>",
1038            if self.display.is_some() {
1039                "display"
1040            } else {
1041                ""
1042            }
1043        )
1044    }
1045    fn height(&self) -> ET::Dim {
1046        *self.computed_height.get_or_init(|| {
1047            self.children
1048                .iter()
1049                .map(|c| c.height())
1050                .max()
1051                .unwrap_or_default()
1052        })
1053    }
1054    fn width(&self) -> ET::Dim {
1055        *self
1056            .computed_width
1057            .get_or_init(|| self.children.iter().map(|c| c.width()).sum())
1058    }
1059    fn depth(&self) -> ET::Dim {
1060        *self.computed_depth.get_or_init(|| {
1061            self.children
1062                .iter()
1063                .map(|c| c.depth())
1064                .max()
1065                .unwrap_or_default()
1066        })
1067    }
1068    fn nodetype(&self) -> NodeType {
1069        NodeType::Math
1070    }
1071    fn sourceref(&self) -> Option<(&SourceRef<ET>, &SourceRef<ET>)> {
1072        Some((&self.start, &self.end))
1073    }
1074}
1075
1076impl<ET: EngineTypes> MathGroup<ET> {
1077    /// Create a new math group by closing a list of unresolved math nodes, iterating
1078    /// over it and resolving each node by determining the appropriate [`MathFontStyle`] for
1079    /// it.
1080    /// If `display` is `Some`, this is a display math group, and the two
1081    /// skips are the `\abovedisplayskip` and `\belowdisplayskip` values.
1082    pub fn close(
1083        state: &ET::State,
1084        display: Option<(Skip<ET::Dim>, Skip<ET::Dim>)>,
1085        start: SourceRef<ET>,
1086        end: SourceRef<ET>,
1087        children: Vec<MathNode<ET, UnresolvedMathFontStyle<ET>>>,
1088        eqno: Option<(EqNoPosition, Vec<MathNode<ET, UnresolvedMathFontStyle<ET>>>)>,
1089    ) -> Self {
1090        let style = if display.is_some() {
1091            MathStyle {
1092                style: MathStyleType::Display,
1093                cramped: false,
1094            }
1095        } else {
1096            MathStyle {
1097                style: MathStyleType::Text,
1098                cramped: false,
1099            }
1100        };
1101        let nch = Self::close_i(state, children, style);
1102        MathGroup {
1103            display,
1104            children: nch.into(),
1105            start,
1106            end,
1107            computed_width: OnceCell::new(),
1108            computed_height: OnceCell::new(),
1109            computed_depth: OnceCell::new(),
1110            eqno: eqno.map(|(pos, ch)| {
1111                (
1112                    pos,
1113                    Self::close_i(
1114                        state,
1115                        ch,
1116                        MathStyle {
1117                            style: MathStyleType::Text,
1118                            cramped: false,
1119                        },
1120                    )
1121                    .into(),
1122                )
1123            }),
1124        }
1125    }
1126    fn close_i(
1127        state: &ET::State,
1128        ls: Vec<MathNode<ET, UnresolvedMathFontStyle<ET>>>,
1129        mut style: MathStyle,
1130    ) -> Vec<MathNode<ET, MathFontStyle<ET>>> {
1131        ls.into_iter()
1132            .flat_map(|n| match n {
1133                MathNode::HSkip(s) => Some(MathNode::HSkip(s)),
1134                MathNode::HFil => Some(MathNode::HFil),
1135                MathNode::HFill => Some(MathNode::HFill),
1136                MathNode::HFilneg => Some(MathNode::HFilneg),
1137                MathNode::Hss => Some(MathNode::Hss),
1138                MathNode::Marker(UnresolvedMarkers::Display) => {
1139                    style.style = MathStyleType::Display;
1140                    None
1141                }
1142                MathNode::Marker(UnresolvedMarkers::Text) => {
1143                    style.style = MathStyleType::Text;
1144                    None
1145                }
1146                MathNode::Marker(UnresolvedMarkers::Script) => {
1147                    style.style = MathStyleType::Script;
1148                    None
1149                }
1150                MathNode::Marker(UnresolvedMarkers::ScriptScript) => {
1151                    style.style = MathStyleType::ScriptScript;
1152                    None
1153                }
1154                MathNode::Over {
1155                    start,
1156                    end,
1157                    top,
1158                    sep,
1159                    bottom,
1160                    left,
1161                    right,
1162                } => Some(MathNode::Over {
1163                    start,
1164                    end,
1165                    top: Self::close_i(state, top.into_vec(), style.numerator()).into(),
1166                    sep,
1167                    bottom: Self::close_i(state, bottom.into_vec(), style.denominator()).into(),
1168                    left: left.map(|(c, s)| (c, Self::resolve_style(state, style, s))),
1169                    right: right.map(|(c, s)| (c, Self::resolve_style(state, style, s))),
1170                }),
1171                MathNode::Choice(c) => Some(match style {
1172                    MathStyle {
1173                        style: MathStyleType::Display,
1174                        ..
1175                    } => MathNode::Choice(ResolvedChoice(
1176                        Self::close_i(state, c.display.into_vec(), style).into(),
1177                    )),
1178                    MathStyle {
1179                        style: MathStyleType::Text,
1180                        ..
1181                    } => MathNode::Choice(ResolvedChoice(
1182                        Self::close_i(state, c.text.into_vec(), style).into(),
1183                    )),
1184                    MathStyle {
1185                        style: MathStyleType::Script,
1186                        ..
1187                    } => MathNode::Choice(ResolvedChoice(
1188                        Self::close_i(state, c.script.into_vec(), style).into(),
1189                    )),
1190                    MathStyle {
1191                        style: MathStyleType::ScriptScript,
1192                        ..
1193                    } => MathNode::Choice(ResolvedChoice(
1194                        Self::close_i(state, c.scriptscript.into_vec(), style).into(),
1195                    )),
1196                }),
1197                MathNode::Space => Some(MathNode::Space),
1198                MathNode::Leaders(l) => Some(MathNode::Leaders(l)),
1199                MathNode::HKern(d) => Some(MathNode::HKern(d)),
1200                MathNode::Penalty(p) => Some(MathNode::Penalty(p)),
1201                MathNode::Mark(i, tl) => Some(MathNode::Mark(i, tl)),
1202                MathNode::VRule {
1203                    width,
1204                    height,
1205                    depth,
1206                    start,
1207                    end,
1208                } => Some(MathNode::VRule {
1209                    width,
1210                    height,
1211                    depth,
1212                    start,
1213                    end,
1214                }),
1215                MathNode::Whatsit(w) => Some(MathNode::Whatsit(w)),
1216                MathNode::Custom(n) => Some(MathNode::Custom(n)),
1217                MathNode::MSkip {
1218                    skip,
1219                    style: unresolved,
1220                } => Some(MathNode::MSkip {
1221                    skip,
1222                    style: Self::resolve_style(state, style, unresolved),
1223                }),
1224                MathNode::MKern {
1225                    kern,
1226                    style: unresolved,
1227                } => Some(MathNode::MKern {
1228                    kern,
1229                    style: Self::resolve_style(state, style, unresolved),
1230                }),
1231                MathNode::Atom(a) => Some(MathNode::Atom(MathAtom {
1232                    nucleus: Self::resolve_nucleus(state, a.nucleus, style),
1233                    sup: a
1234                        .sup
1235                        .map(|l| Self::close_i(state, l.into_vec(), style.sup()).into()),
1236                    sub: a
1237                        .sub
1238                        .map(|l| Self::close_i(state, l.into_vec(), style.sub()).into()),
1239                })),
1240            })
1241            .collect()
1242    }
1243    fn resolve_nucleus(
1244        state: &ET::State,
1245        n: MathNucleus<ET, UnresolvedMathFontStyle<ET>>,
1246        style: MathStyle,
1247    ) -> MathNucleus<ET, MathFontStyle<ET>> {
1248        match n {
1249            MathNucleus::Simple {
1250                cls: MathClass::Op,
1251                kernel,
1252                limits,
1253            } => MathNucleus::Simple {
1254                cls: MathClass::Op,
1255                kernel: Self::resolve_kernel(state, kernel, style),
1256                limits: match limits {
1257                    Some(l) => Some(l),
1258                    None => {
1259                        if style.style == MathStyleType::Display {
1260                            Some(true)
1261                        } else {
1262                            Some(false)
1263                        }
1264                    }
1265                },
1266            },
1267            MathNucleus::LeftRight {
1268                start,
1269                left,
1270                children,
1271                right,
1272                end,
1273            } => MathNucleus::LeftRight {
1274                start,
1275                left: left.map(|(c, s)| (c, Self::resolve_style(state, style, s))),
1276                children: Self::close_i(state, children.into_vec(), style).into(),
1277                right: right.map(|(c, s)| (c, Self::resolve_style(state, style, s))),
1278                end,
1279            },
1280            MathNucleus::Middle(c, f) => {
1281                MathNucleus::Middle(c, Self::resolve_style(state, style, f))
1282            }
1283            MathNucleus::Simple {
1284                cls,
1285                kernel,
1286                limits,
1287            } => MathNucleus::Simple {
1288                cls,
1289                kernel: Self::resolve_kernel(state, kernel, style),
1290                limits,
1291            },
1292            MathNucleus::Inner(k) => MathNucleus::Inner(Self::resolve_kernel(state, k, style)),
1293            MathNucleus::Overline(k) => {
1294                MathNucleus::Overline(Self::resolve_kernel(state, k, style))
1295            }
1296            MathNucleus::Underline(k) => {
1297                MathNucleus::Underline(Self::resolve_kernel(state, k, style))
1298            }
1299            MathNucleus::Accent {
1300                accent: (c, f),
1301                inner,
1302            } => MathNucleus::Accent {
1303                accent: (c, Self::resolve_style(state, style, f)),
1304                inner: Self::close_i(state, inner.into_vec(), style).into(),
1305            },
1306            MathNucleus::Radical { rad: (c, f), inner } => MathNucleus::Radical {
1307                rad: (c, Self::resolve_style(state, style, f)),
1308                inner: Self::close_i(state, inner.into_vec(), style).into(),
1309            },
1310            MathNucleus::VCenter {
1311                start,
1312                end,
1313                children,
1314                scaled,
1315            } => MathNucleus::VCenter {
1316                start,
1317                end,
1318                children,
1319                scaled,
1320            },
1321        }
1322    }
1323    fn resolve_kernel(
1324        state: &ET::State,
1325        n: MathKernel<ET, UnresolvedMathFontStyle<ET>>,
1326        style: MathStyle,
1327    ) -> MathKernel<ET, MathFontStyle<ET>> {
1328        match n {
1329            MathKernel::Empty => MathKernel::Empty,
1330            MathKernel::Char {
1331                char,
1332                style: unresolved,
1333            } => MathKernel::Char {
1334                char,
1335                style: Self::resolve_style(state, style, unresolved),
1336            },
1337            MathKernel::Box(b) => MathKernel::Box(b),
1338            MathKernel::List {
1339                start,
1340                children,
1341                end,
1342            } => MathKernel::List {
1343                start,
1344                children: Self::close_i(state, children.into_vec(), style).into(),
1345                end,
1346            },
1347        }
1348    }
1349    fn resolve_style(
1350        state: &ET::State,
1351        style: MathStyle,
1352        unresolved: UnresolvedMathFontStyle<ET>,
1353    ) -> MathFontStyle<ET> {
1354        match style.style {
1355            MathStyleType::Script => MathFontStyle {
1356                style: style.style,
1357                cramped: style.cramped,
1358                font: state.get_scriptfont(unresolved.fam()).clone(),
1359            },
1360            MathStyleType::ScriptScript => MathFontStyle {
1361                style: style.style,
1362                cramped: style.cramped,
1363                font: state.get_scriptscriptfont(unresolved.fam()).clone(),
1364            },
1365            _ => MathFontStyle {
1366                style: style.style,
1367                cramped: style.cramped,
1368                font: state.get_textfont(unresolved.fam()).clone(),
1369            },
1370        }
1371    }
1372}
1373
1374/// The position of an eqno in a math list, i.e. `\eqno` (right) or `\leqno` (left).
1375#[derive(Debug, Copy, Clone)]
1376pub enum EqNoPosition {
1377    Left,
1378    Right,
1379}
1380
1381/// Convenience struct for characters in math mode
1382#[derive(Debug, Clone)]
1383pub struct MathChar<ET: EngineTypes> {
1384    /// The character
1385    pub char: ET::Char,
1386    /// The math class determined from its mathcode
1387    pub cls: MathClass,
1388    /// The font style
1389    pub style: UnresolvedMathFontStyle<ET>,
1390}
1391impl<ET: EngineTypes> MathChar<ET> {
1392    /// Convert this into an unresolved [`MathAtom`].
1393    pub fn to_atom(self) -> MathAtom<ET, UnresolvedMathFontStyle<ET>> {
1394        MathAtom {
1395            nucleus: self.to_nucleus(),
1396            sup: None,
1397            sub: None,
1398        }
1399    }
1400    pub fn to_nucleus(self) -> MathNucleus<ET, UnresolvedMathFontStyle<ET>> {
1401        MathNucleus::Simple {
1402            cls: self.cls,
1403            kernel: MathKernel::Char {
1404                char: self.char,
1405                style: self.style,
1406            },
1407            limits: None,
1408        }
1409    }
1410    /// Create a new [`MathChar`] from a mathcode. If this was triggered by
1411    /// an actual character (rather than e.g. `\mathcar`), `source` is that
1412    /// character.
1413    pub fn from_u32(mathcode: u32, state: &ET::State, source: Option<ET::Char>) -> Self {
1414        let (mut cls, mut fam, pos) = {
1415            if mathcode == 0 {
1416                (
1417                    0,
1418                    0,
1419                    match source {
1420                        Some(c) => c.try_into().ok().unwrap(),
1421                        _ => 0,
1422                    },
1423                )
1424            } else {
1425                let char = (mathcode & 0xFF) as u8; // num % (16 * 16)
1426                let fam = ((mathcode >> 8) & 0xF) as u8; // (rest % 16)
1427                let rest_fam_shifted = (mathcode >> 12) & 0xF; // (((rest - fam) / 16) % 16)
1428                (rest_fam_shifted as u8, fam, char)
1429            }
1430        };
1431        if cls == 7 {
1432            let i = state.get_primitive_int(PRIMITIVES.fam).into();
1433            match i {
1434                i if !(0..=15).contains(&i) => {
1435                    cls = 0;
1436                }
1437                i => {
1438                    cls = 0;
1439                    fam = i as u8;
1440                }
1441            }
1442        }
1443        if cls > 7 {
1444            panic!("Invalid math class: {mathcode}({source:?}): {cls} {pos} {fam}");
1445        }
1446        let cls = MathClass::from(cls);
1447        let char = ET::Char::from(pos);
1448        MathChar {
1449            char,
1450            cls,
1451            style: UnresolvedMathFontStyle::of_fam(fam),
1452        }
1453    }
1454}
1455
1456/// Convenience struct for math delimiters, as constructed from a delimiter code
1457#[derive(Clone, Debug)]
1458pub struct Delimiter<ET: EngineTypes> {
1459    /// The small variant of the delimiter
1460    pub small: MathChar<ET>,
1461    /// The large variant of the delimiter
1462    pub large: MathChar<ET>,
1463}
1464impl<ET: EngineTypes> Delimiter<ET> {
1465    fn default() -> Self {
1466        Delimiter {
1467            small: MathChar {
1468                char: ET::Char::from(0),
1469                cls: MathClass::Ord,
1470                style: UnresolvedMathFontStyle::of_fam(0),
1471            },
1472            large: MathChar {
1473                char: ET::Char::from(0),
1474                cls: MathClass::Ord,
1475                style: UnresolvedMathFontStyle::of_fam(0),
1476            },
1477        }
1478    }
1479    /// Create a new [`Delimiter`] from a delimiter code.
1480    pub fn from_int(num: ET::Int, state: &ET::State) -> Either<Self, (Self, i64)> {
1481        let num = num.into();
1482        if num < 0 || num > u32::MAX.into() {
1483            return either::Right((Self::default(), num));
1484        }
1485        let num = num as u32;
1486        let large = num & 0xFFF;
1487        let small = num >> 12;
1488        either::Left(Delimiter {
1489            small: MathChar::from_u32(small, state, None),
1490            large: MathChar::from_u32(large, state, None),
1491        })
1492    }
1493}
1494
1495/// An open list of unresolved math nodes.
1496/// TODO: rethink this
1497#[derive(Clone, Debug)]
1498pub enum MathNodeList<ET: EngineTypes> {
1499    /// A simple list of nodes
1500    Simple(Vec<MathNode<ET, UnresolvedMathFontStyle<ET>>>),
1501    /// An open list after encountering an `\over` or `\above` or `\atop` or
1502    /// a related command. The current list up to that point is moved to the `top`,
1503    /// subsequent nodes are added to `bottom`. (see [`MathNode::Over`]
1504    Over {
1505        top: Vec<MathNode<ET, UnresolvedMathFontStyle<ET>>>,
1506        sep: Option<ET::Dim>,
1507        bottom: Vec<MathNode<ET, UnresolvedMathFontStyle<ET>>>,
1508        left: Option<(ET::Char, UnresolvedMathFontStyle<ET>)>,
1509        right: Option<(ET::Char, UnresolvedMathFontStyle<ET>)>,
1510    },
1511    /// An open list after encountering an `\eqno` or `\leqno`.
1512    /// The current list up to that point is moved to `main`,
1513    /// subsequent nodes are added to `eqno`. This can
1514    /// only happen, if this list's direct "parent" is a horizontal
1515    /// (i.e. non-math) list.
1516    EqNo {
1517        pos: EqNoPosition,
1518        main: Vec<MathNode<ET, UnresolvedMathFontStyle<ET>>>,
1519        eqno: Vec<MathNode<ET, UnresolvedMathFontStyle<ET>>>,
1520    },
1521}
1522impl<ET: EngineTypes> Default for MathNodeList<ET> {
1523    fn default() -> Self {
1524        MathNodeList::Simple(Vec::new())
1525    }
1526}
1527impl<ET: EngineTypes> MathNodeList<ET> {
1528    /// Push a node to the list.
1529    pub fn push(&mut self, n: MathNode<ET, UnresolvedMathFontStyle<ET>>) {
1530        match self {
1531            MathNodeList::Simple(v) => v.push(n),
1532            MathNodeList::Over { bottom, .. } => bottom.push(n),
1533            MathNodeList::EqNo { eqno, .. } => eqno.push(n),
1534        }
1535    }
1536    /// Close the list, returning the list of nodes and the optional eqno.
1537    pub fn close(
1538        self,
1539        start: SourceRef<ET>,
1540        end: SourceRef<ET>,
1541    ) -> (
1542        Vec<MathNode<ET, UnresolvedMathFontStyle<ET>>>,
1543        Option<(EqNoPosition, Vec<MathNode<ET, UnresolvedMathFontStyle<ET>>>)>,
1544    ) {
1545        match self {
1546            MathNodeList::Simple(v) => (v, None),
1547            MathNodeList::Over {
1548                top,
1549                sep,
1550                bottom,
1551                left,
1552                right,
1553            } => (
1554                vec![MathNode::Over {
1555                    start,
1556                    end,
1557                    top: top.into(),
1558                    bottom: bottom.into(),
1559                    sep,
1560                    left,
1561                    right,
1562                }],
1563                None,
1564            ),
1565            MathNodeList::EqNo { main, eqno, pos } => (main, Some((pos, eqno))),
1566        }
1567    }
1568    /// Get the "open" list that nodes should be added to mutably
1569    pub fn list_mut(&mut self) -> &mut Vec<MathNode<ET, UnresolvedMathFontStyle<ET>>> {
1570        match self {
1571            MathNodeList::Simple(v) => v,
1572            MathNodeList::Over { bottom, .. } => bottom,
1573            MathNodeList::EqNo { eqno, .. } => eqno,
1574        }
1575    }
1576    /// Get the "open" list that nodes should be added to immutably
1577    pub fn list(&self) -> &Vec<MathNode<ET, UnresolvedMathFontStyle<ET>>> {
1578        match self {
1579            MathNodeList::Simple(v) => v,
1580            MathNodeList::Over { bottom, .. } => bottom,
1581            MathNodeList::EqNo { eqno, .. } => eqno,
1582        }
1583    }
1584}
1585
1586///Types of open math lists
1587#[derive(Clone, Debug)]
1588pub enum MathNodeListType<ET: EngineTypes> {
1589    /// The top-most math list
1590    Top {
1591        /// whether delimited by `$$` rather than `$`
1592        display: bool,
1593    },
1594    /// complex list target
1595    Target(ListTarget<ET, MathNode<ET, UnresolvedMathFontStyle<ET>>>),
1596    /// A list opened by `\left`, to be closed by `\right`
1597    LeftRight(Option<Delimiter<ET>>),
1598}