Skip to main content

pulldown_latex/
event.rs

1//! The definition of the [`Event`] enum, which is used as a logical
2//! representation of `LaTeX` content.
3//!
4//! A stream of `Result<Event, ParserError>`s is produced by the [`Parser`], which can then be typeset/rendered
5//! by a renderer. This crate only provides a simple `mathml` renderer available through the
6//! [`push_mathml`] and [`write_mathml`] functions.
7//!
8//! This module tries to be comprehensive in explaining the invariants that are be upheld by the [`Parser`].
9//! If a user of this crate, or a renderer implementor finds a case where the invariants are not
10//! satisfied, then it is a bug in the parser, and should be reported.
11//!
12//! [`Parser`]: crate::parser::Parser
13//! [`push_mathml`]: crate::mathml::push_mathml
14//! [`write_mathml`]: crate::mathml::write_mathml
15
16use std::fmt::Display;
17
18/// All events that can be produced by the parser.
19///
20/// # For Renderer Implementors
21///
22/// When an [`Event`] is referreing to an "_element_", it is referring to the next logical unit of
23/// content in the stream. This can be a single [`Event::Content`] element, a group marked
24/// by [`Event::Begin`] and [`Event::End`], an [`Event::Visual`] or an [`Event::Script`] element,
25/// an [`Event::Space`], or an [`Event::StateChange`].
26///
27/// [`EnvironmentFlow::Alignment`] and [`EnvironmentFlow::NewLine`] events are not considered
28/// elements, and must never occur when an element is expected.
29///
30/// ### Examples
31///
32/// The following examples all constitute a single element:
33///
34/// __Input__: `\text{Hello, world!}`
35/// ```
36/// # use pulldown_latex::event::{Event, Content};
37/// [Event::Content(Content::Text("Hello, world!"))];
38/// ```
39///
40/// __Input__: `x^2_{\text{max}}`
41/// ```
42/// # use pulldown_latex::event::{Event, Content, Grouping, ScriptType, ScriptPosition};
43/// [
44///     Event::Script {
45///         ty: ScriptType::SubSuperscript,
46///         position: ScriptPosition::Right,
47///     },
48///     Event::Begin(Grouping::Normal),
49///     Event::Content(Content::Text("max")),
50///     Event::End,
51///     Event::Content(Content::Ordinary {
52///         content: 'x',
53///         stretchy: false,
54///     }),
55/// ];
56/// ```
57#[derive(Debug, Clone, PartialEq)]
58pub enum Event<'a> {
59    /// The event is a [`Content`] element.
60    Content(Content<'a>),
61    /// The events following this one constitute a "group" which counts as a single _element_
62    /// (i.e., a set of elements within `{}` in `LaTeX`), until the [`Event::End`] event
63    /// is reached.
64    Begin(Grouping),
65    /// Marks the end of a group initiated with [`Event::Begin`].
66    End,
67    /// The `n` elements following this one constitute the content of the [`Visual`] element,
68    /// where `n` is specified in the documentation of for each of the [`Visual`] variants.
69    Visual(Visual),
70    /// The `n` elements following this one constitute a base and its script(s), where `n` is
71    /// specified in the documentation for each of the [`ScriptType`] variants.
72    Script {
73        /// The type of the script.
74        ty: ScriptType,
75        /// The position of the script.
76        position: ScriptPosition,
77    },
78    /// This event specifes a custom spacing. This is produced by commands such as
79    /// `\kern`, `\hspace`, etc.
80    ///
81    /// If any of the components are `None`, then the spacing is set to 0 for that component.
82    Space {
83        /// The amount of space to add before the element.
84        width: Option<Dimension>,
85        /// The amount of space to add after the element.
86        height: Option<Dimension>,
87        /// The amount of depth (space below the baseline) to add.
88        depth: Option<Dimension>,
89    },
90    /// This event specifies a state change in the renderer.
91    ///
92    /// This state change only applies to the current group nesting and deeper groups.
93    StateChange(StateChange),
94
95    /// This is a flow event that is emitted in mathematical environments such as `align`,
96    /// `cases`, `array`, etc.
97    EnvironmentFlow(EnvironmentFlow),
98}
99
100/// Base events that produce `mathml` nodes
101#[derive(Debug, Clone, Copy, PartialEq)]
102pub enum Content<'a> {
103    /// Text content that should be typeset following the rules of `LaTeX`'s `text` mode.
104    Text(&'a str),
105    /// A number, which can include decimal points and commas.
106    Number(&'a str),
107    /// A function identifier, such as `sin`, `lim`, or a custom function with
108    /// `\operatorname{arccotan}`.
109    Function(&'a str),
110    /// A variable identifier, such as `x`, `\theta`, `\aleph`, etc., and other stuff that do not have
111    /// any spacing around them. This includes things that normally go in under and overscripts
112    /// which may be stretchy, e.g., `→`, `‾`, etc.
113    Ordinary {
114        /// The content character.
115        content: char,
116        /// Whether the character is stretchy.
117        ///
118        /// This applies to characters that are in under and overscripts, such as `→`.
119        stretchy: bool,
120    },
121    /// A large operator, e.g., `\sum`, `\int`, `\prod`, etc.
122    LargeOp {
123        /// The content character.
124        content: char,
125        /// Whether the operator is a small variant, e.g., `\smallint`.
126        small: bool,
127    },
128    /// A binary operator, e.g., `+`, `*`, `⊗`, `?`, etc.
129    BinaryOp {
130        /// The content character.
131        content: char,
132        /// Whether the operator is a small variant, e.g., `\smallsetminus`.
133        small: bool,
134    },
135    /// A relation, e.g., `=`, `≠`, `≈`, etc.
136    Relation {
137        /// The content of the relation.
138        content: RelationContent,
139        /// Whether the relation is a small variant, e.g., `\shortparallel`.
140        small: bool,
141    },
142    /// An opening, closing, or fence delimiter, e.g., `(`, `[`, `{`, `|`, `)`, `]`, `}`, etc.
143    Delimiter {
144        /// The delimiter character.
145        content: char,
146        /// The size of the delimiter, if any.
147        size: Option<DelimiterSize>,
148        /// The type of the delimiter.
149        ty: DelimiterType,
150    },
151    /// A punctuation character, such as `,`, `.`, or `;`.
152    Punctuation(char),
153}
154
155/// Modifies the visual representation of the following element(s)
156#[derive(Debug, Clone, Copy, PartialEq)]
157pub enum Visual {
158    /// The following element is the content of the root.
159    SquareRoot,
160    /// The 2 following elements are the radicand and the index of the root.
161    Root,
162    /// The 2 following elements are the numerator and denominator of the fraction.
163    ///
164    /// If the content of the variant is `None`, then the size of the middle line is set to the
165    /// default size, otherwise the size is set to the specified size.
166    Fraction(Option<Dimension>),
167    /// The "negation" operator as in "not equal" (≠) or "does not exist" (∄). This applies to the
168    /// next event in the stream.
169    ///
170    /// This event can occur before an arbitrary event, not just a `Content` event. It is left to
171    /// the renderer to determine how to apply the negation. In `LaTeX`, the renderer usually
172    /// generates an akward looking negation across the next element, when it does not correspond
173    /// to a commonly negated element.
174    Negation,
175}
176
177/// Logical type of the script. This is used to determine how to render the scripts.
178///
179/// Things like subscripts, underscripts, and movable scripts can be represented when using this
180/// `enum` in conjunction with the [`ScriptPosition`] `enum`.
181#[derive(Debug, Clone, Copy, PartialEq)]
182pub enum ScriptType {
183    /// The 2 following elements are the base and and the subscript
184    Subscript,
185    /// The 2 following elements are the base and and the superscript
186    Superscript,
187    /// The 3 following elements are the base, subscript and superscript
188    SubSuperscript,
189}
190
191/// Position of the script. This is used to determine how to render the scripts.
192#[derive(Debug, Clone, Copy, PartialEq)]
193pub enum ScriptPosition {
194    /// The scripts are rendered to the (bottom and top) right of the operator.
195    Right,
196    /// The scripts are rendered above and below the operator instead of to the right.
197    AboveBelow,
198    /// Is set to `AboveBelow` by preference, but should be changed to `Right` when rendering in
199    /// inline mode.
200    ///
201    /// This is used by the `lim` and `sum` (Σ) operators for example.
202    Movable,
203}
204
205/// Represents a state change for the following content.
206///
207/// State changes take effect for the current group nesting and all deeper groups.
208/// State changes are not maintained across `NewLine` and `Alignment` events, and are also reset
209/// when entering a new group that is not a `Grouping::Normal`, or a `Grouping::LeftRight`. The
210/// exception to the latter is `StateChange::Style`, which is maintained across all groups.
211#[derive(Debug, Clone, Copy, PartialEq)]
212pub enum StateChange {
213    /// Changes the font of the content.
214    ///
215    /// If the font is `None`, then the default renderer font is used, otherwise the font is set to
216    /// the specified font.
217    Font(Option<Font>),
218    /// Changes the color of the content.
219    Color(ColorChange),
220    /// Changes the style of the content (mostly affects the sizing of the content).
221    ///
222    /// __Important__: This state change does not affect scripts and root indices.
223    Style(Style),
224}
225
226/// Available font styles from LaTeX.
227#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228pub enum Font {
229    /// The bold and calligraphic font-face.
230    BoldScript,
231    /// The bold and italic font-face.
232    BoldItalic,
233    /// The bold font-face.
234    Bold,
235    /// The `\boldsymbol` font-face: bold italic for characters that are italic
236    /// by default (Latin letters, lowercase Greek), bold upright for characters
237    /// that are upright by default (capital Greek, digits).
238    BoldSymbol,
239    /// The fraktur font-face.
240    Fraktur,
241    /// The calligraphic font-face.
242    Script,
243    /// The monospace font-face.
244    Monospace,
245    /// The sans-serif font-face.
246    SansSerif,
247    /// The double-struck font-face.
248    DoubleStruck,
249    /// The double-struck italic font-face. Covers the 5 letters at U+2145–U+2149
250    /// (ⅅ, ⅆ, ⅇ, ⅈ, ⅉ); no other characters have double-struck italic forms in Unicode.
251    DoubleStruckItalic,
252    /// The italic font-face.
253    Italic,
254    /// The bold fraktur font-face.
255    BoldFraktur,
256    /// The bold sans-serif font-face.
257    SansSerifBoldItalic,
258    /// The sans-serif italic font-face.
259    SansSerifItalic,
260    /// The sans-serif bold font-face.
261    BoldSansSerif,
262    /// The normal font-face.
263    UpRight,
264}
265
266/// The style of the content.
267///
268/// This is analogous to the different "modes" in `LaTeX`, such as `display`, `text`, etc., which
269/// are set by commands like `\displaystyle`, `\textstyle`, etc.
270#[derive(Debug, Clone, Copy, PartialEq)]
271pub enum Style {
272    /// Set by the `\displaystyle` command.
273    Display,
274    /// Set by the `\textstyle` command.
275    Text,
276    /// Set by the `\scriptstyle` command.
277    Script,
278    /// Set by the `\scriptscriptstyle` command.
279    ScriptScript,
280}
281
282/// Represents a color change.
283#[derive(Debug, Clone, Copy, PartialEq)]
284pub struct ColorChange {
285    /// The color to change to.
286    ///
287    /// A string that represents the color to change to, either as a hex RGB color in the form #RRGGBB,
288    /// or as one of the color names existing as part of CSS3 (e.g., "red").
289    pub color: (u8, u8, u8),
290    /// The target of the color change.
291    ///
292    /// Specifies which part of the content to change the color of.
293    pub target: ColorTarget,
294}
295
296/// The target of the color change.
297#[derive(Debug, Clone, Copy, PartialEq)]
298pub enum ColorTarget {
299    /// The text of the content.
300    Text,
301    /// The background of the content.
302    Background,
303    /// The border surrounding the content.
304    Border,
305}
306
307/// Represents a grouping of elements, which is itself a single logical element.
308///
309/// This can be created by a lot of different `LaTeX` commands, such as `{}`, `\left`, `\right`,
310/// `\begin{...}`, `\end{...}`, etc.
311#[derive(Debug, Clone, PartialEq)]
312pub enum Grouping {
313    /// A normal form of grouping, usually induced by `{}` or `\begingroup` and `\endgroup` in `LaTeX`.
314    Normal,
315    /// A grouping that is induced by `\left` and `\right` in `LaTeX`.
316    LeftRight(Option<char>, Option<char>),
317    /// The array environment of `LaTeX`.
318    ///
319    /// It's content is an array of columns, which represents the column specification in `LaTeX`.
320    ///
321    /// ## Example
322    ///
323    /// __Input__: `\begin{array}{lcr} ... \end{array}`
324    /// __Generates__:
325    /// ```
326    /// # use pulldown_latex::event::{ArrayColumn, ColumnAlignment, Grouping};
327    /// Grouping::Array(Box::new([
328    ///     ArrayColumn::Column(ColumnAlignment::Left),
329    ///     ArrayColumn::Column(ColumnAlignment::Center),
330    ///     ArrayColumn::Column(ColumnAlignment::Right),
331    /// ]));
332    /// ```
333    /// ## Invariant
334    ///
335    /// The content of the `Array` variant is guaranteed to be non-empty, and contain at least one
336    /// [`ArrayColumn::Column`].
337    Array(Box<[ArrayColumn]>),
338    /// The `matrix` environment of `LaTeX`.
339    Matrix {
340        /// The default alignment is `ColumnAlignment::Center`, but it can be specified by in `LaTeX`
341        /// when using the `\begin{matrix*}[l] ... \end{matrix*}` syntax.
342        alignment: ColumnAlignment,
343    },
344    /// The `cases` environment of `LaTeX`.
345    Cases {
346        /// `left` is true if the environment is `cases` and false if the environment is `rcases`.
347        left: bool,
348    },
349    /// The `equation` environment of `LaTeX`.
350    Equation {
351        /// If `eq_numbers` is true, then equation numbers are displayed.
352        eq_numbers: bool,
353    },
354    /// The `align` environment of `LaTeX`.
355    Align {
356        /// If `eq_numbers` is true, then equation numbers are displayed.
357        eq_numbers: bool,
358    },
359    /// The `aligned` environment of `LaTeX`.
360    Aligned,
361    /// The `subarray` environment of `LaTeX`.
362    SubArray {
363        /// The alignment of the columns in the subarray.
364        alignment: ColumnAlignment,
365    },
366    /// The `alignat` environment of `LaTeX`.
367    Alignat {
368        /// `pairs` specifies the number of left-right column pairs specified in the environment
369        /// declaration.
370        pairs: u16,
371        /// If `eq_numbers` is true, then equation numbers are displayed.
372        eq_numbers: bool,
373    },
374    /// The `alignedat` environment of `LaTeX`.
375    Alignedat {
376        /// `pairs` specifies the number of left-right column pairs specified in the environment
377        pairs: u16,
378    },
379    /// The `gather` environment of `LaTeX`.
380    Gather {
381        /// If `eq_numbers` is true, then equation numbers are displayed.
382        eq_numbers: bool,
383    },
384    /// The `gathered` environment of `LaTeX`.
385    Gathered,
386    /// The `multline` environment of `LaTeX`.
387    Multline,
388    /// The `split` environment of `LaTeX`.
389    Split,
390}
391
392impl Grouping {
393    pub(crate) fn is_math_env(&self) -> bool {
394        !matches!(self, Self::Normal | Self::LeftRight(_, _))
395    }
396}
397
398#[derive(Debug, Clone, PartialEq)]
399pub enum EnvironmentFlow {
400    /// This event specifies an alignment mark in a mathematical environment.
401    ///
402    /// This event is only emitted when inside a `Grouping` that allows it.
403    Alignment,
404    /// This event specifies a line break in a mathematical environment.
405    ///
406    /// This event is only emitted when inside a of `Grouping` that allows it.
407    NewLine {
408        /// The amount of space to add after the line break.
409        spacing: Option<Dimension>,
410        /// The horizontal lines to draw after the line break.
411        horizontal_lines: Box<[Line]>,
412    },
413
414    /// This event is emitted specifically when an environment begins with horizontal lines
415    /// as the first element.
416    ///
417    /// ### Examples
418    /// ```
419    /// use pulldown_latex::{
420    ///     event::{ArrayColumn, ColumnAlignment, Content, EnvironmentFlow, Event, Grouping, Line},
421    ///     Parser, Storage,
422    /// };
423    ///
424    /// const INPUT: &str = r#"\begin{array}{|c|c|c|} \hline a & b & c \\ \hline d & e & f \\
425    /// \hline \end{array}"#;
426    ///
427    /// let storage = Storage::new();
428    /// let mut parser = Parser::new(INPUT, &storage);
429    /// let events = parser.collect::<Result<Vec<_>, _>>().unwrap();
430    ///
431    /// assert_eq!(
432    ///     events,
433    ///     vec![
434    ///         Event::Begin(Grouping::Array(Box::new([
435    ///             ArrayColumn::Separator(Line::Solid),
436    ///             ArrayColumn::Column(ColumnAlignment::Center),
437    ///             ArrayColumn::Separator(Line::Solid),
438    ///             ArrayColumn::Column(ColumnAlignment::Center),
439    ///             ArrayColumn::Separator(Line::Solid),
440    ///             ArrayColumn::Column(ColumnAlignment::Center),
441    ///             ArrayColumn::Separator(Line::Solid),
442    ///         ]))),
443    ///         Event::EnvironmentFlow(EnvironmentFlow::StartLines {
444    ///             lines: Box::new([Line::Solid]),
445    ///         }),
446    ///         Event::Content(Content::Ordinary {
447    ///             content: 'a',
448    ///             stretchy: false,
449    ///         }),
450    ///         Event::EnvironmentFlow(EnvironmentFlow::Alignment),
451    ///         Event::Content(Content::Ordinary {
452    ///             content: 'b',
453    ///             stretchy: false,
454    ///         }),
455    ///         Event::EnvironmentFlow(EnvironmentFlow::Alignment),
456    ///         Event::Content(Content::Ordinary {
457    ///             content: 'c',
458    ///             stretchy: false,
459    ///         }),
460    ///         Event::EnvironmentFlow(EnvironmentFlow::NewLine {
461    ///             spacing: None,
462    ///             horizontal_lines: Box::new([Line::Solid]),
463    ///         }),
464    ///         Event::Content(Content::Ordinary {
465    ///             content: 'd',
466    ///             stretchy: false,
467    ///         }),
468    ///         Event::EnvironmentFlow(EnvironmentFlow::Alignment),
469    ///         Event::Content(Content::Ordinary {
470    ///             content: 'e',
471    ///             stretchy: false,
472    ///         }),
473    ///         Event::EnvironmentFlow(EnvironmentFlow::Alignment),
474    ///         Event::Content(Content::Ordinary {
475    ///             content: 'f',
476    ///             stretchy: false,
477    ///         }),
478    ///         Event::EnvironmentFlow(EnvironmentFlow::NewLine {
479    ///             spacing: None,
480    ///             horizontal_lines: Box::new([Line::Solid]),
481    ///         }),
482    ///         Event::End,
483    ///     ]
484    /// );
485    /// ```
486    StartLines { lines: Box<[Line]> },
487}
488
489#[derive(Debug, Clone, Copy)]
490pub(crate) enum GroupingKind {
491    Normal,
492    OptionalArgument,
493    BeginEnd,
494    LeftRight,
495    Array { display: bool },
496    Matrix { ty: MatrixType, column_spec: bool },
497    Cases { left: bool, display: bool },
498    Equation { eq_numbers: bool },
499    Align { eq_numbers: bool },
500    Aligned,
501    SubArray,
502    Alignat { eq_numbers: bool },
503    Alignedat,
504    Gather { eq_numbers: bool },
505    Gathered,
506    Multline,
507    Split,
508}
509
510impl GroupingKind {
511    pub fn opening_str(&self) -> &'static str {
512        match self {
513            Self::Normal => "{",
514            Self::OptionalArgument => "[",
515            Self::BeginEnd => "\\begin",
516            Self::LeftRight => "\\left",
517            Self::Array { display: false } => "\\begin{array}",
518            Self::Array { display: true } => "\\begin{darray}",
519            Self::Matrix { ty, column_spec } => match (ty, column_spec) {
520                (MatrixType::Normal, true) => "\\begin{matrix*}",
521                (MatrixType::Normal, false) => "\\begin{matrix}",
522                (MatrixType::Small, true) => "\\begin{smallmatrix*}",
523                (MatrixType::Small, false) => "\\begin{smallmatrix}",
524                (MatrixType::Parens, true) => "\\begin{pmatrix*}",
525                (MatrixType::Parens, false) => "\\begin{pmatrix}",
526                (MatrixType::Brackets, true) => "\\begin{bmatrix*}",
527                (MatrixType::Brackets, false) => "\\begin{bmatrix}",
528                (MatrixType::Braces, true) => "\\begin{Bmatrix*}",
529                (MatrixType::Braces, false) => "\\begin{Bmatrix}",
530                (MatrixType::Vertical, true) => "\\begin{vmatrix*}",
531                (MatrixType::Vertical, false) => "\\begin{vmatrix}",
532                (MatrixType::DoubleVertical, true) => "\\begin{Vmatrix*}",
533                (MatrixType::DoubleVertical, false) => "\\begin{Vmatrix}",
534            },
535            Self::Cases { left, display } => match (left, display) {
536                (true, false) => "\\begin{cases}",
537                (true, true) => "\\begin{dcases}",
538                (false, false) => "\\begin{rcases}",
539                (false, true) => "\\begin{drcases}",
540            },
541            Self::Equation { eq_numbers: true } => "\\begin{equation}",
542            Self::Equation { eq_numbers: false } => "\\begin{equation*}",
543            Self::Align { eq_numbers: true } => "\\begin{align}",
544            Self::Align { eq_numbers: false } => "\\begin{align*}",
545            Self::Aligned => "\\begin{aligned}",
546            Self::SubArray => "\\begin{subarray}",
547            Self::Alignat { eq_numbers: true } => "\\begin{alignat}",
548            Self::Alignat { eq_numbers: false } => "\\begin{alignat*}",
549            Self::Alignedat => "\\begin{alignedat}",
550            Self::Gather { eq_numbers: true } => "\\begin{gather}",
551            Self::Gather { eq_numbers: false } => "\\begin{gather*}",
552            Self::Gathered => "\\begin{gathered}",
553            Self::Multline => "\\begin{multline}",
554            Self::Split => "\\begin{split}",
555        }
556    }
557
558    pub fn closing_str(&self) -> &'static str {
559        match self {
560            Self::Normal => "}",
561            Self::OptionalArgument => "]",
562            Self::BeginEnd => "\\end",
563            Self::LeftRight => "\\right",
564            Self::Array { display: false } => "\\end{array}",
565            Self::Array { display: true } => "\\end{darray}",
566            Self::Matrix { ty, column_spec } => match (ty, column_spec) {
567                (MatrixType::Normal, true) => "\\end{matrix*}",
568                (MatrixType::Normal, false) => "\\end{matrix}",
569                (MatrixType::Small, true) => "\\end{smallmatrix*}",
570                (MatrixType::Small, false) => "\\end{smallmatrix}",
571                (MatrixType::Parens, true) => "\\end{pmatrix*}",
572                (MatrixType::Parens, false) => "\\end{pmatrix}",
573                (MatrixType::Brackets, true) => "\\end{bmatrix*}",
574                (MatrixType::Brackets, false) => "\\end{bmatrix}",
575                (MatrixType::Braces, true) => "\\end{Bmatrix*}",
576                (MatrixType::Braces, false) => "\\end{Bmatrix}",
577                (MatrixType::Vertical, true) => "\\end{vmatrix*}",
578                (MatrixType::Vertical, false) => "\\end{vmatrix}",
579                (MatrixType::DoubleVertical, true) => "\\end{Vmatrix*}",
580                (MatrixType::DoubleVertical, false) => "\\end{Vmatrix}",
581            },
582            Self::Cases { left, display } => match (left, display) {
583                (true, false) => "\\end{cases}",
584                (true, true) => "\\end{dcases}",
585                (false, false) => "\\end{rcases}",
586                (false, true) => "\\end{drcases}",
587            },
588            Self::Equation { eq_numbers: true } => "\\end{equation}",
589            Self::Equation { eq_numbers: false } => "\\end{equation*}",
590            Self::Align { eq_numbers: true } => "\\end{align}",
591            Self::Align { eq_numbers: false } => "\\end{align*}",
592            Self::Aligned => "\\end{aligned}",
593            Self::SubArray => "\\end{subarray}",
594            Self::Alignat { eq_numbers: true } => "\\end{alignat}",
595            Self::Alignat { eq_numbers: false } => "\\end{alignat*}",
596            Self::Alignedat => "\\end{alignedat}",
597            Self::Gather { eq_numbers: true } => "\\end{gather}",
598            Self::Gather { eq_numbers: false } => "\\end{gather*}",
599            Self::Gathered => "\\end{gathered}",
600            Self::Multline => "\\end{multline}",
601            Self::Split => "\\end{split}",
602        }
603    }
604}
605
606#[derive(Debug, Clone, Copy)]
607pub(crate) enum MatrixType {
608    Normal,
609    Small,
610    Parens,
611    Brackets,
612    Braces,
613    Vertical,
614    DoubleVertical,
615}
616
617/// Represents a column in a matrix or array environment.
618#[derive(Debug, Clone, Copy, PartialEq)]
619pub enum ColumnAlignment {
620    /// Content in the column is left-aligned.
621    Left,
622    /// Content in the column is center-aligned.
623    Center,
624    /// Content in the column is right-aligned.
625    Right,
626}
627
628/// Represents a column in an array environment specification.
629///
630/// It can either be a column specification or a vertical separator specification.
631#[derive(Debug, Clone, Copy, PartialEq)]
632pub enum ArrayColumn {
633    /// A column specification.
634    Column(ColumnAlignment),
635    /// A vertical separator specification.
636    Separator(Line),
637}
638
639/// Represents a delimiter size.
640#[derive(Debug, Clone, Copy, PartialEq)]
641pub enum DelimiterSize {
642    /// Corresponds to `\bigl`, `\bigr`, etc.
643    Big,
644    /// Corresponds to `\Bigl`, `\Bigr`, etc.
645    BIG,
646    /// Corresponds to `\biggl`, `\biggr`, etc.
647    Bigg,
648    /// Corresponds to `\Biggl`, `\Biggr`, etc.
649    BIGG,
650}
651
652impl DelimiterSize {
653    pub(crate) fn to_em(self) -> f32 {
654        match self {
655            DelimiterSize::Big => 1.2,
656            DelimiterSize::BIG => 1.8,
657            DelimiterSize::Bigg => 2.4,
658            DelimiterSize::BIGG => 3.,
659        }
660    }
661}
662
663/// Whether the delimiter is an opening, closing, or fence delimiter.
664#[derive(Debug, Clone, Copy, PartialEq)]
665pub enum DelimiterType {
666    /// Corresponds to the left delimiter.
667    Open,
668    /// Corresponds to a delimiter that is introduced by the command `\middle`.
669    Fence,
670    /// Corresponds to the right delimiter.
671    Close,
672}
673
674/// Represents a line in a `LaTeX` environment.
675#[derive(Debug, Clone, Copy, PartialEq)]
676pub enum Line {
677    /// A solid line.
678    Solid,
679    /// A dashed line.
680    Dashed,
681}
682
683/// Sometimes mathematical relations can be made of more than one character, so we need a way to
684/// represent them when one character is not enough.
685#[derive(Debug, Clone, Copy, PartialEq)]
686pub struct RelationContent {
687    content: (char, Option<char>),
688}
689
690impl RelationContent {
691    pub(crate) fn single_char(content: char) -> Self {
692        Self {
693            content: (content, None),
694        }
695    }
696
697    pub(crate) fn double_char(first: char, second: char) -> Self {
698        Self {
699            content: (first, Some(second)),
700        }
701    }
702
703    /// Write the content of the relation to a buffer, and output the filled slice of that
704    /// buffer.
705    ///
706    /// To ensure a successful operation, the buffer must be at least 8 bytes long.
707    pub fn encode_utf8_to_buf<'a>(&self, buf: &'a mut [u8]) -> &'a [u8] {
708        let mut len = self.content.0.encode_utf8(buf).len();
709        if let Some(second) = self.content.1 {
710            len += second.encode_utf8(&mut buf[len..]).len();
711        }
712        &buf[..len]
713    }
714}
715
716/// Represents a glue specification.
717pub type Glue = (Dimension, Option<Dimension>, Option<Dimension>);
718
719/// Represents a LaTeX dimension.
720#[derive(Debug, Clone, Copy, PartialEq)]
721pub struct Dimension {
722    /// The value of the dimension.
723    pub value: f32,
724    /// The unit of the dimension.
725    pub unit: DimensionUnit,
726}
727
728impl Dimension {
729    /// Creates a new dimension.
730    pub fn new(value: f32, unit: DimensionUnit) -> Self {
731        Self { value, unit }
732    }
733}
734
735/// Displays a LaTeX dimension into a CSS dimension string.
736///
737/// The conversion is done using the following relations between the TeX and CSS units:
738/// | TeX unit         | CSS unit          |
739/// | ---------------- | ----------------- |
740/// | 1 em             | 1 em              |
741/// | 18 mu            | 1 em              |
742/// | 1 ex             | 1 ex              |
743/// | 1 mm             | 1 mm              |
744/// | 1 cm             | 1 cm              |
745/// | 1 in             | 1 in              |
746/// | 1 bp             | 1 pt              |
747/// | 72.27 pt         | 72 pt             |
748/// | 72.27 pc         | 72 pc             |
749/// | 65536 * 72.27 sp | 1 pt              |
750/// | 1157 * 72.27 dd  | 1238 * 72 pt      |
751/// | 1157 * 72.27 cc  | 12 * 1238 * 72 pt |
752impl Display for Dimension {
753    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
754        const BP_PER_PT: f32 = 72. / 72.27;
755        const BP_PER_SP: f32 = 1. / 65536. * BP_PER_PT;
756        const BP_PER_DD: f32 = 1238. / 1157. * BP_PER_PT;
757        const BP_PER_CC: f32 = 12. * BP_PER_DD;
758        const EM_PER_MU: f32 = 1. / 18.;
759        let mut value = self.value;
760        let unit = match self.unit {
761            DimensionUnit::Em => "em",
762            DimensionUnit::Mu => {
763                value *= EM_PER_MU;
764                "mu"
765            }
766            DimensionUnit::Ex => "ex",
767            DimensionUnit::Mm => "mm",
768            DimensionUnit::Cm => "cm",
769            DimensionUnit::In => "in",
770            DimensionUnit::Bp => "pt",
771            DimensionUnit::Pt => {
772                value *= BP_PER_PT;
773                "pt"
774            }
775            DimensionUnit::Pc => {
776                value *= BP_PER_PT;
777                "pc"
778            }
779            DimensionUnit::Sp => {
780                value *= BP_PER_SP;
781                "pt"
782            }
783            DimensionUnit::Dd => {
784                value *= BP_PER_DD;
785                "pt"
786            }
787            DimensionUnit::Cc => {
788                value *= BP_PER_CC;
789                "pt"
790            }
791        };
792        write!(f, "{}{}", value, unit)
793    }
794}
795
796// From the TeXbook, p. 57, 60, 167.
797/// Represents a dimension unit in LaTeX.
798#[derive(Debug, Clone, Copy, PartialEq, Eq)]
799pub enum DimensionUnit {
800    /// The `em` unit.
801    Em,
802    /// The "math" unit.
803    Mu,
804    /// The `ex` unit.
805    Ex,
806    /// The "point" unit.
807    Pt,
808    /// The "picas" unit.
809    Pc,
810    /// The "inch" unit.
811    In,
812    /// The "big point" unit.
813    Bp,
814    /// The "centimeter" unit.
815    Cm,
816    /// The "millimeter" unit.
817    Mm,
818    /// The "didot point" unit.
819    Dd,
820    /// The "cicero" unit.
821    Cc,
822    /// The "scaled point" unit.
823    Sp,
824}