Skip to main content

pulldown_latex/parser/
primitives.rs

1//! A module that implements the behavior of every primitive of the supported LaTeX syntax. This
2//! includes every primitive macro and active character.
3
4use core::panic;
5
6use crate::event::{
7    ArrayColumn as AC, ColorChange as CC, ColorTarget as CT, ColumnAlignment, Content as C,
8    DelimiterSize, DelimiterType, Dimension, DimensionUnit, EnvironmentFlow, Event as E, Font,
9    Grouping as G, GroupingKind, Line, MatrixType, RelationContent, ScriptPosition as SP,
10    ScriptType as ST, StateChange as SC, Style as S, Visual as V,
11};
12
13use super::{
14    lex,
15    tables::{
16        char_delimiter_map, control_sequence_delimiter_map, is_binary, is_relation, token_to_delim,
17    },
18    AlignmentCount, Argument, CharToken, ErrorKind, InnerParser, InnerResult, Instruction as I,
19    Token,
20};
21
22impl<'b, 'store> InnerParser<'b, 'store> {
23    /// Handle a character token, returning a corresponding event.
24    ///
25    /// This function specially treats numbers as `mi`.
26    ///
27    /// ## Panics
28    /// - This function will panic if the `\` or `%` character is given
29    pub(super) fn handle_char_token(&mut self, token: CharToken<'store>) -> InnerResult<()> {
30        let instruction = I::Event(match token.into() {
31            '\\' => panic!("(internal error: please report) the `\\` character should never be observed as a token"),
32            '%' => panic!("(internal error: please report) the `%` character should never be observed as a token"),
33            '_' => {
34                if self.state.handling_argument {
35                    return Err(ErrorKind::ScriptAsArgument)
36                }
37                self.buffer.extend([
38                    I::Event(E::Begin(G::Normal)),
39                ]);
40                self.content = token.as_str();
41                E::End
42            }
43            '^' => {
44                if self.state.handling_argument {
45                    return Err(ErrorKind::ScriptAsArgument)
46                }
47                self.buffer.extend([
48                    I::Event(E::Begin(G::Normal)),
49                ]);
50                self.content = token.as_str();
51                E::End
52            }
53            '$' => return Err(ErrorKind::MathShift),
54            '#' => return Err(ErrorKind::HashSign),
55            '&' if self
56                    .state
57                    .allowed_alignment_count
58                    .as_deref()
59                    .is_some_and(AlignmentCount::can_increment) && !self.state.handling_argument => {
60                       self
61                           .state
62                           .allowed_alignment_count
63                           .as_mut()
64                           .expect("we have checked that `allowed_alignment_count` is Some")
65                           .increment();
66                        E::EnvironmentFlow(EnvironmentFlow::Alignment)
67                    },
68            '&' => return Err(ErrorKind::Alignment),
69            '{' => {
70                let str = &mut self.content;
71                let group = lex::group_content(str, GroupingKind::Normal)?;
72                self.buffer.extend([
73                    I::Event(E::Begin(G::Normal)),
74                    I::SubGroup { content: group, allowed_alignment_count: None },
75                    I::Event(E::End)
76                ]);
77                return Ok(())
78            },
79            '}' => {
80                return Err(ErrorKind::UnbalancedGroup(None))
81            },
82
83            '~' => {
84                E::Content(C::Text("&nbsp;"))
85            },
86
87            '0'..='9' => {
88                let content = token.as_str();
89                let len = if self.state.handling_argument {
90                    1
91                } else {
92                    let mut len = content
93                        .chars()
94                        .skip(1)
95                        .take_while(|&c| matches!(c, '.' | ',' | '0'..='9'))
96                        .count()
97                        + 1;
98                    if matches!(content.as_bytes()[len - 1], b'.' | b',') {
99                        len -= 1;
100                    }
101                    len
102                };
103                let (number, rest) = content.split_at(len);
104                self.content = rest;
105                self.buffer
106                    .push(I::Event(E::Content(C::Number(number))));
107                return Ok(())
108            }
109            // Punctuation
110            '.' | ',' | ';' => E::Content(C::Punctuation(token.into())),
111            '\'' => ordinary('′'),
112            '-' => binary('−'),
113            '*' => binary('∗'),
114            c if is_binary(c) => binary(c),
115            c if is_relation(c) => relation(c),
116            c if char_delimiter_map(c).is_some() => {
117                let (content, ty) = char_delimiter_map(c).unwrap();
118                if ty == DelimiterType::Fence {
119                    ordinary(content)
120                } else {
121                    E::Content(C::Delimiter {
122                        content,
123                        size: None,
124                        ty,
125                    })
126                }
127            }
128            c => ordinary(c),
129        });
130        self.buffer.push(instruction);
131        Ok(())
132    }
133
134    /// Handle a supported control sequence, pushing instructions to the provided stack.
135    pub(super) fn handle_primitive(&mut self, control_sequence: &'store str) -> InnerResult<()> {
136        let event = match control_sequence {
137            "arccos" | "cos" | "csc" | "exp" | "ker" | "sinh" | "arcsin" | "cosh" | "deg"
138            | "lg" | "ln" | "arctan" | "cot" | "det" | "hom" | "log" | "sec" | "tan" | "arg"
139            | "coth" | "dim" | "sin" | "tanh" | "sgn" => E::Content(C::Function(control_sequence)),
140            "lim" | "Pr" | "sup" | "max" | "inf" | "gcd" | "min" => {
141                self.state.allow_script_modifiers = true;
142                self.state.script_position = SP::Movable;
143                E::Content(C::Function(control_sequence))
144            }
145            "liminf" => {
146                self.state.allow_script_modifiers = true;
147                self.state.script_position = SP::Movable;
148                E::Content(C::Function("lim inf"))
149            }
150            "limsup" => {
151                self.state.allow_script_modifiers = true;
152                self.state.script_position = SP::Movable;
153                E::Content(C::Function("lim sup"))
154            }
155
156            "operatorname" => {
157                self.state.allow_script_modifiers = true;
158                let argument = lex::argument(&mut self.content)?;
159                match argument {
160                    Argument::Token(Token::ControlSequence(_)) => {
161                        return Err(ErrorKind::ControlSequenceAsArgument)
162                    }
163                    Argument::Token(Token::Character(char_)) => {
164                        E::Content(C::Function(char_.as_str()))
165                    }
166                    Argument::Group(content) => E::Content(C::Function(content)),
167                }
168            }
169            "bmod" => E::Content(C::Function("mod")),
170            "pmod" => {
171                let argument = lex::argument(&mut self.content)?;
172                self.buffer.extend([
173                    I::Event(E::Space {
174                        width: Some(Dimension::new(1., DimensionUnit::Em)),
175                        height: None,
176                        depth: None,
177                    }),
178                    I::Event(E::Begin(G::Normal)),
179                    I::Event(E::Content(C::Delimiter {
180                        content: '(',
181                        size: None,
182                        ty: DelimiterType::Open,
183                    })),
184                    I::Event(E::Content(C::Function("mod"))),
185                ]);
186                self.handle_argument(argument)?;
187                self.buffer.extend([
188                    I::Event(E::End),
189                    I::Event(E::Content(C::Delimiter {
190                        content: ')',
191                        size: None,
192                        ty: DelimiterType::Close,
193                    })),
194                ]);
195                return Ok(());
196            }
197
198            // TODO: Operators with '*', for operatorname* and friends
199
200            ////////////////////////////////
201            // Atom-type (\math*) commands //
202            ////////////////////////////////
203            // These commands set the math class of their argument, which
204            // influences spacing in MathML output.
205            "mathord" => return self.atom_group(AtomClass::Ord),
206            "mathop" => {
207                self.state.allow_script_modifiers = true;
208                self.state.script_position = SP::Movable;
209                return self.atom_group(AtomClass::Op);
210            }
211            "mathbin" => return self.atom_group(AtomClass::Bin),
212            "mathrel" => return self.atom_group(AtomClass::Rel),
213            "mathopen" => return self.atom_group(AtomClass::Open),
214            "mathclose" => return self.atom_group(AtomClass::Close),
215            "mathpunct" => return self.atom_group(AtomClass::Punct),
216            "mathinner" => return self.atom_group(AtomClass::Inner),
217
218            /////////////////////////
219            // Non-Latin Alphabets //
220            /////////////////////////
221            // Lowercase Greek letters
222            "alpha" => ordinary('α'),
223            "beta" => ordinary('β'),
224            "gamma" => ordinary('γ'),
225            "delta" => ordinary('δ'),
226            "epsilon" => ordinary('ϵ'),
227            "zeta" => ordinary('ζ'),
228            "eta" => ordinary('η'),
229            "theta" => ordinary('θ'),
230            "iota" => ordinary('ι'),
231            "kappa" => ordinary('κ'),
232            "lambda" => ordinary('λ'),
233            "mu" => ordinary('µ'),
234            "nu" => ordinary('ν'),
235            "xi" => ordinary('ξ'),
236            "pi" => ordinary('π'),
237            "rho" => ordinary('ρ'),
238            "sigma" => ordinary('σ'),
239            "tau" => ordinary('τ'),
240            "upsilon" => ordinary('υ'),
241            "phi" => ordinary('ϕ'),
242            "chi" => ordinary('χ'),
243            "psi" => ordinary('ψ'),
244            "omega" => ordinary('ω'),
245            "omicron" => ordinary('ο'),
246            // Uppercase Greek letters
247            "Alpha" => ordinary('Α'),
248            "Beta" => ordinary('Β'),
249            "Gamma" => ordinary('Γ'),
250            "Delta" => ordinary('Δ'),
251            "Epsilon" => ordinary('Ε'),
252            "Zeta" => ordinary('Ζ'),
253            "Eta" => ordinary('Η'),
254            "Theta" => ordinary('Θ'),
255            "Iota" => ordinary('Ι'),
256            "Kappa" => ordinary('Κ'),
257            "Lambda" => ordinary('Λ'),
258            "Mu" => ordinary('Μ'),
259            "Nu" => ordinary('Ν'),
260            "Xi" => ordinary('Ξ'),
261            "Pi" => ordinary('Π'),
262            "Rho" => ordinary('Ρ'),
263            "Sigma" => ordinary('Σ'),
264            "Tau" => ordinary('Τ'),
265            "Upsilon" => ordinary('Υ'),
266            "Phi" => ordinary('Φ'),
267            "Chi" => ordinary('Χ'),
268            "Psi" => ordinary('Ψ'),
269            "Omega" => ordinary('Ω'),
270            "Omicron" => ordinary('Ο'),
271            // Lowercase Greek Variants
272            "varepsilon" => ordinary('ε'),
273            "vartheta" => ordinary('ϑ'),
274            "varkappa" => ordinary('ϰ'),
275            "varrho" => ordinary('ϱ'),
276            "varsigma" => ordinary('ς'),
277            "varpi" => ordinary('ϖ'),
278            "varphi" => ordinary('φ'),
279            // Uppercase Greek Variants
280            "varGamma" => ordinary('𝛤'),
281            "varDelta" => ordinary('𝛥'),
282            "varTheta" => ordinary('𝛩'),
283            "varLambda" => ordinary('𝛬'),
284            "varXi" => ordinary('𝛯'),
285            "varPi" => ordinary('𝛱'),
286            "varSigma" => ordinary('𝛴'),
287            "varUpsilon" => ordinary('𝛶'),
288            "varPhi" => ordinary('𝛷'),
289            "varPsi" => ordinary('𝛹'),
290            "varOmega" => ordinary('𝛺'),
291
292            // Hebrew letters
293            "aleph" => ordinary('ℵ'),
294            "beth" => ordinary('ℶ'),
295            "gimel" => ordinary('ℷ'),
296            "daleth" => ordinary('ℸ'),
297            // Other symbols
298            "digamma" => ordinary('ϝ'),
299            "eth" => ordinary('ð'),
300            "ell" => ordinary('ℓ'),
301            "nabla" => ordinary('∇'),
302            "partial" => ordinary('∂'),
303            "Finv" => ordinary('Ⅎ'),
304            "Game" => ordinary('ℷ'),
305            "hbar" | "hslash" => ordinary('ℏ'),
306            "imath" => ordinary('ı'),
307            "jmath" => ordinary('ȷ'),
308            "Im" => ordinary('ℑ'),
309            "Re" => ordinary('ℜ'),
310            "wp" => ordinary('℘'),
311            "Bbbk" => ordinary('𝕜'),
312            "Angstrom" => ordinary('Å'),
313            "backepsilon" => ordinary('϶'),
314
315            ///////////////////////////
316            // Symbols & Punctuation //
317            ///////////////////////////
318            "dots" => {
319                if self.content.trim_start().starts_with(['.', ',']) {
320                    ordinary('…')
321                } else {
322                    ordinary('⋯')
323                }
324            }
325            "ldots" | "dotso" | "dotsc" => ordinary('…'),
326            "cdots" | "dotsi" | "dotsm" | "dotsb" | "idotsin" => ordinary('⋯'),
327            "ddots" => ordinary('⋱'),
328            "iddots" => ordinary('⋰'),
329            "vdots" => ordinary('⋮'),
330            "mathellipsis" => ordinary('…'),
331            "infty" => ordinary('∞'),
332            "checkmark" => ordinary('✓'),
333            "ballotx" => ordinary('✗'),
334            "dagger" | "dag" => ordinary('†'),
335            "ddagger" | "ddag" => ordinary('‡'),
336            "angle" => ordinary('∠'),
337            "measuredangle" => ordinary('∡'),
338            "lq" => ordinary('‘'),
339            "Box" => ordinary('□'),
340            "sphericalangle" => ordinary('∢'),
341            "square" => ordinary('□'),
342            "top" => ordinary('⊤'),
343            "rq" => ordinary('′'),
344            "blacksquare" => ordinary('■'),
345            "bot" => ordinary('⊥'),
346            "triangledown" => ordinary('▽'),
347            "Bot" => ordinary('⫫'),
348            "triangleleft" => ordinary('◃'),
349            "triangleright" => ordinary('▹'),
350            "cent" => ordinary('¢'),
351            "colon" | "ratio" | "vcentcolon" => ordinary(':'),
352            "bigtriangledown" => ordinary('▽'),
353            "pounds" | "mathsterling" => ordinary('£'),
354            "bigtriangleup" => ordinary('△'),
355            "blacktriangle" => ordinary('▲'),
356            "blacktriangledown" => ordinary('▼'),
357            "yen" => ordinary('¥'),
358            "blacktriangleleft" => ordinary('◀'),
359            "euro" => ordinary('€'),
360            "blacktriangleright" => ordinary('▶'),
361            "Diamond" => ordinary('◊'),
362            "degree" => ordinary('°'),
363            "lozenge" => ordinary('◊'),
364            "blacklozenge" => ordinary('⧫'),
365            "mho" => ordinary('℧'),
366            "bigstar" => ordinary('★'),
367            "diagdown" => ordinary('╲'),
368            "maltese" => ordinary('✠'),
369            "diagup" => ordinary('╱'),
370            "P" => ordinary('¶'),
371            "clubsuit" => ordinary('♣'),
372            "varclubsuit" => ordinary('♧'),
373            "S" => ordinary('§'),
374            "diamondsuit" => ordinary('♢'),
375            "vardiamondsuit" => ordinary('♦'),
376            "copyright" => ordinary('©'),
377            "heartsuit" => ordinary('♡'),
378            "varheartsuit" => ordinary('♥'),
379            "circledR" => ordinary('®'),
380            "spadesuit" => ordinary('♠'),
381            "varspadesuit" => ordinary('♤'),
382            "circledS" => ordinary('Ⓢ'),
383            "female" => ordinary('♀'),
384            "male" => ordinary('♂'),
385            "astrosun" => ordinary('☉'),
386            "sun" => ordinary('☼'),
387            "leftmoon" => ordinary('☾'),
388            "rightmoon" => ordinary('☽'),
389            "smiley" => ordinary('☺'),
390            "Earth" => ordinary('⊕'),
391            "flat" => ordinary('♭'),
392            "standardstate" => ordinary('⦵'),
393            "natural" => ordinary('♮'),
394            "sharp" => ordinary('♯'),
395            "permil" => ordinary('‰'),
396            "QED" => ordinary('∎'),
397            "lightning" => ordinary('↯'),
398            "diameter" => ordinary('⌀'),
399            "leftouterjoin" => ordinary('⟕'),
400            "rightouterjoin" => ordinary('⟖'),
401            "concavediamond" => ordinary('⟡'),
402            "concavediamondtickleft" => ordinary('⟢'),
403            "concavediamondtickright" => ordinary('⟣'),
404            "fullouterjoin" => ordinary('⟗'),
405            "triangle" | "vartriangle" => ordinary('△'),
406            "whitesquaretickleft" => ordinary('⟤'),
407            "whitesquaretickright" => ordinary('⟥'),
408
409            ////////////////////////
410            // Font state changes //
411            ////////////////////////
412            // LaTeX native absolute font changes (old behavior a.k.a NFSS 1)
413            "bf" => self.font_change(Font::Bold),
414            "cal" => self.font_change(Font::Script),
415            "it" => self.font_change(Font::Italic),
416            "rm" => self.font_change(Font::UpRight),
417            "sf" => self.font_change(Font::SansSerif),
418            "tt" => self.font_change(Font::Monospace),
419            // amsfonts font changes (old behavior a.k.a NFSS 1)
420            // unicode-math font changes (old behavior a.k.a NFSS 1)
421            // changes, as described in https://mirror.csclub.uwaterloo.ca/CTAN/macros/unicodetex/latex/unicode-math/unicode-math.pdf
422            // (section. 3.1)
423            "mathbf" | "symbf" | "mathbfup" | "symbfup" => {
424                return self.font_group(Some(Font::Bold))
425            }
426            "boldsymbol" => return self.font_group(Some(Font::BoldSymbol)),
427            "mathcal" | "symcal" => return self.font_group(Some(Font::Script)),
428            "mathit" | "symit" => return self.font_group(Some(Font::Italic)),
429            "mathrm" | "symrm" | "mathup" | "symup" => return self.font_group(Some(Font::UpRight)),
430            "mathsf" | "symsf" | "mathsfup" | "symsfup" => {
431                return self.font_group(Some(Font::SansSerif))
432            }
433            "mathtt" | "symtt" => return self.font_group(Some(Font::Monospace)),
434            "mathbb" | "symbb" => return self.font_group(Some(Font::DoubleStruck)),
435            "mathbbit" | "symbbit" => return self.font_group(Some(Font::DoubleStruckItalic)),
436            "mathfrak" | "symfrak" => return self.font_group(Some(Font::Fraktur)),
437            "mathbfcal" | "symbfcal" => return self.font_group(Some(Font::BoldScript)),
438            "mathsfit" | "symsfit" => return self.font_group(Some(Font::SansSerifItalic)),
439            "mathbfit" | "symbfit" => return self.font_group(Some(Font::BoldItalic)),
440            "mathbffrak" | "symbffrak" => return self.font_group(Some(Font::BoldFraktur)),
441            "mathbfsfup" | "symbfsfup" => return self.font_group(Some(Font::BoldSansSerif)),
442            "mathbfsfit" | "symbfsfit" => return self.font_group(Some(Font::SansSerifBoldItalic)),
443            "mathnormal" | "symnormal" => return self.font_group(None),
444
445            ////////////////////////
446            // Style state change //
447            ////////////////////////
448            "displaystyle" => self.style_change(S::Display),
449            "textstyle" => self.style_change(S::Text),
450            "scriptstyle" => self.style_change(S::Script),
451            "scriptscriptstyle" => self.style_change(S::ScriptScript),
452
453            ////////////////////////
454            // Color state change //
455            ////////////////////////
456            "color" => {
457                let Argument::Group(color) = lex::argument(&mut self.content)? else {
458                    return Err(ErrorKind::Argument);
459                };
460                self.state.skip_scripts = true;
461
462                let color = lex::color(color).ok_or(ErrorKind::UnknownColor)?;
463                E::StateChange(SC::Color(CC {
464                    color,
465                    target: CT::Text,
466                }))
467            }
468            "textcolor" => {
469                let str = &mut self.content;
470                let Argument::Group(color) = lex::argument(str)? else {
471                    return Err(ErrorKind::Argument);
472                };
473
474                let color = lex::color(color).ok_or(ErrorKind::UnknownColor)?;
475                let modified = lex::argument(str)?;
476
477                self.buffer.extend([
478                    I::Event(E::Begin(G::Normal)),
479                    I::Event(E::StateChange(SC::Color(CC {
480                        color,
481                        target: CT::Text,
482                    }))),
483                ]);
484                self.handle_argument(modified)?;
485                E::End
486            }
487            "colorbox" => {
488                let Argument::Group(color) = lex::argument(&mut self.content)? else {
489                    return Err(ErrorKind::Argument);
490                };
491
492                let color = lex::color(color).ok_or(ErrorKind::UnknownColor)?;
493                self.buffer.extend([
494                    I::Event(E::Begin(G::Normal)),
495                    I::Event(E::StateChange(SC::Color(CC {
496                        color,
497                        target: CT::Background,
498                    }))),
499                ]);
500                self.text_argument(None)?;
501                E::End
502            }
503            "fcolorbox" => {
504                let str = &mut self.content;
505                let Argument::Group(frame_color) = lex::argument(str)? else {
506                    return Err(ErrorKind::Argument);
507                };
508                let Argument::Group(background_color) = lex::argument(str)? else {
509                    return Err(ErrorKind::Argument);
510                };
511
512                let frame_color = lex::color(frame_color).ok_or(ErrorKind::UnknownColor)?;
513                let background_color =
514                    lex::color(background_color).ok_or(ErrorKind::UnknownColor)?;
515                self.buffer.extend([
516                    I::Event(E::Begin(G::Normal)),
517                    I::Event(E::StateChange(SC::Color(CC {
518                        color: frame_color,
519                        target: CT::Border,
520                    }))),
521                    I::Event(E::StateChange(SC::Color(CC {
522                        color: background_color,
523                        target: CT::Background,
524                    }))),
525                ]);
526                self.text_argument(None)?;
527                E::End
528            }
529
530            ///////////////////////////////
531            // Delimiters size modifiers //
532            ///////////////////////////////
533            // Sizes taken from `texzilla`
534            // Big left and right seem to not care about which delimiter is used. i.e., \bigl) and \bigr) are the same.
535            "big" | "bigl" | "bigr" | "bigm" => return self.sized_delim(DelimiterSize::Big),
536            "Big" | "Bigl" | "Bigr" | "Bigm" => return self.sized_delim(DelimiterSize::BIG),
537            "bigg" | "biggl" | "biggr" | "biggm" => return self.sized_delim(DelimiterSize::Bigg),
538            "Bigg" | "Biggl" | "Biggr" | "Biggm" => return self.sized_delim(DelimiterSize::BIGG),
539
540            "left" => {
541                let curr_str = &mut self.content;
542                let opening = if let Some(rest) = curr_str.strip_prefix('.') {
543                    *curr_str = rest;
544                    None
545                } else {
546                    Some(lex::delimiter(curr_str)?.0)
547                };
548
549                let curr_str = &mut self.content;
550                let group_content = lex::group_content(curr_str, GroupingKind::LeftRight)?;
551                let closing = if let Some(rest) = curr_str.strip_prefix('.') {
552                    *curr_str = rest;
553                    None
554                } else {
555                    Some(lex::delimiter(curr_str)?.0)
556                };
557
558                self.buffer.extend([
559                    I::Event(E::Begin(G::LeftRight(opening, closing))),
560                    I::SubGroup {
561                        content: group_content,
562                        allowed_alignment_count: None,
563                    },
564                    I::Event(E::End),
565                ]);
566
567                return Ok(());
568            }
569            // TODO: Check the conditions for this op. Does it need to be
570            // within a left-right group?
571            "middle" => {
572                let delimiter = lex::delimiter(&mut self.content)?;
573                E::Content(C::Delimiter {
574                    content: delimiter.0,
575                    size: Some(DelimiterSize::Big),
576                    ty: DelimiterType::Fence,
577                })
578            }
579            "right" => {
580                return Err(ErrorKind::UnbalancedGroup(None));
581            }
582
583            ///////////////////
584            // Big Operators //
585            ///////////////////
586            // NOTE: All of the following operators allow limit modifiers.
587            // The following operators have above and below limits by default.
588            "sum" => self.large_op('∑', true),
589            "prod" => self.large_op('∏', true),
590            "coprod" => self.large_op('∐', true),
591            "bigvee" => self.large_op('⋁', true),
592            "bigwedge" => self.large_op('⋀', true),
593            "bigcup" => self.large_op('⋃', true),
594            "bigcap" => self.large_op('⋂', true),
595            "biguplus" => self.large_op('⨄', true),
596            "bigoplus" => self.large_op('⨁', true),
597            "bigotimes" => self.large_op('⨂', true),
598            "bigodot" => self.large_op('⨀', true),
599            "bigsqcup" => self.large_op('⨆', true),
600            "bigsqcap" => self.large_op('⨅', true),
601            "bigtimes" => self.large_op('⨉', true),
602            "intop" => self.large_op('∫', true),
603            // The following operators do not have above and below limits by default.
604            "int" => self.large_op('∫', false),
605            "iint" => self.large_op('∬', false),
606            "iiint" => self.large_op('∭', false),
607            "smallint" => {
608                self.state.allow_script_modifiers = true;
609                E::Content(C::LargeOp {
610                    content: '∫',
611                    small: true,
612                })
613            }
614            "iiiint" => self.large_op('⨌', false),
615            "intcap" => self.large_op('⨙', false),
616            "intcup" => self.large_op('⨚', false),
617            "oint" => self.large_op('∮', false),
618            "varointclockwise" => self.large_op('∲', false),
619            "intclockwise" => self.large_op('∱', false),
620            "oiint" => self.large_op('∯', false),
621            "pointint" => self.large_op('⨕', false),
622            "rppolint" => self.large_op('⨒', false),
623            "scpolint" => self.large_op('⨓', false),
624            "oiiint" => self.large_op('∰', false),
625            "intlarhk" => self.large_op('⨗', false),
626            "sqint" => self.large_op('⨖', false),
627            "intx" => self.large_op('⨘', false),
628            "intbar" => self.large_op('⨍', false),
629            "intBar" => self.large_op('⨎', false),
630            "fint" => self.large_op('⨏', false),
631
632            /////////////
633            // Accents //
634            /////////////
635            "acute" => return self.accent('´', false),
636            "bar" | "overline" => return self.accent('‾', false),
637            "underbar" | "underline" => return self.underscript('_'),
638            "breve" => return self.accent('˘', false),
639            "check" => return self.accent('ˇ', false),
640            "dot" => return self.accent('˙', false),
641            "ddot" => return self.accent('¨', false),
642            "grave" => return self.accent('`', false),
643            "hat" => return self.accent('^', false),
644            "tilde" => return self.accent('~', false),
645            "vec" => return self.accent('→', false),
646            "mathring" => return self.accent('˚', false),
647
648            // Arrows
649            "overleftarrow" => return self.accent('←', true),
650            "underleftarrow" => return self.underscript('←'),
651            "overrightarrow" => return self.accent('→', true),
652            "Overrightarrow" => return self.accent('⇒', true),
653            "underrightarrow" => return self.underscript('→'),
654            "overleftrightarrow" => return self.accent('↔', true),
655            "underleftrightarrow" => return self.underscript('↔'),
656            "overleftharpoon" => return self.accent('↼', true),
657            "overrightharpoon" => return self.accent('⇀', true),
658
659            // Wide ops
660            "widecheck" => return self.accent('ˇ', true),
661            "widehat" => return self.accent('^', true),
662            "widetilde" => return self.accent('~', true),
663            "wideparen" | "overparen" => return self.accent('⏜', true),
664
665            // Groups
666            "overgroup" => {
667                self.state.script_position = SP::AboveBelow;
668                return self.accent('⏠', true);
669            }
670            "undergroup" => {
671                self.state.script_position = SP::AboveBelow;
672                return self.underscript('⏡');
673            }
674            "overbrace" => {
675                self.state.script_position = SP::AboveBelow;
676                return self.accent('⏞', true);
677            }
678            "underbrace" => {
679                self.state.script_position = SP::AboveBelow;
680                return self.underscript('⏟');
681            }
682            "underparen" => {
683                self.state.script_position = SP::AboveBelow;
684                return self.underscript('⏝');
685            }
686            "overbracket" => {
687                self.state.script_position = SP::AboveBelow;
688                return self.accent('⎴', true);
689            }
690            "underbracket" => {
691                self.state.script_position = SP::AboveBelow;
692                return self.underscript('⎵');
693            }
694
695            // Primes
696            "prime" => ordinary('′'),
697            "dprime" => ordinary('″'),
698            "trprime" => ordinary('‴'),
699            "qprime" => ordinary('⁗'),
700            "backprime" => ordinary('‵'),
701            "backdprime" => ordinary('‶'),
702            "backtrprime" => ordinary('‷'),
703
704            /////////////
705            // Spacing //
706            /////////////
707            "," | "thinspace" => E::Space {
708                width: Some(Dimension::new(3. / 18., DimensionUnit::Em)),
709                height: None,
710                depth: None,
711            },
712            ">" | ":" | "medspace" => E::Space {
713                width: Some(Dimension::new(4. / 18., DimensionUnit::Em)),
714                height: None,
715                depth: None,
716            },
717            ";" | "thickspace" => E::Space {
718                width: Some(Dimension::new(5. / 18., DimensionUnit::Em)),
719                height: None,
720                depth: None,
721            },
722            "enspace" => E::Space {
723                width: Some(Dimension::new(0.5, DimensionUnit::Em)),
724                height: None,
725                depth: None,
726            },
727            "quad" => E::Space {
728                width: Some(Dimension::new(1., DimensionUnit::Em)),
729                height: None,
730                depth: None,
731            },
732            "qquad" => E::Space {
733                width: Some(Dimension::new(2., DimensionUnit::Em)),
734                height: None,
735                depth: None,
736            },
737            "mathstrut" => E::Space {
738                width: None,
739                height: Some(Dimension::new(0.7, DimensionUnit::Em)),
740                depth: None,
741            },
742            "strut" => E::Space {
743                width: None,
744                height: Some(Dimension::new(1.0, DimensionUnit::Em)),
745                depth: None,
746            },
747            "~" | "nobreakspace" => E::Content(C::Text("&nbsp;")),
748            // Variable spacing
749            "kern" => {
750                let dimension = lex::dimension_or_braced(&mut self.content)?;
751                E::Space {
752                    width: Some(dimension),
753                    height: None,
754                    depth: None,
755                }
756            }
757            "hskip" => {
758                let glue = lex::glue_or_braced(&mut self.content)?;
759                E::Space {
760                    width: Some(glue.0),
761                    height: None,
762                    depth: None,
763                }
764            }
765            "mkern" => {
766                let dimension = lex::dimension_or_braced(&mut self.content)?;
767                if dimension.unit == DimensionUnit::Mu {
768                    E::Space {
769                        width: Some(dimension),
770                        height: None,
771                        depth: None,
772                    }
773                } else {
774                    return Err(ErrorKind::MathUnit);
775                }
776            }
777            "mskip" => {
778                let glue = lex::glue_or_braced(&mut self.content)?;
779                if glue.0.unit == DimensionUnit::Mu
780                    && glue
781                        .1
782                        .map_or(true, |Dimension { unit, .. }| unit == DimensionUnit::Mu)
783                    && glue
784                        .2
785                        .map_or(true, |Dimension { unit, .. }| unit == DimensionUnit::Mu)
786                {
787                    E::Space {
788                        width: Some(glue.0),
789                        height: None,
790                        depth: None,
791                    }
792                } else {
793                    return Err(ErrorKind::MathUnit);
794                }
795            }
796            "hspace" => {
797                let Argument::Group(mut argument) = lex::argument(&mut self.content)? else {
798                    return Err(ErrorKind::DimensionArgument);
799                };
800                let glue = lex::glue(&mut argument)?;
801                E::Space {
802                    width: Some(glue.0),
803                    height: None,
804                    depth: None,
805                }
806            }
807            // MathJax extension: `\Space{width}{height}{depth}`.
808            "Space" => {
809                let Argument::Group(mut width_arg) = lex::argument(&mut self.content)? else {
810                    return Err(ErrorKind::DimensionArgument);
811                };
812                let width = lex::dimension(&mut width_arg)?;
813                let Argument::Group(mut height_arg) = lex::argument(&mut self.content)? else {
814                    return Err(ErrorKind::DimensionArgument);
815                };
816                let height = lex::dimension(&mut height_arg)?;
817                let Argument::Group(mut depth_arg) = lex::argument(&mut self.content)? else {
818                    return Err(ErrorKind::DimensionArgument);
819                };
820                let depth = lex::dimension(&mut depth_arg)?;
821                E::Space {
822                    width: Some(width),
823                    height: Some(height),
824                    depth: Some(depth),
825                }
826            }
827            // Negative spacing
828            "!" | "negthinspace" => E::Space {
829                width: Some(Dimension::new(-3. / 18., DimensionUnit::Em)),
830                height: None,
831                depth: None,
832            },
833            "negmedspace" => E::Space {
834                width: Some(Dimension::new(-4. / 18., DimensionUnit::Em)),
835                height: None,
836                depth: None,
837            },
838            "negthickspace" => E::Space {
839                width: Some(Dimension::new(-5. / 18., DimensionUnit::Em)),
840                height: None,
841                depth: None,
842            },
843
844            ////////////////////////
845            // Logic & Set Theory //
846            ////////////////////////
847            "forall" => ordinary('∀'),
848            "exists" => ordinary('∃'),
849            "complement" => ordinary('∁'),
850            "nexists" => ordinary('∄'),
851            "neg" | "lnot" => ordinary('¬'),
852
853            "therefore" => relation('∴'),
854            "because" => relation('∵'),
855            "subset" => relation('⊂'),
856            "supset" => relation('⊃'),
857            "strictif" => relation('⥽'),
858            "strictfi" => relation('⥼'),
859            "mapsto" => relation('↦'),
860            "implies" => relation('⟹'),
861            "mid" => relation('∣'),
862            "to" => relation('→'),
863            "impliedby" => relation('⟸'),
864            "in" | "isin" => relation('∈'),
865            "ni" => relation('∋'),
866            "gets" => relation('←'),
867            "iff" => relation('⟺'),
868            "notni" => relation('∌'),
869
870            "land" => binary('∧'),
871
872            "emptyset" => ordinary('∅'),
873            "varnothing" => ordinary('⌀'),
874
875            //////////////////////
876            // Binary Operators //
877            //////////////////////
878            "ldotp" => binary('.'),
879            "cdotp" => binary('·'),
880            "cdot" => binary('⋅'),
881            "centerdot" => binary('·'),
882            "circ" => binary('∘'),
883            "bullet" => binary('∙'),
884            "circledast" => binary('⊛'),
885            "circledcirc" => binary('⊚'),
886            "circleddash" => binary('⊝'),
887            "bigcirc" => binary('◯'),
888            "leftthreetimes" => binary('⋋'),
889            "rhd" => binary('⊳'),
890            "lhd" => binary('⊲'),
891            "rightthreetimes" => binary('⋌'),
892            "rtimes" => binary('⋊'),
893            "ltimes" => binary('⋉'),
894            "leftmodels" => binary('⊨'),
895            "amalg" => binary('⨿'),
896            "ast" => binary('*'),
897            "asymp" => binary('≍'),
898            "And" | "with" => binary('&'),
899            "lor" => binary('∨'),
900            "setminus" => binary('∖'),
901            "Cup" => binary('⋓'),
902            "cup" => binary('∪'),
903            "sqcup" => binary('⊔'),
904            "sqcap" => binary('⊓'),
905            "lessdot" => binary('⋖'),
906            "smallsetminus" => E::Content(C::BinaryOp {
907                content: '∖',
908                small: false,
909            }),
910            "barwedge" => binary('⌅'),
911            "curlyvee" => binary('⋎'),
912            "curlywedge" => binary('⋏'),
913            "sslash" => binary('⫽'),
914            "div" => binary('÷'),
915            "mp" => binary('∓'),
916            "times" => binary('×'),
917            "boxdot" => binary('⊡'),
918            "divideontimes" => binary('⋇'),
919            "odot" => binary('⊙'),
920            "unlhd" => binary('⊴'),
921            "boxminus" => binary('⊟'),
922            "dotplus" => binary('∔'),
923            "ominus" => binary('⊖'),
924            "unrhd" => binary('⊵'),
925            "boxplus" => binary('⊞'),
926            "doublebarwedge" => binary('⩞'),
927            "oplus" => binary('⊕'),
928            "uplus" => binary('⊎'),
929            "boxtimes" => binary('⊠'),
930            "doublecap" => binary('⋒'),
931            "otimes" => binary('⊗'),
932            "vee" => binary('∨'),
933            "veebar" => binary('⊻'),
934            "Cap" => binary('⋒'),
935            "parr" => binary('⅋'),
936            "wedge" => binary('∧'),
937            "cap" => binary('∩'),
938            "gtrdot" => binary('⋗'),
939            "pm" => binary('±'),
940            "intercal" => binary('⊺'),
941            "wr" => binary('≀'),
942            "circledvert" => binary('⦶'),
943            "blackhourglass" => binary('⧗'),
944            "circlehbar" => binary('⦵'),
945            "operp" => binary('⦹'),
946            "boxast" => binary('⧆'),
947            "boxbox" => binary('⧈'),
948            "oslash" => binary('⊘'),
949            "boxcircle" => binary('⧇'),
950            "diamond" => binary('⋄'),
951            "Otimes" => binary('⨷'),
952            "hourglass" => binary('⧖'),
953            "otimeshat" => binary('⨶'),
954            "triangletimes" => binary('⨻'),
955            "lozengeminus" => binary('⟠'),
956            "star" => binary('⋆'),
957            "obar" => binary('⌽'),
958            "obslash" => binary('⦸'),
959            "triangleminus" => binary('⨺'),
960            "odiv" => binary('⨸'),
961            "triangleplus" => binary('⨹'),
962            "circledequal" => binary('⊜'),
963            "ogreaterthan" => binary('⧁'),
964            "circledparallel" => binary('⦷'),
965            "olessthan" => binary('⧀'),
966
967            ///////////////
968            // Relations //
969            ///////////////
970            "eqcirc" => relation('≖'),
971            "lessgtr" => relation('≶'),
972            "smile" | "sincoh" => relation('⌣'),
973            "eqcolon" | "minuscolon" => relation('∹'),
974            "lesssim" => relation('≲'),
975            "sqsubset" => relation('⊏'),
976            "ll" => relation('≪'),
977            "sqsubseteq" => relation('⊑'),
978            "eqqcolon" => relation('≕'),
979            "lll" => relation('⋘'),
980            "sqsupset" => relation('⊐'),
981            "llless" => relation('⋘'),
982            "sqsupseteq" => relation('⊒'),
983            "approx" => relation('≈'),
984            "eqdef" => relation('≝'),
985            "lt" => relation('<'),
986            "stareq" => relation('≛'),
987            "approxeq" => relation('≊'),
988            "eqsim" => relation('≂'),
989            "measeq" => relation('≞'),
990            "Subset" => relation('⋐'),
991            "arceq" => relation('≘'),
992            "eqslantgtr" => relation('⪖'),
993            "eqslantless" => relation('⪕'),
994            "models" => relation('⊨'),
995            "subseteq" => relation('⊆'),
996            "backcong" => relation('≌'),
997            "equiv" => relation('≡'),
998            "multimap" => relation('⊸'),
999            "subseteqq" => relation('⫅'),
1000            "fallingdotseq" => relation('≒'),
1001            "multimapboth" => relation('⧟'),
1002            "succ" => relation('≻'),
1003            "backsim" => relation('∽'),
1004            "frown" => relation('⌢'),
1005            "multimapinv" => relation('⟜'),
1006            "succapprox" => relation('⪸'),
1007            "backsimeq" => relation('⋍'),
1008            "ge" => relation('≥'),
1009            "origof" => relation('⊶'),
1010            "succcurlyeq" => relation('≽'),
1011            "between" => relation('≬'),
1012            "geq" => relation('≥'),
1013            "owns" => relation('∋'),
1014            "succeq" => relation('⪰'),
1015            "bumpeq" => relation('≏'),
1016            "geqq" => relation('≧'),
1017            "parallel" => relation('∥'),
1018            "succsim" => relation('≿'),
1019            "Bumpeq" => relation('≎'),
1020            "geqslant" => relation('⩾'),
1021            "perp" => relation('⟂'),
1022            "Supset" => relation('⋑'),
1023            "circeq" => relation('≗'),
1024            "gg" => relation('≫'),
1025            "Perp" => relation('⫫'),
1026            "coh" => relation('⌢'),
1027            "ggg" => relation('⋙'),
1028            "pitchfork" => relation('⋔'),
1029            "supseteq" => relation('⊇'),
1030            "gggtr" => relation('⋙'),
1031            "prec" => relation('≺'),
1032            "supseteqq" => relation('⫆'),
1033            "gt" => relation('>'),
1034            "precapprox" => relation('⪷'),
1035            "thickapprox" => relation('≈'),
1036            "gtrapprox" => relation('⪆'),
1037            "preccurlyeq" => relation('≼'),
1038            "thicksim" => relation('∼'),
1039            "gtreqless" => relation('⋛'),
1040            "preceq" => relation('⪯'),
1041            "trianglelefteq" => relation('⊴'),
1042            "coloneqq" | "colonequals" => relation('≔'),
1043            "gtreqqless" => relation('⪌'),
1044            "precsim" => relation('≾'),
1045            "triangleq" => relation('≜'),
1046            "Coloneqq" | "coloncolonequals" => relation('⩴'),
1047            "gtrless" => relation('≷'),
1048            "propto" => relation('∝'),
1049            "trianglerighteq" => relation('⊵'),
1050            "gtrsim" => relation('≳'),
1051            "questeq" => relation('≟'),
1052            "varpropto" => relation('∝'),
1053            "imageof" => relation('⊷'),
1054            "cong" => relation('≅'),
1055            "risingdotseq" => relation('≓'),
1056            "vartriangleleft" => relation('⊲'),
1057            "curlyeqprec" => relation('⋞'),
1058            "scoh" => relation('⌢'),
1059            "vartriangleright" => relation('⊳'),
1060            "curlyeqsucc" => relation('⋟'),
1061            "le" => relation('≤'),
1062            "shortmid" => E::Content(C::Relation {
1063                content: RelationContent::single_char('∣'),
1064                small: true,
1065            }),
1066            "shortparallel" => E::Content(C::Relation {
1067                content: RelationContent::single_char('∥'),
1068                small: true,
1069            }),
1070            "vdash" => relation('⊢'),
1071            "dashv" => relation('⊣'),
1072            "leq" => relation('≤'),
1073            "vDash" => relation('⊨'),
1074            "dblcolon" | "coloncolon" => relation('∷'),
1075            "leqq" => relation('≦'),
1076            "sim" => relation('∼'),
1077            "Vdash" => relation('⊩'),
1078            "doteq" => relation('≐'),
1079            "leqslant" => relation('⩽'),
1080            "simeq" => relation('≃'),
1081            "Dash" => relation('⊫'),
1082            "Doteq" => relation('≑'),
1083            "lessapprox" => relation('⪅'),
1084            "Vvdash" => relation('⊪'),
1085            "doteqdot" => relation('≑'),
1086            "lesseqgtr" => relation('⋚'),
1087            "smallfrown" => relation('⌢'),
1088            "veeeq" => relation('≚'),
1089            "eqeq" => relation('⩵'),
1090            "lesseqqgtr" => relation('⪋'),
1091            "smallsmile" => E::Content(C::Relation {
1092                content: RelationContent::single_char('⌣'),
1093                small: true,
1094            }),
1095            "wedgeq" => relation('≙'),
1096            "bowtie" | "Join" => relation('⋈'),
1097            // Negated relations
1098            "gnapprox" => relation('⪊'),
1099            "ngeqslant" => relation('≱'),
1100            "nsubset" => relation('⊄'),
1101            "nVdash" => relation('⊮'),
1102            "gneq" => relation('⪈'),
1103            "ngtr" => relation('≯'),
1104            "nsubseteq" => relation('⊈'),
1105            "precnapprox" => relation('⪹'),
1106            "gneqq" => relation('≩'),
1107            "nleq" => relation('≰'),
1108            "nsubseteqq" => relation('⊈'),
1109            "precneqq" => relation('⪵'),
1110            "gnsim" => relation('⋧'),
1111            "nleqq" => relation('≰'),
1112            "nsucc" => relation('⊁'),
1113            "precnsim" => relation('⋨'),
1114            "nleqslant" => relation('≰'),
1115            "nsucceq" => relation('⋡'),
1116            "subsetneq" => relation('⊊'),
1117            "lnapprox" => relation('⪉'),
1118            "nless" => relation('≮'),
1119            "nsupset" => relation('⊅'),
1120            "subsetneqq" => relation('⫋'),
1121            "lneq" => relation('⪇'),
1122            "nmid" => relation('∤'),
1123            "nsupseteq" => relation('⊉'),
1124            "succnapprox" => relation('⪺'),
1125            "lneqq" => relation('≨'),
1126            "notin" => relation('∉'),
1127            "nsupseteqq" => relation('⊉'),
1128            "succneqq" => relation('⪶'),
1129            "lnsim" => relation('⋦'),
1130            "ntriangleleft" => relation('⋪'),
1131            "succnsim" => relation('⋩'),
1132            "nparallel" => relation('∦'),
1133            "ntrianglelefteq" => relation('⋬'),
1134            "supsetneq" => relation('⊋'),
1135            "ncong" => relation('≆'),
1136            "nprec" => relation('⊀'),
1137            "ntriangleright" => relation('⋫'),
1138            "supsetneqq" => relation('⫌'),
1139            "ne" => relation('≠'),
1140            "npreceq" => relation('⋠'),
1141            "ntrianglerighteq" => relation('⋭'),
1142            "neq" => relation('≠'),
1143            "nshortmid" => E::Content(C::Relation {
1144                content: RelationContent::single_char('∤'),
1145                small: true,
1146            }),
1147            "nvdash" => relation('⊬'),
1148            "ngeq" => relation('≱'),
1149            "nshortparallel" => E::Content(C::Relation {
1150                content: RelationContent::single_char('∦'),
1151                small: true,
1152            }),
1153            "nvDash" => relation('⊭'),
1154            "ngeqq" => relation('≱'),
1155            "nsim" => relation('≁'),
1156            "nVDash" => relation('⊯'),
1157            "varsupsetneqq" => multirelation('⫌', '\u{fe00}'),
1158            "varsubsetneqq" => multirelation('⫋', '\u{fe00}'),
1159            "varsubsetneq" => multirelation('⊊', '\u{fe00}'),
1160            "varsupsetneq" => multirelation('⊋', '\u{fe00}'),
1161            "gvertneqq" => multirelation('≩', '\u{fe00}'),
1162            "lvertneqq" => multirelation('≨', '\u{fe00}'),
1163            "Eqcolon" | "minuscoloncolon" => multirelation('−', '∷'),
1164            "Eqqcolon" => multirelation('=', '∷'),
1165            "approxcolon" => multirelation('≈', ':'),
1166            "colonapprox" => multirelation(':', '≈'),
1167            "approxcoloncolon" => multirelation('≈', '∷'),
1168            "Colonapprox" | "coloncolonapprox" => multirelation('∷', '≈'),
1169            "coloneq" | "colonminus" => multirelation(':', '−'),
1170            "Coloneq" | "coloncolonminus" => multirelation('∷', '−'),
1171            "colonsim" => multirelation(':', '∼'),
1172            "Colonsim" | "coloncolonsim" => multirelation('∷', '∼'),
1173
1174            ////////////
1175            // Arrows //
1176            ////////////
1177            "circlearrowleft" => relation('↺'),
1178            "Leftrightarrow" => relation('⇔'),
1179            "restriction" => relation('↾'),
1180            "circlearrowright" => relation('↻'),
1181            "leftrightarrows" => relation('⇆'),
1182            "rightarrow" => relation('→'),
1183            "curvearrowleft" => relation('↶'),
1184            "leftrightharpoons" => relation('⇋'),
1185            "Rightarrow" => relation('⇒'),
1186            "curvearrowright" => relation('↷'),
1187            "leftrightsquigarrow" => relation('↭'),
1188            "rightarrowtail" => relation('↣'),
1189            "dashleftarrow" => relation('⇠'),
1190            "Lleftarrow" => relation('⇚'),
1191            "rightharpoondown" => relation('⇁'),
1192            "dashrightarrow" => relation('⇢'),
1193            "longleftarrow" => relation('⟵'),
1194            "rightharpoonup" => relation('⇀'),
1195            "downarrow" => relation('↓'),
1196            "Longleftarrow" => relation('⟸'),
1197            "rightleftarrows" => relation('⇄'),
1198            "Downarrow" => relation('⇓'),
1199            "longleftrightarrow" => relation('⟷'),
1200            "rightleftharpoons" => relation('⇌'),
1201            "downdownarrows" => relation('⇊'),
1202            "Longleftrightarrow" => relation('⟺'),
1203            "rightrightarrows" => relation('⇉'),
1204            "downharpoonleft" => relation('⇃'),
1205            "longmapsto" => relation('⟼'),
1206            "rightsquigarrow" => relation('⇝'),
1207            "downharpoonright" => relation('⇂'),
1208            "longrightarrow" => relation('⟶'),
1209            "Rrightarrow" => relation('⇛'),
1210            "Longrightarrow" => relation('⟹'),
1211            "Rsh" => relation('↱'),
1212            "hookleftarrow" => relation('↩'),
1213            "looparrowleft" => relation('↫'),
1214            "searrow" => relation('↘'),
1215            "hookrightarrow" => relation('↪'),
1216            "looparrowright" => relation('↬'),
1217            "swarrow" => relation('↙'),
1218            "Lsh" => relation('↰'),
1219            "mapsfrom" => relation('↤'),
1220            "twoheadleftarrow" => relation('↞'),
1221            "twoheadrightarrow" => relation('↠'),
1222            "leadsto" => relation('⇝'),
1223            "nearrow" => relation('↗'),
1224            "uparrow" => relation('↑'),
1225            "leftarrow" => relation('←'),
1226            "nleftarrow" => relation('↚'),
1227            "Uparrow" => relation('⇑'),
1228            "Leftarrow" => relation('⇐'),
1229            "nLeftarrow" => relation('⇍'),
1230            "updownarrow" => relation('↕'),
1231            "leftarrowtail" => relation('↢'),
1232            "nleftrightarrow" => relation('↮'),
1233            "Updownarrow" => relation('⇕'),
1234            "leftharpoondown" => relation('↽'),
1235            "nLeftrightarrow" => relation('⇎'),
1236            "upharpoonleft" => relation('↿'),
1237            "leftharpoonup" => relation('↼'),
1238            "nrightarrow" => relation('↛'),
1239            "upharpoonright" => relation('↾'),
1240            "leftleftarrows" => relation('⇇'),
1241            "nRightarrow" => relation('⇏'),
1242            "upuparrows" => relation('⇈'),
1243            "leftrightarrow" => relation('↔'),
1244            "nwarrow" => relation('↖'),
1245            "xleftarrow" => {
1246                let below = lex::optional_argument(&mut self.content);
1247                let above = lex::argument(&mut self.content)?;
1248                self.buffer.extend([
1249                    I::Event(E::Script {
1250                        ty: if below.is_some() {
1251                            ST::SubSuperscript
1252                        } else {
1253                            ST::Superscript
1254                        },
1255                        position: SP::AboveBelow,
1256                    }),
1257                    I::Event(relation('←')),
1258                ]);
1259                if let Some(below) = below {
1260                    self.handle_argument(Argument::Group(below))?;
1261                }
1262                self.handle_argument(above)?;
1263                return Ok(());
1264            }
1265            "xrightarrow" => {
1266                let below = lex::optional_argument(&mut self.content);
1267                let above = lex::argument(&mut self.content)?;
1268                self.buffer.extend([
1269                    I::Event(E::Script {
1270                        ty: if below.is_some() {
1271                            ST::SubSuperscript
1272                        } else {
1273                            ST::Superscript
1274                        },
1275                        position: SP::AboveBelow,
1276                    }),
1277                    I::Event(relation('→')),
1278                ]);
1279                if let Some(below) = below {
1280                    self.handle_argument(Argument::Group(below))?;
1281                }
1282                self.handle_argument(above)?;
1283                return Ok(());
1284            }
1285
1286            ///////////////
1287            // Fractions //
1288            ///////////////
1289            "frac" => {
1290                return self.fraction_like(None, None, None, None);
1291            }
1292            // TODO: better errors for this
1293            "genfrac" => {
1294                let str = &mut self.content;
1295                let ldelim_argument = lex::argument(str)?;
1296                let ldelim = match ldelim_argument {
1297                    Argument::Token(token) => {
1298                        Some(token_to_delim(token).ok_or(ErrorKind::Delimiter)?)
1299                    }
1300                    Argument::Group(group) => {
1301                        if group.is_empty() {
1302                            None
1303                        } else {
1304                            return Err(ErrorKind::Delimiter);
1305                        }
1306                    }
1307                };
1308                let rdelim_argument = lex::argument(str)?;
1309                let rdelim = match rdelim_argument {
1310                    Argument::Token(token) => {
1311                        Some(token_to_delim(token).ok_or(ErrorKind::Delimiter)?)
1312                    }
1313                    Argument::Group(group) => {
1314                        if group.is_empty() {
1315                            None
1316                        } else {
1317                            return Err(ErrorKind::Delimiter);
1318                        }
1319                    }
1320                };
1321                let bar_size_argument = lex::argument(str)?;
1322                let bar_size = match bar_size_argument {
1323                    Argument::Token(_) => return Err(ErrorKind::DimensionArgument),
1324                    Argument::Group("") => None,
1325                    Argument::Group(mut group) => lex::dimension(&mut group).and_then(|dim| {
1326                        if group.is_empty() {
1327                            Ok(Some(dim))
1328                        } else {
1329                            Err(ErrorKind::DimensionArgument)
1330                        }
1331                    })?,
1332                };
1333                let display_style_argument = lex::argument(str)?;
1334                let display_style = match display_style_argument {
1335                    Argument::Token(t) => match t {
1336                        Token::ControlSequence(_) => return Err(ErrorKind::Argument),
1337                        Token::Character(c) => Some(match c.into() {
1338                            '0' => S::Display,
1339                            '1' => S::Text,
1340                            '2' => S::Script,
1341                            '3' => S::ScriptScript,
1342                            _ => return Err(ErrorKind::Argument),
1343                        }),
1344                    },
1345                    Argument::Group(group) => match group {
1346                        "0" => Some(S::Display),
1347                        "1" => Some(S::Text),
1348                        "2" => Some(S::Script),
1349                        "3" => Some(S::ScriptScript),
1350                        "" => None,
1351                        _ => return Err(ErrorKind::Argument),
1352                    },
1353                };
1354
1355                self.fraction_like(
1356                    ldelim.map(|d| d.0),
1357                    rdelim.map(|d| d.0),
1358                    bar_size,
1359                    display_style,
1360                )?;
1361
1362                return Ok(());
1363            }
1364            "cfrac" | "dfrac" => {
1365                self.fraction_like(None, None, None, Some(S::Display))?;
1366                return Ok(());
1367            }
1368            "tfrac" => {
1369                self.fraction_like(None, None, None, Some(S::Text))?;
1370                return Ok(());
1371            }
1372            "binom" => {
1373                self.fraction_like(
1374                    Some('('),
1375                    Some(')'),
1376                    Some(Dimension::new(0., DimensionUnit::Em)),
1377                    None,
1378                )?;
1379                return Ok(());
1380            }
1381            "dbinom" => {
1382                self.fraction_like(
1383                    Some('('),
1384                    Some(')'),
1385                    Some(Dimension::new(0., DimensionUnit::Em)),
1386                    Some(S::Display),
1387                )?;
1388                return Ok(());
1389            }
1390            "tbinom" => {
1391                self.fraction_like(
1392                    Some('('),
1393                    Some(')'),
1394                    Some(Dimension::new(0., DimensionUnit::Em)),
1395                    Some(S::Text),
1396                )?;
1397                return Ok(());
1398            }
1399            "overset" | "stackrel" => {
1400                self.buffer.push(I::Event(E::Script {
1401                    ty: ST::Superscript,
1402                    position: SP::AboveBelow,
1403                }));
1404                let before_over_index = self.buffer.len();
1405                let over = lex::argument(&mut self.content)?;
1406                self.handle_argument(over)?;
1407                let over_events = self.buffer.split_off(before_over_index);
1408                let base = lex::argument(&mut self.content)?;
1409                self.handle_argument(base)?;
1410                self.buffer.extend(over_events);
1411                return Ok(());
1412            }
1413            "underset" => {
1414                self.buffer.push(I::Event(E::Script {
1415                    ty: ST::Subscript,
1416                    position: SP::AboveBelow,
1417                }));
1418                let before_under_index = self.buffer.len();
1419                let under = lex::argument(&mut self.content)?;
1420                self.handle_argument(under)?;
1421                let under_events = self.buffer.split_off(before_under_index);
1422                let base = lex::argument(&mut self.content)?;
1423                self.handle_argument(base)?;
1424                self.buffer.extend(under_events);
1425                return Ok(());
1426            }
1427            "buildrel" => {
1428                let mut over_content = lex::content_with_suffix(&mut self.content, r"\over")?;
1429                self.buffer.push(I::Event(E::Script {
1430                    ty: ST::Superscript,
1431                    position: SP::AboveBelow,
1432                }));
1433                let before_over_index = self.buffer.len();
1434                let over = lex::argument(&mut over_content)?;
1435                self.handle_argument(over)?;
1436                let over_events = self.buffer.split_off(before_over_index);
1437                let base = lex::argument(&mut self.content)?;
1438                self.handle_argument(base)?;
1439                self.buffer.extend(over_events);
1440                return Ok(());
1441            }
1442            "substack" => {
1443                let content = lex::brace_argument(&mut self.content)?;
1444                self.buffer.push(I::Event(E::Begin(G::SubArray {
1445                    alignment: ColumnAlignment::Center,
1446                })));
1447                self.buffer.push(I::SubGroup {
1448                    content,
1449                    allowed_alignment_count: Some(AlignmentCount::new(0)),
1450                });
1451                self.buffer.push(I::Event(E::End));
1452                return Ok(());
1453            }
1454            "sideset" => {
1455                let _left = lex::argument(&mut self.content)?;
1456                let _right = lex::argument(&mut self.content)?;
1457                let base = lex::argument(&mut self.content)?;
1458                self.handle_argument(base)?;
1459                return Ok(());
1460            }
1461
1462            //////////////
1463            // Radicals //
1464            //////////////
1465            "sqrt" => {
1466                if let Some(index) = lex::optional_argument(&mut self.content) {
1467                    self.buffer.push(I::Event(E::Visual(V::Root)));
1468                    let arg = lex::argument(&mut self.content)?;
1469                    self.handle_argument(arg)?;
1470                    self.buffer.push(I::SubGroup {
1471                        content: index,
1472                        allowed_alignment_count: None,
1473                    });
1474                } else {
1475                    self.buffer.push(I::Event(E::Visual(V::SquareRoot)));
1476                    let arg = lex::argument(&mut self.content)?;
1477                    self.handle_argument(arg)?;
1478                }
1479                return Ok(());
1480            }
1481            "surd" => {
1482                self.buffer.extend([
1483                    I::Event(E::Visual(V::SquareRoot)),
1484                    I::Event(E::Space {
1485                        width: Some(Dimension::new(0., DimensionUnit::Em)),
1486                        height: Some(Dimension::new(0.7, DimensionUnit::Em)),
1487                        depth: None,
1488                    }),
1489                ]);
1490                return Ok(());
1491            }
1492
1493            "backslash" => ordinary('\\'),
1494
1495            ///////////////////
1496            // Miscellaneous //
1497            ///////////////////
1498            "#" | "%" | "&" | "$" | "_" => ordinary(
1499                control_sequence
1500                    .chars()
1501                    .next()
1502                    .expect("the control sequence contains one of the matched characters"),
1503            ),
1504            "|" => ordinary('∥'),
1505            "text" => return self.text_argument(None),
1506            // Text-mode font selectors usable inside math mode (KaTeX/MathJax compatibility).
1507            // These behave like `\text{…}` but apply the corresponding font to the inner content.
1508            "textrm" => return self.text_argument(Some(Font::UpRight)),
1509            "textbf" => return self.text_argument(Some(Font::Bold)),
1510            "textit" => return self.text_argument(Some(Font::Italic)),
1511            "textsf" => return self.text_argument(Some(Font::SansSerif)),
1512            "texttt" => return self.text_argument(Some(Font::Monospace)),
1513
1514            "not" | "cancel" => {
1515                self.buffer.push(I::Event(E::Visual(V::Negation)));
1516                let argument = lex::argument(&mut self.content)?;
1517                self.handle_argument(argument)?;
1518                return Ok(());
1519            }
1520            "char" => {
1521                let number = lex::unsigned_integer(&mut self.content)?;
1522                if number > 255 {
1523                    return Err(ErrorKind::InvalidCharNumber);
1524                }
1525                E::Content(C::Ordinary {
1526                    content: char::from_u32(number as u32)
1527                        .expect("the number is a valid char since it is less than 256"),
1528                    stretchy: false,
1529                })
1530            }
1531            "relax" => {
1532                return if self.state.handling_argument {
1533                    Err(ErrorKind::Relax)
1534                } else {
1535                    Ok(())
1536                }
1537            }
1538
1539            "begingroup" => {
1540                let group = lex::group_content(&mut self.content, GroupingKind::BeginEnd)?;
1541                self.buffer.extend([
1542                    I::Event(E::Begin(G::Normal)),
1543                    I::SubGroup {
1544                        content: group,
1545                        allowed_alignment_count: None,
1546                    },
1547                    I::Event(E::End),
1548                ]);
1549                return Ok(());
1550            }
1551            "endgroup" => return Err(ErrorKind::UnbalancedGroup(None)),
1552
1553            "begin" => {
1554                let Argument::Group(argument) = lex::argument(&mut self.content)? else {
1555                    return Err(ErrorKind::Argument);
1556                };
1557
1558                let mut style = None;
1559                let mut wrap: Option<(char, char)> = None;
1560
1561                let (environment, align_count, grouping_kind) = match argument {
1562                    "array" => {
1563                        let (grouping, count) = self.array_environment()?;
1564                        (grouping, count, GroupingKind::Array { display: false })
1565                    }
1566                    "darray" => {
1567                        style = Some(S::Display);
1568                        let (grouping, count) = self.array_environment()?;
1569                        (grouping, count, GroupingKind::Array { display: true })
1570                    }
1571                    "matrix" => (
1572                        G::Matrix {
1573                            alignment: ColumnAlignment::Center,
1574                        },
1575                        u16::MAX,
1576                        GroupingKind::Matrix {
1577                            ty: MatrixType::Normal,
1578                            column_spec: false,
1579                        },
1580                    ),
1581                    "matrix*" => (
1582                        G::Matrix {
1583                            alignment: self
1584                                .optional_alignment()?
1585                                .unwrap_or(ColumnAlignment::Center),
1586                        },
1587                        u16::MAX,
1588                        GroupingKind::Matrix {
1589                            ty: MatrixType::Normal,
1590                            column_spec: true,
1591                        },
1592                    ),
1593                    "smallmatrix" => {
1594                        style = Some(S::Text);
1595                        (
1596                            G::Matrix {
1597                                alignment: ColumnAlignment::Center,
1598                            },
1599                            u16::MAX,
1600                            GroupingKind::Matrix {
1601                                ty: MatrixType::Small,
1602                                column_spec: false,
1603                            },
1604                        )
1605                    }
1606                    "pmatrix" => {
1607                        wrap = Some(('(', ')'));
1608                        (
1609                            G::Matrix {
1610                                alignment: ColumnAlignment::Center,
1611                            },
1612                            u16::MAX,
1613                            GroupingKind::Matrix {
1614                                ty: MatrixType::Parens,
1615                                column_spec: false,
1616                            },
1617                        )
1618                    }
1619                    "pmatrix*" => {
1620                        wrap = Some(('(', ')'));
1621                        (
1622                            G::Matrix {
1623                                alignment: self
1624                                    .optional_alignment()?
1625                                    .unwrap_or(ColumnAlignment::Center),
1626                            },
1627                            u16::MAX,
1628                            GroupingKind::Matrix {
1629                                ty: MatrixType::Parens,
1630                                column_spec: true,
1631                            },
1632                        )
1633                    }
1634                    "bmatrix" => {
1635                        wrap = Some(('[', ']'));
1636                        (
1637                            G::Matrix {
1638                                alignment: ColumnAlignment::Center,
1639                            },
1640                            u16::MAX,
1641                            GroupingKind::Matrix {
1642                                ty: MatrixType::Brackets,
1643                                column_spec: false,
1644                            },
1645                        )
1646                    }
1647                    "bmatrix*" => {
1648                        wrap = Some(('[', ']'));
1649                        (
1650                            G::Matrix {
1651                                alignment: self
1652                                    .optional_alignment()?
1653                                    .unwrap_or(ColumnAlignment::Center),
1654                            },
1655                            u16::MAX,
1656                            GroupingKind::Matrix {
1657                                ty: MatrixType::Brackets,
1658                                column_spec: true,
1659                            },
1660                        )
1661                    }
1662                    "vmatrix" => {
1663                        wrap = Some(('|', '|'));
1664                        (
1665                            G::Matrix {
1666                                alignment: ColumnAlignment::Center,
1667                            },
1668                            u16::MAX,
1669                            GroupingKind::Matrix {
1670                                ty: MatrixType::Vertical,
1671                                column_spec: false,
1672                            },
1673                        )
1674                    }
1675                    "vmatrix*" => {
1676                        wrap = Some(('|', '|'));
1677                        (
1678                            G::Matrix {
1679                                alignment: self
1680                                    .optional_alignment()?
1681                                    .unwrap_or(ColumnAlignment::Center),
1682                            },
1683                            u16::MAX,
1684                            GroupingKind::Matrix {
1685                                ty: MatrixType::Vertical,
1686                                column_spec: true,
1687                            },
1688                        )
1689                    }
1690                    "Vmatrix" => {
1691                        wrap = Some(('‖', '‖'));
1692                        (
1693                            G::Matrix {
1694                                alignment: ColumnAlignment::Center,
1695                            },
1696                            u16::MAX,
1697                            GroupingKind::Matrix {
1698                                ty: MatrixType::DoubleVertical,
1699                                column_spec: false,
1700                            },
1701                        )
1702                    }
1703                    "Vmatrix*" => {
1704                        wrap = Some(('‖', '‖'));
1705                        (
1706                            G::Matrix {
1707                                alignment: self
1708                                    .optional_alignment()?
1709                                    .unwrap_or(ColumnAlignment::Center),
1710                            },
1711                            u16::MAX,
1712                            GroupingKind::Matrix {
1713                                ty: MatrixType::DoubleVertical,
1714                                column_spec: true,
1715                            },
1716                        )
1717                    }
1718                    "Bmatrix" => {
1719                        wrap = Some(('{', '}'));
1720                        (
1721                            G::Matrix {
1722                                alignment: ColumnAlignment::Center,
1723                            },
1724                            u16::MAX,
1725                            GroupingKind::Matrix {
1726                                ty: MatrixType::Braces,
1727                                column_spec: false,
1728                            },
1729                        )
1730                    }
1731                    "Bmatrix*" => {
1732                        wrap = Some(('{', '}'));
1733                        (
1734                            G::Matrix {
1735                                alignment: self
1736                                    .optional_alignment()?
1737                                    .unwrap_or(ColumnAlignment::Center),
1738                            },
1739                            u16::MAX,
1740                            GroupingKind::Matrix {
1741                                ty: MatrixType::Braces,
1742                                column_spec: true,
1743                            },
1744                        )
1745                    }
1746                    "cases" => (
1747                        G::Cases { left: true },
1748                        1,
1749                        GroupingKind::Cases {
1750                            left: true,
1751                            display: false,
1752                        },
1753                    ),
1754                    "dcases" => {
1755                        style = Some(S::Display);
1756                        (
1757                            G::Cases { left: true },
1758                            1,
1759                            GroupingKind::Cases {
1760                                left: true,
1761                                display: true,
1762                            },
1763                        )
1764                    }
1765                    "rcases" => (
1766                        G::Cases { left: false },
1767                        1,
1768                        GroupingKind::Cases {
1769                            left: false,
1770                            display: false,
1771                        },
1772                    ),
1773                    "drcases" => {
1774                        style = Some(S::Display);
1775                        (
1776                            G::Cases { left: false },
1777                            1,
1778                            GroupingKind::Cases {
1779                                left: false,
1780                                display: true,
1781                            },
1782                        )
1783                    }
1784                    "equation" => (
1785                        G::Equation { eq_numbers: true },
1786                        0,
1787                        GroupingKind::Equation { eq_numbers: true },
1788                    ),
1789                    "equation*" => (
1790                        G::Equation { eq_numbers: false },
1791                        0,
1792                        GroupingKind::Equation { eq_numbers: false },
1793                    ),
1794                    "align" => (
1795                        G::Align { eq_numbers: true },
1796                        u16::MAX,
1797                        GroupingKind::Align { eq_numbers: true },
1798                    ),
1799                    "align*" => (
1800                        G::Align { eq_numbers: false },
1801                        u16::MAX,
1802                        GroupingKind::Align { eq_numbers: false },
1803                    ),
1804                    "aligned" => (G::Aligned, u16::MAX, GroupingKind::Aligned),
1805                    "gather" => (
1806                        G::Gather { eq_numbers: true },
1807                        0,
1808                        GroupingKind::Gather { eq_numbers: true },
1809                    ),
1810                    "gather*" => (
1811                        G::Gather { eq_numbers: false },
1812                        0,
1813                        GroupingKind::Gather { eq_numbers: false },
1814                    ),
1815                    "gathered" => (G::Gathered, 0, GroupingKind::Gathered),
1816                    "alignat" => {
1817                        let pairs = match lex::argument(&mut self.content)? {
1818                            Argument::Group(mut content) => lex::unsigned_integer(&mut content),
1819                            _ => Err(ErrorKind::Argument),
1820                        }? as u16;
1821                        (
1822                            G::Alignat {
1823                                pairs,
1824                                eq_numbers: true,
1825                            },
1826                            (pairs * 2).saturating_sub(1),
1827                            GroupingKind::Alignat { eq_numbers: true },
1828                        )
1829                    }
1830                    "alignat*" => {
1831                        let pairs = match lex::argument(&mut self.content)? {
1832                            Argument::Group(mut content) => lex::unsigned_integer(&mut content),
1833                            _ => Err(ErrorKind::Argument),
1834                        }? as u16;
1835                        (
1836                            G::Alignat {
1837                                pairs,
1838                                eq_numbers: false,
1839                            },
1840                            (pairs * 2).saturating_sub(1),
1841                            GroupingKind::Alignat { eq_numbers: false },
1842                        )
1843                    }
1844                    "alignedat" => {
1845                        let pairs = match lex::argument(&mut self.content)? {
1846                            Argument::Group(mut content) => lex::unsigned_integer(&mut content),
1847                            _ => Err(ErrorKind::Argument),
1848                        }? as u16;
1849                        (
1850                            G::Alignedat { pairs },
1851                            (pairs * 2).saturating_sub(1),
1852                            GroupingKind::Alignedat,
1853                        )
1854                    }
1855                    "subarray" => {
1856                        let alignment = match lex::argument(&mut self.content)? {
1857                            Argument::Group("l") => ColumnAlignment::Left,
1858                            Argument::Group("c") => ColumnAlignment::Center,
1859                            Argument::Group("r") => ColumnAlignment::Right,
1860                            _ => return Err(ErrorKind::Argument),
1861                        };
1862                        (G::SubArray { alignment }, 0, GroupingKind::SubArray)
1863                    }
1864                    "multline" => (G::Multline, 0, GroupingKind::Multline),
1865                    "split" => (G::Split, 1, GroupingKind::Split),
1866                    _ => return Err(ErrorKind::Environment),
1867                };
1868
1869                let wrap_used = if let Some((left, right)) = wrap {
1870                    self.buffer
1871                        .push(I::Event(E::Begin(G::LeftRight(Some(left), Some(right)))));
1872                    true
1873                } else {
1874                    false
1875                };
1876
1877                let horizontal_lines = lex::horizontal_lines(&mut self.content);
1878                let content = lex::group_content(&mut self.content, grouping_kind)?;
1879                self.buffer.push(I::Event(E::Begin(environment)));
1880                if let Some(style) = style {
1881                    self.buffer.push(I::Event(E::StateChange(SC::Style(style))));
1882                }
1883                if !horizontal_lines.is_empty() {
1884                    self.buffer
1885                        .push(I::Event(E::EnvironmentFlow(EnvironmentFlow::StartLines {
1886                            lines: horizontal_lines,
1887                        })));
1888                }
1889                self.buffer.extend([
1890                    I::SubGroup {
1891                        content,
1892                        allowed_alignment_count: Some(AlignmentCount::new(align_count)),
1893                    },
1894                    I::Event(E::End),
1895                ]);
1896
1897                if wrap_used {
1898                    self.buffer.push(I::Event(E::End));
1899                }
1900                return Ok(());
1901            }
1902            "end" => return Err(ErrorKind::UnbalancedGroup(None)),
1903            "\\" | "cr"
1904                if self.state.allowed_alignment_count.is_some()
1905                    && !self.state.handling_argument =>
1906            {
1907                self.state.allowed_alignment_count.as_mut().unwrap().reset();
1908                let additional_space =
1909                    if let Some(mut arg) = lex::optional_argument(&mut self.content) {
1910                        Some(lex::dimension(&mut arg)?)
1911                    } else {
1912                        None
1913                    };
1914
1915                let horizontal_lines = lex::horizontal_lines(&mut self.content);
1916                E::EnvironmentFlow(EnvironmentFlow::NewLine {
1917                    spacing: additional_space,
1918                    horizontal_lines,
1919                })
1920            }
1921            "\\" | "cr" => return Err(ErrorKind::NewLine),
1922
1923            // Delimiters
1924            cs if control_sequence_delimiter_map(cs).is_some() => {
1925                let (content, ty) = control_sequence_delimiter_map(cs).unwrap();
1926                E::Content(C::Delimiter {
1927                    content,
1928                    size: None,
1929                    ty,
1930                })
1931            }
1932
1933            // Spacing
1934            c if c.trim_start().is_empty() => E::Content(C::Text("&nbsp;")),
1935
1936            // Macros
1937            "def" => {
1938                let (cs, parameter_text, replacement_text) = lex::definition(&mut self.content)?;
1939                self.state.skip_scripts = true;
1940                return self
1941                    .macro_context
1942                    .define(cs, parameter_text, replacement_text);
1943            }
1944            "let" => {
1945                let (cs, token) = lex::let_assignment(&mut self.content)?;
1946                self.state.skip_scripts = true;
1947                self.macro_context.assign(cs, token);
1948                return Ok(());
1949            }
1950            "futurelet" => {
1951                let (cs, token, rest) = lex::futurelet_assignment(&mut self.content)?;
1952                self.state.skip_scripts = true;
1953                self.macro_context.assign(cs, token);
1954                self.content = rest;
1955
1956                return Ok(());
1957            }
1958            "newcommand" => return self.new_command(Some(false)),
1959            "renewcommand" => return self.new_command(Some(true)),
1960            "providecommand" => return self.new_command(None),
1961            _ => return Err(ErrorKind::UnknownPrimitive),
1962        };
1963        self.buffer.push(I::Event(event));
1964        Ok(())
1965    }
1966
1967    /// Return a delimiter with the given size from the next character in the parser.
1968    fn sized_delim(&mut self, size: DelimiterSize) -> InnerResult<()> {
1969        let current = &mut self.content;
1970        let (content, ty) = lex::delimiter(current)?;
1971        self.buffer.push(I::Event(E::Content(C::Delimiter {
1972            content,
1973            size: Some(size),
1974            ty,
1975        })));
1976        Ok(())
1977    }
1978
1979    /// Implementation of the `\math<class>` atom-type commands (e.g. `\mathord`,
1980    /// `\mathrel`, `\mathbin`, `\mathop`, `\mathopen`, `\mathclose`, `\mathpunct`,
1981    /// `\mathinner`).
1982    ///
1983    /// These commands set the math class of their argument, which determines
1984    /// spacing in the rendered MathML output. When the argument is a single
1985    /// character, it is emitted as the matching [`Content`] variant. When the
1986    /// argument is a group, its contents are parsed normally and wrapped in
1987    /// an [`Event::Begin`]/[`Event::End`] pair so that the surrounding
1988    /// spacing is governed by the requested atom class.
1989    ///
1990    /// [`Content`]: crate::event::Content
1991    /// [`Event::Begin`]: crate::event::Event::Begin
1992    /// [`Event::End`]: crate::event::Event::End
1993    fn atom_group(&mut self, class: AtomClass) -> InnerResult<()> {
1994        let argument = lex::argument(&mut self.content)?;
1995
1996        // Try to extract a single ASCII/Unicode character so we can emit a
1997        // directly-classified Content variant. This gives the most faithful
1998        // spacing for the common case (e.g. `\mathbin{+}`).
1999        let single_char: Option<char> = match argument {
2000            Argument::Token(Token::Character(char_)) => Some(char_.into()),
2001            Argument::Group(s) => {
2002                let s = s.trim();
2003                let mut chars = s.chars();
2004                let first = chars.next();
2005                match (first, chars.next()) {
2006                    (Some(c), None) => Some(c),
2007                    _ => None,
2008                }
2009            }
2010            Argument::Token(Token::ControlSequence(_)) => None,
2011        };
2012
2013        if let Some(c) = single_char {
2014            let event = match class {
2015                AtomClass::Ord => ordinary(c),
2016                AtomClass::Op => E::Content(C::LargeOp {
2017                    content: c,
2018                    small: false,
2019                }),
2020                AtomClass::Bin => binary(c),
2021                AtomClass::Rel => relation(c),
2022                AtomClass::Open => E::Content(C::Delimiter {
2023                    content: c,
2024                    size: None,
2025                    ty: DelimiterType::Open,
2026                }),
2027                AtomClass::Close => E::Content(C::Delimiter {
2028                    content: c,
2029                    size: None,
2030                    ty: DelimiterType::Close,
2031                }),
2032                AtomClass::Punct => E::Content(C::Punctuation(c)),
2033                AtomClass::Inner => ordinary(c),
2034            };
2035            self.buffer.push(I::Event(event));
2036            return Ok(());
2037        }
2038
2039        // Multi-character group (or a control sequence). For \mathop with a
2040        // textual group, emit it as a `Function` so it renders as a
2041        // multi-letter operator (mirroring `\operatorname`). Otherwise wrap
2042        // the parsed argument in a normal group; the inner content keeps its
2043        // own atom classes, but the whole construct presents as an
2044        // `Atom::Inner` for spacing purposes — which is what TeX does for
2045        // `\mathinner`, and is a reasonable approximation for the other
2046        // classes when given multi-token arguments.
2047        if matches!(class, AtomClass::Op) {
2048            if let Argument::Group(content) = argument {
2049                self.buffer.push(I::Event(E::Content(C::Function(content))));
2050                return Ok(());
2051            }
2052        }
2053
2054        self.buffer.push(I::Event(E::Begin(G::Normal)));
2055        self.handle_argument(argument)?;
2056        self.buffer.push(I::Event(E::End));
2057        Ok(())
2058    }
2059
2060    /// Override the `font_state` for the argument to the command.
2061    fn font_group(&mut self, font: Option<Font>) -> InnerResult<()> {
2062        let argument = lex::argument(&mut self.content)?;
2063        self.buffer.extend([
2064            I::Event(E::Begin(G::Normal)),
2065            I::Event(E::StateChange(SC::Font(font))),
2066        ]);
2067        match argument {
2068            Argument::Token(token) => {
2069                match token {
2070                    Token::ControlSequence(cs) => self.handle_primitive(cs)?,
2071                    Token::Character(c) => self.handle_char_token(c)?,
2072                };
2073            }
2074            Argument::Group(group) => {
2075                self.buffer.push(I::SubGroup {
2076                    content: group,
2077                    allowed_alignment_count: None,
2078                });
2079            }
2080        };
2081        self.buffer.push(I::Event(E::End));
2082        Ok(())
2083    }
2084
2085    /// Accent commands. parse the argument, and overset the accent.
2086    fn accent(&mut self, accent: char, stretchy: bool) -> InnerResult<()> {
2087        let argument = lex::argument(&mut self.content)?;
2088        self.buffer.push(I::Event(E::Script {
2089            ty: ST::Superscript,
2090            position: SP::AboveBelow,
2091        }));
2092        self.handle_argument(argument)?;
2093        self.buffer.push(I::Event(E::Content(C::Ordinary {
2094            content: accent,
2095            stretchy,
2096        })));
2097        Ok(())
2098    }
2099
2100    /// Underscript commands. parse the argument, and underset the accent.
2101    fn underscript(&mut self, content: char) -> InnerResult<()> {
2102        let argument = lex::argument(&mut self.content)?;
2103        self.buffer.push(I::Event(E::Script {
2104            ty: ST::Subscript,
2105            position: SP::AboveBelow,
2106        }));
2107        self.handle_argument(argument)?;
2108        self.buffer.push(I::Event(E::Content(C::Ordinary {
2109            content,
2110            stretchy: true,
2111        })));
2112
2113        Ok(())
2114    }
2115
2116    fn large_op(&mut self, op: char, movable: bool) -> E<'store> {
2117        self.state.allow_script_modifiers = true;
2118        self.state.script_position = if movable { SP::Movable } else { SP::Right };
2119        E::Content(C::LargeOp {
2120            content: op,
2121            small: false,
2122        })
2123    }
2124
2125    fn font_change(&mut self, font: Font) -> E<'store> {
2126        self.state.skip_scripts = true;
2127        E::StateChange(SC::Font(Some(font)))
2128    }
2129
2130    fn style_change(&mut self, style: S) -> E<'store> {
2131        self.state.skip_scripts = true;
2132        E::StateChange(SC::Style(style))
2133    }
2134
2135    fn text_argument(&mut self, font: Option<Font>) -> InnerResult<()> {
2136        let argument = lex::argument(&mut self.content)?;
2137        let text = match argument {
2138            Argument::Token(Token::Character(c)) => c.as_str(),
2139            Argument::Group(inner) => inner,
2140            _ => return Err(ErrorKind::ControlSequenceAsArgument),
2141        };
2142        if let Some(font) = font {
2143            self.buffer.extend([
2144                I::Event(E::Begin(G::Normal)),
2145                I::Event(E::StateChange(SC::Font(Some(font)))),
2146                I::Event(E::Content(C::Text(text))),
2147                I::Event(E::End),
2148            ]);
2149        } else {
2150            self.buffer.push(I::Event(E::Content(C::Text(text))));
2151        }
2152        Ok(())
2153    }
2154
2155    fn fraction_like(
2156        &mut self,
2157        open: Option<char>,
2158        close: Option<char>,
2159        bar_size: Option<Dimension>,
2160        style: Option<S>,
2161    ) -> InnerResult<()> {
2162        let open_close_group = open.is_some() || close.is_some();
2163        if open_close_group {
2164            self.buffer
2165                .push(I::Event(E::Begin(G::LeftRight(open, close))));
2166        }
2167        if let Some(style) = style {
2168            if !open_close_group {
2169                self.buffer.push(I::Event(E::Begin(G::Normal)));
2170            }
2171            self.buffer.push(I::Event(E::StateChange(SC::Style(style))));
2172        };
2173
2174        self.buffer.push(I::Event(E::Visual(V::Fraction(bar_size))));
2175        let numerator = lex::argument(&mut self.content)?;
2176        self.handle_argument(numerator)?;
2177        let denominator = lex::argument(&mut self.content)?;
2178        self.handle_argument(denominator)?;
2179        if open_close_group || style.is_some() {
2180            self.buffer.push(I::Event(E::End));
2181        }
2182
2183        Ok(())
2184    }
2185
2186    fn array_environment(&mut self) -> InnerResult<(G, u16)> {
2187        let Argument::Group(array_columns_str) = lex::argument(&mut self.content)? else {
2188            return Err(ErrorKind::Argument);
2189        };
2190
2191        let mut column_count: u16 = 0;
2192        let mut contains_column = false;
2193        let array_columns = array_columns_str
2194            .chars()
2195            .map(|c| {
2196                column_count += 1;
2197                Ok(match c {
2198                    'c' => {
2199                        contains_column = true;
2200                        AC::Column(ColumnAlignment::Center)
2201                    }
2202                    'l' => {
2203                        contains_column = true;
2204                        AC::Column(ColumnAlignment::Left)
2205                    }
2206                    'r' => {
2207                        contains_column = true;
2208                        AC::Column(ColumnAlignment::Right)
2209                    }
2210                    '|' => {
2211                        column_count -= 1;
2212                        AC::Separator(Line::Solid)
2213                    }
2214                    ':' => {
2215                        column_count -= 1;
2216                        AC::Separator(Line::Dashed)
2217                    }
2218                    _ => return Err(ErrorKind::Argument),
2219                })
2220            })
2221            .collect::<Result<_, _>>()?;
2222
2223        if !contains_column {
2224            return Err(ErrorKind::ArrayNoColumns);
2225        }
2226
2227        Ok((G::Array(array_columns), column_count.saturating_sub(1)))
2228    }
2229
2230    fn optional_alignment(&mut self) -> InnerResult<Option<ColumnAlignment>> {
2231        let alignment = lex::optional_argument(&mut self.content);
2232        Ok(match alignment {
2233            Some("c") => Some(ColumnAlignment::Center),
2234            Some("l") => Some(ColumnAlignment::Left),
2235            Some("r") => Some(ColumnAlignment::Right),
2236            None => None,
2237            _ => return Err(ErrorKind::Argument),
2238        })
2239    }
2240
2241    fn new_command(&mut self, should_already_exist: Option<bool>) -> InnerResult<()> {
2242        let mut group = lex::brace_argument(&mut self.content)?;
2243        let cs = lex::control_sequence(&mut group)?;
2244
2245        if should_already_exist.is_some_and(|sae| sae != self.macro_context.contains(cs)) {
2246            return Err(if should_already_exist.unwrap() {
2247                ErrorKind::MacroNotDefined
2248            } else {
2249                ErrorKind::MacroAlreadyDefined
2250            });
2251        }
2252
2253        let arg_count = (lex::optional_argument(&mut self.content).ok_or(ErrorKind::Argument)?)
2254            .parse::<u8>()
2255            .map_err(|_| ErrorKind::Number)?;
2256        let first_arg_default = lex::optional_argument(&mut self.content);
2257        if arg_count > 9 && arg_count >= first_arg_default.is_some() as u8 {
2258            return Err(ErrorKind::TooManyParams);
2259        }
2260
2261        let replacement_text = lex::brace_argument(&mut self.content)?;
2262
2263        if self.macro_context.contains(cs) && should_already_exist.is_none() {
2264            return Ok(());
2265        }
2266        self.macro_context
2267            .insert_command(cs, arg_count, first_arg_default, replacement_text)?;
2268        Ok(())
2269    }
2270}
2271
2272#[inline]
2273fn ordinary(ident: char) -> E<'static> {
2274    E::Content(C::Ordinary {
2275        content: ident,
2276        stretchy: false,
2277    })
2278}
2279
2280#[inline]
2281fn relation(rel: char) -> E<'static> {
2282    E::Content(C::Relation {
2283        content: RelationContent::single_char(rel),
2284        small: false,
2285    })
2286}
2287
2288fn multirelation(first: char, second: char) -> E<'static> {
2289    E::Content(C::Relation {
2290        content: RelationContent::double_char(first, second),
2291        small: false,
2292    })
2293}
2294
2295#[inline]
2296fn binary(op: char) -> E<'static> {
2297    E::Content(C::BinaryOp {
2298        content: op,
2299        small: false,
2300    })
2301}
2302
2303/// Math atom classes used by the `\math<class>` family of commands.
2304///
2305/// These mirror the TeXbook's eight atom classes and are used by
2306/// [`InnerParser::atom_group`] to pick an appropriate [`Content`] variant
2307/// for the argument.
2308///
2309/// [`Content`]: crate::event::Content
2310#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2311enum AtomClass {
2312    Ord,
2313    Op,
2314    Bin,
2315    Rel,
2316    Open,
2317    Close,
2318    Punct,
2319    Inner,
2320}
2321
2322// TODO implementations:
2323// - `raise`, `lower`
2324// - `hbox`, `mbox`?
2325// - `vcenter`
2326// - `rule`
2327// - `mathchoice` (TeXbook p. 151)
2328
2329// Unimplemented primitives:
2330// `sl` (slanted) font: https://tug.org/texinfohtml/latex2e.html#index-_005csl
2331// `bbit` (double-struck italic) font
2332// `symliteral` wtf is this? (in unicode-math)
2333// `sc` (small caps) font: https://tug.org/texinfohtml/latex2e.html#index-_005csc