1use crate::color::Color;
4use crate::dim::Dim;
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum AtomKind {
9 Ord,
11 Op,
13 Bin,
15 Rel,
17 Open,
19 Close,
21 Punct,
23 Inner,
25}
26
27impl AtomKind {
28 fn gold(self) -> &'static str {
29 match self {
30 Self::Ord => "Ord",
31 Self::Op => "Op",
32 Self::Bin => "Bin",
33 Self::Rel => "Rel",
34 Self::Open => "Open",
35 Self::Close => "Close",
36 Self::Punct => "Punct",
37 Self::Inner => "Inner",
38 }
39 }
40}
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44pub enum AccentKind {
45 Hat,
47 Check,
49 Breve,
51 Acute,
53 Grave,
55 Tilde,
57 Bar,
59 Vec,
61 Dot,
63 Ddot,
65 Dddot,
67 Ddddot,
69 WideHat,
71 WideTilde,
73 Overline,
75 Underline,
77 Overbrace,
79 Underbrace,
81 Overleftarrow,
83 Overrightarrow,
85 Overleftrightarrow,
87 Underleftarrow,
89 Underrightarrow,
91 Underleftrightarrow,
93 Cancel,
95 BCancel,
97 XCancel,
99 Boxed,
101 Ring,
103 Not,
105}
106
107impl AccentKind {
108 pub(crate) fn gold(self) -> &'static str {
109 match self {
110 Self::Hat => "hat",
111 Self::Check => "check",
112 Self::Breve => "breve",
113 Self::Acute => "acute",
114 Self::Grave => "grave",
115 Self::Tilde => "tilde",
116 Self::Bar => "bar",
117 Self::Vec => "vec",
118 Self::Dot => "dot",
119 Self::Ddot => "ddot",
120 Self::Dddot => "dddot",
121 Self::Ddddot => "ddddot",
122 Self::WideHat => "widehat",
123 Self::WideTilde => "widetilde",
124 Self::Overline => "overline",
125 Self::Underline => "underline",
126 Self::Overbrace => "overbrace",
127 Self::Underbrace => "underbrace",
128 Self::Overleftarrow => "overleftarrow",
129 Self::Overrightarrow => "overrightarrow",
130 Self::Overleftrightarrow => "overleftrightarrow",
131 Self::Underleftarrow => "underleftarrow",
132 Self::Underrightarrow => "underrightarrow",
133 Self::Underleftrightarrow => "underleftrightarrow",
134 Self::Cancel => "cancel",
135 Self::BCancel => "bcancel",
136 Self::XCancel => "xcancel",
137 Self::Boxed => "boxed",
138 Self::Ring => "mathring",
139 Self::Not => "not",
140 }
141 }
142}
143
144#[derive(Clone, Copy, Debug, PartialEq, Eq)]
146pub enum TextStyle {
147 Rm,
149 Bf,
151 It,
153 Sf,
155 Tt,
157 Bb,
159 Cal,
161 Frak,
163 Scr,
165 Boldsymbol,
167 Pmb,
169 Text,
171}
172
173impl TextStyle {
174 fn gold(self) -> &'static str {
175 match self {
176 Self::Rm => "rm",
177 Self::Bf => "bf",
178 Self::It => "it",
179 Self::Sf => "sf",
180 Self::Tt => "tt",
181 Self::Bb => "bb",
182 Self::Cal => "cal",
183 Self::Frak => "frak",
184 Self::Scr => "scr",
185 Self::Boldsymbol => "boldsymbol",
186 Self::Pmb => "pmb",
187 Self::Text => "text",
188 }
189 }
190}
191
192#[derive(Clone, Debug, PartialEq, Eq)]
194pub enum SpaceKind {
195 Thin,
197 Medium,
199 Thick,
201 NegThin,
203 Quad,
205 Qquad,
207 ControlSpace,
209 Hspace(Dim),
211}
212
213#[derive(Clone, Copy, Debug, PartialEq, Eq)]
215pub enum MatrixStyle {
216 Matrix,
218 Pmatrix,
220 Bmatrix,
222 Vmatrix,
224 VVmatrix,
226 BBmatrix,
228 Cases,
230 Array,
232 Aligned,
234 Align,
236 Gather,
238 Multline,
240 Equation,
242 Split,
244}
245
246impl MatrixStyle {
247 fn gold(self) -> &'static str {
248 match self {
249 Self::Matrix => "matrix",
250 Self::Pmatrix => "pmatrix",
251 Self::Bmatrix => "bmatrix",
252 Self::Vmatrix => "vmatrix",
253 Self::VVmatrix => "Vmatrix",
254 Self::BBmatrix => "Bmatrix",
255 Self::Cases => "cases",
256 Self::Array => "array",
257 Self::Aligned => "aligned",
258 Self::Align => "align",
259 Self::Gather => "gather",
260 Self::Multline => "multline",
261 Self::Equation => "equation",
262 Self::Split => "split",
263 }
264 }
265
266 #[must_use]
268 pub fn is_display_env(self) -> bool {
269 matches!(
270 self,
271 Self::Align | Self::Gather | Self::Multline | Self::Equation
272 )
273 }
274
275 #[must_use]
277 pub fn numbers_rows(self) -> bool {
278 matches!(self, Self::Align | Self::Gather)
279 }
280
281 #[must_use]
283 pub fn numbers_once(self) -> bool {
284 matches!(self, Self::Equation | Self::Multline)
285 }
286}
287
288#[derive(Clone, Copy, Debug, PartialEq, Eq)]
290pub enum ColSpec {
291 Left,
293 Center,
295 Right,
297 VRule,
299}
300
301impl ColSpec {
302 fn gold(self) -> char {
303 match self {
304 Self::Left => 'l',
305 Self::Center => 'c',
306 Self::Right => 'r',
307 Self::VRule => '|',
308 }
309 }
310
311 #[must_use]
313 pub fn is_rule(self) -> bool {
314 matches!(self, Self::VRule)
315 }
316}
317
318#[derive(Clone, Debug, PartialEq, Eq)]
320pub enum EqNumber {
321 Default,
323 Suppress,
325 Tag {
327 star: bool,
329 body: Box<MathNode>,
331 },
332}
333
334#[derive(Clone, Debug, PartialEq, Eq)]
336pub enum EnvRow {
337 Cells {
339 cells: Vec<MathNode>,
341 number: EqNumber,
343 labels: Vec<String>,
345 },
346 Hline,
348 Intertext(Box<MathNode>),
350}
351
352impl EnvRow {
353 #[must_use]
355 pub fn cells(cells: Vec<MathNode>) -> Self {
356 Self::Cells {
357 cells,
358 number: EqNumber::Default,
359 labels: Vec::new(),
360 }
361 }
362
363 fn gold(&self) -> String {
364 match self {
365 Self::Hline => "(hline)".into(),
366 Self::Intertext(n) => format!("(intertext {})", n.gold()),
367 Self::Cells {
368 cells,
369 number,
370 labels,
371 } => {
372 let mut s = String::from("(");
373 for (i, c) in cells.iter().enumerate() {
374 if i > 0 {
375 s.push(' ');
376 }
377 s.push_str(&c.gold());
378 }
379 match number {
380 EqNumber::Default => {}
381 EqNumber::Suppress => s.push_str(" (nonumber)"),
382 EqNumber::Tag { star: false, body } => {
383 s.push_str(&format!(" (tag {})", body.gold()));
384 }
385 EqNumber::Tag { star: true, body } => {
386 s.push_str(&format!(" (tagstar {})", body.gold()));
387 }
388 }
389 for lab in labels {
390 s.push_str(&format!(" (label {lab})"));
391 }
392 s.push(')');
393 s
394 }
395 }
396 }
397}
398
399#[derive(Clone, Copy, Debug, PartialEq, Eq)]
401pub enum IntegralKind {
402 Int,
404 Iint,
406 Iiint,
408 Oint,
410 Oiint,
412}
413
414impl IntegralKind {
415 fn gold(self) -> &'static str {
416 match self {
417 Self::Int => "int",
418 Self::Iint => "iint",
419 Self::Iiint => "iiint",
420 Self::Oint => "oint",
421 Self::Oiint => "oiint",
422 }
423 }
424}
425
426#[derive(Clone, Copy, Debug, PartialEq, Eq)]
428pub enum PhantomKind {
429 Full,
431 Vertical,
433 Horizontal,
435}
436
437impl PhantomKind {
438 fn gold(self) -> &'static str {
439 match self {
440 Self::Full => "phantom",
441 Self::Vertical => "vphantom",
442 Self::Horizontal => "hphantom",
443 }
444 }
445}
446
447#[derive(Clone, Debug, PartialEq, Eq)]
449pub enum Delimiter {
450 Empty,
452 Char(char),
454 Named(String),
456}
457
458impl Delimiter {
459 fn gold(&self) -> String {
460 match self {
461 Self::Empty => ".".into(),
462 Self::Char(c) => c.to_string(),
463 Self::Named(n) => {
464 if n == "{" || n == "}" || n == "|" {
465 format!("\\{n}")
466 } else {
467 n.clone()
468 }
469 }
470 }
471 }
472}
473
474#[derive(Clone, Copy, Debug, PartialEq, Eq)]
476pub enum DelimSize {
477 Big,
479 Big2,
481 Bigg,
483 Bigg2,
485}
486
487impl DelimSize {
488 fn gold(self) -> &'static str {
489 match self {
490 Self::Big => "big",
491 Self::Big2 => "Big",
492 Self::Bigg => "bigg",
493 Self::Bigg2 => "Bigg",
494 }
495 }
496
497 #[must_use]
499 pub fn from_command(name: &str) -> Option<Self> {
500 match name {
501 "big" | "bigl" | "bigr" | "bigm" => Some(Self::Big),
502 "Big" | "Bigl" | "Bigr" | "Bigm" => Some(Self::Big2),
503 "bigg" | "biggl" | "biggr" | "biggm" => Some(Self::Bigg),
504 "Bigg" | "Biggl" | "Biggr" | "Biggm" => Some(Self::Bigg2),
505 _ => None,
506 }
507 }
508
509 #[must_use]
511 pub fn class_from_command(name: &str) -> Option<AtomKind> {
512 if name.ends_with('l') {
513 Some(AtomKind::Open)
514 } else if name.ends_with('r') {
515 Some(AtomKind::Close)
516 } else if name.ends_with('m') {
517 Some(AtomKind::Rel)
518 } else {
519 None
520 }
521 }
522}
523
524#[derive(Clone, Debug, PartialEq, Eq)]
537pub enum MathNode {
538 Atom(char, AtomKind),
540 Fraction(Box<MathNode>, Box<MathNode>),
542 Radical(Option<Box<MathNode>>, Box<MathNode>),
544 Superscript(Box<MathNode>, Box<MathNode>),
546 Subscript(Box<MathNode>, Box<MathNode>),
548 SubSup(Box<MathNode>, Box<MathNode>, Box<MathNode>),
550 Delimited(Delimiter, Box<MathNode>, Delimiter),
552 SizedDelim(Delimiter, DelimSize, AtomKind),
554 Row(Vec<MathNode>),
556 Matrix(MatrixStyle, Vec<ColSpec>, Vec<EnvRow>),
558 Substack(Vec<MathNode>),
560 Ref(String),
562 Tag {
564 star: bool,
566 body: Box<MathNode>,
568 },
569 Label(String),
571 NoNumber,
573 Hline,
575 Intertext(Box<MathNode>),
577 Sum(Option<Box<MathNode>>, Option<Box<MathNode>>),
579 Integral(IntegralKind, Option<Box<MathNode>>, Option<Box<MathNode>>),
581 Product(Option<Box<MathNode>>, Option<Box<MathNode>>),
583 Limit(Option<Box<MathNode>>),
585 OverUnder(Box<MathNode>, Option<Box<MathNode>>, Option<Box<MathNode>>),
587 Accent(Box<MathNode>, AccentKind),
589 CancelTo(Box<MathNode>, Box<MathNode>),
591 Text(String, TextStyle),
593 Space(SpaceKind),
595 Operator(String, bool),
597 Symbol(String),
599 Color(Color, Box<MathNode>),
601 TextColor(Color, Box<MathNode>),
603 ColorBox(Color, Box<MathNode>),
605 FColorBox(Color, Color, Box<MathNode>),
607 Strut(Dim, Dim),
609 Phantom(PhantomKind, Box<MathNode>),
611}
612
613impl MathNode {
614 #[must_use]
616 pub fn gold(&self) -> String {
617 match self {
618 Self::Atom(c, k) => format!("(atom {} {})", k.gold(), quote_atom(*c)),
619 Self::Fraction(n, d) => format!("(frac {} {})", n.gold(), d.gold()),
620 Self::Radical(None, r) => format!("(sqrt {})", r.gold()),
621 Self::Radical(Some(i), r) => format!("(sqrtn {} {})", i.gold(), r.gold()),
622 Self::Superscript(b, e) => format!("(sup {} {})", b.gold(), e.gold()),
623 Self::Subscript(b, s) => format!("(sub {} {})", b.gold(), s.gold()),
624 Self::SubSup(b, s, e) => format!("(subsup {} {} {})", b.gold(), s.gold(), e.gold()),
625 Self::Delimited(l, b, r) => {
626 format!("(delim {} {} {})", l.gold(), b.gold(), r.gold())
627 }
628 Self::SizedDelim(d, sz, k) => {
629 format!("(big {} {} {})", sz.gold(), k.gold(), quote_delim(d))
630 }
631 Self::Row(items) => {
632 if items.is_empty() {
633 "(row)".into()
634 } else {
635 let mut s = String::from("(row");
636 for it in items {
637 s.push(' ');
638 s.push_str(&it.gold());
639 }
640 s.push(')');
641 s
642 }
643 }
644 Self::Matrix(style, spec, rows) => {
645 let mut s = format!("(matrix {}", style.gold());
646 if !spec.is_empty() {
647 s.push(' ');
648 for c in spec {
649 s.push(c.gold());
650 }
651 }
652 for row in rows {
653 s.push(' ');
654 s.push_str(&row.gold());
655 }
656 s.push(')');
657 s
658 }
659 Self::Substack(lines) => {
660 let mut s = String::from("(substack");
661 for ln in lines {
662 s.push(' ');
663 s.push_str(&ln.gold());
664 }
665 s.push(')');
666 s
667 }
668 Self::Ref(k) => format!("(ref {k})"),
669 Self::Tag { star: false, body } => format!("(tag {})", body.gold()),
670 Self::Tag { star: true, body } => format!("(tagstar {})", body.gold()),
671 Self::Label(k) => format!("(label {k})"),
672 Self::NoNumber => "(nonumber)".into(),
673 Self::Hline => "(hline)".into(),
674 Self::Intertext(n) => format!("(intertext {})", n.gold()),
675 Self::Sum(lo, hi) => format!("(sum {} {})", opt(lo), opt(hi)),
676 Self::Integral(k, lo, hi) => {
677 format!("({} {} {})", k.gold(), opt(lo), opt(hi))
678 }
679 Self::Product(lo, hi) => format!("(prod {} {})", opt(lo), opt(hi)),
680 Self::Limit(lo) => format!("(lim {})", opt(lo)),
681 Self::OverUnder(b, over, under) => {
682 format!("(overunder {} {} {})", b.gold(), opt(over), opt(under))
683 }
684 Self::Accent(b, a) => format!("(accent {} {})", a.gold(), b.gold()),
685 Self::CancelTo(v, e) => format!("(cancelto {} {})", v.gold(), e.gold()),
686 Self::Text(t, st) => format!("(text {} {})", st.gold(), quote_text(t)),
687 Self::Space(SpaceKind::Thin) => "(space thin)".into(),
688 Self::Space(SpaceKind::Medium) => "(space medium)".into(),
689 Self::Space(SpaceKind::Thick) => "(space thick)".into(),
690 Self::Space(SpaceKind::NegThin) => "(space negthin)".into(),
691 Self::Space(SpaceKind::Quad) => "(space quad)".into(),
692 Self::Space(SpaceKind::Qquad) => "(space qquad)".into(),
693 Self::Space(SpaceKind::ControlSpace) => "(space control)".into(),
694 Self::Space(SpaceKind::Hspace(d)) => format!("(space hspace {})", dim_gold(d)),
695 Self::Operator(name, false) => format!("(op {name})"),
696 Self::Operator(name, true) => format!("(op {name} limits)"),
697 Self::Symbol(name) => format!("(symbol {name})"),
698 Self::Color(c, b) => format!("(color {} {})", c.css_hex(), b.gold()),
699 Self::TextColor(c, b) => format!("(textcolor {} {})", c.css_hex(), b.gold()),
700 Self::ColorBox(c, b) => format!("(colorbox {} {})", c.css_hex(), b.gold()),
701 Self::FColorBox(border, fill, b) => {
702 format!(
703 "(fcolorbox {} {} {})",
704 border.css_hex(),
705 fill.css_hex(),
706 b.gold()
707 )
708 }
709 Self::Strut(h, d) => format!("(strut {} {})", dim_gold(h), dim_gold(d)),
710 Self::Phantom(k, b) => format!("({} {})", k.gold(), b.gold()),
711 }
712 }
713}
714
715fn opt(n: &Option<Box<MathNode>>) -> String {
716 match n {
717 None => "_".into(),
718 Some(x) => x.gold(),
719 }
720}
721
722fn quote_delim(d: &Delimiter) -> String {
723 match d {
724 Delimiter::Empty => ".".into(),
725 Delimiter::Char(c) => quote_atom(*c),
726 Delimiter::Named(n) => n.clone(),
727 }
728}
729
730fn quote_atom(c: char) -> String {
731 match c {
732 '"' => "'\"'".into(),
733 '\'' => "\"'\"".into(),
734 _ => format!("\"{c}\""),
735 }
736}
737
738fn quote_text(t: &str) -> String {
739 format!("\"{}\"", t.replace('\\', "\\\\").replace('"', "\\\""))
740}
741
742fn dim_gold(d: &Dim) -> String {
743 const RATIOS: [(i64, i64); 10] = [
744 (0, 1),
745 (1, 1),
746 (2, 1),
747 (1, 2),
748 (1, 18),
749 (2, 18),
750 (3, 18),
751 (7, 10),
752 (3, 10),
753 (1, 10),
754 ];
755 for (n, den) in RATIOS {
756 if d.eq_dim(&Dim::ratio(n, den)) {
757 if den == 1 {
758 return n.to_string();
759 }
760 return format!("{n}/{den}");
761 }
762 }
763 for i in -64i64..65 {
764 if d.eq_dim(&Dim::from_i64(i)) {
765 return i.to_string();
766 }
767 }
768 d.to_dec_string()
769}