Skip to main content

retex_engine/
math.rs

1//! Building math lists (*TeX: The Program* §1136–§1206, with e-TeX's
2//! `\middle` and LuaTeX's extensions).
3//!
4//! A formula is read into an mlist of noads (see [`crate::nodes`]), which
5//! [`crate::mlist`] converts into an hlist when the formula ends. LuaTeX
6//! differences implemented here (established with black-box tests):
7//!
8//! * there is no "insufficient symbol fonts" check;
9//! * `\mathchar`, `\Umathchar` and math characters given by `\mathchardef`
10//!   are accepted in horizontal mode, where they typeset the character
11//!   from the text font of the family (vertical mode starts a paragraph);
12//! * the `Missing delimiter` help text has no full stop after "proceed";
13//! * the `\Umath…` variants (`\Uradical`, `\Uroot`, `\Umathaccent`,
14//!   `\Uleft`, `\Uskewed`, `\Ustack`, `\Usuperscript`, …) are supported with
15//!   their keywords.
16
17use std::rc::Rc;
18
19use crate::Engine;
20use crate::build::PackSpec;
21use crate::cmd::Cmd;
22use crate::eqtb::{Loc, Val};
23use crate::input::TokKind;
24use crate::maincontrol::{IGNORE_DEPTH, Mode};
25use crate::nodes::{
26    DEFAULT_CODE, FenceData, FractionData, NodeData, NodeRef, accent_sub, fence_sub, id, noad_opt,
27    noad_sub, radical_sub,
28};
29use crate::primitives_gen::{assign_dimen as dp, assign_int as ip, assign_toks as tp};
30use crate::save::GroupCode;
31use crate::token::Token;
32
33/// A noad field that `scan_math` fills.
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub enum Field {
36    /// The nucleus.
37    Nucleus = 0,
38    /// The superscript.
39    Sup = 1,
40    /// The subscript.
41    Sub = 2,
42    /// The degree of a `\Uroot`.
43    Degree = 3,
44}
45
46impl Field {
47    fn from_i64(v: i64) -> Field {
48        match v & 3 {
49            0 => Field::Nucleus,
50            1 => Field::Sup,
51            2 => Field::Sub,
52            _ => Field::Degree,
53        }
54    }
55}
56
57fn field_ref(noad: NodeRef, f: Field) -> i64 {
58    (i64::from(noad.0) << 2) | f as i64
59}
60
61fn field_from_ref(v: i64) -> (NodeRef, Field) {
62    (NodeRef((v >> 2) as u32), Field::from_i64(v))
63}
64
65/// A math character: class (0–7, 8 for "math active"), family, character.
66#[derive(Clone, Copy, Debug, PartialEq, Eq)]
67pub struct MathChar {
68    /// The class.
69    pub class: u32,
70    /// The family.
71    pub fam: u32,
72    /// The character.
73    pub chr: u32,
74}
75
76impl MathChar {
77    /// A `math_given` value (see [`Engine::scan_math_given`]).
78    pub fn from_given(g: u32) -> MathChar {
79        if g & crate::codes::MATH_GIVEN_PACKED != 0 {
80            MathChar::from_packed(i64::from(g & !crate::codes::MATH_GIVEN_PACKED))
81        } else {
82            MathChar::from_classic(g as i32)
83        }
84    }
85
86    /// Decodes TeX's 15-bit form (`"8000` is math active).
87    pub fn from_classic(c: i32) -> MathChar {
88        if c >= 0x8000 {
89            return MathChar {
90                class: 8,
91                fam: 0,
92                chr: 0,
93            };
94        }
95        let c = c.max(0) as u32;
96        MathChar {
97            class: c >> 12,
98            fam: (c >> 8) & 0xF,
99            chr: c & 0xFF,
100        }
101    }
102
103    /// Decodes LuaTeX's packed form (`\Umathcharnum`, `\mathcode`).
104    pub fn from_packed(v: i64) -> MathChar {
105        if v == crate::codes::MATH_ACTIVE {
106            return MathChar {
107                class: 8,
108                fam: 0,
109                chr: 0,
110            };
111        }
112        MathChar {
113            class: crate::codes::math_class(v),
114            fam: crate::codes::math_fam(v),
115            chr: crate::codes::math_char(v),
116        }
117    }
118}
119
120/// The parts (small family, small character, large family, large
121/// character) of an internal delimiter code, or `None` for −1.
122pub fn delcode_parts(v: i64) -> Option<(u32, u32, u32, u32)> {
123    if v < 0 {
124        return None;
125    }
126    if v & (1 << 62) != 0 {
127        Some((
128            ((v >> 53) & 0xF) as u32,
129            ((v >> 32) & 0xFF) as u32,
130            ((v >> 21) & 0xF) as u32,
131            (v & 0xFF) as u32,
132        ))
133    } else {
134        Some((((v >> 21) & 0xFF) as u32, (v & 0x1F_FFFF) as u32, 0, 0))
135    }
136}
137
138/// The noad subtype for a math class.
139fn class_subtype(class: u32) -> u16 {
140    match class {
141        1 => noad_sub::OP_DISPLAY_LIMITS,
142        2 => noad_sub::BIN,
143        3 => noad_sub::REL,
144        4 => noad_sub::OPEN,
145        5 => noad_sub::CLOSE,
146        6 => noad_sub::PUNCT,
147        _ => noad_sub::ORD,
148    }
149}
150
151/// `cramped_style`.
152pub const fn cramped_style(s: i32) -> i32 {
153    2 * (s / 2) + 1
154}
155
156/// `sub_style`.
157pub const fn sub_style(s: i32) -> i32 {
158    2 * (s / 4) + 4 + 1
159}
160
161/// `sup_style`.
162pub const fn sup_style(s: i32) -> i32 {
163    2 * (s / 4) + 4 + (s % 2)
164}
165
166/// `num_style`.
167pub const fn num_style(s: i32) -> i32 {
168    s + 2 - 2 * (s / 6)
169}
170
171/// `denom_style`.
172pub const fn denom_style(s: i32) -> i32 {
173    2 * (s / 2) + 1 + 2 - 2 * (s / 6)
174}
175
176impl Engine {
177    // ----- node constructors ------------------------------------------
178
179    /// A new noad of the given subtype with empty fields (`new_noad`).
180    pub fn new_noad(&mut self, sub: u16) -> NodeRef {
181        self.new_node(id::NOAD, sub, NodeData::Noad(Box::default()))
182    }
183
184    /// A new `math_char` kernel.
185    pub fn new_math_char(&mut self, fam: u32, chr: u32) -> NodeRef {
186        self.new_node(id::MATH_CHAR, 0, NodeData::MathChar { fam, chr })
187    }
188
189    /// A new `sub_box` or `sub_mlist` kernel.
190    pub fn new_sub_list(&mut self, mlist: bool, head: NodeRef) -> NodeRef {
191        self.new_node(
192            if mlist { id::SUB_MLIST } else { id::SUB_BOX },
193            0,
194            NodeData::SubList { head },
195        )
196    }
197
198    /// A new delimiter node.
199    pub fn new_delim(&mut self, parts: (u32, u32, u32, u32)) -> NodeRef {
200        self.new_node(
201            id::DELIM,
202            0,
203            NodeData::Delim {
204                small_fam: parts.0,
205                small_char: parts.1,
206                large_fam: parts.2,
207                large_char: parts.3,
208            },
209        )
210    }
211
212    /// A new style node.
213    pub fn new_style(&mut self, s: u8) -> NodeRef {
214        self.new_node(id::STYLE, u16::from(s), NodeData::Style { style: s })
215    }
216
217    /// The kernel in field `f` of noad `p`.
218    pub fn noad_field(&self, p: NodeRef, f: Field) -> NodeRef {
219        match self.nodes.noad(p) {
220            Some(n) => match f {
221                Field::Nucleus => n.nucleus,
222                Field::Sup => n.sup,
223                Field::Sub => n.sub,
224                Field::Degree => n.degree,
225            },
226            None => NodeRef::NULL,
227        }
228    }
229
230    /// Replaces the kernel in field `f` of noad `p` (the old one is not
231    /// freed).
232    pub fn set_noad_field(&mut self, p: NodeRef, f: Field, k: NodeRef) {
233        if let Some(n) = self.nodes.noad_mut(p) {
234            match f {
235                Field::Nucleus => n.nucleus = k,
236                Field::Sup => n.sup = k,
237                Field::Sub => n.sub = k,
238                Field::Degree => n.degree = k,
239            }
240        }
241    }
242
243    // ----- entering math mode -----------------------------------------
244
245    /// Enters a math level (`push_math`).
246    fn push_math(&mut self, c: GroupCode, style: i32) {
247        self.push_nest(Mode::Math);
248        self.cur_list_mut().incompleat_noad = NodeRef::NULL;
249        self.cur_list_mut().math_style = style;
250        self.new_save_level(c);
251        if c == GroupCode::MathShift {
252            self.begin_math_dirs();
253        }
254    }
255
256    /// `$` in horizontal mode (`init_math`).
257    pub fn init_math(&mut self) {
258        let shift_cs = self.cur_cmd == Cmd::MathShiftCs;
259        let display = if shift_cs {
260            // \Ustartmath (2) or \Ustartdisplaymath (0).
261            self.cur_chr == 0 && self.mode == Mode::Horizontal
262        } else {
263            self.get_token();
264            if self.cur_cmd == Cmd::MathShift && self.mode == Mode::Horizontal {
265                true
266            } else {
267                self.back_input();
268                false
269            }
270        };
271        if display {
272            self.go_into_display_math();
273        } else {
274            self.go_into_ordinary_math();
275        }
276    }
277
278    fn go_into_ordinary_math(&mut self) {
279        self.push_math(GroupCode::MathShift, 2);
280        self.eq_define(Loc::IntPar(ip::FAM), Val::Int(-1));
281        let t = self.eqtb.toks_param(tp::EVERYMATH).clone();
282        if !t.is_empty() {
283            self.begin_token_list(t, TokKind::EveryMathText);
284        }
285    }
286
287    /// Starts a display: breaks the paragraph so far into lines and
288    /// computes `\predisplaysize`, `\displaywidth` and `\displayindent`.
289    fn go_into_display_math(&mut self) {
290        let max_dimen = 0o7777777777;
291        let mut w = -max_dimen;
292        let (head, tail) = (self.cur_list().head, self.cur_list().tail);
293        let only_local_par = self.nodes.next(head) == tail
294            && matches!(self.nodes.data(tail), NodeData::LocalPar { .. });
295        if head == tail || only_local_par {
296            // `\noindent$$` or `$${ }$$`.
297            if only_local_par {
298                self.nodes.flush_list(tail);
299                self.nodes.set_next(head, NodeRef::NULL);
300            }
301            self.pop_nest();
302        } else {
303            let wp = self.eqtb.int(ip::DISPLAYWIDOWPENALTY);
304            self.line_break(wp);
305            w = self.pre_display_width();
306        }
307        // Now in vertical mode: the length and shift of the display lines.
308        let prev_graf = self.cur_list().prev_graf;
309        let (l, s) = self.display_line_size(prev_graf);
310        self.push_math(GroupCode::MathShift, 0);
311        self.mode = Mode::DisplayMath;
312        self.cur_list_mut().mode = Mode::DisplayMath;
313        self.eq_define(Loc::IntPar(ip::FAM), Val::Int(-1));
314        self.eq_define(Loc::DimenPar(dp::PREDISPLAYSIZE), Val::Int(i64::from(w)));
315        self.eq_define(Loc::DimenPar(dp::DISPLAYWIDTH), Val::Int(i64::from(l)));
316        self.eq_define(Loc::DimenPar(dp::DISPLAYINDENT), Val::Int(i64::from(s)));
317        let t = self.eqtb.toks_param(tp::EVERYDISPLAY).clone();
318        if !t.is_empty() {
319            self.begin_token_list(t, TokKind::EveryDisplayText);
320        }
321        if self.nest.len() == 2 {
322            self.build_page_from("before_display");
323        }
324    }
325
326    /// The natural width by which the last line of the paragraph before a
327    /// display extends to the right, plus two quads (`\predisplaygapfactor`
328    /// thousandths of a quad); `max_dimen` if it depends on glue setting.
329    fn pre_display_width(&mut self) -> i32 {
330        let max_dimen = 0o7777777777;
331        let jb = self.ts.just_box;
332        if jb.is_null() {
333            return -max_dimen;
334        }
335        let Some(b) = self.nodes.box_data(jb).cloned() else {
336            return -max_dimen;
337        };
338        let f = self.eqtb.cur_font.val;
339        let gap = self.eqtb.int(ip::PREDISPLAYGAPFACTOR);
340        let q = self.quad(f);
341        // Observed: (quad div 1000) * \predisplaygapfactor.
342        let extra = (q / 1000).wrapping_mul(gap);
343        let mut v = b.shift + extra;
344        let mut w = -max_dimen;
345        let mut p = b.head;
346        while !p.is_null() {
347            let (d, visible) = match self.nodes.data(p) {
348                NodeData::Glyph(_) => (self.glyph_dimensions(p).0, true),
349                NodeData::Box(bx) => (bx.width, true),
350                NodeData::Rule { width, .. } => (*width, true),
351                NodeData::Kern { kern, .. } => (*kern, false),
352                NodeData::MarginKern { width, .. } => (*width, false),
353                NodeData::Math { .. } => (self.nodes.width(p), false),
354                NodeData::Glue { spec, .. } => {
355                    let spec = *spec;
356                    if b.glue_sign == 1 {
357                        if b.glue_order == spec.stretch_order && spec.stretch != 0 {
358                            v = max_dimen;
359                        }
360                    } else if b.glue_sign == 2
361                        && b.glue_order == spec.shrink_order
362                        && spec.shrink != 0
363                    {
364                        v = max_dimen;
365                    }
366                    (
367                        spec.width,
368                        self.nodes.subtype(p) >= crate::nodes::glue_sub::A_LEADERS,
369                    )
370                }
371                // Observed: the replacement text of a discretionary counts
372                // up to its last glyph, box or rule (an explicit hyphen is
373                // one in LuaTeX).
374                NodeData::Disc { replace, .. } => {
375                    let mut r = *replace;
376                    let (mut u, mut last) = (0, None);
377                    while !r.is_null() {
378                        let (d, vis) = match self.nodes.data(r) {
379                            NodeData::Glyph(_) => (self.glyph_dimensions(r).0, true),
380                            NodeData::Box(bx) => (bx.width, true),
381                            NodeData::Rule { width, .. } => (*width, true),
382                            NodeData::Kern { kern, .. } => (*kern, false),
383                            _ => (0, false),
384                        };
385                        u += d;
386                        if vis {
387                            last = Some(u);
388                        }
389                        r = self.nodes.next(r);
390                    }
391                    last.map_or((0, false), |u| (u, true))
392                }
393                _ => (0, false),
394            };
395            if visible {
396                if v < max_dimen {
397                    v += d;
398                    w = v;
399                } else {
400                    w = max_dimen;
401                    break;
402                }
403            } else if v < max_dimen {
404                v += d;
405            }
406            p = self.nodes.next(p);
407        }
408        w
409    }
410
411    /// The length and shift of line `prev_graf + 2` (§1149).
412    fn display_line_size(&self, prev_graf: i32) -> (i32, i32) {
413        let hsize = self.eqtb.dimen(dp::HSIZE);
414        match &self.eqtb.par_shape.val {
415            None => {
416                let hi = self.eqtb.dimen(dp::HANGINDENT);
417                let ha = self.eqtb.int(ip::HANGAFTER);
418                if hi != 0 && ((ha >= 0 && prev_graf + 2 > ha) || (prev_graf + 1 < -ha)) {
419                    (hsize - hi.abs(), if hi > 0 { hi } else { 0 })
420                } else {
421                    (hsize, 0)
422                }
423            }
424            Some(shape) => {
425                let n = (shape.len() / 2) as i32;
426                let i = if prev_graf + 2 >= n { n } else { prev_graf + 2 };
427                let k = 2 * (i.max(1) as usize - 1);
428                let indent = shape.get(k).copied().unwrap_or(0);
429                let len = shape.get(k + 1).copied().unwrap_or(hsize);
430                (len, indent)
431            }
432        }
433    }
434
435    // ----- the main switch --------------------------------------------
436
437    /// Commands in math mode (the `mmode+…` cases of `main_control`).
438    pub fn math_command(&mut self) {
439        let cmd = self.cur_cmd;
440        match cmd {
441            Cmd::Letter | Cmd::OtherChar | Cmd::CharGiven => {
442                let code = self.eqtb.math.get(self.cur_chr);
443                self.set_math_char(MathChar::from_packed(code));
444            }
445            Cmd::CharNum => {
446                self.scan_char_num();
447                self.cur_chr = self.cur_val as u32;
448                let code = self.eqtb.math.get(self.cur_chr);
449                self.set_math_char(MathChar::from_packed(code));
450            }
451            Cmd::MathCharNum => {
452                let mc = self.scan_math_char_num();
453                self.set_math_char(mc);
454            }
455            Cmd::MathGiven => {
456                let mc = MathChar::from_given(self.cur_chr);
457                self.set_math_char(mc);
458            }
459            Cmd::XmathGiven => {
460                let mc = MathChar::from_packed(i64::from(self.cur_chr));
461                self.set_math_char(mc);
462            }
463            Cmd::DelimNum => {
464                let mc = self.scan_delim_num_char();
465                self.set_math_char(mc);
466            }
467            Cmd::LeftBrace => {
468                let n = self.new_noad(noad_sub::ORD);
469                self.tail_append(n);
470                self.back_input();
471                let st = self.cur_list().math_style;
472                self.scan_math(n, Field::Nucleus, st);
473            }
474            Cmd::MathComp => {
475                let sub = self.cur_chr as u16;
476                let n = self.new_noad(sub);
477                self.tail_append(n);
478                let st = self.cur_list().math_style;
479                let st = if sub == noad_sub::OVER || sub == noad_sub::UNDER {
480                    if sub == noad_sub::OVER {
481                        cramped_style(st)
482                    } else {
483                        st
484                    }
485                } else {
486                    st
487                };
488                self.scan_math(n, Field::Nucleus, st);
489            }
490            Cmd::LimitSwitch => self.math_limit_switch(),
491            Cmd::SupMark | Cmd::SubMark => {
492                let sup = cmd == Cmd::SupMark;
493                self.sub_sup(sup, false);
494            }
495            Cmd::SuperSubScript => {
496                let sup = self.cur_chr == Cmd::SupMark as u32;
497                self.sub_sup(sup, false);
498            }
499            Cmd::NoSuperSubScript => {
500                let sup = self.cur_chr == Cmd::SupMark as u32;
501                self.sub_sup(sup, true);
502            }
503            Cmd::Radical => self.math_radical(),
504            Cmd::Accent | Cmd::MathAccent => self.math_ac(),
505            Cmd::Vcenter => {
506                self.scan_spec(GroupCode::Vcenter, None);
507                self.normal_paragraph();
508                self.push_nest(Mode::InternalVertical);
509                self.cur_list_mut().prev_depth = IGNORE_DEPTH;
510                let t = self.eqtb.toks_param(tp::EVERYVBOX).clone();
511                if !t.is_empty() {
512                    self.begin_token_list(t, TokKind::EveryVboxText);
513                }
514            }
515            Cmd::MathStyle => {
516                let s = self.cur_chr.min(7) as u8;
517                let n = self.new_style(s);
518                self.tail_append(n);
519                self.cur_list_mut().math_style = i32::from(s);
520            }
521            Cmd::NonScript => {
522                let g = self.new_glue_node(
523                    crate::glue::Glue::ZERO,
524                    crate::nodes::glue_sub::COND_MATH_GLUE,
525                );
526                self.tail_append(g);
527            }
528            Cmd::MathChoice => {
529                if self.cur_chr == 0 {
530                    self.append_choices();
531                } else {
532                    // \Ustack: a subformula in numerator style.
533                    let n = self.new_noad(noad_sub::ORD);
534                    self.tail_append(n);
535                    let st = num_style(self.cur_list().math_style);
536                    self.scan_left_brace();
537                    let sub = self.new_sub_list(true, NodeRef::NULL);
538                    self.set_noad_field(n, Field::Nucleus, sub);
539                    self.push_saved(field_ref(n, Field::Nucleus));
540                    self.push_math(GroupCode::Math, st);
541                }
542            }
543            Cmd::Above => self.math_fraction(),
544            Cmd::MathShift => {
545                if self.save.cur_group == GroupCode::MathShift {
546                    self.after_math();
547                } else {
548                    self.off_save();
549                }
550            }
551            Cmd::MathShiftCs => {
552                // \Ustopmath (3) and \Ustopdisplaymath (1) end the
553                // formula; the start commands are not allowed here.
554                let c = self.cur_chr;
555                let ok = (c == 3 && self.mode == Mode::Math)
556                    || (c == 1 && self.mode == Mode::DisplayMath);
557                if (c == 1 || c == 3) && self.save.cur_group == GroupCode::MathShift && ok {
558                    self.after_math();
559                } else if c == 1 || c == 3 {
560                    if self.save.cur_group == GroupCode::MathShift {
561                        self.print_err("Invalid math code");
562                        self.help(&["I'm going to ignore this math shift command."]);
563                        self.error();
564                    } else {
565                        self.off_save();
566                    }
567                } else {
568                    self.report_illegal_case();
569                }
570            }
571            Cmd::EqNo => {
572                if self.privileged() {
573                    if self.save.cur_group == GroupCode::MathShift {
574                        self.start_eq_no();
575                    } else {
576                        self.off_save();
577                    }
578                }
579            }
580            Cmd::LeftRight => self.math_left_right(),
581            Cmd::Endv
582            | Cmd::ParEnd
583            | Cmd::Stop
584            | Cmd::Vskip
585            | Cmd::UnVbox
586            | Cmd::Valign
587            | Cmd::Hrule => self.insert_dollar_sign(),
588            Cmd::Halign => {
589                if self.privileged() {
590                    if self.save.cur_group == GroupCode::MathShift {
591                        self.init_align();
592                    } else {
593                        self.off_save();
594                    }
595                }
596            }
597            _ => {
598                let (c, r) = (self.cur_cmd, self.cur_chr);
599                let s = self.print_to_string(|e| e.print_cmd_chr(c, r));
600                self.not_implemented(&String::from_utf8_lossy(&s));
601            }
602        }
603    }
604
605    /// `\mathchar`, `\Umathchar` and `\Umathcharnum`.
606    fn scan_math_char_num(&mut self) -> MathChar {
607        match self.cur_chr {
608            0 => {
609                let g = self.scan_math_given();
610                MathChar::from_given(g)
611            }
612            1 => {
613                let v = self.scan_umath_triple();
614                MathChar::from_packed(v)
615            }
616            _ => {
617                self.scan_int();
618                MathChar::from_packed(i64::from(self.cur_val))
619            }
620        }
621    }
622
623    /// `\delimiter` (its 15-bit part) and `\Udelimiter` as a math character.
624    fn scan_delim_num_char(&mut self) -> MathChar {
625        if self.cur_chr == 0 {
626            self.scan_twenty_seven_bit_int();
627            MathChar::from_classic(self.cur_val / 0o10000)
628        } else {
629            let v = self.scan_umath_triple();
630            MathChar::from_packed(v)
631        }
632    }
633
634    /// Whether `\fam` is a valid family (`fam_in_range`).
635    fn fam_in_range(&self) -> Option<u32> {
636        let f = self.eqtb.int(ip::FAM);
637        if (0..crate::eqtb::NUM_FAMILIES as i32).contains(&f) {
638            Some(f as u32)
639        } else {
640            None
641        }
642    }
643
644    /// The family of a math character, taking class 7 and `\fam` into
645    /// account.
646    fn effective_fam(&self, mc: MathChar) -> u32 {
647        if mc.class == 7
648            && let Some(f) = self.fam_in_range()
649        {
650            return f;
651        }
652        let vf = self.eqtb.int(ip::VARIABLEFAM);
653        if vf >= 0
654            && mc.fam == vf as u32
655            && let Some(f) = self.fam_in_range()
656        {
657            return f;
658        }
659        mc.fam
660    }
661
662    /// Treats `cur_chr` as an active character (§1152).
663    fn treat_as_active(&mut self) {
664        let cs = self.hash.active(self.cur_chr);
665        self.cur_cs = cs;
666        let m = self.eqtb.meaning(cs).clone();
667        self.cur_cmd = m.cmd;
668        self.cur_chr = m.chr;
669        self.x_token();
670        self.back_input();
671    }
672
673    /// Appends a noad for a math character (`set_math_char`).
674    fn set_math_char(&mut self, mc: MathChar) {
675        if mc.class >= 8 {
676            self.treat_as_active();
677            return;
678        }
679        let fam = self.effective_fam(mc);
680        let k = self.new_math_char(fam, mc.chr);
681        let sub = class_subtype(mc.class);
682        let p = self.new_noad(sub);
683        self.set_noad_field(p, Field::Nucleus, k);
684        self.tail_append(p);
685    }
686
687    /// Fills field `f` of noad `p` with the next math character or
688    /// subformula (`scan_math`); `style` is the style of a subformula.
689    /// Returns true if a subformula group was opened.
690    fn scan_math(&mut self, p: NodeRef, f: Field, style: i32) -> bool {
691        self.scan_math_flag(p, f, style, 0)
692    }
693
694    /// [`scan_math`](Self::scan_math) with extra bits in the saved field
695    /// reference (bit 62: scan the nucleus when the group ends).
696    fn scan_math_flag(&mut self, p: NodeRef, f: Field, style: i32, flag: i64) -> bool {
697        self.get_nonblank_nonrelax_noncall();
698        let mc = loop {
699            match self.cur_cmd {
700                Cmd::Letter | Cmd::OtherChar | Cmd::CharGiven => {
701                    let code = self.eqtb.math.get(self.cur_chr);
702                    let mc = MathChar::from_packed(code);
703                    if mc.class == 8 {
704                        self.treat_as_active();
705                        self.get_nonblank_nonrelax_noncall();
706                        continue;
707                    }
708                    break mc;
709                }
710                Cmd::CharNum => {
711                    self.scan_char_num();
712                    self.cur_chr = self.cur_val as u32;
713                    self.cur_cmd = Cmd::CharGiven;
714                }
715                Cmd::MathCharNum => break self.scan_math_char_num(),
716                Cmd::MathGiven => break MathChar::from_given(self.cur_chr),
717                Cmd::XmathGiven => break MathChar::from_packed(i64::from(self.cur_chr)),
718                Cmd::DelimNum => break self.scan_delim_num_char(),
719                _ => {
720                    // A subformula enclosed in braces.
721                    self.back_input();
722                    self.scan_left_brace();
723                    let sub = self.new_sub_list(true, NodeRef::NULL);
724                    let old = self.noad_field(p, f);
725                    self.nodes.flush_list(old);
726                    self.set_noad_field(p, f, sub);
727                    self.push_saved(field_ref(p, f) | flag);
728                    self.push_math(GroupCode::Math, style);
729                    return true;
730                }
731            }
732        };
733        self.finish_scan_math(p, f, mc);
734        false
735    }
736
737    fn finish_scan_math(&mut self, p: NodeRef, f: Field, mc: MathChar) {
738        let fam = if mc.class == 8 {
739            0
740        } else {
741            self.effective_fam(mc)
742        };
743        let k = self.new_math_char(fam, mc.chr);
744        let old = self.noad_field(p, f);
745        self.nodes.flush_list(old);
746        self.set_noad_field(p, f, k);
747    }
748
749    /// `\limits`, `\nolimits`, `\displaylimits` (`math_limit_switch`).
750    fn math_limit_switch(&mut self) {
751        let (head, tail) = (self.cur_list().head, self.cur_list().tail);
752        if head != tail
753            && self.nodes.id(tail) == id::NOAD
754            && matches!(
755                self.nodes.subtype(tail),
756                noad_sub::OP_DISPLAY_LIMITS | noad_sub::OP_LIMITS | noad_sub::OP_NO_LIMITS
757            )
758        {
759            self.nodes.set_subtype(tail, self.cur_chr as u16);
760            return;
761        }
762        self.print_err("Limit controls must follow a math operator");
763        self.help(&["I'm ignoring this misplaced \\limits or \\nolimits command."]);
764        self.error();
765    }
766
767    /// Scans a delimiter (`scan_delimiter`); `radical` selects TeX's
768    /// 27-bit form, `fam_char` LuaTeX's `<fam> <char>` form. Returns a new
769    /// delimiter node.
770    fn scan_delimiter(&mut self, radical: bool, fam_char: bool) -> NodeRef {
771        let parts: Option<(u32, u32, u32, u32)> = if fam_char {
772            self.scan_math_family();
773            let fam = self.cur_val as u32;
774            self.scan_char_num();
775            Some((fam, self.cur_val as u32, 0, 0))
776        } else if radical {
777            self.scan_twenty_seven_bit_int();
778            classic_delimiter(self.cur_val)
779        } else {
780            self.get_nonblank_nonrelax_noncall();
781            match self.cur_cmd {
782                Cmd::Letter | Cmd::OtherChar => delcode_parts(self.eqtb.del.get(self.cur_chr)),
783                Cmd::DelimNum => {
784                    if self.cur_chr == 0 {
785                        self.scan_twenty_seven_bit_int();
786                        classic_delimiter(self.cur_val)
787                    } else {
788                        let v = self.scan_umath_triple();
789                        let mc = MathChar::from_packed(v);
790                        Some((mc.fam, mc.chr, 0, 0))
791                    }
792                }
793                _ => None,
794            }
795        };
796        let parts = match parts {
797            Some(p) => p,
798            None => {
799                self.print_err("Missing delimiter (. inserted)");
800                self.help(&[
801                    "I was expecting to see something like `(' or `\\{' or",
802                    "`\\}' here. If you typed, e.g., `{' instead of `\\{', you",
803                    "should probably delete the `{' by typing `1' now, so that",
804                    "braces don't get unbalanced. Otherwise just proceed",
805                    "Acceptable delimiters are characters whose \\delcode is",
806                    "nonnegative, or you can use `\\delimiter <delimiter code>'.",
807                ]);
808                self.back_error();
809                (0, 0, 0, 0)
810            }
811        };
812        self.new_delim(parts)
813    }
814
815    /// `\radical` and LuaTeX's radical-like commands (`math_radical`).
816    fn math_radical(&mut self) {
817        let c = self.cur_chr as u16;
818        let p = self.new_node(id::RADICAL, c, NodeData::Noad(Box::default()));
819        self.tail_append(p);
820        let mut opts = 0u32;
821        let mut width = 0i32;
822        if c == radical_sub::UHEXTENSIBLE || c >= radical_sub::UUNDERDELIMITER {
823            loop {
824                if self.scan_keyword("width") {
825                    self.scan_normal_dimen();
826                    width = self.cur_val;
827                } else if self.scan_keyword("left") {
828                    opts |= noad_opt::LEFT;
829                } else if self.scan_keyword("middle") {
830                    opts |= noad_opt::MIDDLE;
831                } else if self.scan_keyword("right") {
832                    opts |= noad_opt::RIGHT;
833                } else {
834                    break;
835                }
836            }
837        }
838        let d = self.scan_delimiter(c == radical_sub::RADICAL, c != radical_sub::RADICAL);
839        if let Some(n) = self.nodes.noad_mut(p) {
840            n.left = d;
841            n.options = opts;
842            n.width = width;
843        }
844        let st = self.cur_list().math_style;
845        match c {
846            radical_sub::UHEXTENSIBLE => {}
847            radical_sub::UROOT => {
848                // The degree first, then the radicand. The degree is a
849                // subformula in scriptscript style.
850                // The radicand follows when the degree group ends.
851                if !self.scan_math_flag(p, Field::Degree, 6, 1 << 62) {
852                    self.scan_math(p, Field::Nucleus, cramped_style(st));
853                }
854            }
855            radical_sub::UUNDERDELIMITER => {
856                self.scan_math(p, Field::Nucleus, sub_style(st));
857            }
858            radical_sub::UOVERDELIMITER => {
859                self.scan_math(p, Field::Nucleus, sup_style(st));
860            }
861            radical_sub::UDELIMITERUNDER => {
862                self.scan_math(p, Field::Nucleus, st);
863            }
864            _ => {
865                self.scan_math(p, Field::Nucleus, cramped_style(st));
866            }
867        }
868    }
869
870    /// Scans `[fixed] <class> <fam> <char>` for `\Umathaccent`.
871    fn scan_accent_char(&mut self, umath: bool) -> (NodeRef, bool) {
872        let fixed = umath && self.scan_keyword("fixed");
873        let mc = if umath {
874            let v = self.scan_umath_triple();
875            MathChar::from_packed(v)
876        } else {
877            let g = self.scan_math_given();
878            MathChar::from_given(g)
879        };
880        let fam = if mc.class == 8 {
881            0
882        } else {
883            self.effective_fam(mc)
884        };
885        (self.new_math_char(fam, mc.chr), fixed)
886    }
887
888    /// `\mathaccent` and `\Umathaccent` (`math_ac`).
889    fn math_ac(&mut self) {
890        if self.cur_cmd == Cmd::Accent {
891            self.print_err("Please use ");
892            self.print_esc_str("mathaccent");
893            self.printer.print_str(" for accents in math mode");
894            self.help(&[
895                "I'm changing \\accent to \\mathaccent here; wish me luck.",
896                "(Accents are not the same in formulas as they are in text.)",
897            ]);
898            self.error();
899        }
900        let umath = self.cur_cmd == Cmd::MathAccent && self.cur_chr == 1;
901        let p = self.new_node(
902            id::ACCENT,
903            accent_sub::BOTH_FLEXIBLE,
904            NodeData::Noad(Box::default()),
905        );
906        self.tail_append(p);
907        let mut sub = accent_sub::BOTH_FLEXIBLE;
908        let (mut top, mut bot) = (NodeRef::NULL, NodeRef::NULL);
909        let fraction = 0;
910        let mut overlay = false;
911        if umath {
912            let (want_top, want_bot) = if self.scan_keyword("bottom") {
913                (false, true)
914            } else if self.scan_keyword("both") {
915                (true, true)
916            } else if self.scan_keyword("overlay") {
917                overlay = true;
918                (true, false)
919            } else {
920                let _ = self.scan_keyword("top");
921                (true, false)
922            };
923            if want_top {
924                let (k, fixed) = self.scan_accent_char(true);
925                top = k;
926                if fixed {
927                    sub |= accent_sub::FIXED_TOP;
928                }
929            }
930            if want_bot {
931                let (k, fixed) = self.scan_accent_char(true);
932                bot = k;
933                if fixed {
934                    sub |= accent_sub::FIXED_BOTTOM;
935                }
936            }
937        } else {
938            top = self.scan_accent_char(false).0;
939        }
940        self.nodes.set_subtype(p, sub);
941        if let Some(n) = self.nodes.noad_mut(p) {
942            n.accent = top;
943            n.bot_accent = bot;
944            n.fraction = fraction;
945            if overlay {
946                n.options |= noad_opt::OVERLAY;
947            }
948        }
949        let st = cramped_style(self.cur_list().math_style);
950        self.scan_math(p, Field::Nucleus, st);
951    }
952
953    /// `^` and `_` (`sub_sup`); `no_style` is set for `\Unosuperscript`
954    /// and `\Unosubscript`.
955    fn sub_sup(&mut self, sup: bool, no_style: bool) {
956        let field = if sup { Field::Sup } else { Field::Sub };
957        let (head, tail) = (self.cur_list().head, self.cur_list().tail);
958        let mut target = NodeRef::NULL;
959        let mut t_nonempty = false;
960        if tail != head && self.nodes.scripts_allowed(tail) {
961            target = tail;
962            t_nonempty = !self.noad_field(tail, field).is_null();
963        }
964        if target.is_null() || t_nonempty {
965            // Observed: the new noad's nucleus is an empty sub-list.
966            let n = self.new_noad(noad_sub::ORD);
967            let k = self.new_sub_list(true, NodeRef::NULL);
968            self.set_noad_field(n, Field::Nucleus, k);
969            self.tail_append(n);
970            target = n;
971            if t_nonempty {
972                if sup {
973                    self.print_err("Double superscript");
974                    self.help(&["I treat `x^1^2' essentially like `x^1{}^2'."]);
975                } else {
976                    self.print_err("Double subscript");
977                    self.help(&["I treat `x_1_2' essentially like `x_1{}_2'."]);
978                }
979                self.error();
980            }
981        }
982        let st = self.cur_list().math_style;
983        let style = if no_style {
984            if let Some(n) = self.nodes.noad_mut(target) {
985                n.options |= if sup {
986                    noad_opt::NO_SUPER_SCRIPT
987                } else {
988                    noad_opt::NO_SUB_SCRIPT
989                };
990            }
991            st
992        } else if sup {
993            sup_style(st)
994        } else {
995            sub_style(st)
996        };
997        self.scan_math(target, field, style);
998    }
999
1000    /// `\over`, `\atop`, `\above` and their variants (`math_fraction`).
1001    fn math_fraction(&mut self) {
1002        let c = self.cur_chr;
1003        // LuaTeX codes: above 0, over 1, atop 2, skewed 3; +4 with delims.
1004        let delimited = c >= 4;
1005        let kind = c % 4;
1006        if !self.cur_list().incompleat_noad.is_null() {
1007            if delimited {
1008                let a = self.scan_delimiter(false, false);
1009                let b = self.scan_delimiter(false, false);
1010                self.nodes.flush_list(a);
1011                self.nodes.flush_list(b);
1012            }
1013            if kind == 3 {
1014                let m = self.scan_delimiter(false, false);
1015                self.nodes.flush_list(m);
1016                self.scan_fraction_options();
1017            } else if kind == 0 {
1018                let _ = self.scan_keyword("exact");
1019                self.scan_normal_dimen();
1020            }
1021            self.print_err("Ambiguous; you need another { and }");
1022            self.help(&[
1023                "I'm ignoring this fraction specification, since I don't",
1024                "know whether a construction like `x \\over y \\over z'",
1025                "means `{x \\over y} \\over z' or `x \\over {y \\over z}'.",
1026            ]);
1027            self.error();
1028            return;
1029        }
1030        let head = self.cur_list().head;
1031        let first = self.nodes.next(head);
1032        let num = self.new_sub_list(true, first);
1033        let mut fr = FractionData {
1034            thickness: DEFAULT_CODE,
1035            num,
1036            denom: NodeRef::NULL,
1037            left: NodeRef::NULL,
1038            right: NodeRef::NULL,
1039            middle: NodeRef::NULL,
1040            options: 0,
1041            new_hlist: NodeRef::NULL,
1042        };
1043        self.nodes.set_next(head, NodeRef::NULL);
1044        self.cur_list_mut().tail = head;
1045        if delimited {
1046            fr.left = self.scan_delimiter(false, false);
1047            fr.right = self.scan_delimiter(false, false);
1048        }
1049        match kind {
1050            0 => {
1051                if self.scan_keyword("exact") {
1052                    fr.options |= noad_opt::EXACT;
1053                }
1054                self.scan_normal_dimen();
1055                fr.thickness = self.cur_val;
1056            }
1057            1 => fr.thickness = DEFAULT_CODE,
1058            2 => fr.thickness = 0,
1059            _ => {
1060                fr.middle = self.scan_delimiter(false, false);
1061                fr.options |= self.scan_fraction_options();
1062                fr.thickness = 0;
1063            }
1064        }
1065        let n = self.new_node(id::FRACTION, 0, NodeData::Fraction(Box::new(fr)));
1066        self.cur_list_mut().incompleat_noad = n;
1067        // The denominator is read in the cramped style (observed with
1068        // \mathstyle; in \Ustack this gives the denominator style).
1069        let st = cramped_style(self.cur_list().math_style);
1070        self.cur_list_mut().math_style = st;
1071    }
1072
1073    /// The `noaxis`/`exact` options of `\Uskewed`.
1074    fn scan_fraction_options(&mut self) -> u32 {
1075        let mut o = 0;
1076        loop {
1077            if self.scan_keyword("noaxis") {
1078                o |= noad_opt::NO_AXIS;
1079            } else if self.scan_keyword("exact") {
1080                o |= noad_opt::EXACT;
1081            } else {
1082                return o;
1083            }
1084        }
1085    }
1086
1087    /// Finishes the current mlist and pops the nest (`fin_mlist`); `p`, if
1088    /// not null, is a right (or middle) fence ending the list.
1089    fn fin_mlist(&mut self, p: NodeRef) -> NodeRef {
1090        let inc = self.cur_list().incompleat_noad;
1091        let head = self.cur_list().head;
1092        let q;
1093        if !inc.is_null() {
1094            let first = self.nodes.next(head);
1095            let denom = self.new_sub_list(true, first);
1096            if let NodeData::Fraction(f) = self.nodes.data_mut(inc) {
1097                f.denom = denom;
1098            }
1099            if p.is_null() {
1100                q = inc;
1101            } else {
1102                // A \left…\over…\right: the left fence (and any middle
1103                // fences up to delim_ptr) stays outside the fraction.
1104                let num = match self.nodes.data(inc) {
1105                    NodeData::Fraction(f) => f.num,
1106                    _ => NodeRef::NULL,
1107                };
1108                let num_list = self.nodes.list_ptr_kernel(num);
1109                q = num_list;
1110                let dp = self.cur_list().delim_ptr;
1111                if num_list.is_null() || self.nodes.id(num_list) != id::FENCE || dp.is_null() {
1112                    self.confusion("right");
1113                }
1114                let after = self.nodes.next(dp);
1115                self.nodes.set_kernel_list(num, after);
1116                if !after.is_null() {
1117                    self.nodes.set_prev(after, NodeRef::NULL);
1118                }
1119                self.nodes.link(dp, inc);
1120                self.nodes.link(inc, p);
1121            }
1122        } else {
1123            let tail = self.cur_list().tail;
1124            self.nodes.link(tail, p);
1125            q = self.nodes.next(head);
1126        }
1127        self.nodes.set_next(head, NodeRef::NULL);
1128        if !q.is_null() {
1129            self.nodes.set_prev(q, NodeRef::NULL);
1130        }
1131        self.pop_nest();
1132        q
1133    }
1134
1135    /// Right braces ending math groups, `\mathchoice` groups and
1136    /// `\vcenter` (cases of `handle_right_brace`).
1137    pub fn math_right_brace(&mut self) {
1138        match self.save.cur_group {
1139            GroupCode::Math => {
1140                self.unsave();
1141                let r = self.pop_saved();
1142                let root_follows = r & (1 << 62) != 0;
1143                let (noad, f) = field_from_ref(r & !(1 << 62));
1144                let p = self.fin_mlist(NodeRef::NULL);
1145                let k = self.noad_field(noad, f);
1146                self.nodes.set_kernel_list(k, p);
1147                if !p.is_null() && self.nodes.next(p).is_null() {
1148                    let pid = self.nodes.id(p);
1149                    // Observed: \mathflattenmode says which single noads
1150                    // are flattened (1 ord, 2 bin, 4 rel, 8 punct,
1151                    // 16 inner).
1152                    let flatten = self.eqtb.int(ip::MATHFLATTENMODE);
1153                    let bit = match self.nodes.subtype(p) {
1154                        noad_sub::ORD => 1,
1155                        noad_sub::BIN => 2,
1156                        noad_sub::REL => 4,
1157                        noad_sub::PUNCT => 8,
1158                        noad_sub::INNER => 16,
1159                        _ => 0,
1160                    };
1161                    if pid == id::NOAD && flatten & bit != 0 {
1162                        let (sub, sup) = (
1163                            self.noad_field(p, Field::Sub),
1164                            self.noad_field(p, Field::Sup),
1165                        );
1166                        if sub.is_null() && sup.is_null() {
1167                            // {x}: the field becomes x's nucleus.
1168                            let nuc = self.noad_field(p, Field::Nucleus);
1169                            self.set_noad_field(p, Field::Nucleus, NodeRef::NULL);
1170                            self.nodes.flush_list(p);
1171                            self.nodes.set_kernel_list(k, NodeRef::NULL);
1172                            self.nodes.flush_list(k);
1173                            self.set_noad_field(noad, f, nuc);
1174                        }
1175                    } else if pid == id::ACCENT
1176                        && f == Field::Nucleus
1177                        && noad == self.cur_list().tail
1178                        && self.nodes.id(noad) == id::NOAD
1179                        && self.nodes.subtype(noad) == noad_sub::ORD
1180                    {
1181                        // Replace the tail of the list by the accent noad.
1182                        let head = self.cur_list().head;
1183                        let mut q = head;
1184                        while self.nodes.next(q) != noad {
1185                            q = self.nodes.next(q);
1186                        }
1187                        self.nodes.set_kernel_list(k, NodeRef::NULL);
1188                        self.nodes.link(q, p);
1189                        self.nodes.set_next(noad, NodeRef::NULL);
1190                        self.nodes.flush_list(noad);
1191                        self.cur_list_mut().tail = p;
1192                    }
1193                }
1194                if root_follows {
1195                    let st = cramped_style(self.cur_list().math_style);
1196                    self.scan_math(noad, Field::Nucleus, st);
1197                }
1198            }
1199            GroupCode::MathChoice => self.build_choices(),
1200            GroupCode::Vcenter => {
1201                if self.par_token_before_group_end(1) {
1202                    return;
1203                }
1204                self.end_graf();
1205                self.unsave();
1206                let (spec, _dir) = self.pop_spec_pub();
1207                let head = self.cur_list().head;
1208                let first = self.nodes.next(head);
1209                self.nodes.set_next(head, NodeRef::NULL);
1210                let first = self.filter_list("vpack_filter", first, "vcenter");
1211                let d = self.eqtb.dimen(dp::BOXMAXDEPTH);
1212                let b = self.vpackage(first, spec, d);
1213                self.pop_nest();
1214                let n = self.new_noad(noad_sub::VCENTER);
1215                let k = self.new_sub_list(false, b);
1216                self.set_noad_field(n, Field::Nucleus, k);
1217                self.tail_append(n);
1218            }
1219            _ => self.confusion("rightbrace"),
1220        }
1221    }
1222
1223    /// `\mathchoice` (`append_choices`).
1224    fn append_choices(&mut self) {
1225        let c = self.new_node(
1226            id::CHOICE,
1227            0,
1228            NodeData::Choice(Box::new([NodeRef::NULL; 4])),
1229        );
1230        self.tail_append(c);
1231        self.push_saved(0);
1232        self.push_math(GroupCode::MathChoice, 0);
1233        self.scan_left_brace();
1234    }
1235
1236    /// The end of one of the four `\mathchoice` lists (`build_choices`).
1237    fn build_choices(&mut self) {
1238        self.unsave();
1239        let p = self.fin_mlist(NodeRef::NULL);
1240        let k = self.pop_saved();
1241        let tail = self.cur_list().tail;
1242        if let NodeData::Choice(c) = self.nodes.data_mut(tail) {
1243            c[(k as usize).min(3)] = p;
1244        }
1245        if k >= 3 {
1246            return;
1247        }
1248        self.push_saved(k + 1);
1249        self.push_math(GroupCode::MathChoice, 2 * (k as i32 + 1));
1250        self.scan_left_brace();
1251    }
1252
1253    /// `\left`, `\middle`, `\right` and their `\U…` forms
1254    /// (`math_left_right`).
1255    fn math_left_right(&mut self) {
1256        let c = self.cur_chr;
1257        // LuaTeX codes: left 1, middle 2, right 3; \Uleft 11, \Umiddle 12,
1258        // \Uright 13, \Uvextensible 14.
1259        if c == 14 {
1260            self.math_vextensible();
1261            return;
1262        }
1263        let t = match c % 10 {
1264            1 => fence_sub::LEFT,
1265            2 => fence_sub::MIDDLE,
1266            _ => fence_sub::RIGHT,
1267        };
1268        if t != fence_sub::LEFT && self.save.cur_group != GroupCode::MathLeft {
1269            if self.save.cur_group == GroupCode::MathShift {
1270                // LuaTeX reads the delimiter without checking it.
1271                self.get_nonblank_nonrelax_noncall();
1272                if self.cur_cmd == Cmd::DelimNum {
1273                    if self.cur_chr == 0 {
1274                        self.scan_twenty_seven_bit_int();
1275                    } else {
1276                        self.scan_umath_triple();
1277                    }
1278                }
1279                self.print_err("Extra ");
1280                if t == fence_sub::MIDDLE {
1281                    self.print_esc_str("middle");
1282                    self.help(&["I'm ignoring a \\middle that had no matching \\left."]);
1283                } else {
1284                    self.print_esc_str("right");
1285                    self.help(&["I'm ignoring a \\right that had no matching \\left."]);
1286                }
1287                self.error();
1288            } else {
1289                self.off_save();
1290            }
1291            return;
1292        }
1293        let mut fe = FenceData {
1294            class: -1,
1295            ..FenceData::default()
1296        };
1297        if c >= 10 {
1298            loop {
1299                if self.scan_keyword("height") {
1300                    self.scan_normal_dimen();
1301                    fe.height = self.cur_val;
1302                } else if self.scan_keyword("depth") {
1303                    self.scan_normal_dimen();
1304                    fe.depth = self.cur_val;
1305                } else if self.scan_keyword("axis") {
1306                    fe.options |= noad_opt::AXIS;
1307                } else if self.scan_keyword("noaxis") {
1308                    fe.options |= noad_opt::NO_AXIS;
1309                } else if self.scan_keyword("exact") {
1310                    fe.options |= noad_opt::EXACT;
1311                } else if self.scan_keyword("class") {
1312                    self.scan_int();
1313                    fe.class = self.cur_val;
1314                } else {
1315                    break;
1316                }
1317            }
1318        }
1319        fe.delim = self.scan_delimiter(false, false);
1320        let p = self.new_node(id::FENCE, t, NodeData::Fence(Box::new(fe)));
1321        let q = if t == fence_sub::LEFT {
1322            p
1323        } else {
1324            let q = self.fin_mlist(p);
1325            self.unsave();
1326            q
1327        };
1328        if t != fence_sub::RIGHT {
1329            let st = self.cur_list().math_style;
1330            self.push_math(GroupCode::MathLeft, st);
1331            let head = self.cur_list().head;
1332            self.nodes.link(head, q);
1333            self.cur_list_mut().tail = p;
1334            self.cur_list_mut().delim_ptr = p;
1335        } else {
1336            let n = self.new_noad(noad_sub::INNER);
1337            let k = self.new_sub_list(true, q);
1338            self.set_noad_field(n, Field::Nucleus, k);
1339            self.tail_append(n);
1340        }
1341    }
1342
1343    /// `\Uvextensible`: a stand-alone extensible delimiter.
1344    fn math_vextensible(&mut self) {
1345        let mut fe = FenceData {
1346            class: -1,
1347            ..FenceData::default()
1348        };
1349        loop {
1350            if self.scan_keyword("height") {
1351                self.scan_normal_dimen();
1352                fe.height = self.cur_val;
1353            } else if self.scan_keyword("depth") {
1354                self.scan_normal_dimen();
1355                fe.depth = self.cur_val;
1356            } else if self.scan_keyword("axis") {
1357                fe.options |= noad_opt::AXIS;
1358            } else if self.scan_keyword("noaxis") {
1359                fe.options |= noad_opt::NO_AXIS;
1360            } else if self.scan_keyword("exact") {
1361                fe.options |= noad_opt::EXACT;
1362            } else {
1363                break;
1364            }
1365        }
1366        fe.delim = self.scan_delimiter(false, false);
1367        // Observed: a fence of subtype "no".
1368        let p = self.new_node(id::FENCE, fence_sub::NO, NodeData::Fence(Box::new(fe)));
1369        let n = self.new_noad(noad_sub::INNER);
1370        let k = self.new_sub_list(true, p);
1371        self.set_noad_field(n, Field::Nucleus, k);
1372        self.tail_append(n);
1373    }
1374
1375    /// `\eqno` and `\leqno` in display math (`start_eq_no`).
1376    fn start_eq_no(&mut self) {
1377        self.push_saved(i64::from(self.cur_chr));
1378        self.go_into_ordinary_math();
1379    }
1380
1381    /// Appends a box in math mode as an ord noad with a `sub_box`
1382    /// nucleus (§1076).
1383    pub fn math_box_end(&mut self, b: NodeRef) {
1384        let n = self.new_noad(noad_sub::ORD);
1385        let k = self.new_sub_list(false, b);
1386        self.set_noad_field(n, Field::Nucleus, k);
1387        self.tail_append(n);
1388    }
1389
1390    /// Mathchars in horizontal mode: LuaTeX typesets the character in the
1391    /// text font of its family.
1392    pub fn math_char_in_text(&mut self) {
1393        let mc = match self.cur_cmd {
1394            Cmd::MathCharNum => self.scan_math_char_num(),
1395            Cmd::MathGiven => MathChar::from_given(self.cur_chr),
1396            _ => MathChar::from_packed(i64::from(self.cur_chr)),
1397        };
1398        if mc.class >= 8 {
1399            self.treat_as_active();
1400            return;
1401        }
1402        let f = self.fam_fnt(mc.fam, 0);
1403        let g = self.new_char_node(f, mc.chr);
1404        self.tail_append(g);
1405        self.cur_list_mut().space_factor = 1000;
1406    }
1407
1408    /// The font of family `fam` in size `size` (0 text, 1 script,
1409    /// 2 scriptscript): `fam_fnt`.
1410    pub fn fam_fnt(&self, fam: u32, size: u32) -> u32 {
1411        self.eqtb
1412            .math_font
1413            .get((size * crate::eqtb::NUM_FAMILIES as u32 + fam) as usize)
1414            .map_or(0, |s| s.val)
1415    }
1416
1417    // ----- leaving math mode ------------------------------------------
1418
1419    /// The end of a formula (`after_math`).
1420    fn after_math(&mut self) {
1421        let mut m = self.mode;
1422        let mut l = false;
1423        let mut p = self.fin_mlist(NodeRef::NULL);
1424        let a;
1425        if self.mode == Mode::DisplayMath && m == Mode::Math {
1426            // The end of an equation number.
1427            self.check_dollar_follows();
1428            let hl = self.formula_to_hlist(p, 2, false);
1429            a = self.hpack(hl, PackSpec::NATURAL);
1430            self.take_outer_math_dirs();
1431            self.unsave();
1432            if self.pop_saved() == 1 {
1433                l = true;
1434            }
1435            m = self.mode;
1436            p = self.fin_mlist(NodeRef::NULL);
1437        } else {
1438            a = NodeRef::NULL;
1439        }
1440        if m == Mode::Math {
1441            self.finish_math_in_text(p);
1442        } else {
1443            if a.is_null() {
1444                self.check_dollar_follows();
1445            }
1446            self.finish_displayed_math(p, a, l);
1447        }
1448    }
1449
1450    /// The math and text directions outside the formula whose group is
1451    /// the current one.
1452    fn take_outer_math_dirs(&mut self) -> Option<(i32, i32)> {
1453        let level = self.save.cur_level;
1454        while let Some(&(l, m, t)) = self.outer_math_dirs.last() {
1455            if l < level {
1456                break;
1457            }
1458            self.outer_math_dirs.pop();
1459            if l == level {
1460                return Some((m, t));
1461            }
1462        }
1463        None
1464    }
1465
1466    /// Checks that a second `$` ends a display.
1467    fn check_dollar_follows(&mut self) {
1468        self.get_x_token();
1469        if self.cur_cmd != Cmd::MathShift
1470            && !(self.cur_cmd == Cmd::MathShiftCs && self.cur_chr == 1)
1471        {
1472            self.print_err("Display math should end with $$");
1473            self.help(&[
1474                "The `$' that I just saw supposedly matches a previous `$$'.",
1475                "So I shall assume that you typed `$$' both times.",
1476            ]);
1477            self.back_error();
1478        }
1479    }
1480
1481    fn finish_math_in_text(&mut self, p: NodeRef) {
1482        let ms = self.eqtb.dimen(dp::MATHSURROUND);
1483        let mg = self
1484            .eqtb
1485            .glue(crate::primitives_gen::assign_glue::MATHSURROUNDSKIP);
1486        let pen = self.mode == Mode::Horizontal;
1487        // As in TeX, the formula's group ends last: the opening math node
1488        // is the tail while the mlist_to_hlist callback runs (luamml reads
1489        // it), and the nodes take the attributes inside the formula.
1490        // A formula whose direction differs from the text's is wrapped in
1491        // direction nodes (see `dirs`).
1492        // The directions are those outside the formula.
1493        let (math_dir, text_dir) = self.take_outer_math_dirs().unwrap_or((
1494            self.dir_param(crate::primitives_gen::assign_dir::MATHDIR),
1495            self.dir_param(crate::primitives_gen::assign_dir::TEXTDIR),
1496        ));
1497        let wrap = (math_dir != text_dir).then(|| math_dir.rem_euclid(4) as i8);
1498        let b = self.new_math_node(ms, mg, 0);
1499        self.tail_append(b);
1500        if let Some(d) = wrap {
1501            let p = self.new_dir_node(d, false);
1502            self.tail_append(p);
1503        }
1504        let hl = self.formula_to_hlist(p, 2, pen);
1505        let tail = self.cur_list().tail;
1506        self.nodes.link(tail, hl);
1507        let mut t = tail;
1508        while !self.nodes.next(t).is_null() {
1509            t = self.nodes.next(t);
1510        }
1511        self.cur_list_mut().tail = t;
1512        if let Some(d) = wrap {
1513            let p = self.new_dir_node(d, true);
1514            self.tail_append(p);
1515        }
1516        let e = self.new_math_node(ms, mg, 1);
1517        self.tail_append(e);
1518        self.cur_list_mut().space_factor = 1000;
1519        self.unsave();
1520    }
1521
1522    /// A math node with `\mathsurround` and `\mathsurroundskip`.
1523    fn new_math_node(&mut self, surround: i32, skip: crate::glue::Glue, sub: u16) -> NodeRef {
1524        let p = self.new_node(
1525            id::MATH,
1526            sub,
1527            NodeData::Math {
1528                surround,
1529                glue: crate::glue::Glue::ZERO,
1530            },
1531        );
1532        if (skip.width != 0 || skip.stretch != 0 || skip.shrink != 0)
1533            && let NodeData::Math { glue, .. } = self.nodes.data_mut(p)
1534        {
1535            *glue = skip;
1536        }
1537        p
1538    }
1539
1540    /// Places a finished display (§1199–§1205).
1541    fn finish_displayed_math(&mut self, p: NodeRef, a: NodeRef, l: bool) {
1542        use crate::nodes::glue_sub as gs;
1543        self.take_outer_math_dirs();
1544        let hl = self.formula_to_hlist(p, 0, false);
1545        let adj_head = self.nodes.new_temp();
1546        self.ts.adjust_tail = Some(adj_head);
1547        let mut b = self.hpack(hl, PackSpec::NATURAL);
1548        let p = self.nodes.list_ptr(b);
1549        let t = self.ts.adjust_tail.unwrap_or(adj_head);
1550        self.ts.adjust_tail = None;
1551        let mut w = self.nodes.box_data(b).map_or(0, |x| x.width);
1552        let z = self.eqtb.dimen(dp::DISPLAYWIDTH);
1553        let s = self.eqtb.dimen(dp::DISPLAYINDENT);
1554        let (mut e, q) = if a.is_null() {
1555            (0, 0)
1556        } else {
1557            let e = self.nodes.box_data(a).map_or(0, |x| x.width);
1558            let f2 = self.fam_fnt(2, 0);
1559            let mq = self.math_quad_text(f2);
1560            let step = self.eqtb.int(ip::MATHEQNOGAPSTEP);
1561            let gap = if step == 1000 {
1562                mq
1563            } else {
1564                retex_arith::xn_over_d(mq, step, 1000).map_or(0, |r| r.quotient)
1565            };
1566            (e, e + gap)
1567        };
1568        if w + q > z {
1569            // Squeeze the equation as much as possible.
1570            let tot = self.ts.last_totals;
1571            if e != 0
1572                && (w - tot.1[0] + q <= z
1573                    || tot.1[1] != 0
1574                    || tot.1[2] != 0
1575                    || tot.1[3] != 0
1576                    || tot.1[4] != 0)
1577            {
1578                self.nodes.set_list_ptr(b, NodeRef::NULL);
1579                self.nodes.free(b);
1580                b = self.hpack(p, PackSpec::Exactly(z - q));
1581            } else {
1582                e = 0;
1583                if w > z {
1584                    self.nodes.set_list_ptr(b, NodeRef::NULL);
1585                    self.nodes.free(b);
1586                    b = self.hpack(p, PackSpec::Exactly(z));
1587                }
1588            }
1589            w = self.nodes.box_data(b).map_or(0, |x| x.width);
1590        }
1591        self.nodes.set_subtype(b, 6); // equation
1592        // The displacement of the left edge of the equation.
1593        let mut d = half(z - w);
1594        if e > 0 && d < 2 * e {
1595            d = half(z - w - e);
1596            let p = self.nodes.list_ptr(b);
1597            if !p.is_null() && self.nodes.id(p) == id::GLUE {
1598                d = 0;
1599            }
1600        }
1601        // The glue or equation number preceding the display.
1602        let pen = self.new_penalty_node(self.eqtb.int(ip::PREDISPLAYPENALTY));
1603        self.tail_append(pen);
1604        let pds = self.eqtb.dimen(dp::PREDISPLAYSIZE);
1605        let (g1, mut g2) = if d + s <= pds || l {
1606            (gs::ABOVE_DISPLAY, gs::BELOW_DISPLAY)
1607        } else {
1608            (gs::ABOVE_DISPLAY_SHORT, gs::BELOW_DISPLAY_SHORT)
1609        };
1610        let mode = self.eqtb.int(ip::MATHDISPLAYSKIPMODE);
1611        if l && e == 0 {
1612            if let Some(bd) = self.nodes.box_data_mut(a) {
1613                bd.shift = s;
1614            }
1615            self.nodes.set_subtype(a, 7);
1616            self.append_to_vlist(a, "equation_number");
1617            let pen = self.new_penalty_node(10000);
1618            self.tail_append(pen);
1619        } else {
1620            self.append_display_skip(g1, mode, true);
1621        }
1622        // The display and perhaps the equation number. LuaTeX packs an
1623        // equation with its number into a box of the display width:
1624        // kern d, equation, kern, number (or number, kern, equation,
1625        // kern d for \leqno), not shifted by d.
1626        if e != 0 {
1627            let r = self.new_kern_node(z - w - e - d, 0);
1628            self.nodes.set_subtype(a, 7);
1629            // Observed: the kern after a \leqno equation is z - w - d
1630            // (which makes the line wider than z when d was reduced).
1631            let k = self.new_kern_node(if l { z - w - d } else { d }, 0);
1632            if l {
1633                self.nodes.link(a, r);
1634                self.nodes.link(r, b);
1635                self.nodes.link(b, k);
1636                b = a;
1637            } else {
1638                self.nodes.link(k, b);
1639                self.nodes.link(b, r);
1640                self.nodes.link(r, a);
1641                b = k;
1642            }
1643            b = self.hpack(b, PackSpec::NATURAL);
1644            d = 0;
1645        }
1646        if let Some(bd) = self.nodes.box_data_mut(b) {
1647            bd.shift = s + d;
1648        }
1649        self.append_to_vlist(b, "equation");
1650        // The glue or equation number following the display.
1651        if !a.is_null() && e == 0 && !l {
1652            let pen = self.new_penalty_node(10000);
1653            self.tail_append(pen);
1654            let aw = self.nodes.box_data(a).map_or(0, |x| x.width);
1655            if let Some(bd) = self.nodes.box_data_mut(a) {
1656                bd.shift = s + z - aw;
1657            }
1658            self.nodes.set_subtype(a, 7);
1659            self.append_to_vlist(a, "equation_number");
1660            g2 = 0;
1661        }
1662        if t != adj_head {
1663            let first = self.nodes.next(adj_head);
1664            let tail = self.cur_list().tail;
1665            self.nodes.link(tail, first);
1666            self.cur_list_mut().tail = t;
1667        }
1668        self.nodes.free(adj_head);
1669        let pen = self.new_penalty_node(self.eqtb.int(ip::POSTDISPLAYPENALTY));
1670        self.tail_append(pen);
1671        if g2 > 0 {
1672            self.append_display_skip(g2, mode, false);
1673        }
1674        self.resume_after_display();
1675    }
1676
1677    /// Appends `\abovedisplayskip` and friends, honouring
1678    /// `\mathdisplayskipmode` (0 and 1: TeX's rule, an above skip always
1679    /// and a below skip always; 2: only when not zero; 3: never).
1680    fn append_display_skip(&mut self, sub: u16, mode: i32, _above: bool) {
1681        let g = self.eqtb.glue(crate::build::glue_param_of_subtype(sub));
1682        let zero = g.width == 0 && g.stretch == 0 && g.shrink == 0;
1683        let skip = match mode {
1684            2 => zero,
1685            3 => true,
1686            _ => false,
1687        };
1688        if !skip {
1689            let n = self.new_param_glue(sub);
1690            self.tail_append(n);
1691        }
1692    }
1693
1694    /// `math_quad(text_size)` from the font in family 2.
1695    fn math_quad_text(&self, f2: u32) -> i32 {
1696        let v = self.math_param_value(0, 2);
1697        if v != crate::codes::UNDEFINED_MATH_PARAM {
1698            v
1699        } else {
1700            self.quad(f2)
1701        }
1702    }
1703
1704    /// The value of math parameter `p` in style `s`.
1705    pub fn math_param_value(&self, p: u32, s: u32) -> i32 {
1706        self.eqtb
1707            .math_param
1708            .get(&(p * 8 + s))
1709            .map_or(crate::codes::UNDEFINED_MATH_PARAM, |x| x.val)
1710    }
1711
1712    /// Resumes the paragraph after a display (`resume_after_display`).
1713    fn resume_after_display(&mut self) {
1714        if self.save.cur_group != GroupCode::MathShift {
1715            self.confusion("display");
1716        }
1717        self.unsave();
1718        let pg = self.cur_list().prev_graf + 3;
1719        self.cur_list_mut().prev_graf = pg;
1720        self.push_nest(Mode::Horizontal);
1721        self.cur_list_mut().space_factor = 1000;
1722        let l = self.cur_lang() as i32;
1723        self.cur_list_mut().clang = l;
1724        let lp = self.new_local_par();
1725        self.tail_append(lp);
1726        // Scan an optional space.
1727        self.get_x_token();
1728        if self.cur_cmd != Cmd::Spacer {
1729            self.back_input();
1730        }
1731        if self.nest.len() == 2 {
1732            self.build_page_from("after_display");
1733        }
1734    }
1735
1736    /// Finishes an alignment in a display (§1206).
1737    pub fn finish_display_alignment(&mut self, p: NodeRef, q: NodeRef, aux_save: i32) {
1738        self.do_assignments();
1739        if self.cur_cmd != Cmd::MathShift {
1740            self.print_err("Missing $$ inserted");
1741            self.help(&[
1742                "Displays can use special alignments (like \\eqalignno)",
1743                "only if nothing but the alignment itself is between $$'s.",
1744            ]);
1745            self.back_error();
1746        } else {
1747            self.check_dollar_follows();
1748        }
1749        self.pop_nest();
1750        let pen = self.new_penalty_node(self.eqtb.int(ip::PREDISPLAYPENALTY));
1751        self.tail_append(pen);
1752        let g = self.new_param_glue(crate::nodes::glue_sub::ABOVE_DISPLAY);
1753        self.tail_append(g);
1754        let tail = self.cur_list().tail;
1755        self.nodes.link(tail, p);
1756        if !p.is_null() {
1757            self.cur_list_mut().tail = q;
1758        }
1759        let pen = self.new_penalty_node(self.eqtb.int(ip::POSTDISPLAYPENALTY));
1760        self.tail_append(pen);
1761        let g = self.new_param_glue(crate::nodes::glue_sub::BELOW_DISPLAY);
1762        self.tail_append(g);
1763        self.cur_list_mut().prev_depth = aux_save;
1764        self.resume_after_display();
1765    }
1766
1767    /// Empties the current math list (`flush_math`).
1768    pub fn flush_math(&mut self) {
1769        let head = self.cur_list().head;
1770        let first = self.nodes.next(head);
1771        self.nodes.flush_list(first);
1772        let inc = self.cur_list().incompleat_noad;
1773        self.nodes.flush_list(inc);
1774        self.nodes.set_next(head, NodeRef::NULL);
1775        self.cur_list_mut().tail = head;
1776        self.cur_list_mut().incompleat_noad = NodeRef::NULL;
1777    }
1778
1779    /// Inserts the tokens of a list as if typed (used by error recovery).
1780    pub fn ins_tokens(&mut self, toks: Vec<Token>) {
1781        self.ins_list(Rc::from(toks));
1782    }
1783}
1784
1785pub use retex_arith::half;
1786
1787/// The parts of a TeX 27-bit delimiter code.
1788fn classic_delimiter(v: i32) -> Option<(u32, u32, u32, u32)> {
1789    if v < 0 {
1790        return None;
1791    }
1792    let v = v as u32;
1793    Some((
1794        (v / 0o4000000) % 16,
1795        (v / 0o10000) % 256,
1796        (v / 256) % 16,
1797        v % 256,
1798    ))
1799}