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