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, 0, 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();
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                _ => (0, false),
372            };
373            if visible {
374                if v < max_dimen {
375                    v += d;
376                    w = v;
377                } else {
378                    w = max_dimen;
379                    break;
380                }
381            } else if v < max_dimen {
382                v += d;
383            }
384            p = self.nodes.next(p);
385        }
386        w
387    }
388
389    /// The length and shift of line `prev_graf + 2` (§1149).
390    fn display_line_size(&self, prev_graf: i32) -> (i32, i32) {
391        let hsize = self.eqtb.dimen(dp::HSIZE);
392        match &self.eqtb.par_shape.val {
393            None => {
394                let hi = self.eqtb.dimen(dp::HANGINDENT);
395                let ha = self.eqtb.int(ip::HANGAFTER);
396                if hi != 0 && ((ha >= 0 && prev_graf + 2 > ha) || (prev_graf + 1 < -ha)) {
397                    (hsize - hi.abs(), if hi > 0 { hi } else { 0 })
398                } else {
399                    (hsize, 0)
400                }
401            }
402            Some(shape) => {
403                let n = (shape.len() / 2) as i32;
404                let i = if prev_graf + 2 >= n { n } else { prev_graf + 2 };
405                let k = 2 * (i.max(1) as usize - 1);
406                let indent = shape.get(k).copied().unwrap_or(0);
407                let len = shape.get(k + 1).copied().unwrap_or(hsize);
408                (len, indent)
409            }
410        }
411    }
412
413    // ----- the main switch --------------------------------------------
414
415    /// Commands in math mode (the `mmode+…` cases of `main_control`).
416    pub fn math_command(&mut self) {
417        let cmd = self.cur_cmd;
418        match cmd {
419            Cmd::Letter | Cmd::OtherChar | Cmd::CharGiven => {
420                let code = self.eqtb.math.get(self.cur_chr);
421                self.set_math_char(MathChar::from_packed(code));
422            }
423            Cmd::CharNum => {
424                self.scan_char_num();
425                self.cur_chr = self.cur_val as u32;
426                let code = self.eqtb.math.get(self.cur_chr);
427                self.set_math_char(MathChar::from_packed(code));
428            }
429            Cmd::MathCharNum => {
430                let mc = self.scan_math_char_num();
431                self.set_math_char(mc);
432            }
433            Cmd::MathGiven => {
434                let mc = MathChar::from_given(self.cur_chr);
435                self.set_math_char(mc);
436            }
437            Cmd::XmathGiven => {
438                let mc = MathChar::from_packed(i64::from(self.cur_chr));
439                self.set_math_char(mc);
440            }
441            Cmd::DelimNum => {
442                let mc = self.scan_delim_num_char();
443                self.set_math_char(mc);
444            }
445            Cmd::LeftBrace => {
446                let n = self.new_noad(noad_sub::ORD);
447                self.tail_append(n);
448                self.back_input();
449                let st = self.cur_list().math_style;
450                self.scan_math(n, Field::Nucleus, st);
451            }
452            Cmd::MathComp => {
453                let sub = self.cur_chr as u16;
454                let n = self.new_noad(sub);
455                self.tail_append(n);
456                let st = self.cur_list().math_style;
457                let st = if sub == noad_sub::OVER || sub == noad_sub::UNDER {
458                    if sub == noad_sub::OVER {
459                        cramped_style(st)
460                    } else {
461                        st
462                    }
463                } else {
464                    st
465                };
466                self.scan_math(n, Field::Nucleus, st);
467            }
468            Cmd::LimitSwitch => self.math_limit_switch(),
469            Cmd::SupMark | Cmd::SubMark => {
470                let sup = cmd == Cmd::SupMark;
471                self.sub_sup(sup, false);
472            }
473            Cmd::SuperSubScript => {
474                let sup = self.cur_chr == Cmd::SupMark as u32;
475                self.sub_sup(sup, false);
476            }
477            Cmd::NoSuperSubScript => {
478                let sup = self.cur_chr == Cmd::SupMark as u32;
479                self.sub_sup(sup, true);
480            }
481            Cmd::Radical => self.math_radical(),
482            Cmd::Accent | Cmd::MathAccent => self.math_ac(),
483            Cmd::Vcenter => {
484                self.scan_spec(GroupCode::Vcenter, None);
485                self.normal_paragraph();
486                self.push_nest(Mode::InternalVertical);
487                self.cur_list_mut().prev_depth = IGNORE_DEPTH;
488                let t = self.eqtb.toks_param(tp::EVERYVBOX).clone();
489                if !t.is_empty() {
490                    self.begin_token_list(t, TokKind::EveryVboxText);
491                }
492            }
493            Cmd::MathStyle => {
494                let s = self.cur_chr.min(7) as u8;
495                let n = self.new_style(s);
496                self.tail_append(n);
497                self.cur_list_mut().math_style = i32::from(s);
498            }
499            Cmd::NonScript => {
500                let g = self.new_glue_node(
501                    crate::glue::Glue::ZERO,
502                    crate::nodes::glue_sub::COND_MATH_GLUE,
503                );
504                self.tail_append(g);
505            }
506            Cmd::MathChoice => {
507                if self.cur_chr == 0 {
508                    self.append_choices();
509                } else {
510                    // \Ustack: a subformula in numerator style.
511                    let n = self.new_noad(noad_sub::ORD);
512                    self.tail_append(n);
513                    let st = num_style(self.cur_list().math_style);
514                    self.scan_left_brace();
515                    let sub = self.new_sub_list(true, NodeRef::NULL);
516                    self.set_noad_field(n, Field::Nucleus, sub);
517                    self.push_saved(field_ref(n, Field::Nucleus));
518                    self.push_math(GroupCode::Math, st);
519                }
520            }
521            Cmd::Above => self.math_fraction(),
522            Cmd::MathShift => {
523                if self.save.cur_group == GroupCode::MathShift {
524                    self.after_math();
525                } else {
526                    self.off_save();
527                }
528            }
529            Cmd::MathShiftCs => {
530                // \Ustopmath (3) and \Ustopdisplaymath (1) end the
531                // formula; the start commands are not allowed here.
532                let c = self.cur_chr;
533                let ok = (c == 3 && self.mode == Mode::Math)
534                    || (c == 1 && self.mode == Mode::DisplayMath);
535                if (c == 1 || c == 3) && self.save.cur_group == GroupCode::MathShift && ok {
536                    self.after_math();
537                } else if c == 1 || c == 3 {
538                    if self.save.cur_group == GroupCode::MathShift {
539                        self.print_err("Invalid math code");
540                        self.help(&["I'm going to ignore this math shift command."]);
541                        self.error();
542                    } else {
543                        self.off_save();
544                    }
545                } else {
546                    self.report_illegal_case();
547                }
548            }
549            Cmd::EqNo => {
550                if self.privileged() {
551                    if self.save.cur_group == GroupCode::MathShift {
552                        self.start_eq_no();
553                    } else {
554                        self.off_save();
555                    }
556                }
557            }
558            Cmd::LeftRight => self.math_left_right(),
559            Cmd::Endv
560            | Cmd::ParEnd
561            | Cmd::Stop
562            | Cmd::Vskip
563            | Cmd::UnVbox
564            | Cmd::Valign
565            | Cmd::Hrule => self.insert_dollar_sign(),
566            Cmd::Halign => {
567                if self.privileged() {
568                    if self.save.cur_group == GroupCode::MathShift {
569                        self.init_align();
570                    } else {
571                        self.off_save();
572                    }
573                }
574            }
575            _ => {
576                let (c, r) = (self.cur_cmd, self.cur_chr);
577                let s = self.print_to_string(|e| e.print_cmd_chr(c, r));
578                self.not_implemented(&String::from_utf8_lossy(&s));
579            }
580        }
581    }
582
583    /// `\mathchar`, `\Umathchar` and `\Umathcharnum`.
584    fn scan_math_char_num(&mut self) -> MathChar {
585        match self.cur_chr {
586            0 => {
587                let g = self.scan_math_given();
588                MathChar::from_given(g)
589            }
590            1 => {
591                let v = self.scan_umath_triple();
592                MathChar::from_packed(v)
593            }
594            _ => {
595                self.scan_int();
596                MathChar::from_packed(i64::from(self.cur_val))
597            }
598        }
599    }
600
601    /// `\delimiter` (its 15-bit part) and `\Udelimiter` as a math character.
602    fn scan_delim_num_char(&mut self) -> MathChar {
603        if self.cur_chr == 0 {
604            self.scan_twenty_seven_bit_int();
605            MathChar::from_classic(self.cur_val / 0o10000)
606        } else {
607            let v = self.scan_umath_triple();
608            MathChar::from_packed(v)
609        }
610    }
611
612    /// Whether `\fam` is a valid family (`fam_in_range`).
613    fn fam_in_range(&self) -> Option<u32> {
614        let f = self.eqtb.int(ip::FAM);
615        if (0..crate::eqtb::NUM_FAMILIES as i32).contains(&f) {
616            Some(f as u32)
617        } else {
618            None
619        }
620    }
621
622    /// The family of a math character, taking class 7 and `\fam` into
623    /// account.
624    fn effective_fam(&self, mc: MathChar) -> u32 {
625        if mc.class == 7
626            && let Some(f) = self.fam_in_range()
627        {
628            return f;
629        }
630        let vf = self.eqtb.int(ip::VARIABLEFAM);
631        if vf >= 0
632            && mc.fam == vf as u32
633            && let Some(f) = self.fam_in_range()
634        {
635            return f;
636        }
637        mc.fam
638    }
639
640    /// Treats `cur_chr` as an active character (§1152).
641    fn treat_as_active(&mut self) {
642        let cs = self.hash.active(self.cur_chr);
643        self.cur_cs = cs;
644        let m = self.eqtb.meaning(cs).clone();
645        self.cur_cmd = m.cmd;
646        self.cur_chr = m.chr;
647        self.x_token();
648        self.back_input();
649    }
650
651    /// Appends a noad for a math character (`set_math_char`).
652    fn set_math_char(&mut self, mc: MathChar) {
653        if mc.class >= 8 {
654            self.treat_as_active();
655            return;
656        }
657        let fam = self.effective_fam(mc);
658        let k = self.new_math_char(fam, mc.chr);
659        let sub = class_subtype(mc.class);
660        let p = self.new_noad(sub);
661        self.set_noad_field(p, Field::Nucleus, k);
662        self.tail_append(p);
663    }
664
665    /// Fills field `f` of noad `p` with the next math character or
666    /// subformula (`scan_math`); `style` is the style of a subformula.
667    /// Returns true if a subformula group was opened.
668    fn scan_math(&mut self, p: NodeRef, f: Field, style: i32) -> bool {
669        self.scan_math_flag(p, f, style, 0)
670    }
671
672    /// [`scan_math`](Self::scan_math) with extra bits in the saved field
673    /// reference (bit 62: scan the nucleus when the group ends).
674    fn scan_math_flag(&mut self, p: NodeRef, f: Field, style: i32, flag: i64) -> bool {
675        self.get_nonblank_nonrelax_noncall();
676        let mc = loop {
677            match self.cur_cmd {
678                Cmd::Letter | Cmd::OtherChar | Cmd::CharGiven => {
679                    let code = self.eqtb.math.get(self.cur_chr);
680                    let mc = MathChar::from_packed(code);
681                    if mc.class == 8 {
682                        self.treat_as_active();
683                        self.get_nonblank_nonrelax_noncall();
684                        continue;
685                    }
686                    break mc;
687                }
688                Cmd::CharNum => {
689                    self.scan_char_num();
690                    self.cur_chr = self.cur_val as u32;
691                    self.cur_cmd = Cmd::CharGiven;
692                }
693                Cmd::MathCharNum => break self.scan_math_char_num(),
694                Cmd::MathGiven => break MathChar::from_given(self.cur_chr),
695                Cmd::XmathGiven => break MathChar::from_packed(i64::from(self.cur_chr)),
696                Cmd::DelimNum => break self.scan_delim_num_char(),
697                _ => {
698                    // A subformula enclosed in braces.
699                    self.back_input();
700                    self.scan_left_brace();
701                    let sub = self.new_sub_list(true, NodeRef::NULL);
702                    let old = self.noad_field(p, f);
703                    self.nodes.flush_list(old);
704                    self.set_noad_field(p, f, sub);
705                    self.push_saved(field_ref(p, f) | flag);
706                    self.push_math(GroupCode::Math, style);
707                    return true;
708                }
709            }
710        };
711        self.finish_scan_math(p, f, mc);
712        false
713    }
714
715    fn finish_scan_math(&mut self, p: NodeRef, f: Field, mc: MathChar) {
716        let fam = if mc.class == 8 {
717            0
718        } else {
719            self.effective_fam(mc)
720        };
721        let k = self.new_math_char(fam, mc.chr);
722        let old = self.noad_field(p, f);
723        self.nodes.flush_list(old);
724        self.set_noad_field(p, f, k);
725    }
726
727    /// `\limits`, `\nolimits`, `\displaylimits` (`math_limit_switch`).
728    fn math_limit_switch(&mut self) {
729        let (head, tail) = (self.cur_list().head, self.cur_list().tail);
730        if head != tail
731            && self.nodes.id(tail) == id::NOAD
732            && matches!(
733                self.nodes.subtype(tail),
734                noad_sub::OP_DISPLAY_LIMITS | noad_sub::OP_LIMITS | noad_sub::OP_NO_LIMITS
735            )
736        {
737            self.nodes.set_subtype(tail, self.cur_chr as u16);
738            return;
739        }
740        self.print_err("Limit controls must follow a math operator");
741        self.help(&["I'm ignoring this misplaced \\limits or \\nolimits command."]);
742        self.error();
743    }
744
745    /// Scans a delimiter (`scan_delimiter`); `radical` selects TeX's
746    /// 27-bit form, `fam_char` LuaTeX's `<fam> <char>` form. Returns a new
747    /// delimiter node.
748    fn scan_delimiter(&mut self, radical: bool, fam_char: bool) -> NodeRef {
749        let parts: Option<(u32, u32, u32, u32)> = if fam_char {
750            self.scan_math_family();
751            let fam = self.cur_val as u32;
752            self.scan_char_num();
753            Some((fam, self.cur_val as u32, 0, 0))
754        } else if radical {
755            self.scan_twenty_seven_bit_int();
756            classic_delimiter(self.cur_val)
757        } else {
758            self.get_nonblank_nonrelax_noncall();
759            match self.cur_cmd {
760                Cmd::Letter | Cmd::OtherChar => delcode_parts(self.eqtb.del.get(self.cur_chr)),
761                Cmd::DelimNum => {
762                    if self.cur_chr == 0 {
763                        self.scan_twenty_seven_bit_int();
764                        classic_delimiter(self.cur_val)
765                    } else {
766                        let v = self.scan_umath_triple();
767                        let mc = MathChar::from_packed(v);
768                        Some((mc.fam, mc.chr, 0, 0))
769                    }
770                }
771                _ => None,
772            }
773        };
774        let parts = match parts {
775            Some(p) => p,
776            None => {
777                self.print_err("Missing delimiter (. inserted)");
778                self.help(&[
779                    "I was expecting to see something like `(' or `\\{' or",
780                    "`\\}' here. If you typed, e.g., `{' instead of `\\{', you",
781                    "should probably delete the `{' by typing `1' now, so that",
782                    "braces don't get unbalanced. Otherwise just proceed",
783                    "Acceptable delimiters are characters whose \\delcode is",
784                    "nonnegative, or you can use `\\delimiter <delimiter code>'.",
785                ]);
786                self.back_error();
787                (0, 0, 0, 0)
788            }
789        };
790        self.new_delim(parts)
791    }
792
793    /// `\radical` and LuaTeX's radical-like commands (`math_radical`).
794    fn math_radical(&mut self) {
795        let c = self.cur_chr as u16;
796        let p = self.new_node(id::RADICAL, c, NodeData::Noad(Box::default()));
797        self.tail_append(p);
798        let mut opts = 0u32;
799        let mut width = 0i32;
800        if c == radical_sub::UHEXTENSIBLE || c >= radical_sub::UUNDERDELIMITER {
801            loop {
802                if self.scan_keyword("width") {
803                    self.scan_normal_dimen();
804                    width = self.cur_val;
805                } else if self.scan_keyword("left") {
806                    opts |= noad_opt::LEFT;
807                } else if self.scan_keyword("middle") {
808                    opts |= noad_opt::MIDDLE;
809                } else if self.scan_keyword("right") {
810                    opts |= noad_opt::RIGHT;
811                } else {
812                    break;
813                }
814            }
815        }
816        let d = self.scan_delimiter(c == radical_sub::RADICAL, c != radical_sub::RADICAL);
817        if let Some(n) = self.nodes.noad_mut(p) {
818            n.left = d;
819            n.options = opts;
820            n.width = width;
821        }
822        let st = self.cur_list().math_style;
823        match c {
824            radical_sub::UHEXTENSIBLE => {}
825            radical_sub::UROOT => {
826                // The degree first, then the radicand. The degree is a
827                // subformula in scriptscript style.
828                // The radicand follows when the degree group ends.
829                if !self.scan_math_flag(p, Field::Degree, 6, 1 << 62) {
830                    self.scan_math(p, Field::Nucleus, cramped_style(st));
831                }
832            }
833            radical_sub::UUNDERDELIMITER => {
834                self.scan_math(p, Field::Nucleus, sub_style(st));
835            }
836            radical_sub::UOVERDELIMITER => {
837                self.scan_math(p, Field::Nucleus, sup_style(st));
838            }
839            radical_sub::UDELIMITERUNDER => {
840                self.scan_math(p, Field::Nucleus, st);
841            }
842            _ => {
843                self.scan_math(p, Field::Nucleus, cramped_style(st));
844            }
845        }
846    }
847
848    /// Scans `[fixed] <class> <fam> <char>` for `\Umathaccent`.
849    fn scan_accent_char(&mut self, umath: bool) -> (NodeRef, bool) {
850        let fixed = umath && self.scan_keyword("fixed");
851        let mc = if umath {
852            let v = self.scan_umath_triple();
853            MathChar::from_packed(v)
854        } else {
855            let g = self.scan_math_given();
856            MathChar::from_given(g)
857        };
858        let fam = if mc.class == 8 {
859            0
860        } else {
861            self.effective_fam(mc)
862        };
863        (self.new_math_char(fam, mc.chr), fixed)
864    }
865
866    /// `\mathaccent` and `\Umathaccent` (`math_ac`).
867    fn math_ac(&mut self) {
868        if self.cur_cmd == Cmd::Accent {
869            self.back_input();
870            self.get_token();
871            self.print_err("Please use ");
872            self.print_esc_str("mathaccent");
873            self.printer.print_str(" for accents in math mode");
874            self.help(&[
875                "I'm changing \\accent to \\mathaccent here; wish me luck.",
876                "(Accents are not the same in formulas as they are in text.)",
877            ]);
878            self.error();
879        }
880        let umath = self.cur_cmd == Cmd::MathAccent && self.cur_chr == 1;
881        let p = self.new_node(
882            id::ACCENT,
883            accent_sub::BOTH_FLEXIBLE,
884            NodeData::Noad(Box::default()),
885        );
886        self.tail_append(p);
887        let mut sub = accent_sub::BOTH_FLEXIBLE;
888        let (mut top, mut bot) = (NodeRef::NULL, NodeRef::NULL);
889        let fraction = 0;
890        let mut overlay = false;
891        if umath {
892            let (want_top, want_bot) = if self.scan_keyword("bottom") {
893                (false, true)
894            } else if self.scan_keyword("both") {
895                (true, true)
896            } else if self.scan_keyword("overlay") {
897                overlay = true;
898                (true, false)
899            } else {
900                let _ = self.scan_keyword("top");
901                (true, false)
902            };
903            if want_top {
904                let (k, fixed) = self.scan_accent_char(true);
905                top = k;
906                if fixed {
907                    sub |= accent_sub::FIXED_TOP;
908                }
909            }
910            if want_bot {
911                let (k, fixed) = self.scan_accent_char(true);
912                bot = k;
913                if fixed {
914                    sub |= accent_sub::FIXED_BOTTOM;
915                }
916            }
917        } else {
918            top = self.scan_accent_char(false).0;
919        }
920        self.nodes.set_subtype(p, sub);
921        if let Some(n) = self.nodes.noad_mut(p) {
922            n.accent = top;
923            n.bot_accent = bot;
924            n.fraction = fraction;
925            if overlay {
926                n.options |= noad_opt::OVERLAY;
927            }
928        }
929        let st = cramped_style(self.cur_list().math_style);
930        self.scan_math(p, Field::Nucleus, st);
931    }
932
933    /// `^` and `_` (`sub_sup`); `no_style` is set for `\Unosuperscript`
934    /// and `\Unosubscript`.
935    fn sub_sup(&mut self, sup: bool, no_style: bool) {
936        let field = if sup { Field::Sup } else { Field::Sub };
937        let (head, tail) = (self.cur_list().head, self.cur_list().tail);
938        let mut target = NodeRef::NULL;
939        let mut t_nonempty = false;
940        if tail != head && self.nodes.scripts_allowed(tail) {
941            target = tail;
942            t_nonempty = !self.noad_field(tail, field).is_null();
943        }
944        if target.is_null() || t_nonempty {
945            let n = self.new_noad(noad_sub::ORD);
946            self.tail_append(n);
947            target = n;
948            if t_nonempty {
949                if sup {
950                    self.print_err("Double superscript");
951                    self.help(&["I treat `x^1^2' essentially like `x^1{}^2'."]);
952                } else {
953                    self.print_err("Double subscript");
954                    self.help(&["I treat `x_1_2' essentially like `x_1{}_2'."]);
955                }
956                self.error();
957            }
958        }
959        let st = self.cur_list().math_style;
960        let style = if no_style {
961            if let Some(n) = self.nodes.noad_mut(target) {
962                n.options |= if sup {
963                    noad_opt::NO_SUPER_SCRIPT
964                } else {
965                    noad_opt::NO_SUB_SCRIPT
966                };
967            }
968            st
969        } else if sup {
970            sup_style(st)
971        } else {
972            sub_style(st)
973        };
974        self.scan_math(target, field, style);
975    }
976
977    /// `\over`, `\atop`, `\above` and their variants (`math_fraction`).
978    fn math_fraction(&mut self) {
979        let c = self.cur_chr;
980        // LuaTeX codes: above 0, over 1, atop 2, skewed 3; +4 with delims.
981        let delimited = c >= 4;
982        let kind = c % 4;
983        if !self.cur_list().incompleat_noad.is_null() {
984            if delimited {
985                let a = self.scan_delimiter(false, false);
986                let b = self.scan_delimiter(false, false);
987                self.nodes.flush_list(a);
988                self.nodes.flush_list(b);
989            }
990            if kind == 3 {
991                let m = self.scan_delimiter(false, false);
992                self.nodes.flush_list(m);
993                self.scan_fraction_options();
994            } else if kind == 0 {
995                let _ = self.scan_keyword("exact");
996                self.scan_normal_dimen();
997            }
998            self.print_err("Ambiguous; you need another { and }");
999            self.help(&[
1000                "I'm ignoring this fraction specification, since I don't",
1001                "know whether a construction like `x \\over y \\over z'",
1002                "means `{x \\over y} \\over z' or `x \\over {y \\over z}'.",
1003            ]);
1004            self.error();
1005            return;
1006        }
1007        let head = self.cur_list().head;
1008        let first = self.nodes.next(head);
1009        let num = self.new_sub_list(true, first);
1010        let mut fr = FractionData {
1011            thickness: DEFAULT_CODE,
1012            num,
1013            denom: NodeRef::NULL,
1014            left: NodeRef::NULL,
1015            right: NodeRef::NULL,
1016            middle: NodeRef::NULL,
1017            options: 0,
1018            new_hlist: NodeRef::NULL,
1019        };
1020        self.nodes.set_next(head, NodeRef::NULL);
1021        self.cur_list_mut().tail = head;
1022        if delimited {
1023            fr.left = self.scan_delimiter(false, false);
1024            fr.right = self.scan_delimiter(false, false);
1025        }
1026        match kind {
1027            0 => {
1028                if self.scan_keyword("exact") {
1029                    fr.options |= noad_opt::EXACT;
1030                }
1031                self.scan_normal_dimen();
1032                fr.thickness = self.cur_val;
1033            }
1034            1 => fr.thickness = DEFAULT_CODE,
1035            2 => fr.thickness = 0,
1036            _ => {
1037                fr.middle = self.scan_delimiter(false, false);
1038                fr.options |= self.scan_fraction_options();
1039                fr.thickness = 0;
1040            }
1041        }
1042        let n = self.new_node(id::FRACTION, 0, NodeData::Fraction(Box::new(fr)));
1043        self.cur_list_mut().incompleat_noad = n;
1044    }
1045
1046    /// The `noaxis`/`exact` options of `\Uskewed`.
1047    fn scan_fraction_options(&mut self) -> u32 {
1048        let mut o = 0;
1049        loop {
1050            if self.scan_keyword("noaxis") {
1051                o |= noad_opt::NO_AXIS;
1052            } else if self.scan_keyword("exact") {
1053                o |= noad_opt::EXACT;
1054            } else {
1055                return o;
1056            }
1057        }
1058    }
1059
1060    /// Finishes the current mlist and pops the nest (`fin_mlist`); `p`, if
1061    /// not null, is a right (or middle) fence ending the list.
1062    fn fin_mlist(&mut self, p: NodeRef) -> NodeRef {
1063        let inc = self.cur_list().incompleat_noad;
1064        let head = self.cur_list().head;
1065        let q;
1066        if !inc.is_null() {
1067            let first = self.nodes.next(head);
1068            let denom = self.new_sub_list(true, first);
1069            if let NodeData::Fraction(f) = self.nodes.data_mut(inc) {
1070                f.denom = denom;
1071            }
1072            if p.is_null() {
1073                q = inc;
1074            } else {
1075                // A \left…\over…\right: the left fence (and any middle
1076                // fences up to delim_ptr) stays outside the fraction.
1077                let num = match self.nodes.data(inc) {
1078                    NodeData::Fraction(f) => f.num,
1079                    _ => NodeRef::NULL,
1080                };
1081                let num_list = self.nodes.list_ptr_kernel(num);
1082                q = num_list;
1083                let dp = self.cur_list().delim_ptr;
1084                if num_list.is_null() || self.nodes.id(num_list) != id::FENCE || dp.is_null() {
1085                    self.confusion("right");
1086                }
1087                let after = self.nodes.next(dp);
1088                self.nodes.set_kernel_list(num, after);
1089                if !after.is_null() {
1090                    self.nodes.set_prev(after, NodeRef::NULL);
1091                }
1092                self.nodes.link(dp, inc);
1093                self.nodes.link(inc, p);
1094            }
1095        } else {
1096            let tail = self.cur_list().tail;
1097            self.nodes.link(tail, p);
1098            q = self.nodes.next(head);
1099        }
1100        self.nodes.set_next(head, NodeRef::NULL);
1101        if !q.is_null() {
1102            self.nodes.set_prev(q, NodeRef::NULL);
1103        }
1104        self.pop_nest();
1105        q
1106    }
1107
1108    /// Right braces ending math groups, `\mathchoice` groups and
1109    /// `\vcenter` (cases of `handle_right_brace`).
1110    pub fn math_right_brace(&mut self) {
1111        match self.save.cur_group {
1112            GroupCode::Math => {
1113                self.unsave();
1114                let r = self.pop_saved();
1115                let root_follows = r & (1 << 62) != 0;
1116                let (noad, f) = field_from_ref(r & !(1 << 62));
1117                let p = self.fin_mlist(NodeRef::NULL);
1118                let k = self.noad_field(noad, f);
1119                self.nodes.set_kernel_list(k, p);
1120                if !p.is_null() && self.nodes.next(p).is_null() {
1121                    let pid = self.nodes.id(p);
1122                    if pid == id::NOAD && self.nodes.subtype(p) == noad_sub::ORD {
1123                        let (sub, sup) = (
1124                            self.noad_field(p, Field::Sub),
1125                            self.noad_field(p, Field::Sup),
1126                        );
1127                        if sub.is_null() && sup.is_null() {
1128                            // {x}: the field becomes x's nucleus.
1129                            let nuc = self.noad_field(p, Field::Nucleus);
1130                            self.set_noad_field(p, Field::Nucleus, NodeRef::NULL);
1131                            self.nodes.flush_list(p);
1132                            self.nodes.set_kernel_list(k, NodeRef::NULL);
1133                            self.nodes.flush_list(k);
1134                            self.set_noad_field(noad, f, nuc);
1135                        }
1136                    } else if pid == id::ACCENT
1137                        && f == Field::Nucleus
1138                        && noad == self.cur_list().tail
1139                        && self.nodes.id(noad) == id::NOAD
1140                        && self.nodes.subtype(noad) == noad_sub::ORD
1141                    {
1142                        // Replace the tail of the list by the accent noad.
1143                        let head = self.cur_list().head;
1144                        let mut q = head;
1145                        while self.nodes.next(q) != noad {
1146                            q = self.nodes.next(q);
1147                        }
1148                        self.nodes.set_kernel_list(k, NodeRef::NULL);
1149                        self.nodes.link(q, p);
1150                        self.nodes.set_next(noad, NodeRef::NULL);
1151                        self.nodes.flush_list(noad);
1152                        self.cur_list_mut().tail = p;
1153                    }
1154                }
1155                if root_follows {
1156                    let st = cramped_style(self.cur_list().math_style);
1157                    self.scan_math(noad, Field::Nucleus, st);
1158                }
1159            }
1160            GroupCode::MathChoice => self.build_choices(),
1161            GroupCode::Vcenter => {
1162                self.end_graf();
1163                self.unsave();
1164                let (spec, _dir) = self.pop_spec_pub();
1165                let head = self.cur_list().head;
1166                let first = self.nodes.next(head);
1167                self.nodes.set_next(head, NodeRef::NULL);
1168                let first = self.filter_list("vpack_filter", first, "vcenter");
1169                let d = self.eqtb.dimen(dp::BOXMAXDEPTH);
1170                let b = self.vpackage(first, spec, d);
1171                self.pop_nest();
1172                let n = self.new_noad(noad_sub::VCENTER);
1173                let k = self.new_sub_list(false, b);
1174                self.set_noad_field(n, Field::Nucleus, k);
1175                self.tail_append(n);
1176            }
1177            _ => self.confusion("rightbrace"),
1178        }
1179    }
1180
1181    /// `\mathchoice` (`append_choices`).
1182    fn append_choices(&mut self) {
1183        let c = self.new_node(
1184            id::CHOICE,
1185            0,
1186            NodeData::Choice(Box::new([NodeRef::NULL; 4])),
1187        );
1188        self.tail_append(c);
1189        self.push_saved(0);
1190        self.push_math(GroupCode::MathChoice, 0);
1191        self.scan_left_brace();
1192    }
1193
1194    /// The end of one of the four `\mathchoice` lists (`build_choices`).
1195    fn build_choices(&mut self) {
1196        self.unsave();
1197        let p = self.fin_mlist(NodeRef::NULL);
1198        let k = self.pop_saved();
1199        let tail = self.cur_list().tail;
1200        if let NodeData::Choice(c) = self.nodes.data_mut(tail) {
1201            c[(k as usize).min(3)] = p;
1202        }
1203        if k >= 3 {
1204            return;
1205        }
1206        self.push_saved(k + 1);
1207        self.push_math(GroupCode::MathChoice, 2 * (k as i32 + 1));
1208        self.scan_left_brace();
1209    }
1210
1211    /// `\left`, `\middle`, `\right` and their `\U…` forms
1212    /// (`math_left_right`).
1213    fn math_left_right(&mut self) {
1214        let c = self.cur_chr;
1215        // LuaTeX codes: left 1, middle 2, right 3; \Uleft 11, \Umiddle 12,
1216        // \Uright 13, \Uvextensible 14.
1217        if c == 14 {
1218            self.math_vextensible();
1219            return;
1220        }
1221        let t = match c % 10 {
1222            1 => fence_sub::LEFT,
1223            2 => fence_sub::MIDDLE,
1224            _ => fence_sub::RIGHT,
1225        };
1226        if t != fence_sub::LEFT && self.save.cur_group != GroupCode::MathLeft {
1227            if self.save.cur_group == GroupCode::MathShift {
1228                // LuaTeX reads the delimiter without checking it.
1229                self.get_nonblank_nonrelax_noncall();
1230                if self.cur_cmd == Cmd::DelimNum {
1231                    if self.cur_chr == 0 {
1232                        self.scan_twenty_seven_bit_int();
1233                    } else {
1234                        self.scan_umath_triple();
1235                    }
1236                }
1237                self.print_err("Extra ");
1238                if t == fence_sub::MIDDLE {
1239                    self.print_esc_str("middle");
1240                    self.help(&["I'm ignoring a \\middle that had no matching \\left."]);
1241                } else {
1242                    self.print_esc_str("right");
1243                    self.help(&["I'm ignoring a \\right that had no matching \\left."]);
1244                }
1245                self.error();
1246            } else {
1247                self.off_save();
1248            }
1249            return;
1250        }
1251        let mut fe = FenceData {
1252            class: -1,
1253            ..FenceData::default()
1254        };
1255        if c >= 10 {
1256            loop {
1257                if self.scan_keyword("height") {
1258                    self.scan_normal_dimen();
1259                    fe.height = self.cur_val;
1260                } else if self.scan_keyword("depth") {
1261                    self.scan_normal_dimen();
1262                    fe.depth = self.cur_val;
1263                } else if self.scan_keyword("axis") {
1264                    fe.options |= noad_opt::AXIS;
1265                } else if self.scan_keyword("noaxis") {
1266                    fe.options |= noad_opt::NO_AXIS;
1267                } else if self.scan_keyword("exact") {
1268                    fe.options |= noad_opt::EXACT;
1269                } else if self.scan_keyword("class") {
1270                    self.scan_int();
1271                    fe.class = self.cur_val;
1272                } else {
1273                    break;
1274                }
1275            }
1276        }
1277        fe.delim = self.scan_delimiter(false, false);
1278        let p = self.new_node(id::FENCE, t, NodeData::Fence(Box::new(fe)));
1279        let q = if t == fence_sub::LEFT {
1280            p
1281        } else {
1282            let q = self.fin_mlist(p);
1283            self.unsave();
1284            q
1285        };
1286        if t != fence_sub::RIGHT {
1287            let st = self.cur_list().math_style;
1288            self.push_math(GroupCode::MathLeft, st);
1289            let head = self.cur_list().head;
1290            self.nodes.link(head, q);
1291            self.cur_list_mut().tail = p;
1292            self.cur_list_mut().delim_ptr = p;
1293        } else {
1294            let n = self.new_noad(noad_sub::INNER);
1295            let k = self.new_sub_list(true, q);
1296            self.set_noad_field(n, Field::Nucleus, k);
1297            self.tail_append(n);
1298        }
1299    }
1300
1301    /// `\Uvextensible`: a stand-alone extensible delimiter.
1302    fn math_vextensible(&mut self) {
1303        let mut fe = FenceData {
1304            class: -1,
1305            ..FenceData::default()
1306        };
1307        loop {
1308            if self.scan_keyword("height") {
1309                self.scan_normal_dimen();
1310                fe.height = self.cur_val;
1311            } else if self.scan_keyword("depth") {
1312                self.scan_normal_dimen();
1313                fe.depth = self.cur_val;
1314            } else if self.scan_keyword("axis") {
1315                fe.options |= noad_opt::AXIS;
1316            } else if self.scan_keyword("noaxis") {
1317                fe.options |= noad_opt::NO_AXIS;
1318            } else if self.scan_keyword("exact") {
1319                fe.options |= noad_opt::EXACT;
1320            } else {
1321                break;
1322            }
1323        }
1324        fe.delim = self.scan_delimiter(false, false);
1325        let p = self.new_node(id::FENCE, fence_sub::MIDDLE, NodeData::Fence(Box::new(fe)));
1326        let n = self.new_noad(noad_sub::INNER);
1327        let k = self.new_sub_list(true, p);
1328        self.set_noad_field(n, Field::Nucleus, k);
1329        self.tail_append(n);
1330    }
1331
1332    /// `\eqno` and `\leqno` in display math (`start_eq_no`).
1333    fn start_eq_no(&mut self) {
1334        self.push_saved(i64::from(self.cur_chr));
1335        self.go_into_ordinary_math();
1336    }
1337
1338    /// Appends a box in math mode as an ord noad with a `sub_box`
1339    /// nucleus (§1076).
1340    pub fn math_box_end(&mut self, b: NodeRef) {
1341        let n = self.new_noad(noad_sub::ORD);
1342        let k = self.new_sub_list(false, b);
1343        self.set_noad_field(n, Field::Nucleus, k);
1344        self.tail_append(n);
1345    }
1346
1347    /// Mathchars in horizontal mode: LuaTeX typesets the character in the
1348    /// text font of its family.
1349    pub fn math_char_in_text(&mut self) {
1350        let mc = match self.cur_cmd {
1351            Cmd::MathCharNum => self.scan_math_char_num(),
1352            Cmd::MathGiven => MathChar::from_given(self.cur_chr),
1353            _ => MathChar::from_packed(i64::from(self.cur_chr)),
1354        };
1355        if mc.class >= 8 {
1356            self.treat_as_active();
1357            return;
1358        }
1359        let f = self.fam_fnt(mc.fam, 0);
1360        let g = self.new_char_node(f, mc.chr);
1361        self.tail_append(g);
1362        self.cur_list_mut().space_factor = 1000;
1363    }
1364
1365    /// The font of family `fam` in size `size` (0 text, 1 script,
1366    /// 2 scriptscript): `fam_fnt`.
1367    pub fn fam_fnt(&self, fam: u32, size: u32) -> u32 {
1368        self.eqtb
1369            .math_font
1370            .get((size * crate::eqtb::NUM_FAMILIES as u32 + fam) as usize)
1371            .map_or(0, |s| s.val)
1372    }
1373
1374    // ----- leaving math mode ------------------------------------------
1375
1376    /// The end of a formula (`after_math`).
1377    fn after_math(&mut self) {
1378        let mut m = self.mode;
1379        let mut l = false;
1380        let mut p = self.fin_mlist(NodeRef::NULL);
1381        let a;
1382        if self.mode == Mode::DisplayMath && m == Mode::Math {
1383            // The end of an equation number.
1384            self.check_dollar_follows();
1385            let hl = self.mlist_to_hlist(p, 2, false);
1386            a = self.hpack(hl, PackSpec::NATURAL);
1387            self.unsave();
1388            if self.pop_saved() == 1 {
1389                l = true;
1390            }
1391            m = self.mode;
1392            p = self.fin_mlist(NodeRef::NULL);
1393        } else {
1394            a = NodeRef::NULL;
1395        }
1396        if m == Mode::Math {
1397            self.finish_math_in_text(p);
1398        } else {
1399            if a.is_null() {
1400                self.check_dollar_follows();
1401            }
1402            self.finish_displayed_math(p, a, l);
1403        }
1404    }
1405
1406    /// Checks that a second `$` ends a display.
1407    fn check_dollar_follows(&mut self) {
1408        self.get_x_token();
1409        if self.cur_cmd != Cmd::MathShift
1410            && !(self.cur_cmd == Cmd::MathShiftCs && self.cur_chr == 1)
1411        {
1412            self.print_err("Display math should end with $$");
1413            self.help(&[
1414                "The `$' that I just saw supposedly matches a previous `$$'.",
1415                "So I shall assume that you typed `$$' both times.",
1416            ]);
1417            self.back_error();
1418        }
1419    }
1420
1421    fn finish_math_in_text(&mut self, p: NodeRef) {
1422        let ms = self.eqtb.dimen(dp::MATHSURROUND);
1423        let mg = self
1424            .eqtb
1425            .glue(crate::primitives_gen::assign_glue::MATHSURROUNDSKIP);
1426        let pen = self.mode == Mode::Horizontal;
1427        let hl = self.mlist_to_hlist(p, 2, pen);
1428        self.unsave();
1429        // A formula whose direction differs from the text's is wrapped in
1430        // direction nodes (see `dirs`).
1431        let math_dir = self.dir_param(crate::primitives_gen::assign_dir::MATHDIR);
1432        let text_dir = self.dir_param(crate::primitives_gen::assign_dir::TEXTDIR);
1433        let wrap = (math_dir != text_dir).then(|| math_dir.rem_euclid(4) as i8);
1434        let b = self.new_math_node(ms, mg, 0);
1435        self.tail_append(b);
1436        if let Some(d) = wrap {
1437            let p = self.new_dir_node(d, false);
1438            self.tail_append(p);
1439        }
1440        let tail = self.cur_list().tail;
1441        self.nodes.link(tail, hl);
1442        let mut t = tail;
1443        while !self.nodes.next(t).is_null() {
1444            t = self.nodes.next(t);
1445        }
1446        self.cur_list_mut().tail = t;
1447        if let Some(d) = wrap {
1448            let p = self.new_dir_node(d, true);
1449            self.tail_append(p);
1450        }
1451        let e = self.new_math_node(ms, mg, 1);
1452        self.tail_append(e);
1453        self.cur_list_mut().space_factor = 1000;
1454    }
1455
1456    /// A math node with `\mathsurround` and `\mathsurroundskip`.
1457    fn new_math_node(&mut self, surround: i32, skip: crate::glue::Glue, sub: u16) -> NodeRef {
1458        let p = self.new_node(
1459            id::MATH,
1460            sub,
1461            NodeData::Math {
1462                surround,
1463                glue: crate::glue::Glue::ZERO,
1464            },
1465        );
1466        if (skip.width != 0 || skip.stretch != 0 || skip.shrink != 0)
1467            && let NodeData::Math { glue, .. } = self.nodes.data_mut(p)
1468        {
1469            *glue = skip;
1470        }
1471        p
1472    }
1473
1474    /// Places a finished display (§1199–§1205).
1475    fn finish_displayed_math(&mut self, p: NodeRef, a: NodeRef, l: bool) {
1476        use crate::nodes::glue_sub as gs;
1477        let hl = self.mlist_to_hlist(p, 0, false);
1478        let adj_head = self.nodes.new_temp();
1479        self.ts.adjust_tail = Some(adj_head);
1480        let mut b = self.hpack(hl, PackSpec::NATURAL);
1481        let p = self.nodes.list_ptr(b);
1482        let t = self.ts.adjust_tail.unwrap_or(adj_head);
1483        self.ts.adjust_tail = None;
1484        let mut w = self.nodes.box_data(b).map_or(0, |x| x.width);
1485        let z = self.eqtb.dimen(dp::DISPLAYWIDTH);
1486        let s = self.eqtb.dimen(dp::DISPLAYINDENT);
1487        let (mut e, q) = if a.is_null() {
1488            (0, 0)
1489        } else {
1490            let e = self.nodes.box_data(a).map_or(0, |x| x.width);
1491            let f2 = self.fam_fnt(2, 0);
1492            let mq = self.math_quad_text(f2);
1493            let step = self.eqtb.int(ip::MATHEQNOGAPSTEP);
1494            let gap = if step == 1000 {
1495                mq
1496            } else {
1497                retex_arith::xn_over_d(mq, step, 1000).map_or(0, |r| r.quotient)
1498            };
1499            (e, e + gap)
1500        };
1501        if w + q > z {
1502            // Squeeze the equation as much as possible.
1503            let tot = self.ts.last_totals;
1504            if e != 0
1505                && (w - tot.1[0] + q <= z
1506                    || tot.1[1] != 0
1507                    || tot.1[2] != 0
1508                    || tot.1[3] != 0
1509                    || tot.1[4] != 0)
1510            {
1511                self.nodes.set_list_ptr(b, NodeRef::NULL);
1512                self.nodes.free(b);
1513                b = self.hpack(p, PackSpec::Exactly(z - q));
1514            } else {
1515                e = 0;
1516                if w > z {
1517                    self.nodes.set_list_ptr(b, NodeRef::NULL);
1518                    self.nodes.free(b);
1519                    b = self.hpack(p, PackSpec::Exactly(z));
1520                }
1521            }
1522            w = self.nodes.box_data(b).map_or(0, |x| x.width);
1523        }
1524        self.nodes.set_subtype(b, 6); // equation
1525        // The displacement of the left edge of the equation.
1526        let mut d = half(z - w);
1527        if e > 0 && d < 2 * e {
1528            d = half(z - w - e);
1529            let p = self.nodes.list_ptr(b);
1530            if !p.is_null() && self.nodes.id(p) == id::GLUE {
1531                d = 0;
1532            }
1533        }
1534        // The glue or equation number preceding the display.
1535        let pen = self.new_penalty_node(self.eqtb.int(ip::PREDISPLAYPENALTY));
1536        self.tail_append(pen);
1537        let pds = self.eqtb.dimen(dp::PREDISPLAYSIZE);
1538        let (g1, mut g2) = if d + s <= pds || l {
1539            (gs::ABOVE_DISPLAY, gs::BELOW_DISPLAY)
1540        } else {
1541            (gs::ABOVE_DISPLAY_SHORT, gs::BELOW_DISPLAY_SHORT)
1542        };
1543        let mode = self.eqtb.int(ip::MATHDISPLAYSKIPMODE);
1544        if l && e == 0 {
1545            if let Some(bd) = self.nodes.box_data_mut(a) {
1546                bd.shift = s;
1547            }
1548            self.nodes.set_subtype(a, 7);
1549            self.append_to_vlist(a);
1550            let pen = self.new_penalty_node(10000);
1551            self.tail_append(pen);
1552        } else {
1553            self.append_display_skip(g1, mode, true);
1554        }
1555        // The display and perhaps the equation number. LuaTeX packs an
1556        // equation with its number into a box of the display width:
1557        // kern d, equation, kern, number (or number, kern, equation,
1558        // kern d for \leqno), not shifted by d.
1559        if e != 0 {
1560            let r = self.new_kern_node(z - w - e - d, 0);
1561            self.nodes.set_subtype(a, 7);
1562            // Observed: the kern after a \leqno equation is z - w - d
1563            // (which makes the line wider than z when d was reduced).
1564            let k = self.new_kern_node(if l { z - w - d } else { d }, 0);
1565            if l {
1566                self.nodes.link(a, r);
1567                self.nodes.link(r, b);
1568                self.nodes.link(b, k);
1569                b = a;
1570            } else {
1571                self.nodes.link(k, b);
1572                self.nodes.link(b, r);
1573                self.nodes.link(r, a);
1574                b = k;
1575            }
1576            b = self.hpack(b, PackSpec::NATURAL);
1577            d = 0;
1578        }
1579        if let Some(bd) = self.nodes.box_data_mut(b) {
1580            bd.shift = s + d;
1581        }
1582        self.append_to_vlist(b);
1583        // The glue or equation number following the display.
1584        if !a.is_null() && e == 0 && !l {
1585            let pen = self.new_penalty_node(10000);
1586            self.tail_append(pen);
1587            let aw = self.nodes.box_data(a).map_or(0, |x| x.width);
1588            if let Some(bd) = self.nodes.box_data_mut(a) {
1589                bd.shift = s + z - aw;
1590            }
1591            self.nodes.set_subtype(a, 7);
1592            self.append_to_vlist(a);
1593            g2 = 0;
1594        }
1595        if t != adj_head {
1596            let first = self.nodes.next(adj_head);
1597            let tail = self.cur_list().tail;
1598            self.nodes.link(tail, first);
1599            self.cur_list_mut().tail = t;
1600        }
1601        self.nodes.free(adj_head);
1602        let pen = self.new_penalty_node(self.eqtb.int(ip::POSTDISPLAYPENALTY));
1603        self.tail_append(pen);
1604        if g2 > 0 {
1605            self.append_display_skip(g2, mode, false);
1606        }
1607        self.resume_after_display();
1608    }
1609
1610    /// Appends `\abovedisplayskip` and friends, honouring
1611    /// `\mathdisplayskipmode` (0 and 1: TeX's rule, an above skip always
1612    /// and a below skip always; 2: only when not zero; 3: never).
1613    fn append_display_skip(&mut self, sub: u16, mode: i32, _above: bool) {
1614        let g = self.eqtb.glue(crate::build::glue_param_of_subtype(sub));
1615        let zero = g.width == 0 && g.stretch == 0 && g.shrink == 0;
1616        let skip = match mode {
1617            2 => zero,
1618            3 => true,
1619            _ => false,
1620        };
1621        if !skip {
1622            let n = self.new_param_glue(sub);
1623            self.tail_append(n);
1624        }
1625    }
1626
1627    /// `math_quad(text_size)` from the font in family 2.
1628    fn math_quad_text(&self, f2: u32) -> i32 {
1629        let v = self.math_param_value(0, 2);
1630        if v != crate::codes::UNDEFINED_MATH_PARAM {
1631            v
1632        } else {
1633            self.quad(f2)
1634        }
1635    }
1636
1637    /// The value of math parameter `p` in style `s`.
1638    pub fn math_param_value(&self, p: u32, s: u32) -> i32 {
1639        self.eqtb
1640            .math_param
1641            .get(&(p * 8 + s))
1642            .map_or(crate::codes::UNDEFINED_MATH_PARAM, |x| x.val)
1643    }
1644
1645    /// Resumes the paragraph after a display (`resume_after_display`).
1646    fn resume_after_display(&mut self) {
1647        if self.save.cur_group != GroupCode::MathShift {
1648            self.confusion("display");
1649        }
1650        self.unsave();
1651        let pg = self.cur_list().prev_graf + 3;
1652        self.cur_list_mut().prev_graf = pg;
1653        self.push_nest(Mode::Horizontal);
1654        self.cur_list_mut().space_factor = 1000;
1655        let l = self.cur_lang() as i32;
1656        self.cur_list_mut().clang = l;
1657        let lp = self.new_local_par();
1658        self.tail_append(lp);
1659        // Scan an optional space.
1660        self.get_x_token();
1661        if self.cur_cmd != Cmd::Spacer {
1662            self.back_input();
1663        }
1664        if self.nest.len() == 2 {
1665            self.build_page();
1666        }
1667    }
1668
1669    /// Finishes an alignment in a display (§1206).
1670    pub fn finish_display_alignment(&mut self, p: NodeRef, q: NodeRef, aux_save: i32) {
1671        self.do_assignments();
1672        if self.cur_cmd != Cmd::MathShift {
1673            self.print_err("Missing $$ inserted");
1674            self.help(&[
1675                "Displays can use special alignments (like \\eqalignno)",
1676                "only if nothing but the alignment itself is between $$'s.",
1677            ]);
1678            self.back_error();
1679        } else {
1680            self.check_dollar_follows();
1681        }
1682        self.pop_nest();
1683        let pen = self.new_penalty_node(self.eqtb.int(ip::PREDISPLAYPENALTY));
1684        self.tail_append(pen);
1685        let g = self.new_param_glue(crate::nodes::glue_sub::ABOVE_DISPLAY);
1686        self.tail_append(g);
1687        let tail = self.cur_list().tail;
1688        self.nodes.link(tail, p);
1689        if !p.is_null() {
1690            self.cur_list_mut().tail = q;
1691        }
1692        let pen = self.new_penalty_node(self.eqtb.int(ip::POSTDISPLAYPENALTY));
1693        self.tail_append(pen);
1694        let g = self.new_param_glue(crate::nodes::glue_sub::BELOW_DISPLAY);
1695        self.tail_append(g);
1696        self.cur_list_mut().prev_depth = aux_save;
1697        self.resume_after_display();
1698    }
1699
1700    /// Empties the current math list (`flush_math`).
1701    pub fn flush_math(&mut self) {
1702        let head = self.cur_list().head;
1703        let first = self.nodes.next(head);
1704        self.nodes.flush_list(first);
1705        let inc = self.cur_list().incompleat_noad;
1706        self.nodes.flush_list(inc);
1707        self.nodes.set_next(head, NodeRef::NULL);
1708        self.cur_list_mut().tail = head;
1709        self.cur_list_mut().incompleat_noad = NodeRef::NULL;
1710    }
1711
1712    /// Inserts the tokens of a list as if typed (used by error recovery).
1713    pub fn ins_tokens(&mut self, toks: Vec<Token>) {
1714        self.ins_list(Rc::from(toks));
1715    }
1716}
1717
1718pub use retex_arith::half;
1719
1720/// The parts of a TeX 27-bit delimiter code.
1721fn classic_delimiter(v: i32) -> Option<(u32, u32, u32, u32)> {
1722    if v < 0 {
1723        return None;
1724    }
1725    let v = v as u32;
1726    Some((
1727        (v / 0o4000000) % 16,
1728        (v / 0o10000) % 256,
1729        (v / 256) % 16,
1730        v % 256,
1731    ))
1732}