Skip to main content

retex_engine/
mlist.rs

1//! Converting math lists to horizontal lists (*TeX: The Program*
2//! §699–§767, with e-TeX's `\middle`) the way LuaTeX does it for
3//! traditional (TFM) fonts.
4//!
5//! LuaTeX keeps TeX82's algorithm but takes the font-dependent values from
6//! the `\Umath…` parameters of the current style (see
7//! [`crate::mathparams`]). Observed differences from TeX82, all
8//! established with black-box tests:
9//!
10//! * glyphs made from math characters are protected (subtype 256), so
11//!   that the node processing of an enclosing box leaves them alone;
12//! * italic corrections are kern nodes of subtype "italic", the other
13//!   kerns made here are font kerns;
14//! * a missing character from an undefined family is reported each time
15//!   it is fetched (the field is not reset to empty);
16//! * a null delimiter of a `\left`/`\right` fence is not shifted to the
17//!   axis.
18
19use crate::Engine;
20use crate::build::PackSpec;
21use crate::fonts::LigOp;
22use crate::math::{Field, cramped_style, denom_style, half, num_style, sub_style, sup_style};
23use crate::mathparams::mp;
24use crate::nodes::{
25    DEFAULT_CODE, NodeData, NodeRef, fence_sub, glue_sub, id, kern_sub, noad_opt, noad_sub,
26    radical_sub,
27};
28use crate::primitives_gen::{assign_dimen as dp, assign_int as ip};
29
30/// Glyph subtype of characters made by `mlist_to_hlist`.
31pub const PROTECTED_GLYPH: u16 = 256;
32
33/// The TeX82 type of a noad, for the two passes.
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub enum NoadType {
36    /// Ord.
37    Ord = 0,
38    /// Op.
39    Op = 1,
40    /// Bin.
41    Bin = 2,
42    /// Rel.
43    Rel = 3,
44    /// Open.
45    Open = 4,
46    /// Close.
47    Close = 5,
48    /// Punct.
49    Punct = 6,
50    /// Inner.
51    Inner = 7,
52    /// Radical.
53    Radical,
54    /// Fraction.
55    Fraction,
56    /// Underline.
57    Under,
58    /// Overline.
59    Over,
60    /// Math accent.
61    Accent,
62    /// `\vcenter`.
63    Vcenter,
64    /// `\left`.
65    Left,
66    /// `\right` or `\middle`.
67    Right,
68}
69
70/// TeX's inter-atom spacing table (§764): 0 none, 1 conditional thin,
71/// 2 thin, 3 conditional medium, 4 conditional thick.
72const MATH_SPACING: &[u8; 64] = b"0234000122*4000133**3**344*0400400*000000234000111*1111112341011";
73
74impl Engine {
75    /// Whether font `f` is an OpenType math font (it has `MathConstants`),
76    /// which LuaTeX treats differently from TFM fonts.
77    pub fn is_opentype_math_font(&self, f: u32) -> bool {
78        self.font(f).math_constants.is_some()
79    }
80
81    /// The spacing between atoms of classes `l` and `r` in `style`: the
82    /// `\Umath…spacing` parameter when set (plain glue), otherwise TeX's
83    /// table with the current `\thinmuskip`, `\medmuskip` or
84    /// `\thickmuskip` (named glue), or none.
85    pub fn math_spacing(&self, l: u32, r: u32, style: u32) -> Option<(crate::glue::Glue, u16)> {
86        let p = 49 + l * 8 + r;
87        let v = self.math_param_value(p, style);
88        if v != crate::codes::UNDEFINED_MATH_PARAM {
89            return Some((self.math_param_glue(v), 0));
90        }
91        let c = MATH_SPACING[(l * 8 + r) as usize];
92        let x = match c {
93            b'1' if style < 4 => glue_sub::THIN_MU_SKIP,
94            b'2' => glue_sub::THIN_MU_SKIP,
95            b'3' if style < 4 => glue_sub::MED_MU_SKIP,
96            b'4' if style < 4 => glue_sub::THICK_MU_SKIP,
97            _ => return None,
98        };
99        let par = match x {
100            glue_sub::THIN_MU_SKIP => crate::primitives_gen::assign_mu_glue::THINMUSKIP,
101            glue_sub::MED_MU_SKIP => crate::primitives_gen::assign_mu_glue::MEDMUSKIP,
102            _ => crate::primitives_gen::assign_mu_glue::THICKMUSKIP,
103        };
104        Some((self.eqtb.glue(par + crate::eqtb::MU_GLUE_OFFSET), x))
105    }
106}
107
108/// The state of the conversion (`cur_style`, `cur_size`, `cur_mu`).
109#[derive(Clone, Copy, Debug, Default)]
110pub struct MathState {
111    /// The current style.
112    pub style: i32,
113    /// The current size: 0 text, 1 script, 2 scriptscript.
114    pub size: u32,
115    /// The math unit.
116    pub mu: i32,
117    /// A kern to put after the operator box being converted (an OpenType
118    /// operator's italic correction, negated, before its subscript).
119    pub op_kern: i32,
120}
121
122/// The size belonging to a style.
123pub fn style_size(s: i32) -> u32 {
124    if s < 4 { 0 } else { ((s - 2) / 2) as u32 }
125}
126
127impl Engine {
128    /// The TeX82 type of noad `q` (`None` for nodes).
129    pub fn noad_type(&self, q: NodeRef) -> Option<NoadType> {
130        Some(match self.nodes.id(q) {
131            id::NOAD => match self.nodes.subtype(q) {
132                noad_sub::ORD => NoadType::Ord,
133                noad_sub::OP_DISPLAY_LIMITS | noad_sub::OP_LIMITS | noad_sub::OP_NO_LIMITS => {
134                    NoadType::Op
135                }
136                noad_sub::BIN => NoadType::Bin,
137                noad_sub::REL => NoadType::Rel,
138                noad_sub::OPEN => NoadType::Open,
139                noad_sub::CLOSE => NoadType::Close,
140                noad_sub::PUNCT => NoadType::Punct,
141                noad_sub::INNER => NoadType::Inner,
142                noad_sub::UNDER => NoadType::Under,
143                noad_sub::OVER => NoadType::Over,
144                _ => NoadType::Vcenter,
145            },
146            id::RADICAL => NoadType::Radical,
147            id::ACCENT => NoadType::Accent,
148            id::FRACTION => NoadType::Fraction,
149            id::FENCE => {
150                if self.nodes.subtype(q) == fence_sub::LEFT {
151                    NoadType::Left
152                } else {
153                    NoadType::Right
154                }
155            }
156            _ => return None,
157        })
158    }
159
160    /// Sets up `cur_size` and `cur_mu` for `cur_style`.
161    fn set_cur_style(&mut self, s: i32) {
162        self.ts.math.style = s;
163        self.ts.math.size = style_size(s);
164        let q = self.math_par(mp::QUAD);
165        self.ts.math.mu = retex_arith::x_over_n(q, 18).map_or(0, |r| r.quotient);
166    }
167
168    /// Math parameter `p` in the current style (0 when undefined).
169    pub fn math_par(&self, p: u32) -> i32 {
170        let v = self.math_param_value(p, self.ts.math.style as u32);
171        if v == crate::codes::UNDEFINED_MATH_PARAM {
172            0
173        } else {
174            v
175        }
176    }
177
178    /// Math parameter `p` in the current style, `None` when undefined.
179    fn math_par_opt(&self, p: u32) -> Option<i32> {
180        let v = self.math_param_value(p, self.ts.math.style as u32);
181        (v != crate::codes::UNDEFINED_MATH_PARAM).then_some(v)
182    }
183
184    /// Converts mlist `mlist` in style `style` into an hlist, which is
185    /// returned (`mlist_to_hlist`). `penalties` asks for line-break
186    /// penalties after binary operators and relations.
187    pub fn mlist_to_hlist(&mut self, mlist: NodeRef, style: i32, penalties: bool) -> NodeRef {
188        self.fonts.sync_dense();
189        self.init_math_params_once();
190        let saved = self.ts.math;
191        let r = self.mlist_to_hlist_rec(mlist, style, penalties);
192        self.ts.math = saved;
193        r
194    }
195
196    fn get_new_hlist(&self, q: NodeRef) -> NodeRef {
197        match self.nodes.data(q) {
198            NodeData::Noad(n) => n.new_hlist,
199            NodeData::Fraction(f) => f.new_hlist,
200            NodeData::Fence(f) => f.new_hlist,
201            _ => NodeRef::NULL,
202        }
203    }
204
205    fn set_new_hlist(&mut self, q: NodeRef, p: NodeRef) {
206        match self.nodes.data_mut(q) {
207            NodeData::Noad(n) => n.new_hlist = p,
208            NodeData::Fraction(f) => f.new_hlist = p,
209            NodeData::Fence(f) => f.new_hlist = p,
210            _ => {}
211        }
212    }
213
214    fn mlist_to_hlist_rec(&mut self, mlist: NodeRef, style: i32, penalties: bool) -> NodeRef {
215        let mut q = mlist;
216        let mut r = NodeRef::NULL;
217        let mut r_type = NoadType::Op;
218        let mut max_h = 0i32;
219        let mut max_d = 0i32;
220        self.set_cur_style(style);
221        while !q.is_null() {
222            // reswitch:
223            let mut delta = 0;
224            let Some(mut t) = self.noad_type(q) else {
225                // Nodes that can appear in an mlist.
226                let next = self.first_pass_node(q, &mut max_h, &mut max_d);
227                q = next;
228                continue;
229            };
230            let mut action = Action::Convert;
231            loop {
232                match t {
233                    NoadType::Bin => {
234                        if matches!(
235                            r_type,
236                            NoadType::Bin
237                                | NoadType::Op
238                                | NoadType::Rel
239                                | NoadType::Open
240                                | NoadType::Punct
241                                | NoadType::Left
242                        ) {
243                            self.nodes.set_subtype(q, noad_sub::ORD);
244                            t = NoadType::Ord;
245                            continue;
246                        }
247                    }
248                    NoadType::Rel | NoadType::Close | NoadType::Punct | NoadType::Right => {
249                        if r_type == NoadType::Bin {
250                            self.nodes.set_subtype(r, noad_sub::ORD);
251                        }
252                        if t == NoadType::Right {
253                            action = Action::DoneWithNoad;
254                        }
255                    }
256                    NoadType::Left => action = Action::DoneWithNoad,
257                    NoadType::Fraction => {
258                        self.make_fraction(q);
259                        action = Action::CheckDimensions;
260                    }
261                    NoadType::Op => {
262                        delta = self.make_op(q);
263                        if self.nodes.subtype(q) == noad_sub::OP_LIMITS {
264                            action = Action::CheckDimensions;
265                        }
266                    }
267                    NoadType::Ord => self.make_ord(q),
268                    NoadType::Open | NoadType::Inner => {}
269                    NoadType::Radical => self.make_radical(q),
270                    NoadType::Over => self.make_over(q),
271                    NoadType::Under => self.make_under(q),
272                    NoadType::Accent => self.make_math_accent(q),
273                    NoadType::Vcenter => self.make_vcenter(q),
274                }
275                break;
276            }
277            if action == Action::Convert {
278                self.convert_nucleus(q, delta);
279            }
280            if action != Action::DoneWithNoad {
281                // check_dimensions:
282                let nh = self.get_new_hlist(q);
283                let (h, d) = self.list_height_depth(nh);
284                if h > max_h {
285                    max_h = h;
286                }
287                if d > max_d {
288                    max_d = d;
289                }
290            }
291            // done_with_noad:
292            r = q;
293            r_type = self.noad_type(r).unwrap_or(NoadType::Ord);
294            if r_type == NoadType::Right {
295                r_type = NoadType::Left;
296                self.set_cur_style(style);
297            }
298            q = self.nodes.next(q);
299        }
300        if r_type == NoadType::Bin {
301            self.nodes.set_subtype(r, noad_sub::ORD);
302        }
303        self.second_pass(mlist, style, penalties, max_h, max_d)
304    }
305
306    /// The height and depth an `hpack` of list `p` would have.
307    fn list_height_depth(&mut self, p: NodeRef) -> (i32, i32) {
308        let saved = self.ts.adjust_tail.take();
309        let pre = self.ts.pre_adjust_tail.take();
310        let z = self.hpack_quiet(p);
311        self.ts.adjust_tail = saved;
312        self.ts.pre_adjust_tail = pre;
313        let (h, d) = self
314            .nodes
315            .box_data(z)
316            .map_or((0, 0), |b| (b.height, b.depth));
317        self.nodes.set_list_ptr(z, NodeRef::NULL);
318        self.nodes.free(z);
319        (h, d)
320    }
321
322    /// `hpack(p, natural)` without over/underfull reports (natural size
323    /// never reports anyway).
324    fn hpack_quiet(&mut self, p: NodeRef) -> NodeRef {
325        self.hpack(p, PackSpec::NATURAL)
326    }
327
328    /// First-pass processing of a node in an mlist; returns the next item.
329    fn first_pass_node(&mut self, q: NodeRef, max_h: &mut i32, max_d: &mut i32) -> NodeRef {
330        match self.nodes.data(q) {
331            NodeData::Style { style } => {
332                let s = i32::from(*style);
333                self.set_cur_style(s);
334            }
335            NodeData::Choice(c) => {
336                let c = **c;
337                let k = (self.ts.math.style / 2) as usize;
338                let p = c[k.min(3)];
339                for (i, l) in c.iter().enumerate() {
340                    if i != k.min(3) {
341                        self.nodes.flush_list(*l);
342                    }
343                }
344                let st = self.ts.math.style as u8;
345                *self.nodes.get_mut(q) = crate::nodes::Node {
346                    data: NodeData::Style { style: st },
347                    id: id::STYLE,
348                    ..self.nodes.get(q).clone()
349                };
350                if !p.is_null() {
351                    let z = self.nodes.next(q);
352                    self.nodes.link(q, p);
353                    let t = self.nodes.tail_of(p);
354                    self.nodes.link(t, z);
355                }
356            }
357            NodeData::Rule { height, depth, .. } => {
358                if *height > *max_h {
359                    *max_h = *height;
360                }
361                if *depth > *max_d {
362                    *max_d = *depth;
363                }
364            }
365            NodeData::Glue { spec, .. } => {
366                let spec = *spec;
367                let sub = self.nodes.subtype(q);
368                if sub == glue_sub::MU_GLUE {
369                    let g = self.math_glue(spec, self.ts.math.mu);
370                    if let NodeData::Glue { spec, .. } = self.nodes.data_mut(q) {
371                        *spec = g;
372                    }
373                    self.nodes.set_subtype(q, glue_sub::USER);
374                } else if self.ts.math.size != 0 && sub == glue_sub::COND_MATH_GLUE {
375                    let p = self.nodes.next(q);
376                    if !p.is_null() && matches!(self.nodes.id(p), id::GLUE | id::KERN) {
377                        let n = self.nodes.next(p);
378                        self.nodes.link(q, n);
379                        if n.is_null() {
380                            self.nodes.set_next(q, NodeRef::NULL);
381                        }
382                        self.nodes.set_next(p, NodeRef::NULL);
383                        self.nodes.flush_list(p);
384                    }
385                }
386            }
387            NodeData::Kern { kern, .. } => {
388                if self.nodes.subtype(q) == glue_sub::MU_GLUE {
389                    let w = self.mu_mult(*kern, self.ts.math.mu);
390                    if let NodeData::Kern { kern, .. } = self.nodes.data_mut(q) {
391                        *kern = w;
392                    }
393                    // LuaTeX marks converted \mkern kerns as italic kerns.
394                    self.nodes.set_subtype(q, kern_sub::ITALIC);
395                }
396            }
397            _ => {}
398        }
399        self.nodes.next(q)
400    }
401
402    /// Converts a mu dimension (`mu_mult` of `math_kern`).
403    fn mu_mult(&self, x: i32, m: i32) -> i32 {
404        let r = retex_arith::x_over_n_unchecked(m, 0o200000);
405        let (mut n, mut f) = (r.quotient, r.remainder);
406        if f < 0 {
407            n -= 1;
408            f += 0o200000;
409        }
410        let frac = retex_arith::xn_over_d(x, f, 0o200000).map_or(0, |r| r.quotient);
411        retex_arith::nx_plus_y(n, x, frac).unwrap_or(0)
412    }
413
414    /// Converts mu glue to ordinary glue (`math_glue`).
415    pub fn math_glue(&self, g: crate::glue::Glue, m: i32) -> crate::glue::Glue {
416        let mut r = g;
417        r.width = self.mu_mult(g.width, m);
418        if g.stretch_order == 0 {
419            r.stretch = self.mu_mult(g.stretch, m);
420        }
421        if g.shrink_order == 0 {
422            r.shrink = self.mu_mult(g.shrink, m);
423        }
424        r
425    }
426
427    // ----- fetching characters ------------------------------------------
428
429    /// The font, character and metrics of a `math_char` kernel (`fetch`).
430    /// Reports an undefined family or a missing character.
431    fn fetch(&mut self, k: NodeRef) -> Option<(u32, u32, crate::fonts::CharMetrics)> {
432        let (fam, chr) = match self.nodes.data(k) {
433            NodeData::MathChar { fam, chr } => (*fam, *chr),
434            _ => return None,
435        };
436        let size = self.ts.math.size;
437        let f = self.fam_fnt(fam, size);
438        if f == 0 {
439            self.print_err("");
440            self.print_size(size);
441            self.printer.print_int(i64::from(fam));
442            self.printer.print_str(" is undefined (character ");
443            self.printer.print_int(i64::from(chr));
444            self.printer.print_char(')' as u32);
445            self.help(&[
446                "Somewhere in the math formula just ended, you used the",
447                "stated character from an undefined font family. For example,",
448                "plain TeX doesn't allow \\it or \\sl in subscripts. Proceed,",
449                "and I'll try to forget that I needed that character.",
450            ]);
451            self.error();
452            return None;
453        }
454        match self.fonts.char_metrics(f, chr) {
455            Some(m) => Some((f, chr, *m)),
456            None => {
457                self.char_warning(f, chr);
458                None
459            }
460        }
461    }
462
463    /// Prints `\textfont`, `\scriptfont` or `\scriptscriptfont`
464    /// (`print_size`).
465    pub fn print_size(&mut self, s: u32) {
466        self.print_esc_str(match s {
467            0 => "textfont",
468            1 => "scriptfont",
469            _ => "scriptscriptfont",
470        });
471    }
472
473    /// Reports a missing character (`char_warning`) when
474    /// `\tracinglostchars` is positive.
475    pub fn char_warning(&mut self, f: u32, c: u32) {
476        if self.eqtb.int(ip::TRACINGLOSTCHARS) > 0 {
477            let old = self.eqtb.int(ip::TRACINGONLINE);
478            self.begin_diagnostic();
479            self.printer.print_nl("Missing character: There is no ");
480            self.printer.print_char(c);
481            self.printer.print_str(" in font ");
482            let name = self.font(f).name.clone();
483            self.printer.print_bytes(&name);
484            self.printer.print_char('!' as u32);
485            self.end_diagnostic(false);
486            let _ = old;
487        }
488    }
489
490    /// A protected glyph node for a math character.
491    fn new_math_glyph(&mut self, f: u32, c: u32) -> NodeRef {
492        let g = self.new_char_node(f, c);
493        self.nodes.set_subtype(g, PROTECTED_GLYPH);
494        g
495    }
496
497    /// A box holding one character, whose width includes the italic
498    /// correction (`char_box`).
499    fn char_box(&mut self, f: u32, c: u32) -> NodeRef {
500        let m = self.fonts.char_metrics(f, c).copied().unwrap_or_default();
501        // OpenType math boxes leave out the italic correction (observed).
502        let italic = if self.is_opentype_math_font(f) {
503            0
504        } else {
505            m.italic
506        };
507        let b = self.new_null_box_node(true);
508        let g = self.new_math_glyph(f, c);
509        if let Some(bd) = self.nodes.box_data_mut(b) {
510            bd.width = m.width + italic;
511            bd.height = m.height;
512            bd.depth = m.depth;
513            bd.head = g;
514        }
515        b
516    }
517
518    /// The TFM tag and remainder of character `c` in font `f`
519    /// (0 none, 1 lig/kern, 2 list, 3 extensible).
520    fn char_tag(&self, f: u32, c: u32) -> (u8, u32) {
521        if let Some(t) = self.tfm_of(f)
522            && let Some(ci) = t.char_info(c)
523        {
524            return (ci.tag, u32::from(ci.remainder));
525        }
526        if let Some(m) = self.math_char(f, c) {
527            if let Some(n) = m.next {
528                return (2, n);
529            }
530            if m.vert.is_some() {
531                return (3, c);
532            }
533        }
534        (0, 0)
535    }
536
537    // ----- boxes ----------------------------------------------------------
538
539    /// Puts a noad field into a box in style `s` (`clean_box`).
540    fn clean_box(&mut self, k: NodeRef, s: i32) -> NodeRef {
541        let mut q;
542        let kid = if k.is_null() { 0 } else { self.nodes.id(k) };
543        match kid {
544            id::MATH_CHAR | id::MATH_TEXT_CHAR => {
545                let n = self.new_noad(noad_sub::ORD);
546                let kc = self.nodes.copy_node(k);
547                self.set_noad_field(n, Field::Nucleus, kc);
548                q = self.clean_box_mlist(n, s);
549            }
550            id::SUB_BOX => {
551                q = self.nodes.list_ptr_kernel(k);
552                self.nodes.set_kernel_list(k, NodeRef::NULL);
553            }
554            id::SUB_MLIST => {
555                let l = self.nodes.list_ptr_kernel(k);
556                self.nodes.set_kernel_list(k, NodeRef::NULL);
557                q = self.clean_box_mlist(l, s);
558            }
559            _ => {
560                q = self.new_null_box_node(true);
561            }
562        }
563        // found:
564        let x = if q.is_null() || self.nodes.is_glyph(q) {
565            self.hpack_quiet(q)
566        } else if self.nodes.next(q).is_null()
567            && matches!(self.nodes.id(q), id::HLIST | id::VLIST)
568            && self.nodes.box_data(q).is_some_and(|b| b.shift == 0)
569        {
570            q
571        } else {
572            self.hpack_quiet(q)
573        };
574        // Simplify a trivial box.
575        q = self.nodes.list_ptr(x);
576        if !q.is_null() && self.nodes.is_glyph(q) {
577            let r = self.nodes.next(q);
578            if !r.is_null() && self.nodes.next(r).is_null() && self.nodes.id(r) == id::KERN {
579                self.nodes.free(r);
580                self.nodes.set_next(q, NodeRef::NULL);
581            }
582        }
583        x
584    }
585
586    fn clean_box_mlist(&mut self, l: NodeRef, s: i32) -> NodeRef {
587        let saved = self.ts.math;
588        let r = self.mlist_to_hlist_rec(l, s, false);
589        self.ts.math = saved;
590        r
591    }
592
593    /// Centers box `b` in a box of width `w` (`rebox`).
594    fn rebox(&mut self, b: NodeRef, w: i32) -> NodeRef {
595        let bw = self.nodes.box_data(b).map_or(0, |x| x.width);
596        let lp = self.nodes.list_ptr(b);
597        if bw != w && !lp.is_null() {
598            let mut b = b;
599            if self.nodes.id(b) == id::VLIST {
600                b = self.hpack_quiet(b);
601            }
602            let mut p = self.nodes.list_ptr(b);
603            let bw = self.nodes.box_data(b).map_or(0, |x| x.width);
604            if self.nodes.is_glyph(p) && self.nodes.next(p).is_null() {
605                let (f, c) = {
606                    let g = self.nodes.glyph(p).expect("glyph");
607                    (g.font, g.chr)
608                };
609                let v = self.fonts.char_metrics(f, c).map_or(0, |m| m.width);
610                if v != bw {
611                    let k = self.new_kern_node(bw - v, kern_sub::FONT);
612                    self.nodes.link(p, k);
613                }
614            }
615            self.nodes.set_list_ptr(b, NodeRef::NULL);
616            self.nodes.free(b);
617            let ss = crate::glue::Glue {
618                stretch: retex_arith::UNITY,
619                stretch_order: crate::glue::FIL,
620                shrink: retex_arith::UNITY,
621                shrink_order: crate::glue::FIL,
622                ..crate::glue::Glue::ZERO
623            };
624            let g1 = self.new_glue_node(ss, glue_sub::USER);
625            self.nodes.link(g1, p);
626            while !self.nodes.next(p).is_null() {
627                p = self.nodes.next(p);
628            }
629            let g2 = self.new_glue_node(ss, glue_sub::USER);
630            self.nodes.link(p, g2);
631            self.hpack(g1, PackSpec::Exactly(w))
632        } else {
633            if let Some(x) = self.nodes.box_data_mut(b) {
634                x.width = w;
635            }
636            b
637        }
638    }
639
640    /// A rule of thickness `t` (`fraction_rule`).
641    fn fraction_rule(&mut self, t: i32) -> NodeRef {
642        self.new_node(
643            id::RULE,
644            0,
645            NodeData::Rule {
646                width: crate::typeset::NULL_FLAG,
647                depth: 0,
648                height: t,
649                dir: 0,
650                index: 0,
651                left: 0,
652                right: 0,
653            },
654        )
655    }
656
657    /// Box `b` under a kern `k`, a rule `t` and a kern `top` (`overbar`).
658    fn overbar(&mut self, b: NodeRef, k: i32, t: i32, top: i32) -> NodeRef {
659        let p = self.new_kern_node(k, kern_sub::FONT);
660        self.nodes.link(p, b);
661        let q = self.fraction_rule(t);
662        self.nodes.link(q, p);
663        let p = self.new_kern_node(top, kern_sub::FONT);
664        self.nodes.link(p, q);
665        self.vpack(p, PackSpec::NATURAL)
666    }
667
668    // ----- delimiters -----------------------------------------------------
669
670    /// A box with the smallest variant of delimiter `d` whose height plus
671    /// depth is at least `v`, built from extensible pieces if needed,
672    /// centered on the axis of size `s` (`var_delimiter`).
673    pub fn var_delimiter(&mut self, d: NodeRef, s: u32, v: i32, shift: bool) -> NodeRef {
674        let (sf, sc, lf, lc) = self.delim_fields(d);
675        let mut best: Option<(u32, u32, bool)> = None;
676        let mut w = 0i32;
677        'search: for (z0, x0) in [(sf, sc), (lf, lc)] {
678            if z0 == 0 && x0 == 0 {
679                continue;
680            }
681            let mut z = s as i32;
682            while z >= 0 {
683                let g = self.fam_fnt(z0, z as u32);
684                if g != 0 {
685                    let mut y = x0;
686                    let mut guard = 0;
687                    loop {
688                        guard += 1;
689                        if guard > 256 {
690                            break;
691                        }
692                        let Some(m) = self.fonts.char_metrics(g, y).copied() else {
693                            break;
694                        };
695                        let (tag, rem) = self.char_tag(g, y);
696                        // An OpenType variant is used when it is large
697                        // enough; the assembly only after the last one.
698                        let opentype = self.is_opentype_math_font(g);
699                        if tag == 3 && !opentype {
700                            best = Some((g, y, true));
701                            break 'search;
702                        }
703                        let u = m.height + m.depth;
704                        if u > w {
705                            best = Some((g, y, false));
706                            w = u;
707                            if u >= v {
708                                break 'search;
709                            }
710                        }
711                        if opentype && self.vert_parts(g, y).is_some() {
712                            best = Some((g, y, true));
713                            break 'search;
714                        }
715                        if tag == 2 {
716                            y = rem;
717                        } else {
718                            break;
719                        }
720                    }
721                }
722                z -= 1;
723            }
724        }
725        let b = match best {
726            Some((f, c, true)) if self.is_opentype_math_font(f) => {
727                let saved = self.ts.math;
728                self.set_cur_style(if s == 0 { 2 } else { 2 * s as i32 + 2 });
729                let overlap = self.math_par(mp::CONNECTOR_OVERLAP_MIN);
730                self.ts.math = saved;
731                self.ot_extensible_box(f, c, v, overlap)
732            }
733            Some((f, c, true)) => self.extensible_box(f, c, v),
734            Some((f, c, false)) => self.char_box(f, c),
735            None => {
736                let b = self.new_null_box_node(true);
737                let nds = self.eqtb.dimen(dp::NULLDELIMITERSPACE);
738                if let Some(bd) = self.nodes.box_data_mut(b) {
739                    bd.width = nds;
740                }
741                if !shift {
742                    return b;
743                }
744                b
745            }
746        };
747        let saved = self.ts.math;
748        self.set_cur_style(if s == 0 { 2 } else { 2 * s as i32 + 2 });
749        let axis = self.math_par(mp::AXIS);
750        self.ts.math = saved;
751        if let Some(bd) = self.nodes.box_data_mut(b) {
752            bd.shift = half(bd.height - bd.depth) - axis;
753        }
754        b
755    }
756
757    /// For a nucleus that is a list of one accent noad on a character
758    /// (possibly accented itself), that character's font and code.
759    fn nested_accent_char(&mut self, k: NodeRef) -> Option<(u32, u32)> {
760        if k.is_null() || self.nodes.id(k) != id::SUB_MLIST {
761            return None;
762        }
763        let l = self.nodes.list_ptr_kernel(k);
764        if l.is_null()
765            || !self.nodes.next(l).is_null()
766            || self.noad_type(l) != Some(NoadType::Accent)
767        {
768            return None;
769        }
770        let n = self.noad_field(l, Field::Nucleus);
771        if n.is_null() {
772            return None;
773        }
774        if self.nodes.id(n) == id::MATH_CHAR {
775            return self.fetch(n).map(|(f, c, _)| (f, c));
776        }
777        self.nested_accent_char(n)
778    }
779
780    /// The vertical extensible parts of OpenType math character `c`.
781    fn vert_parts(&self, f: u32, c: u32) -> Option<std::rc::Rc<[crate::fonts::ExtPart]>> {
782        self.math_char(f, c)?.vert.clone()
783    }
784
785    /// Builds the vertical assembly of OpenType math character `c` with
786    /// height plus depth at least `v`, as LuaTeX does (observed with Latin
787    /// Modern Math): each extender is repeated the smallest number of
788    /// times that lets the parts, overlapping by at least `overlap`, reach
789    /// `v`; the parts are boxes as high as their advance, separated by
790    /// glue that removes the largest overlap the connectors allow and
791    /// stretches back to the smallest; the vbox is set to `v`.
792    fn ot_extensible_box(&mut self, f: u32, c: u32, v: i32, overlap: i32) -> NodeRef {
793        let parts = self
794            .vert_parts(f, c)
795            .unwrap_or_else(|| std::rc::Rc::from(Vec::new()));
796        let expand = |n: usize| -> Vec<crate::fonts::ExtPart> {
797            let mut out = Vec::new();
798            for p in parts.iter() {
799                let k = if p.extender { n } else { 1 };
800                for _ in 0..k {
801                    out.push(*p);
802                }
803            }
804            out
805        };
806        let joint = |a: &crate::fonts::ExtPart, b: &crate::fonts::ExtPart| {
807            let max = a.end.min(b.start).max(0);
808            (max, overlap.min(max))
809        };
810        let largest = |seq: &[crate::fonts::ExtPart]| -> i64 {
811            let mut t: i64 = seq.iter().map(|p| i64::from(p.advance)).sum();
812            for w in seq.windows(2) {
813                t -= i64::from(joint(&w[0], &w[1]).1);
814            }
815            t
816        };
817        let has_extender = parts.iter().any(|p| p.extender);
818        let mut n = 0;
819        let mut seq = expand(n);
820        while has_extender && largest(&seq) < i64::from(v) && n < 1000 {
821            n += 1;
822            seq = expand(n);
823        }
824        // Build the vbox from the top down.
825        let mut head = NodeRef::NULL;
826        let mut tail = NodeRef::NULL;
827        let mut width = 0;
828        for (i, p) in seq.iter().enumerate().rev() {
829            if i + 1 < seq.len() {
830                let (max, min) = joint(p, &seq[i + 1]);
831                let g = crate::glue::Glue {
832                    width: -max,
833                    stretch: max - min,
834                    ..crate::glue::Glue::ZERO
835                };
836                let gn = self.new_glue_node(g, 0);
837                self.nodes.link(tail, gn);
838                tail = gn;
839            }
840            let m = self
841                .fonts
842                .char_metrics(f, p.code)
843                .copied()
844                .unwrap_or_default();
845            let b = self.new_null_box_node(true);
846            let gl = self.new_math_glyph(f, p.code);
847            if let Some(bd) = self.nodes.box_data_mut(b) {
848                bd.width = m.width;
849                bd.height = p.advance;
850                bd.depth = 0;
851                bd.head = gl;
852            }
853            width = width.max(m.width);
854            if head.is_null() {
855                head = b;
856            } else {
857                self.nodes.link(tail, b);
858            }
859            tail = b;
860        }
861        let natural = largest(&seq) as i32;
862        let target = v.max(natural.min(v));
863        let b = self.vpack(head, crate::build::PackSpec::Exactly(target));
864        if let Some(bd) = self.nodes.box_data_mut(b) {
865            bd.width = width;
866        }
867        b
868    }
869
870    /// Builds an extensible character from the recipe of `c` in font `f`
871    /// (§713–§714).
872    fn extensible_box(&mut self, f: u32, c: u32, v: i32) -> NodeRef {
873        let recipe = self
874            .tfm_of(f)
875            .and_then(|t| {
876                let (_, rem) = self.char_tag(f, c);
877                t.exten.get(rem as usize).copied()
878            })
879            .unwrap_or([0, 0, 0, 0]);
880        let [top, mid, bot, rep] = recipe.map(u32::from);
881        let hd = |e: &Engine, ch: u32| {
882            e.fonts
883                .char_metrics(f, ch)
884                .map_or(0, |m| m.height + m.depth)
885        };
886        let b = self.new_null_box_node(false);
887        let u = hd(self, rep);
888        let mut w = 0;
889        let rm = self.fonts.char_metrics(f, rep).copied().unwrap_or_default();
890        let width = rm.width + rm.italic;
891        if bot != 0 {
892            w += hd(self, bot);
893        }
894        if mid != 0 {
895            w += hd(self, mid);
896        }
897        if top != 0 {
898            w += hd(self, top);
899        }
900        let mut n = 0;
901        if u > 0 {
902            while w < v {
903                w += u;
904                n += 1;
905                if mid != 0 {
906                    w += u;
907                }
908            }
909        }
910        if let Some(bd) = self.nodes.box_data_mut(b) {
911            bd.width = width;
912        }
913        if bot != 0 {
914            self.stack_into_box(b, f, bot);
915        }
916        for _ in 0..n {
917            self.stack_into_box(b, f, rep);
918        }
919        if mid != 0 {
920            self.stack_into_box(b, f, mid);
921            for _ in 0..n {
922                self.stack_into_box(b, f, rep);
923            }
924        }
925        if top != 0 {
926            self.stack_into_box(b, f, top);
927        }
928        // LuaTeX gives the whole height to the box (TeX82 keeps the
929        // height of the topmost piece and puts the rest in the depth).
930        if let Some(bd) = self.nodes.box_data_mut(b) {
931            bd.height = w;
932            bd.depth = 0;
933        }
934        b
935    }
936
937    fn stack_into_box(&mut self, b: NodeRef, f: u32, c: u32) {
938        let p = self.char_box(f, c);
939        let head = self.nodes.list_ptr(b);
940        self.nodes.link(p, head);
941        self.nodes.set_list_ptr(b, p);
942        let h = self.nodes.box_data(p).map_or(0, |x| x.height);
943        if let Some(bd) = self.nodes.box_data_mut(b) {
944            bd.height = h;
945        }
946    }
947
948    // ----- the make_… procedures -----------------------------------------
949
950    fn take_field(&mut self, q: NodeRef, f: Field) -> NodeRef {
951        let k = self.noad_field(q, f);
952        self.set_noad_field(q, f, NodeRef::NULL);
953        k
954    }
955
956    /// Replaces the nucleus of `q` by a `sub_box` holding `b`.
957    fn set_nucleus_box(&mut self, q: NodeRef, b: NodeRef) {
958        let old = self.take_field(q, Field::Nucleus);
959        self.nodes.flush_list(old);
960        let k = self.new_sub_list(false, b);
961        self.set_noad_field(q, Field::Nucleus, k);
962    }
963
964    fn make_over(&mut self, q: NodeRef) {
965        let k = self.noad_field(q, Field::Nucleus);
966        let st = cramped_style(self.ts.math.style);
967        let x = self.clean_box(k, st);
968        let b = self.overbar(
969            x,
970            self.math_par(mp::OVERBAR_VGAP),
971            self.math_par(mp::OVERBAR_RULE),
972            self.math_par(mp::OVERBAR_KERN),
973        );
974        self.set_nucleus_box(q, b);
975    }
976
977    fn make_under(&mut self, q: NodeRef) {
978        let k = self.noad_field(q, Field::Nucleus);
979        let x = self.clean_box(k, self.ts.math.style);
980        let p = self.new_kern_node(self.math_par(mp::UNDERBAR_VGAP), kern_sub::FONT);
981        self.nodes.link(x, p);
982        let r = self.fraction_rule(self.math_par(mp::UNDERBAR_RULE));
983        self.nodes.link(p, r);
984        let y = self.vpack(x, PackSpec::NATURAL);
985        let (yh, yd) = self
986            .nodes
987            .box_data(y)
988            .map_or((0, 0), |b| (b.height, b.depth));
989        let xh = self.nodes.box_data(x).map_or(0, |b| b.height);
990        let delta = yh + yd + self.math_par(mp::UNDERBAR_KERN);
991        if let Some(b) = self.nodes.box_data_mut(y) {
992            b.height = xh;
993            b.depth = delta - xh;
994        }
995        self.set_nucleus_box(q, y);
996    }
997
998    fn make_vcenter(&mut self, q: NodeRef) {
999        let k = self.noad_field(q, Field::Nucleus);
1000        let v = self.nodes.list_ptr_kernel(k);
1001        if v.is_null() || self.nodes.id(v) != id::VLIST {
1002            self.confusion("vcenter");
1003        }
1004        let axis = self.math_par(mp::AXIS);
1005        if let Some(b) = self.nodes.box_data_mut(v) {
1006            let delta = b.height + b.depth;
1007            b.height = axis + half(delta);
1008            b.depth = delta - b.height;
1009        }
1010    }
1011
1012    fn make_radical(&mut self, q: NodeRef) {
1013        let sub = self.nodes.subtype(q);
1014        match sub {
1015            radical_sub::UUNDERDELIMITER
1016            | radical_sub::UOVERDELIMITER
1017            | radical_sub::UDELIMITERUNDER
1018            | radical_sub::UDELIMITEROVER
1019            | radical_sub::UHEXTENSIBLE => {
1020                self.make_delimiter_radical(q, sub);
1021                return;
1022            }
1023            _ => {}
1024        }
1025        let k = self.noad_field(q, Field::Nucleus);
1026        let x = self.clean_box(k, cramped_style(self.ts.math.style));
1027        let (xh, xd) = self
1028            .nodes
1029            .box_data(x)
1030            .map_or((0, 0), |b| (b.height, b.depth));
1031        let mut clr = self.math_par(mp::RADICAL_VGAP);
1032        let theta = self.math_par_opt(mp::RADICAL_RULE);
1033        let kern = self.math_par(mp::RADICAL_KERN);
1034        let t = theta.unwrap_or(self.math_par(mp::FRACTION_RULE));
1035        let left = self.nodes.noad(q).map_or(NodeRef::NULL, |n| n.left);
1036        let size = self.ts.math.size;
1037        let y = self.var_delimiter(left, size, xh + xd + clr + t, true);
1038        let (yh, yd) = self
1039            .nodes
1040            .box_data(y)
1041            .map_or((0, 0), |b| (b.height, b.depth));
1042        let theta = if let Some(th) = theta {
1043            // With \Umathradicalrule set (OpenType fonts): the sign's whole
1044            // size counts, and its top is put at the top of the rule.
1045            let delta = yh + yd - th - (xh + xd + clr);
1046            if delta > 0 {
1047                clr += half(delta);
1048            }
1049            if let Some(b) = self.nodes.box_data_mut(y) {
1050                b.shift = yh - (xh + clr + th);
1051            }
1052            th
1053        } else {
1054            // TeX82 assumes the sign's height is the rule thickness. LuaTeX
1055            // gives an extensible sign its whole size as height, so measure
1056            // it from its topmost piece and convert.
1057            let top_h = if self.nodes.id(y) == id::VLIST {
1058                let h = self.nodes.list_ptr(y);
1059                self.nodes.box_data(h).map_or(yh, |b| b.height)
1060            } else {
1061                yh
1062            };
1063            let eq_depth = yh + yd - top_h;
1064            let delta = eq_depth - (xh + xd + clr);
1065            if delta > 0 {
1066                clr += half(delta);
1067            }
1068            if let Some(b) = self.nodes.box_data_mut(y) {
1069                b.shift = -(xh + clr) + (yh - top_h);
1070            }
1071            top_h
1072        };
1073        let top = if self.math_par_opt(mp::RADICAL_RULE).is_some() {
1074            kern
1075        } else {
1076            theta
1077        };
1078        let ob = self.overbar(x, clr, theta, top);
1079        self.nodes.link(y, ob);
1080        let deg = self.noad_field(q, Field::Degree);
1081        let list = if deg.is_null() {
1082            y
1083        } else {
1084            // \Uroot: the degree in scriptscript style.
1085            let r = self.clean_box(deg, 6);
1086            let (rw, rh, rd) = self
1087                .nodes
1088                .box_data(r)
1089                .map_or((0, 0, 0), |b| (b.width, b.height, b.depth));
1090            let _ = rd;
1091            let before = self.math_par(mp::RADICAL_DEGREE_BEFORE);
1092            let after = self.math_par(mp::RADICAL_DEGREE_AFTER);
1093            let raise = self.math_par(mp::RADICAL_DEGREE_RAISE);
1094            let (yh2, yd2, ys) = self
1095                .nodes
1096                .box_data(y)
1097                .map_or((0, 0, 0), |b| (b.height, b.depth, b.shift));
1098            let ytot = yh2 + yd2;
1099            let shift = retex_arith::xn_over_d(ytot, raise, 100).map_or(0, |r| r.quotient);
1100            // The degree's baseline is `raise` percent of the sign's size
1101            // above the sign's bottom.
1102            if self.math_par_opt(mp::RADICAL_RULE).is_some() {
1103                if let Some(b) = self.nodes.box_data_mut(r) {
1104                    b.shift = -(shift - (yd2 + ys));
1105                }
1106            } else if let Some(b) = self.nodes.box_data_mut(r) {
1107                b.shift = -(shift - yd2 + b.shift.min(0));
1108            }
1109            let _ = rh;
1110            let k1 = self.new_kern_node(before, kern_sub::FONT);
1111            let k2 = self.new_kern_node(after, kern_sub::FONT);
1112            let _ = rw;
1113            self.nodes.link(k1, r);
1114            self.nodes.link(r, k2);
1115            self.nodes.link(k2, y);
1116            k1
1117        };
1118        let b = self.hpack_quiet(list);
1119        self.set_nucleus_box(q, b);
1120    }
1121
1122    /// `\Uoverdelimiter` and friends: a horizontal extensible above or
1123    /// below the nucleus (only approximately LuaTeX's layout).
1124    fn make_delimiter_radical(&mut self, q: NodeRef, sub: u16) {
1125        let k = self.noad_field(q, Field::Nucleus);
1126        let st = self.ts.math.style;
1127        let x = if k.is_null() {
1128            self.new_null_box_node(true)
1129        } else {
1130            self.clean_box(k, st)
1131        };
1132        let left = self.nodes.noad(q).map_or(NodeRef::NULL, |n| n.left);
1133        let width = self.nodes.noad(q).map_or(0, |n| n.width);
1134        let xw = self.nodes.box_data(x).map_or(0, |b| b.width);
1135        let size = self.ts.math.size;
1136        let (sf, sc, _, _) = self.delim_fields(left);
1137        let f = self.fam_fnt(sf, size);
1138        let y = if f != 0 && self.fonts.char_metrics(f, sc).is_some() {
1139            self.char_box(f, sc)
1140        } else {
1141            self.new_null_box_node(true)
1142        };
1143        if sub == radical_sub::UHEXTENSIBLE {
1144            let _ = width;
1145            self.nodes.flush_list(x);
1146            self.set_nucleus_box(q, y);
1147            return;
1148        }
1149        let y = self.rebox(y, xw.max(self.nodes.box_data(y).map_or(0, |b| b.width)));
1150        let x = self.rebox(x, self.nodes.box_data(y).map_or(0, |b| b.width));
1151        let (top, bottom) =
1152            if sub == radical_sub::UOVERDELIMITER || sub == radical_sub::UDELIMITEROVER {
1153                (y, x)
1154            } else {
1155                (x, y)
1156            };
1157        let gap = if sub == radical_sub::UOVERDELIMITER || sub == radical_sub::UDELIMITEROVER {
1158            self.math_par(mp::OVER_DELIMITER_VGAP)
1159        } else {
1160            self.math_par(mp::UNDER_DELIMITER_VGAP)
1161        };
1162        let kk = self.new_kern_node(gap, kern_sub::FONT);
1163        self.nodes.link(top, kk);
1164        self.nodes.link(kk, bottom);
1165        let v = self.vpack(top, PackSpec::NATURAL);
1166        let bh = self.nodes.box_data(bottom).map_or(0, |b| b.height);
1167        let bd = self.nodes.box_data(bottom).map_or(0, |b| b.depth);
1168        if let Some(b) = self.nodes.box_data_mut(v) {
1169            let tot = b.height + b.depth;
1170            if top == y {
1171                b.height = tot - bd;
1172                b.depth = bd;
1173            } else {
1174                b.height = bh;
1175                b.depth = tot - bh;
1176            }
1177        }
1178        self.set_nucleus_box(q, v);
1179    }
1180
1181    /// Places a math accent (`make_math_accent`).
1182    fn make_math_accent(&mut self, q: NodeRef) {
1183        let acc = self.nodes.noad(q).map_or(NodeRef::NULL, |n| n.accent);
1184        if acc.is_null() {
1185            // A bottom accent only: place it below the nucleus.
1186            self.make_bottom_accent(q);
1187            return;
1188        }
1189        let Some((f, mut c, mut i)) = self.fetch(acc) else {
1190            return;
1191        };
1192        // Compute the amount of skew.
1193        let mut s = 0;
1194        let nuc = self.noad_field(q, Field::Nucleus);
1195        let mut nuc_char = None;
1196        if !nuc.is_null()
1197            && self.nodes.id(nuc) == id::MATH_CHAR
1198            && let Some((nf, nc, _)) = self.fetch(nuc)
1199        {
1200            nuc_char = Some((nf, nc));
1201            let sk = self.font(nf).skew_char;
1202            if sk >= 0
1203                && let Some(LigOp::Kern(k)) = self.lig_kern_step(nf, nc, sk as u32)
1204            {
1205                s = k;
1206            }
1207        }
1208        let st = cramped_style(self.ts.math.style);
1209        let mut char_nucleus = !nuc.is_null() && self.nodes.id(nuc) == id::MATH_CHAR;
1210        if nuc_char.is_none()
1211            && self.is_opentype_math_font(f)
1212            && let Some(fc) = self.nested_accent_char(nuc)
1213        {
1214            // An accent on an accented character attaches like one on the
1215            // character (observed with \hat{\hat{x}}).
1216            nuc_char = Some(fc);
1217            char_nucleus = true;
1218        }
1219        let mut x = self.clean_box(nuc, st);
1220        let (w, mut h) = self
1221            .nodes
1222            .box_data(x)
1223            .map_or((0, 0), |b| (b.width, b.height));
1224        // Switch to a larger accent if available and appropriate.
1225        let fixed = matches!(
1226            self.nodes.subtype(q),
1227            crate::nodes::accent_sub::FIXED_TOP | crate::nodes::accent_sub::FIXED_BOTH
1228        );
1229        if !fixed {
1230            loop {
1231                let (tag, rem) = self.char_tag(f, c);
1232                if tag != 2 {
1233                    break;
1234                }
1235                let Some(m) = self.fonts.char_metrics(f, rem).copied() else {
1236                    break;
1237                };
1238                if m.width > w {
1239                    break;
1240                }
1241                c = rem;
1242                i = m;
1243            }
1244        }
1245        let _ = i;
1246        let opentype = self.is_opentype_math_font(f);
1247        // OpenType fonts lower the accent by at most AccentBaseHeight.
1248        let xh = if opentype {
1249            self.font(f)
1250                .math_constants
1251                .as_ref()
1252                .and_then(|m| m.get("AccentBaseHeight").copied())
1253                .unwrap_or_else(|| self.x_height(f))
1254        } else {
1255            self.x_height(f)
1256        };
1257        let mut delta = if h < xh { h } else { xh };
1258        let sup = self.noad_field(q, Field::Sup);
1259        let sub = self.noad_field(q, Field::Sub);
1260        if (!sup.is_null() || !sub.is_null())
1261            && !nuc.is_null()
1262            && self.nodes.id(nuc) == id::MATH_CHAR
1263        {
1264            // Swap the subscript and superscript into box x.
1265            self.nodes.flush_list(x);
1266            let n = self.new_noad(noad_sub::ORD);
1267            let nk = self.take_field(q, Field::Nucleus);
1268            let a = self.take_field(q, Field::Sup);
1269            let b = self.take_field(q, Field::Sub);
1270            self.set_noad_field(n, Field::Nucleus, nk);
1271            self.set_noad_field(n, Field::Sup, a);
1272            self.set_noad_field(n, Field::Sub, b);
1273            let k = self.new_sub_list(true, n);
1274            self.set_noad_field(q, Field::Nucleus, k);
1275            x = self.clean_box(k, self.ts.math.style);
1276            let nh = self.nodes.box_data(x).map_or(0, |b| b.height);
1277            delta = delta + nh - h;
1278            h = nh;
1279        }
1280        let y = self.char_box(f, c);
1281        let yw = self.nodes.box_data(y).map_or(0, |b| b.width);
1282        let shift = if opentype {
1283            // Top accent attachments: that of a character nucleus (else
1284            // half its width) minus that of the accent (else half its
1285            // width).
1286            let a = if char_nucleus {
1287                nuc_char
1288                    .and_then(|(nf, nc)| self.math_char(nf, nc).and_then(|m| m.top_accent))
1289                    .unwrap_or(half(w))
1290            } else {
1291                half(w)
1292            };
1293            let b = self
1294                .math_char(f, c)
1295                .and_then(|m| m.top_accent)
1296                .unwrap_or(half(yw));
1297            a - b
1298        } else if char_nucleus {
1299            // For a character nucleus LuaTeX follows TeX82; otherwise it
1300            // centers with truncating halves (observed: w/2 - wa/2 + 1).
1301            s + half(w - yw)
1302        } else {
1303            s + w / 2 - yw / 2 + 1
1304        };
1305        if let Some(b) = self.nodes.box_data_mut(y) {
1306            b.shift = shift;
1307            b.width = 0;
1308        }
1309        let p = self.new_kern_node(-delta, kern_sub::FONT);
1310        self.nodes.link(p, x);
1311        self.nodes.link(y, p);
1312        let y = self.vpack(y, PackSpec::NATURAL);
1313        let mut xw = self.nodes.box_data(x).map_or(0, |b| b.width);
1314        // Observed: an OpenType accent on a character includes the
1315        // character's italic correction unless it ends the list.
1316        if opentype
1317            && !self.nodes.next(q).is_null()
1318            && let Some((nf, nc)) = nuc_char
1319            && self.nodes.id(nuc) == id::MATH_CHAR
1320        {
1321            xw += self.fonts.char_metrics(nf, nc).map_or(0, |m| m.italic);
1322        }
1323        let yh = self.nodes.box_data(y).map_or(0, |b| b.height);
1324        if let Some(b) = self.nodes.box_data_mut(y) {
1325            b.width = xw;
1326        }
1327        if yh < h {
1328            let p = self.new_kern_node(h - yh, kern_sub::FONT);
1329            let l = self.nodes.list_ptr(y);
1330            self.nodes.link(p, l);
1331            self.nodes.set_list_ptr(y, p);
1332            if let Some(b) = self.nodes.box_data_mut(y) {
1333                b.height = h;
1334            }
1335        }
1336        self.set_nucleus_box(q, y);
1337        let bot = self.nodes.noad(q).map_or(NodeRef::NULL, |n| n.bot_accent);
1338        if !bot.is_null() {
1339            self.make_bottom_accent(q);
1340        }
1341    }
1342
1343    /// A bottom accent (`\Umathaccent bottom`): centered below the
1344    /// nucleus.
1345    fn make_bottom_accent(&mut self, q: NodeRef) {
1346        let bot = match self.nodes.noad_mut(q) {
1347            Some(n) => std::mem::replace(&mut n.bot_accent, NodeRef::NULL),
1348            None => return,
1349        };
1350        let Some((f, mut c, _)) = self.fetch(bot) else {
1351            self.nodes.flush_list(bot);
1352            return;
1353        };
1354        self.nodes.flush_list(bot);
1355        let fixed = matches!(
1356            self.nodes.subtype(q),
1357            crate::nodes::accent_sub::FIXED_BOTTOM | crate::nodes::accent_sub::FIXED_BOTH
1358        );
1359        let nuc = self.noad_field(q, Field::Nucleus);
1360        let char_nucleus = !nuc.is_null() && self.nodes.id(nuc) == id::MATH_CHAR;
1361        let nuc_char = if char_nucleus {
1362            self.fetch(nuc).map(|(nf, nc, _)| (nf, nc))
1363        } else {
1364            None
1365        };
1366        // As for top accents, the scripts of a character go inside.
1367        let sup = self.noad_field(q, Field::Sup);
1368        let sub = self.noad_field(q, Field::Sub);
1369        let x = if char_nucleus && (!sup.is_null() || !sub.is_null()) {
1370            let n = self.new_noad(noad_sub::ORD);
1371            let nk = self.take_field(q, Field::Nucleus);
1372            let a = self.take_field(q, Field::Sup);
1373            let b = self.take_field(q, Field::Sub);
1374            self.set_noad_field(n, Field::Nucleus, nk);
1375            self.set_noad_field(n, Field::Sup, a);
1376            self.set_noad_field(n, Field::Sub, b);
1377            let k = self.new_sub_list(true, n);
1378            self.set_noad_field(q, Field::Nucleus, k);
1379            self.clean_box(k, self.ts.math.style)
1380        } else {
1381            self.clean_box(nuc, self.ts.math.style)
1382        };
1383        let (bw, xh) = self
1384            .nodes
1385            .box_data(x)
1386            .map_or((0, 0), |b| (b.width, b.height));
1387        // The accent is centred on the character itself, not on the box
1388        // with its scripts (observed).
1389        let w = match nuc_char {
1390            Some((nf, nc)) => self.fonts.char_metrics(nf, nc).map_or(bw, |m| {
1391                if self.is_opentype_math_font(nf) {
1392                    m.width
1393                } else {
1394                    m.width + m.italic
1395                }
1396            }),
1397            None => bw,
1398        };
1399        // Switch to a wider accent if available, as for top accents.
1400        if !fixed {
1401            loop {
1402                let (tag, rem) = self.char_tag(f, c);
1403                if tag != 2 {
1404                    break;
1405                }
1406                match self.fonts.char_metrics(f, rem) {
1407                    Some(m) if m.width <= w => c = rem,
1408                    _ => break,
1409                }
1410            }
1411        }
1412        let y = self.char_box(f, c);
1413        let yw = self.nodes.box_data(y).map_or(0, |b| b.width);
1414        let shift = if self.is_opentype_math_font(f) {
1415            let a = nuc_char
1416                .filter(|_| self.noad_field(q, Field::Sup).is_null())
1417                .and_then(|(nf, nc)| self.math_char(nf, nc).and_then(|m| m.top_accent))
1418                .unwrap_or(half(w));
1419            let b = self
1420                .math_char(f, c)
1421                .and_then(|m| m.top_accent)
1422                .unwrap_or(half(yw));
1423            a - b
1424        } else if nuc_char.is_some() {
1425            half(w - yw)
1426        } else {
1427            w / 2 - yw / 2 + 1
1428        };
1429        if let Some(b) = self.nodes.box_data_mut(y) {
1430            b.shift = shift;
1431            b.width = 0;
1432        }
1433        self.nodes.link(x, y);
1434        // Observed: the nucleus and the accent are packed naturally and
1435        // the box is lowered so that the nucleus keeps its baseline.
1436        let v = self.vpack(x, PackSpec::NATURAL);
1437        if let Some(b) = self.nodes.box_data_mut(v) {
1438            b.shift = b.height - xh;
1439            b.width = bw;
1440        }
1441        self.set_nucleus_box(q, v);
1442    }
1443
1444    /// Builds a generalized fraction (`make_fraction`).
1445    fn make_fraction(&mut self, q: NodeRef) {
1446        let (mut thickness, num, denom, left, right, options) = match self.nodes.data(q) {
1447            NodeData::Fraction(f) => (f.thickness, f.num, f.denom, f.left, f.right, f.options),
1448            _ => return,
1449        };
1450        if thickness == DEFAULT_CODE {
1451            thickness = self.math_par(mp::FRACTION_RULE);
1452        }
1453        let cs = self.ts.math.style;
1454        let mut x = self.clean_box(num, num_style(cs));
1455        let mut z = self.clean_box(denom, denom_style(cs));
1456        let (xw, zw) = (
1457            self.nodes.box_data(x).map_or(0, |b| b.width),
1458            self.nodes.box_data(z).map_or(0, |b| b.width),
1459        );
1460        if xw < zw {
1461            x = self.rebox(x, zw);
1462        } else {
1463            z = self.rebox(z, xw);
1464        }
1465        let (mut shift_up, mut shift_down) = if thickness == 0 {
1466            (
1467                self.math_par(mp::STACK_NUM_UP),
1468                self.math_par(mp::STACK_DENOM_DOWN),
1469            )
1470        } else {
1471            (
1472                self.math_par(mp::FRACTION_NUM_UP),
1473                self.math_par(mp::FRACTION_DENOM_DOWN),
1474            )
1475        };
1476        let (xh, xd) = self
1477            .nodes
1478            .box_data(x)
1479            .map_or((0, 0), |b| (b.height, b.depth));
1480        let (zh, zd) = self
1481            .nodes
1482            .box_data(z)
1483            .map_or((0, 0), |b| (b.height, b.depth));
1484        let axis = self.math_par(mp::AXIS);
1485        let delta;
1486        if thickness == 0 {
1487            let clr = self.math_par(mp::STACK_VGAP);
1488            delta = half(clr - ((shift_up - xd) - (zh - shift_down)));
1489            if delta > 0 {
1490                shift_up += delta;
1491                shift_down += delta;
1492            }
1493        } else {
1494            let exact = options & noad_opt::EXACT == noad_opt::EXACT;
1495            let (clr_n, clr_d) = if exact {
1496                (0, 0)
1497            } else if thickness == self.math_par(mp::FRACTION_RULE) {
1498                (
1499                    self.math_par(mp::FRACTION_NUM_VGAP),
1500                    self.math_par(mp::FRACTION_DENOM_VGAP),
1501                )
1502            } else if cs < 2 {
1503                (3 * thickness, 3 * thickness)
1504            } else {
1505                (thickness, thickness)
1506            };
1507            delta = half(thickness);
1508            let delta1 = clr_n - ((shift_up - xd) - (axis + delta));
1509            let delta2 = clr_d - ((axis - delta) - (zh - shift_down));
1510            if delta1 > 0 {
1511                shift_up += delta1;
1512            }
1513            if delta2 > 0 {
1514                shift_down += delta2;
1515            }
1516        }
1517        // Construct a vlist box for the fraction.
1518        let v = self.new_null_box_node(false);
1519        let width = self.nodes.box_data(x).map_or(0, |b| b.width);
1520        if let Some(b) = self.nodes.box_data_mut(v) {
1521            b.height = shift_up + xh;
1522            b.depth = zd + shift_down;
1523            b.width = width;
1524        }
1525        let p;
1526        if thickness == 0 {
1527            p = self.new_kern_node((shift_up - xd) - (zh - shift_down), kern_sub::FONT);
1528            self.nodes.link(p, z);
1529        } else {
1530            let y = self.fraction_rule(thickness);
1531            let p2 = self.new_kern_node((axis - delta) - (zh - shift_down), kern_sub::FONT);
1532            self.nodes.link(y, p2);
1533            self.nodes.link(p2, z);
1534            p = self.new_kern_node((shift_up - xd) - (axis + delta), kern_sub::FONT);
1535            self.nodes.link(p, y);
1536        }
1537        self.nodes.link(x, p);
1538        self.nodes.set_list_ptr(v, x);
1539        // Put the fraction into a box with its delimiters.
1540        let dsize = self.math_par(mp::FRACTION_DEL_SIZE);
1541        let size = self.ts.math.size;
1542        let lx = self.var_delimiter(left, size, dsize, true);
1543        self.nodes.link(lx, v);
1544        let rz = self.var_delimiter(right, size, dsize, true);
1545        self.nodes.link(v, rz);
1546        let h = self.hpack_quiet(lx);
1547        self.set_new_hlist(q, h);
1548    }
1549
1550    /// Builds an operator (`make_op`); returns the italic correction.
1551    fn make_op(&mut self, q: NodeRef) -> i32 {
1552        let cs = self.ts.math.style;
1553        if self.nodes.subtype(q) == noad_sub::OP_DISPLAY_LIMITS && cs < 2 {
1554            self.nodes.set_subtype(q, noad_sub::OP_LIMITS);
1555        }
1556        let limits = self.nodes.subtype(q) == noad_sub::OP_LIMITS;
1557        let nuc = self.noad_field(q, Field::Nucleus);
1558        let mut delta = 0;
1559        let mut opentype = false;
1560        if !nuc.is_null() && self.nodes.id(nuc) == id::MATH_CHAR {
1561            if let Some((f, c, m)) = self.fetch(nuc) {
1562                let mut m = m;
1563                if cs < 2 {
1564                    let (tag, rem) = self.char_tag(f, c);
1565                    if tag == 2
1566                        && let Some(m2) = self.fonts.char_metrics(f, rem).copied()
1567                    {
1568                        m = m2;
1569                        if let NodeData::MathChar { chr, .. } = self.nodes.data_mut(nuc) {
1570                            *chr = rem;
1571                        }
1572                    }
1573                }
1574                delta = m.italic;
1575                opentype = self.is_opentype_math_font(f);
1576            }
1577            let x = self.clean_box(nuc, cs);
1578            let has_sub = !self.noad_field(q, Field::Sub).is_null();
1579            let axis = self.math_par(mp::AXIS);
1580            if let Some(b) = self.nodes.box_data_mut(x) {
1581                if has_sub && !limits && !opentype {
1582                    b.width -= delta;
1583                }
1584                b.shift = half(b.height - b.depth) - axis;
1585            }
1586            self.set_nucleus_box(q, x);
1587            // An OpenType operator's box has no italic correction; with
1588            // \nolimits the subscript moves left by it (a font kern) and
1589            // a lone superscript is not moved.
1590            if opentype && !limits {
1591                if has_sub {
1592                    self.ts.math.op_kern = -delta;
1593                } else {
1594                    delta = 0;
1595                }
1596            }
1597        }
1598        if limits {
1599            // Construct a box with limits above and below it.
1600            let supk = self.noad_field(q, Field::Sup);
1601            let subk = self.noad_field(q, Field::Sub);
1602            let x = self.clean_box(supk, sup_style(cs));
1603            let nk = self.noad_field(q, Field::Nucleus);
1604            let y = self.clean_box(nk, cs);
1605            let z = self.clean_box(subk, sub_style(cs));
1606            let v = self.new_null_box_node(false);
1607            let wy = self.nodes.box_data(y).map_or(0, |b| b.width);
1608            let wx = self.nodes.box_data(x).map_or(0, |b| b.width);
1609            let wz = self.nodes.box_data(z).map_or(0, |b| b.width);
1610            let wv = wy.max(wx).max(wz);
1611            let x = self.rebox(x, wv);
1612            let y = self.rebox(y, wv);
1613            let z = self.rebox(z, wv);
1614            if let Some(b) = self.nodes.box_data_mut(x) {
1615                b.shift = half(delta);
1616            }
1617            if let Some(b) = self.nodes.box_data_mut(z) {
1618                b.shift = -half(delta);
1619            }
1620            let (yh, yd) = self
1621                .nodes
1622                .box_data(y)
1623                .map_or((0, 0), |b| (b.height, b.depth));
1624            let (mut vh, mut vd) = (yh, yd);
1625            let kern = self.math_par(mp::LIMIT_ABOVE_KERN);
1626            let kern_below = self.math_par(mp::LIMIT_BELOW_KERN);
1627            let head;
1628            if supk.is_null() {
1629                self.nodes.free(x);
1630                head = y;
1631            } else {
1632                let (xh, xd) = self
1633                    .nodes
1634                    .box_data(x)
1635                    .map_or((0, 0), |b| (b.height, b.depth));
1636                let mut shift_up = self.math_par(mp::LIMIT_ABOVE_BGAP) - xd;
1637                let g1 = self.math_par(mp::LIMIT_ABOVE_VGAP);
1638                if shift_up < g1 {
1639                    shift_up = g1;
1640                }
1641                let p = self.new_kern_node(shift_up, kern_sub::FONT);
1642                self.nodes.link(p, y);
1643                self.nodes.link(x, p);
1644                let p = self.new_kern_node(kern, kern_sub::FONT);
1645                self.nodes.link(p, x);
1646                head = p;
1647                vh += kern + xh + xd + shift_up;
1648            }
1649            if subk.is_null() {
1650                self.nodes.free(z);
1651            } else {
1652                let (zh, zd) = self
1653                    .nodes
1654                    .box_data(z)
1655                    .map_or((0, 0), |b| (b.height, b.depth));
1656                let mut shift_down = self.math_par(mp::LIMIT_BELOW_BGAP) - zh;
1657                let g2 = self.math_par(mp::LIMIT_BELOW_VGAP);
1658                if shift_down < g2 {
1659                    shift_down = g2;
1660                }
1661                let p = self.new_kern_node(shift_down, kern_sub::FONT);
1662                self.nodes.link(y, p);
1663                self.nodes.link(p, z);
1664                let p = self.new_kern_node(kern_below, kern_sub::FONT);
1665                self.nodes.link(z, p);
1666                vd += kern_below + zh + zd + shift_down;
1667            }
1668            if let Some(b) = self.nodes.box_data_mut(v) {
1669                b.width = wv;
1670                b.height = vh;
1671                b.depth = vd;
1672                b.head = head;
1673            }
1674            // The scripts are now part of the box.
1675            let a = self.take_field(q, Field::Sup);
1676            let b = self.take_field(q, Field::Sub);
1677            self.nodes.flush_list(a);
1678            self.nodes.flush_list(b);
1679            self.set_new_hlist(q, v);
1680        }
1681        delta
1682    }
1683
1684    /// Ligatures and kerns between ordinary characters (`make_ord`).
1685    fn make_ord(&mut self, q: NodeRef) {
1686        'restart: loop {
1687            if !self.noad_field(q, Field::Sub).is_null()
1688                || !self.noad_field(q, Field::Sup).is_null()
1689            {
1690                return;
1691            }
1692            let nuc = self.noad_field(q, Field::Nucleus);
1693            if nuc.is_null() || self.nodes.id(nuc) != id::MATH_CHAR {
1694                return;
1695            }
1696            let p = self.nodes.next(q);
1697            if p.is_null()
1698                || self.nodes.id(p) != id::NOAD
1699                || !matches!(
1700                    self.nodes.subtype(p),
1701                    noad_sub::ORD
1702                        | noad_sub::OP_DISPLAY_LIMITS
1703                        | noad_sub::OP_LIMITS
1704                        | noad_sub::OP_NO_LIMITS
1705                        | noad_sub::BIN
1706                        | noad_sub::REL
1707                        | noad_sub::OPEN
1708                        | noad_sub::CLOSE
1709                        | noad_sub::PUNCT
1710                )
1711            {
1712                return;
1713            }
1714            let pn = self.noad_field(p, Field::Nucleus);
1715            if pn.is_null() || self.nodes.id(pn) != id::MATH_CHAR {
1716                return;
1717            }
1718            let (qfam, pfam, pchr) = match (self.nodes.data(nuc), self.nodes.data(pn)) {
1719                (NodeData::MathChar { fam: a, .. }, NodeData::MathChar { fam: b, chr }) => {
1720                    (*a, *b, *chr)
1721                }
1722                _ => return,
1723            };
1724            if qfam != pfam {
1725                return;
1726            }
1727            self.nodes.get_mut(nuc).id = id::MATH_TEXT_CHAR;
1728            let Some((f, c, _)) = self.fetch(nuc) else {
1729                return;
1730            };
1731            let Some(op) = self.lig_kern_step(f, c, pchr) else {
1732                return;
1733            };
1734            match op {
1735                LigOp::Kern(k) => {
1736                    let kn = self.new_kern_node(k, kern_sub::FONT);
1737                    let n = self.nodes.next(q);
1738                    self.nodes.link(kn, n);
1739                    self.nodes.link(q, kn);
1740                    return;
1741                }
1742                LigOp::Lig { op, chr } => {
1743                    match op {
1744                        1 | 5 => self.set_char(nuc, chr),
1745                        2 | 6 => self.set_char(pn, chr),
1746                        3 | 7 | 11 => {
1747                            let r = self.new_noad(noad_sub::ORD);
1748                            let k = self.new_math_char(qfam, chr);
1749                            if op >= 11 {
1750                                self.nodes.get_mut(k).id = id::MATH_TEXT_CHAR;
1751                            }
1752                            self.set_noad_field(r, Field::Nucleus, k);
1753                            self.nodes.link(q, r);
1754                            self.nodes.link(r, p);
1755                        }
1756                        _ => {
1757                            let n = self.nodes.next(p);
1758                            self.nodes.link(q, n);
1759                            if n.is_null() {
1760                                self.nodes.set_next(q, NodeRef::NULL);
1761                            }
1762                            self.set_char(nuc, chr);
1763                            let a = self.take_field(p, Field::Sub);
1764                            let b = self.take_field(p, Field::Sup);
1765                            self.set_noad_field(q, Field::Sub, a);
1766                            self.set_noad_field(q, Field::Sup, b);
1767                            self.nodes.set_next(p, NodeRef::NULL);
1768                            self.nodes.flush_list(p);
1769                        }
1770                    }
1771                    if op > 3 {
1772                        return;
1773                    }
1774                    self.nodes.get_mut(nuc).id = id::MATH_CHAR;
1775                    continue 'restart;
1776                }
1777            }
1778        }
1779    }
1780
1781    fn set_char(&mut self, k: NodeRef, c: u32) {
1782        if let NodeData::MathChar { chr, .. } = self.nodes.data_mut(k) {
1783            *chr = c;
1784        }
1785    }
1786
1787    /// Converts the nucleus of `q` to an hlist and attaches the scripts
1788    /// (§754–§755).
1789    fn convert_nucleus(&mut self, q: NodeRef, delta: i32) {
1790        let mut delta = delta;
1791        let nuc = self.noad_field(q, Field::Nucleus);
1792        let kid = if nuc.is_null() { 0 } else { self.nodes.id(nuc) };
1793        let has_sub = !self.noad_field(q, Field::Sub).is_null();
1794        let p = match kid {
1795            id::MATH_CHAR | id::MATH_TEXT_CHAR => match self.fetch(nuc) {
1796                Some((f, c, m)) => {
1797                    delta = m.italic;
1798                    let p = self.new_math_glyph(f, c);
1799                    let has_sup = !self.noad_field(q, Field::Sup).is_null();
1800                    if self.is_opentype_math_font(f) {
1801                        // Observed: an OpenType math character gets its
1802                        // italic correction before a character of the same
1803                        // family (a "text char"), before a superscript and
1804                        // in its scripts, but not otherwise.
1805                        if kid == id::MATH_CHAR && !has_sup && !has_sub {
1806                            delta = 0;
1807                        }
1808                    } else if kid == id::MATH_TEXT_CHAR
1809                        && self.font(f).params.get(2).copied().unwrap_or(0) != 0
1810                    {
1811                        delta = 0;
1812                    }
1813                    if !has_sub && delta != 0 {
1814                        let k = self.new_kern_node(delta, kern_sub::ITALIC);
1815                        self.nodes.link(p, k);
1816                        delta = 0;
1817                    }
1818                    p
1819                }
1820                None => NodeRef::NULL,
1821            },
1822            id::SUB_BOX => {
1823                let b = self.nodes.list_ptr_kernel(nuc);
1824                self.nodes.set_kernel_list(nuc, NodeRef::NULL);
1825                let k = std::mem::take(&mut self.ts.math.op_kern);
1826                if k != 0 && !b.is_null() {
1827                    let kn = self.new_kern_node(k, kern_sub::FONT);
1828                    self.nodes.link(b, kn);
1829                }
1830                b
1831            }
1832            id::SUB_MLIST => {
1833                let l = self.nodes.list_ptr_kernel(nuc);
1834                self.nodes.set_kernel_list(nuc, NodeRef::NULL);
1835                let hl = self.clean_box_mlist(l, self.ts.math.style);
1836                self.hpack_quiet(hl)
1837            }
1838            _ => {
1839                // LuaTeX gives an empty nucleus with scripts an empty box.
1840                if has_sub || !self.noad_field(q, Field::Sup).is_null() {
1841                    self.new_null_box_node(true)
1842                } else {
1843                    NodeRef::NULL
1844                }
1845            }
1846        };
1847        self.set_new_hlist(q, p);
1848        let has_sup = !self.noad_field(q, Field::Sup).is_null();
1849        if !has_sub && !has_sup {
1850            return;
1851        }
1852        self.make_scripts(q, delta);
1853    }
1854
1855    /// Attaches subscript and superscript (`make_scripts`).
1856    fn make_scripts(&mut self, q: NodeRef, delta: i32) {
1857        let p = self.get_new_hlist(q);
1858        let cs = self.ts.math.style;
1859        let (mut shift_up, mut shift_down);
1860        if !p.is_null() && self.nodes.is_glyph(p) {
1861            shift_up = 0;
1862            shift_down = 0;
1863        } else {
1864            let (h, d) = self.list_height_depth(p);
1865            shift_up = h - self.math_par(mp::SUP_SHIFT_DROP);
1866            shift_down = d + self.math_par(mp::SUB_SHIFT_DROP);
1867        }
1868        let opts = self.nodes.noad(q).map_or(0, |n| n.options);
1869        let supk = self.take_field(q, Field::Sup);
1870        let subk = self.take_field(q, Field::Sub);
1871        if !p.is_null() && self.nodes.is_glyph(p) {
1872            // LuaTeX looks at character scripts in the current size (for
1873            // OpenType math kerns), which reports undefined families.
1874            for k in [supk, subk] {
1875                if !k.is_null() && self.nodes.id(k) == id::MATH_CHAR {
1876                    let _ = self.fetch(k);
1877                }
1878            }
1879        }
1880        let script_space = self.math_par(mp::SPACE_AFTER_SCRIPT);
1881        let x;
1882        if supk.is_null() {
1883            // A subscript without superscript.
1884            let st = if opts & noad_opt::NO_SUB_SCRIPT == noad_opt::NO_SUB_SCRIPT {
1885                cs
1886            } else {
1887                sub_style(cs)
1888            };
1889            x = self.clean_box(subk, st);
1890            self.nodes.flush_list(subk);
1891            let xh = self.nodes.box_data(x).map_or(0, |b| b.height);
1892            if let Some(b) = self.nodes.box_data_mut(x) {
1893                b.width += script_space;
1894            }
1895            let s1 = self.math_par(mp::SUB_SHIFT_DOWN);
1896            if shift_down < s1 {
1897                shift_down = s1;
1898            }
1899            let clr = xh - self.math_par(mp::SUB_TOP_MAX);
1900            if shift_down < clr {
1901                shift_down = clr;
1902            }
1903            if let Some(b) = self.nodes.box_data_mut(x) {
1904                b.shift = shift_down;
1905            }
1906        } else {
1907            let st = if opts & noad_opt::NO_SUPER_SCRIPT == noad_opt::NO_SUPER_SCRIPT {
1908                cs
1909            } else {
1910                sup_style(cs)
1911            };
1912            let sx = self.clean_box(supk, st);
1913            self.nodes.flush_list(supk);
1914            let xd = self.nodes.box_data(sx).map_or(0, |b| b.depth);
1915            if let Some(b) = self.nodes.box_data_mut(sx) {
1916                b.width += script_space;
1917            }
1918            let clr = self.math_par(mp::SUP_SHIFT_UP);
1919            if shift_up < clr {
1920                shift_up = clr;
1921            }
1922            let clr = xd + self.math_par(mp::SUP_BOTTOM_MIN);
1923            if shift_up < clr {
1924                shift_up = clr;
1925            }
1926            if subk.is_null() {
1927                if let Some(b) = self.nodes.box_data_mut(sx) {
1928                    b.shift = -shift_up;
1929                }
1930                x = sx;
1931            } else {
1932                let st = if opts & noad_opt::NO_SUB_SCRIPT == noad_opt::NO_SUB_SCRIPT {
1933                    cs
1934                } else {
1935                    sub_style(cs)
1936                };
1937                let y = self.clean_box(subk, st);
1938                self.nodes.flush_list(subk);
1939                let yh = self.nodes.box_data(y).map_or(0, |b| b.height);
1940                if let Some(b) = self.nodes.box_data_mut(y) {
1941                    b.width += script_space;
1942                }
1943                let s2 = self.math_par(mp::SUB_SUP_SHIFT_DOWN);
1944                if shift_down < s2 {
1945                    shift_down = s2;
1946                }
1947                let mut clr =
1948                    self.math_par(mp::SUB_SUP_VGAP) - ((shift_up - xd) - (yh - shift_down));
1949                if clr > 0 {
1950                    shift_down += clr;
1951                    clr = self.math_par(mp::SUP_SUB_BOTTOM_MAX) - (shift_up - xd);
1952                    if clr > 0 {
1953                        shift_up += clr;
1954                        shift_down -= clr;
1955                    }
1956                }
1957                if let Some(b) = self.nodes.box_data_mut(sx) {
1958                    b.shift = delta;
1959                }
1960                let p = self.new_kern_node((shift_up - xd) - (yh - shift_down), kern_sub::FONT);
1961                self.nodes.link(sx, p);
1962                self.nodes.link(p, y);
1963                x = self.vpack(sx, PackSpec::NATURAL);
1964                if let Some(b) = self.nodes.box_data_mut(x) {
1965                    b.shift = shift_down;
1966                }
1967            }
1968        }
1969        let nh = self.get_new_hlist(q);
1970        if nh.is_null() {
1971            self.set_new_hlist(q, x);
1972        } else {
1973            let t = self.nodes.tail_of(nh);
1974            self.nodes.link(t, x);
1975        }
1976    }
1977
1978    /// Builds the delimiter of a fence (`make_left_right`).
1979    fn make_left_right(&mut self, q: NodeRef, style: i32, max_d: i32, max_h: i32) -> NoadType {
1980        self.set_cur_style(style);
1981        let (delim, height, depth, options) = match self.nodes.data(q) {
1982            NodeData::Fence(f) => (f.delim, f.height, f.depth, f.options),
1983            _ => (NodeRef::NULL, 0, 0, 0),
1984        };
1985        let axis = self.math_par(mp::AXIS);
1986        let b = if height != 0 || depth != 0 {
1987            let total = height + depth;
1988            let b = self.var_delimiter(delim, self.ts.math.size, total, true);
1989            if options & noad_opt::NO_AXIS == noad_opt::NO_AXIS
1990                && let Some(bd) = self.nodes.box_data_mut(b)
1991            {
1992                bd.shift = 0;
1993            }
1994            b
1995        } else {
1996            let mut delta2 = max_d + axis;
1997            let mut delta1 = max_h + max_d - delta2;
1998            if delta2 > delta1 {
1999                delta1 = delta2;
2000            }
2001            let mut delta = (delta1 / 500) * self.eqtb.int(ip::DELIMITERFACTOR);
2002            delta2 = delta1 + delta1 - self.eqtb.dimen(dp::DELIMITERSHORTFALL);
2003            if delta < delta2 {
2004                delta = delta2;
2005            }
2006            self.var_delimiter(delim, self.ts.math.size, delta, false)
2007        };
2008        self.set_new_hlist(q, b);
2009        if self.nodes.subtype(q) == fence_sub::LEFT {
2010            NoadType::Open
2011        } else {
2012            NoadType::Close
2013        }
2014    }
2015
2016    // ----- the second pass ---------------------------------------------
2017
2018    fn second_pass(
2019        &mut self,
2020        mlist: NodeRef,
2021        style: i32,
2022        penalties: bool,
2023        max_h: i32,
2024        max_d: i32,
2025    ) -> NodeRef {
2026        let head = self.nodes.new_temp();
2027        let mut p = head;
2028        let mut q = mlist;
2029        let mut r_type: Option<NoadType> = None;
2030        self.set_cur_style(style);
2031        let binpen = self.eqtb.int(ip::BINOPPENALTY);
2032        let relpen = self.eqtb.int(ip::RELPENALTY);
2033        while !q.is_null() {
2034            let next = self.nodes.next(q);
2035            let Some(nt) = self.noad_type(q) else {
2036                if let NodeData::Style { style } = self.nodes.data(q) {
2037                    let s = i32::from(*style);
2038                    self.set_cur_style(s);
2039                    self.nodes.free(q);
2040                } else {
2041                    self.nodes.link(p, q);
2042                    self.nodes.set_next(q, NodeRef::NULL);
2043                    p = q;
2044                }
2045                q = next;
2046                continue;
2047            };
2048            let mut t = NoadType::Ord;
2049            let mut pen = 10000;
2050            match nt {
2051                NoadType::Op
2052                | NoadType::Open
2053                | NoadType::Close
2054                | NoadType::Punct
2055                | NoadType::Inner => t = nt,
2056                NoadType::Bin => {
2057                    t = NoadType::Bin;
2058                    pen = binpen;
2059                }
2060                NoadType::Rel => {
2061                    t = NoadType::Rel;
2062                    pen = relpen;
2063                }
2064                NoadType::Fraction => t = NoadType::Inner,
2065                NoadType::Left | NoadType::Right => {
2066                    t = self.make_left_right(q, style, max_d, max_h);
2067                }
2068                _ => {}
2069            }
2070            // Inter-element spacing.
2071            if let Some(rt) = r_type {
2072                let style = self.ts.math.style as u32;
2073                if let Some((g, sub)) = self.math_spacing(rt as u32, t as u32, style) {
2074                    let y = self.math_glue(g, self.ts.math.mu);
2075                    let z = self.new_glue_node(y, sub);
2076                    self.nodes.link(p, z);
2077                    p = z;
2078                }
2079            }
2080            // Append the new_hlist entries and penalties.
2081            let nh = self.get_new_hlist(q);
2082            self.set_new_hlist(q, NodeRef::NULL);
2083            if !nh.is_null() {
2084                self.nodes.link(p, nh);
2085                p = self.nodes.tail_of(nh);
2086            }
2087            if penalties && !next.is_null() && pen < 10000 {
2088                let nid = self.nodes.id(next);
2089                let next_is_rel = self.noad_type(next) == Some(NoadType::Rel);
2090                if nid != id::PENALTY && !next_is_rel {
2091                    let z = self.new_penalty_node(pen);
2092                    self.nodes.set_subtype(z, 5);
2093                    self.nodes.link(p, z);
2094                    p = z;
2095                }
2096            }
2097            if nt == NoadType::Right {
2098                t = NoadType::Open;
2099            }
2100            r_type = Some(t);
2101            // delete_q:
2102            self.nodes.set_next(q, NodeRef::NULL);
2103            self.nodes.flush_list(q);
2104            q = next;
2105        }
2106        let first = self.nodes.next(head);
2107        self.nodes.free(head);
2108        if !first.is_null() {
2109            self.nodes.set_prev(first, NodeRef::NULL);
2110        }
2111        first
2112    }
2113}
2114
2115#[derive(Clone, Copy, PartialEq, Eq)]
2116enum Action {
2117    Convert,
2118    CheckDimensions,
2119    DoneWithNoad,
2120}