1use crate::atom::AtomClass;
13use crate::node::{
14 AccentKind, DelimSize, FracSpec, LineAlign, MathFont, PhantomKind, SpaceKind, StackKind,
15 TextStyle,
16};
17use crate::style::Style;
18
19pub const TIER2_TRACKING: &str = "franken_manim fm-j5t (the tier-2 construct program)";
21
22pub const UNTIERED_TRACKING: &str = "https://github.com/Dicklesworthstone/franken_manim/issues";
24
25pub const LAYOUT_PENDING_TRACKING: &str = "franken_manim fm-kg9 (the fmd-math extensions bead)";
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub enum Tier {
32 T1,
34 T2,
36}
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
43pub enum ConstructStatus {
44 Supported,
46 UnsupportedT2,
49 Unknown,
52}
53
54#[derive(Clone, Debug, PartialEq)]
56pub(crate) enum Cmd {
57 Sym {
60 ch: char,
61 class: AtomClass,
62 delim: bool,
63 },
64 BigOp { ch: char, integral: bool },
66 OpName {
68 rendered: &'static str,
69 limits: bool,
70 },
71 OperatorName,
73 Accent(AccentKind),
75 Frac(FracSpec),
77 OverInfix(FracSpec),
80 Radical,
82 Text,
84 TextStyled(TextStyle),
86 Alphabet(MathFont),
88 StyleSwitch(Style),
90 AlignDecl(LineAlign),
92 LineSpacing(f64),
94 SizeChange(f64),
97 Spacing(SpaceKind),
99 Phantom(PhantomKind),
101 Stack(StackKind),
103 Substack,
107 Color,
109 SizedDelim {
112 size: DelimSize,
113 class: Option<AtomClass>,
114 },
115 Left,
117 Right,
119 Limits,
121 NoLimits,
123 Begin,
125 End,
127 Ding,
130 XArrow {
132 mapsto: bool,
134 },
135 UnsupportedT2,
137}
138
139#[derive(Clone, Copy, Debug, PartialEq, Eq)]
141pub(crate) struct EnvDef {
142 pub(crate) has_spec: bool,
144}
145
146const fn sym(ch: char, class: AtomClass) -> Cmd {
147 Cmd::Sym {
148 ch,
149 class,
150 delim: false,
151 }
152}
153
154const fn delim_sym(ch: char, class: AtomClass) -> Cmd {
155 Cmd::Sym {
156 ch,
157 class,
158 delim: true,
159 }
160}
161
162#[allow(clippy::too_many_lines)]
164pub(crate) fn lookup(name: &str) -> Option<Cmd> {
165 use AtomClass::{Bin, Close, Inner, Open, Ord, Punct, Rel};
166 Some(match name {
167 "alpha" => sym('α', Ord),
169 "beta" => sym('β', Ord),
170 "gamma" => sym('γ', Ord),
171 "delta" => sym('δ', Ord),
172 "epsilon" => sym('ϵ', Ord),
173 "varepsilon" => sym('ε', Ord),
174 "zeta" => sym('ζ', Ord),
175 "eta" => sym('η', Ord),
176 "theta" => sym('θ', Ord),
177 "vartheta" => sym('ϑ', Ord),
178 "iota" => sym('ι', Ord),
179 "kappa" => sym('κ', Ord),
180 "lambda" => sym('λ', Ord),
181 "mu" => sym('μ', Ord),
182 "nu" => sym('ν', Ord),
183 "xi" => sym('ξ', Ord),
184 "pi" => sym('π', Ord),
185 "varpi" => sym('ϖ', Ord),
186 "rho" => sym('ρ', Ord),
187 "varrho" => sym('ϱ', Ord),
188 "sigma" => sym('σ', Ord),
189 "varsigma" => sym('ς', Ord),
190 "tau" => sym('τ', Ord),
191 "upsilon" => sym('υ', Ord),
192 "phi" => sym('ϕ', Ord),
193 "varphi" => sym('φ', Ord),
194 "chi" => sym('χ', Ord),
195 "psi" => sym('ψ', Ord),
196 "omega" => sym('ω', Ord),
197 "Gamma" => sym('Γ', Ord),
199 "Delta" => sym('Δ', Ord),
200 "Theta" => sym('Θ', Ord),
201 "Lambda" => sym('Λ', Ord),
202 "Xi" => sym('Ξ', Ord),
203 "Pi" => sym('Π', Ord),
204 "Sigma" => sym('Σ', Ord),
205 "Upsilon" => sym('ϒ', Ord),
206 "Phi" => sym('Φ', Ord),
207 "Psi" => sym('Ψ', Ord),
208 "Omega" => sym('Ω', Ord),
209 "infty" => sym('∞', Ord),
211 "partial" => sym('∂', Ord),
212 "nabla" => sym('∇', Ord),
213 "hbar" => sym('ℏ', Ord),
214 "ell" => sym('ℓ', Ord),
215 "wp" => sym('℘', Ord),
216 "Re" => sym('ℜ', Ord),
217 "Im" => sym('ℑ', Ord),
218 "aleph" => sym('ℵ', Ord),
219 "imath" | "i" => sym('ı', Ord),
220 "jmath" | "j" => sym('ȷ', Ord),
221 "emptyset" => sym('∅', Ord),
222 "varnothing" => sym('⌀', Ord),
223 "exists" => sym('∃', Ord),
224 "forall" => sym('∀', Ord),
225 "neg" | "lnot" => sym('¬', Ord),
226 "prime" => sym('′', Ord),
227 "angle" => sym('∠', Ord),
228 "triangle" => sym('△', Ord),
229 "checkmark" => sym('✓', Ord),
230 "copyright" => sym('©', Ord),
236 "female" => sym('\u{2640}', Ord),
237 "male" | "mars" => sym('\u{2642}', Ord),
238 "earth" => sym('\u{2641}', Ord),
239 "ding" => Cmd::Ding,
240 "xrightarrow" => Cmd::XArrow { mapsto: false },
241 "xmapsto" => Cmd::XArrow { mapsto: true },
242 "top" => sym('⊤', Ord),
243 "bot" => sym('⊥', Ord),
244 "flat" => sym('♭', Ord),
245 "natural" => sym('♮', Ord),
246 "sharp" => sym('♯', Ord),
247 "clubsuit" => sym('♣', Ord),
248 "diamondsuit" => sym('♢', Ord),
249 "heartsuit" => sym('♡', Ord),
250 "spadesuit" => sym('♠', Ord),
251 "backslash" => delim_sym('\\', Ord),
252 "vert" => delim_sym('|', Ord),
253 "Vert" => delim_sym('‖', Ord),
254 "ldots" | "dots" | "dotsc" | "dotso" => sym('…', Inner),
257 "cdots" | "dotsb" | "hdots" => sym('⋯', Inner),
258 "vdots" => sym('⋮', Ord),
259 "ddots" => sym('⋱', Inner),
260 "pm" => sym('±', Bin),
262 "mp" => sym('∓', Bin),
263 "cdot" => sym('⋅', Bin),
264 "times" => sym('×', Bin),
265 "div" => sym('÷', Bin),
266 "ast" => sym('∗', Bin),
267 "star" => sym('⋆', Bin),
268 "circ" => sym('∘', Bin),
269 "bullet" => sym('∙', Bin),
270 "oplus" => sym('⊕', Bin),
271 "ominus" => sym('⊖', Bin),
272 "otimes" => sym('⊗', Bin),
273 "oslash" => sym('⊘', Bin),
274 "odot" => sym('⊙', Bin),
275 "cup" => sym('∪', Bin),
276 "cap" => sym('∩', Bin),
277 "sqcup" => sym('⊔', Bin),
278 "sqcap" => sym('⊓', Bin),
279 "vee" | "lor" => sym('∨', Bin),
280 "wedge" | "land" => sym('∧', Bin),
281 "setminus" | "smallsetminus" => sym('∖', Bin),
282 "wr" => sym('≀', Bin),
283 "diamond" => sym('⋄', Bin),
284 "triangleleft" => sym('◁', Bin),
285 "triangleright" => sym('▷', Bin),
286 "uplus" => sym('⊎', Bin),
287 "amalg" => sym('⨿', Bin),
288 "dagger" => sym('†', Bin),
289 "ddagger" => sym('‡', Bin),
290 "minus" => sym('−', Bin),
293 "le" | "leq" => sym('≤', Rel),
295 "ge" | "geq" => sym('≥', Rel),
296 "ne" | "neq" => sym('≠', Rel),
297 "equiv" => sym('≡', Rel),
298 "approx" => sym('≈', Rel),
299 "sim" => sym('∼', Rel),
300 "simeq" => sym('≃', Rel),
301 "cong" => sym('≅', Rel),
302 "doteq" => sym('≐', Rel),
303 "propto" => sym('∝', Rel),
304 "perp" => sym('⊥', Rel),
305 "mid" => sym('∣', Rel),
306 "nmid" => sym('∤', Rel),
307 "parallel" => sym('∥', Rel),
308 "in" => sym('∈', Rel),
309 "ni" => sym('∋', Rel),
310 "notin" => sym('∉', Rel),
311 "subset" => sym('⊂', Rel),
312 "supset" => sym('⊃', Rel),
313 "subseteq" => sym('⊆', Rel),
314 "supseteq" => sym('⊇', Rel),
315 "sqsubseteq" => sym('⊑', Rel),
316 "sqsupseteq" => sym('⊒', Rel),
317 "ll" => sym('≪', Rel),
318 "gg" => sym('≫', Rel),
319 "prec" => sym('≺', Rel),
320 "succ" => sym('≻', Rel),
321 "preceq" => sym('⪯', Rel),
322 "succeq" => sym('⪰', Rel),
323 "asymp" => sym('≍', Rel),
324 "bowtie" => sym('⋈', Rel),
325 "models" => sym('⊨', Rel),
326 "vdash" => sym('⊢', Rel),
327 "dashv" => sym('⊣', Rel),
328 "smile" => sym('⌣', Rel),
329 "frown" => sym('⌢', Rel),
330 "colon" => sym(':', Punct),
331 "rightarrow" | "to" => sym('→', Rel),
333 "leftarrow" | "gets" => sym('←', Rel),
334 "leftrightarrow" => sym('↔', Rel),
335 "Rightarrow" => sym('⇒', Rel),
336 "Leftarrow" => sym('⇐', Rel),
337 "Leftrightarrow" => sym('⇔', Rel),
338 "longrightarrow" => sym('⟶', Rel),
339 "longleftarrow" => sym('⟵', Rel),
340 "longleftrightarrow" => sym('⟷', Rel),
341 "Longrightarrow" | "implies" => sym('⟹', Rel),
342 "Longleftarrow" | "impliedby" => sym('⟸', Rel),
343 "Longleftrightarrow" | "iff" => sym('⟺', Rel),
344 "mapsto" => sym('↦', Rel),
345 "longmapsto" => sym('⟼', Rel),
346 "uparrow" => delim_sym('↑', Rel),
347 "downarrow" => delim_sym('↓', Rel),
348 "updownarrow" => delim_sym('↕', Rel),
349 "Uparrow" => delim_sym('⇑', Rel),
350 "Downarrow" => delim_sym('⇓', Rel),
351 "Updownarrow" => delim_sym('⇕', Rel),
352 "nearrow" => sym('↗', Rel),
353 "searrow" => sym('↘', Rel),
354 "swarrow" => sym('↙', Rel),
355 "nwarrow" => sym('↖', Rel),
356 "hookrightarrow" => sym('↪', Rel),
357 "circlearrowleft" => sym('↺', Rel),
358 "circlearrowright" => sym('↻', Rel),
359 "hookleftarrow" => sym('↩', Rel),
360 "rightharpoonup" => sym('⇀', Rel),
361 "rightharpoondown" => sym('⇁', Rel),
362 "leftharpoonup" => sym('↼', Rel),
363 "leftharpoondown" => sym('↽', Rel),
364 "rightleftharpoons" => sym('⇌', Rel),
365 "langle" => delim_sym('⟨', Open),
367 "rangle" => delim_sym('⟩', Close),
368 "lceil" => delim_sym('⌈', Open),
369 "rceil" => delim_sym('⌉', Close),
370 "lfloor" => delim_sym('⌊', Open),
371 "rfloor" => delim_sym('⌋', Close),
372 "lbrace" => delim_sym('{', Open),
373 "rbrace" => delim_sym('}', Close),
374 "lbrack" => delim_sym('[', Open),
375 "rbrack" => delim_sym(']', Close),
376 "sum" => Cmd::BigOp {
378 ch: '∑',
379 integral: false,
380 },
381 "prod" => Cmd::BigOp {
382 ch: '∏',
383 integral: false,
384 },
385 "coprod" => Cmd::BigOp {
386 ch: '∐',
387 integral: false,
388 },
389 "int" => Cmd::BigOp {
390 ch: '∫',
391 integral: true,
392 },
393 "oint" => Cmd::BigOp {
394 ch: '∮',
395 integral: true,
396 },
397 "iint" => Cmd::BigOp {
398 ch: '∬',
399 integral: true,
400 },
401 "oiint" => Cmd::BigOp {
402 ch: '∯',
403 integral: true,
404 },
405 "iiint" => Cmd::BigOp {
406 ch: '∭',
407 integral: true,
408 },
409 "bigcup" => Cmd::BigOp {
410 ch: '⋃',
411 integral: false,
412 },
413 "bigcap" => Cmd::BigOp {
414 ch: '⋂',
415 integral: false,
416 },
417 "bigvee" => Cmd::BigOp {
418 ch: '⋁',
419 integral: false,
420 },
421 "bigwedge" => Cmd::BigOp {
422 ch: '⋀',
423 integral: false,
424 },
425 "bigoplus" => Cmd::BigOp {
426 ch: '⨁',
427 integral: false,
428 },
429 "bigotimes" => Cmd::BigOp {
430 ch: '⨂',
431 integral: false,
432 },
433 "bigodot" => Cmd::BigOp {
434 ch: '⨀',
435 integral: false,
436 },
437 "biguplus" => Cmd::BigOp {
438 ch: '⨄',
439 integral: false,
440 },
441 "bigsqcup" => Cmd::BigOp {
442 ch: '⨆',
443 integral: false,
444 },
445 "sin" => Cmd::OpName {
447 rendered: "sin",
448 limits: false,
449 },
450 "cos" => Cmd::OpName {
451 rendered: "cos",
452 limits: false,
453 },
454 "tan" => Cmd::OpName {
455 rendered: "tan",
456 limits: false,
457 },
458 "cot" => Cmd::OpName {
459 rendered: "cot",
460 limits: false,
461 },
462 "sec" => Cmd::OpName {
463 rendered: "sec",
464 limits: false,
465 },
466 "csc" => Cmd::OpName {
467 rendered: "csc",
468 limits: false,
469 },
470 "arcsin" => Cmd::OpName {
471 rendered: "arcsin",
472 limits: false,
473 },
474 "arccos" => Cmd::OpName {
475 rendered: "arccos",
476 limits: false,
477 },
478 "arctan" => Cmd::OpName {
479 rendered: "arctan",
480 limits: false,
481 },
482 "sinh" => Cmd::OpName {
483 rendered: "sinh",
484 limits: false,
485 },
486 "cosh" => Cmd::OpName {
487 rendered: "cosh",
488 limits: false,
489 },
490 "tanh" => Cmd::OpName {
491 rendered: "tanh",
492 limits: false,
493 },
494 "coth" => Cmd::OpName {
495 rendered: "coth",
496 limits: false,
497 },
498 "exp" => Cmd::OpName {
499 rendered: "exp",
500 limits: false,
501 },
502 "log" => Cmd::OpName {
503 rendered: "log",
504 limits: false,
505 },
506 "lg" => Cmd::OpName {
507 rendered: "lg",
508 limits: false,
509 },
510 "ln" => Cmd::OpName {
511 rendered: "ln",
512 limits: false,
513 },
514 "arg" => Cmd::OpName {
515 rendered: "arg",
516 limits: false,
517 },
518 "deg" => Cmd::OpName {
519 rendered: "deg",
520 limits: false,
521 },
522 "dim" => Cmd::OpName {
523 rendered: "dim",
524 limits: false,
525 },
526 "hom" => Cmd::OpName {
527 rendered: "hom",
528 limits: false,
529 },
530 "ker" => Cmd::OpName {
531 rendered: "ker",
532 limits: false,
533 },
534 "mod" | "bmod" => Cmd::OpName {
537 rendered: "mod",
538 limits: false,
539 },
540 "lim" => Cmd::OpName {
541 rendered: "lim",
542 limits: true,
543 },
544 "limsup" => Cmd::OpName {
545 rendered: "lim sup",
546 limits: true,
547 },
548 "liminf" => Cmd::OpName {
549 rendered: "lim inf",
550 limits: true,
551 },
552 "max" => Cmd::OpName {
553 rendered: "max",
554 limits: true,
555 },
556 "min" => Cmd::OpName {
557 rendered: "min",
558 limits: true,
559 },
560 "sup" => Cmd::OpName {
561 rendered: "sup",
562 limits: true,
563 },
564 "inf" => Cmd::OpName {
565 rendered: "inf",
566 limits: true,
567 },
568 "det" => Cmd::OpName {
569 rendered: "det",
570 limits: true,
571 },
572 "gcd" => Cmd::OpName {
573 rendered: "gcd",
574 limits: true,
575 },
576 "Pr" => Cmd::OpName {
577 rendered: "Pr",
578 limits: true,
579 },
580 "operatorname" => Cmd::OperatorName,
581 "hat" => Cmd::Accent(AccentKind::Hat),
583 "check" => Cmd::Accent(AccentKind::Check),
584 "tilde" => Cmd::Accent(AccentKind::Tilde),
585 "acute" => Cmd::Accent(AccentKind::Acute),
586 "grave" => Cmd::Accent(AccentKind::Grave),
587 "dot" => Cmd::Accent(AccentKind::Dot),
588 "ddot" => Cmd::Accent(AccentKind::Ddot),
589 "dddot" => Cmd::Accent(AccentKind::Dddot),
590 "ddddot" => Cmd::Accent(AccentKind::Ddddot),
591 "breve" => Cmd::Accent(AccentKind::Breve),
592 "bar" => Cmd::Accent(AccentKind::Bar),
593 "vec" => Cmd::Accent(AccentKind::Vec),
594 "mathring" => Cmd::Accent(AccentKind::Ring),
595 "widehat" => Cmd::Accent(AccentKind::WideHat),
596 "widetilde" => Cmd::Accent(AccentKind::WideTilde),
597 "overline" => Cmd::Accent(AccentKind::OverLine),
598 "overbrace" => Cmd::Accent(AccentKind::OverBrace),
599 "underbrace" => Cmd::Accent(AccentKind::UnderBrace),
600 "overrightarrow" => Cmd::Accent(AccentKind::OverRightArrow),
601 "overleftarrow" => Cmd::Accent(AccentKind::OverLeftArrow),
602 "underline" => Cmd::Accent(AccentKind::UnderLine),
605 "frac" => Cmd::Frac(FracSpec {
607 bar: true,
608 delims: None,
609 forced_style: None,
610 }),
611 "dfrac" => Cmd::Frac(FracSpec {
612 bar: true,
613 delims: None,
614 forced_style: Some(Style::Display),
615 }),
616 "tfrac" => Cmd::Frac(FracSpec {
617 bar: true,
618 delims: None,
619 forced_style: Some(Style::Text),
620 }),
621 "binom" => Cmd::Frac(FracSpec {
622 bar: false,
623 delims: Some(('(', ')')),
624 forced_style: None,
625 }),
626 "over" => Cmd::OverInfix(FracSpec {
627 bar: true,
628 delims: None,
629 forced_style: None,
630 }),
631 "choose" => Cmd::OverInfix(FracSpec {
632 bar: false,
633 delims: Some(('(', ')')),
634 forced_style: None,
635 }),
636 "sqrt" => Cmd::Radical,
637 "text" => Cmd::Text,
639 "textbf" => Cmd::TextStyled(TextStyle::Bold),
640 "emph" => Cmd::TextStyled(TextStyle::Emph),
641 "mathbb" => Cmd::Alphabet(MathFont::Blackboard),
642 "mathds" => Cmd::Alphabet(MathFont::Blackboard),
645 "mathcal" => Cmd::Alphabet(MathFont::Calligraphic),
646 "mathrm" => Cmd::Alphabet(MathFont::Roman),
647 "mathbf" => Cmd::Alphabet(MathFont::Bold),
648 "boldsymbol" => Cmd::Alphabet(MathFont::BoldItalic),
649 "mathsf" => Cmd::Alphabet(MathFont::SansSerif),
650 "mathtt" => Cmd::Alphabet(MathFont::Typewriter),
651 "mathit" => Cmd::Alphabet(MathFont::Italic),
652 "displaystyle" => Cmd::StyleSwitch(Style::Display),
654 "textstyle" => Cmd::StyleSwitch(Style::Text),
655 "scriptstyle" => Cmd::StyleSwitch(Style::Script),
656 "scriptscriptstyle" => Cmd::StyleSwitch(Style::ScriptScript),
657 "tiny" => Cmd::SizeChange(0.5),
661 "footnotesize" => Cmd::SizeChange(0.8),
662 "small" => Cmd::SizeChange(0.9),
663 "large" => Cmd::SizeChange(1.2),
664 "Large" => Cmd::SizeChange(1.44),
665 "huge" => Cmd::SizeChange(2.074),
666 "quad" => Cmd::Spacing(SpaceKind::Quad),
667 "qquad" => Cmd::Spacing(SpaceKind::Qquad),
668 "phantom" => Cmd::Phantom(PhantomKind::Full),
669 "hphantom" => Cmd::Phantom(PhantomKind::Horizontal),
670 "vphantom" => Cmd::Phantom(PhantomKind::Vertical),
671 "stackrel" => Cmd::Stack(StackKind::Stackrel),
672 "overset" => Cmd::Stack(StackKind::Overset),
673 "underset" => Cmd::Stack(StackKind::Underset),
674 "substack" => Cmd::Substack,
675 "color" => Cmd::Color,
676 "big" => Cmd::SizedDelim {
678 size: DelimSize::Big,
679 class: None,
680 },
681 "Big" => Cmd::SizedDelim {
682 size: DelimSize::BBig,
683 class: None,
684 },
685 "bigg" => Cmd::SizedDelim {
686 size: DelimSize::Bigg,
687 class: None,
688 },
689 "Bigg" => Cmd::SizedDelim {
690 size: DelimSize::BBigg,
691 class: None,
692 },
693 "bigl" => Cmd::SizedDelim {
694 size: DelimSize::Big,
695 class: Some(AtomClass::Open),
696 },
697 "Bigl" => Cmd::SizedDelim {
698 size: DelimSize::BBig,
699 class: Some(AtomClass::Open),
700 },
701 "biggl" => Cmd::SizedDelim {
702 size: DelimSize::Bigg,
703 class: Some(AtomClass::Open),
704 },
705 "Biggl" => Cmd::SizedDelim {
706 size: DelimSize::BBigg,
707 class: Some(AtomClass::Open),
708 },
709 "bigr" => Cmd::SizedDelim {
710 size: DelimSize::Big,
711 class: Some(AtomClass::Close),
712 },
713 "Bigr" => Cmd::SizedDelim {
714 size: DelimSize::BBig,
715 class: Some(AtomClass::Close),
716 },
717 "biggr" => Cmd::SizedDelim {
718 size: DelimSize::Bigg,
719 class: Some(AtomClass::Close),
720 },
721 "Biggr" => Cmd::SizedDelim {
722 size: DelimSize::BBigg,
723 class: Some(AtomClass::Close),
724 },
725 "bigm" => Cmd::SizedDelim {
726 size: DelimSize::Big,
727 class: Some(AtomClass::Rel),
728 },
729 "Bigm" => Cmd::SizedDelim {
730 size: DelimSize::BBig,
731 class: Some(AtomClass::Rel),
732 },
733 "biggm" => Cmd::SizedDelim {
734 size: DelimSize::Bigg,
735 class: Some(AtomClass::Rel),
736 },
737 "Biggm" => Cmd::SizedDelim {
738 size: DelimSize::BBigg,
739 class: Some(AtomClass::Rel),
740 },
741 "left" => Cmd::Left,
743 "right" => Cmd::Right,
744 "limits" => Cmd::Limits,
745 "nolimits" => Cmd::NoLimits,
746 "begin" => Cmd::Begin,
747 "end" => Cmd::End,
748 "centering" => Cmd::AlignDecl(LineAlign::Center),
751 "doublespacing" => Cmd::LineSpacing(1.667),
753 "dx" => Cmd::UnsupportedT2,
756 _ => return None,
757 })
758}
759
760pub(crate) fn ding_char(code: u32) -> Option<char> {
765 match code {
766 51 => Some('✓'),
767 _ => None,
768 }
769}
770
771pub(crate) fn lookup_env(name: &str) -> Option<EnvDef> {
773 match name {
774 "array" => Some(EnvDef { has_spec: true }),
775 "align" | "align*" | "aligned" | "cases" | "matrix" | "pmatrix" | "bmatrix" | "Bmatrix"
776 | "vmatrix" | "Vmatrix" | "smallmatrix" | "substack" => Some(EnvDef { has_spec: false }),
777 _ => None,
778 }
779}
780
781pub(crate) fn line_align_env(name: &str) -> Option<LineAlign> {
785 match name {
786 "flushleft" => Some(LineAlign::Left),
787 "center" => Some(LineAlign::Center),
788 "flushright" => Some(LineAlign::Right),
789 _ => None,
790 }
791}
792
793pub(crate) fn control_symbol_is_t2(ch: char) -> bool {
797 let _ = ch;
798 false
799}
800
801pub(crate) fn compose_text_accent(accent: char, base: char) -> Option<char> {
806 Some(match (accent, base) {
807 ('\'', 'a') => 'á',
808 ('\'', 'e') => 'é',
809 ('\'', 'i') => 'í',
810 ('\'', 'o') => 'ó',
811 ('\'', 'u') => 'ú',
812 ('\'', 'y') => 'ý',
813 ('\'', 'A') => 'Á',
814 ('\'', 'E') => 'É',
815 ('\'', 'I') => 'Í',
816 ('\'', 'O') => 'Ó',
817 ('\'', 'U') => 'Ú',
818 ('"', 'a') => 'ä',
819 ('"', 'e') => 'ë',
820 ('"', 'i') => 'ï',
821 ('"', 'o') => 'ö',
822 ('"', 'u') => 'ü',
823 ('"', 'y') => 'ÿ',
824 ('"', 'A') => 'Ä',
825 ('"', 'E') => 'Ë',
826 ('"', 'I') => 'Ï',
827 ('"', 'O') => 'Ö',
828 ('"', 'U') => 'Ü',
829 _ => return None,
830 })
831}
832
833pub(crate) fn char_is_delim(ch: char) -> bool {
836 matches!(ch, '(' | ')' | '[' | ']' | '|' | '/' | '<' | '>' | '.')
837}
838
839#[must_use]
845pub fn construct_status(construct: &str) -> ConstructStatus {
846 match construct {
849 "script:sup" | "script:sub" | "prime" | "tie" | "alignment-tab" | "math-island" => {
850 return ConstructStatus::Supported;
851 }
852 _ => {}
853 }
854 if construct.starts_with("char:U+") {
855 return ConstructStatus::Supported;
856 }
857 if let Some(env) = construct.strip_prefix("env:") {
858 if lookup_env(env).is_some() || line_align_env(env).is_some() {
859 return ConstructStatus::Supported;
860 }
861 return ConstructStatus::Unknown;
862 }
863 if let Some(name) = construct.strip_prefix('\\') {
864 let mut chars = name.chars();
866 if let (Some(c), None) = (chars.next(), chars.next()) {
867 if !c.is_ascii_alphabetic() {
868 if control_symbol_is_t2(c) {
869 return ConstructStatus::UnsupportedT2;
870 }
871 return if matches!(
875 c,
876 '\\' | ','
877 | ':'
878 | ';'
879 | '!'
880 | ' '
881 | '{'
882 | '}'
883 | '%'
884 | '$'
885 | '&'
886 | '#'
887 | '_'
888 | '|'
889 | '\''
890 | '"'
891 ) {
892 ConstructStatus::Supported
893 } else {
894 ConstructStatus::Unknown
895 };
896 }
897 }
898 if matches!(name, "newcommand" | "renewcommand") {
901 return ConstructStatus::Supported;
902 }
903 return match lookup(name) {
904 Some(Cmd::UnsupportedT2) => ConstructStatus::UnsupportedT2,
905 Some(_) => ConstructStatus::Supported,
906 None => ConstructStatus::Unknown,
907 };
908 }
909 ConstructStatus::Unknown
910}
911
912#[cfg(test)]
913mod tests {
914 use super::*;
915
916 #[test]
917 fn t1_headliners_are_supported() {
918 for c in [
919 r"\frac",
920 r"\over",
921 r"\text",
922 r"\pi",
923 r"\cdot",
924 r"\left",
925 r"\right",
926 r"\sqrt",
927 r"\sum",
928 r"\mathds",
929 r"\minus",
930 r"\checkmark",
931 r"\textbf",
932 r"\emph",
933 r"\big",
934 r"\substack",
935 r"\,",
936 r"\\",
937 r"\{",
938 "env:array",
939 "env:substack",
940 "env:align*",
941 "env:cases",
942 "env:aligned",
943 "script:sup",
944 "math-island",
945 "prime",
946 "alignment-tab",
947 "tie",
948 ] {
949 assert_eq!(
950 construct_status(c),
951 ConstructStatus::Supported,
952 "expected {c} supported"
953 );
954 }
955 }
956
957 #[test]
958 fn t2_vocabulary_is_tiered() {
959 assert_eq!(
962 construct_status(r"\dx"),
963 ConstructStatus::UnsupportedT2,
964 r"expected \dx tier-2 (the last pending construct)"
965 );
966 for c in [
967 r"\nmid",
968 r"\oiint",
969 r"\i",
970 r"\j",
971 r"\circlearrowright",
972 r"\dddot",
973 r"\ddddot",
974 r"\xrightarrow",
975 r"\xmapsto",
976 r"\doublespacing",
977 r"\'",
978 "\\\"",
979 ] {
980 assert_eq!(
981 construct_status(c),
982 ConstructStatus::Supported,
983 "expected {c} graduated"
984 );
985 }
986 }
987
988 #[test]
989 fn astronomy_and_misc_symbols_are_supported() {
990 for c in [
993 r"\female",
994 r"\male",
995 r"\mars",
996 r"\earth",
997 r"\copyright",
998 r"\ding",
999 ] {
1000 assert_eq!(
1001 construct_status(c),
1002 ConstructStatus::Supported,
1003 "expected {c} supported"
1004 );
1005 }
1006 }
1007
1008 #[test]
1009 fn line_alignment_constructs_are_supported() {
1010 for c in [
1011 r"\centering",
1012 "env:flushleft",
1013 "env:center",
1014 "env:flushright",
1015 ] {
1016 assert_eq!(
1017 construct_status(c),
1018 ConstructStatus::Supported,
1019 "expected {c} supported"
1020 );
1021 }
1022 }
1023
1024 #[test]
1025 fn size_declarations_are_supported() {
1026 for c in [
1027 r"\tiny",
1028 r"\footnotesize",
1029 r"\small",
1030 r"\large",
1031 r"\Large",
1032 r"\huge",
1033 ] {
1034 assert_eq!(
1035 construct_status(c),
1036 ConstructStatus::Supported,
1037 "expected {c} supported"
1038 );
1039 }
1040 }
1041
1042 #[test]
1043 fn unknown_vocabulary_is_unknown() {
1044 assert_eq!(construct_status(r"\notacommand"), ConstructStatus::Unknown);
1045 assert_eq!(construct_status("env:mystery"), ConstructStatus::Unknown);
1046 }
1047
1048 #[test]
1049 fn char_constructs_are_parse_supported() {
1050 assert_eq!(construct_status("char:U+00F6"), ConstructStatus::Supported);
1051 }
1052}