use crate::color::Color;
use crate::dim::Dim;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AtomKind {
Ord,
Op,
Bin,
Rel,
Open,
Close,
Punct,
Inner,
}
impl AtomKind {
fn gold(self) -> &'static str {
match self {
Self::Ord => "Ord",
Self::Op => "Op",
Self::Bin => "Bin",
Self::Rel => "Rel",
Self::Open => "Open",
Self::Close => "Close",
Self::Punct => "Punct",
Self::Inner => "Inner",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AccentKind {
Hat,
Check,
Breve,
Acute,
Grave,
Tilde,
Bar,
Vec,
Dot,
Ddot,
Dddot,
Ddddot,
WideHat,
WideTilde,
Overline,
Underline,
Overbrace,
Underbrace,
Overleftarrow,
Overrightarrow,
Overleftrightarrow,
Underleftarrow,
Underrightarrow,
Underleftrightarrow,
Cancel,
BCancel,
XCancel,
Boxed,
Ring,
Not,
}
impl AccentKind {
pub(crate) fn gold(self) -> &'static str {
match self {
Self::Hat => "hat",
Self::Check => "check",
Self::Breve => "breve",
Self::Acute => "acute",
Self::Grave => "grave",
Self::Tilde => "tilde",
Self::Bar => "bar",
Self::Vec => "vec",
Self::Dot => "dot",
Self::Ddot => "ddot",
Self::Dddot => "dddot",
Self::Ddddot => "ddddot",
Self::WideHat => "widehat",
Self::WideTilde => "widetilde",
Self::Overline => "overline",
Self::Underline => "underline",
Self::Overbrace => "overbrace",
Self::Underbrace => "underbrace",
Self::Overleftarrow => "overleftarrow",
Self::Overrightarrow => "overrightarrow",
Self::Overleftrightarrow => "overleftrightarrow",
Self::Underleftarrow => "underleftarrow",
Self::Underrightarrow => "underrightarrow",
Self::Underleftrightarrow => "underleftrightarrow",
Self::Cancel => "cancel",
Self::BCancel => "bcancel",
Self::XCancel => "xcancel",
Self::Boxed => "boxed",
Self::Ring => "mathring",
Self::Not => "not",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TextStyle {
Rm,
Bf,
It,
Sf,
Tt,
Bb,
Cal,
Frak,
Scr,
Boldsymbol,
Pmb,
Text,
}
impl TextStyle {
fn gold(self) -> &'static str {
match self {
Self::Rm => "rm",
Self::Bf => "bf",
Self::It => "it",
Self::Sf => "sf",
Self::Tt => "tt",
Self::Bb => "bb",
Self::Cal => "cal",
Self::Frak => "frak",
Self::Scr => "scr",
Self::Boldsymbol => "boldsymbol",
Self::Pmb => "pmb",
Self::Text => "text",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SpaceKind {
Thin,
Medium,
Thick,
NegThin,
Quad,
Qquad,
ControlSpace,
Hspace(Dim),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MatrixStyle {
Matrix,
Pmatrix,
Bmatrix,
Vmatrix,
VVmatrix,
BBmatrix,
Cases,
Array,
Aligned,
Align,
Gather,
Multline,
Equation,
Split,
}
impl MatrixStyle {
fn gold(self) -> &'static str {
match self {
Self::Matrix => "matrix",
Self::Pmatrix => "pmatrix",
Self::Bmatrix => "bmatrix",
Self::Vmatrix => "vmatrix",
Self::VVmatrix => "Vmatrix",
Self::BBmatrix => "Bmatrix",
Self::Cases => "cases",
Self::Array => "array",
Self::Aligned => "aligned",
Self::Align => "align",
Self::Gather => "gather",
Self::Multline => "multline",
Self::Equation => "equation",
Self::Split => "split",
}
}
#[must_use]
pub fn is_display_env(self) -> bool {
matches!(
self,
Self::Align | Self::Gather | Self::Multline | Self::Equation
)
}
#[must_use]
pub fn numbers_rows(self) -> bool {
matches!(self, Self::Align | Self::Gather)
}
#[must_use]
pub fn numbers_once(self) -> bool {
matches!(self, Self::Equation | Self::Multline)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ColSpec {
Left,
Center,
Right,
VRule,
}
impl ColSpec {
fn gold(self) -> char {
match self {
Self::Left => 'l',
Self::Center => 'c',
Self::Right => 'r',
Self::VRule => '|',
}
}
#[must_use]
pub fn is_rule(self) -> bool {
matches!(self, Self::VRule)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum EqNumber {
Default,
Suppress,
Tag {
star: bool,
body: Box<MathNode>,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum EnvRow {
Cells {
cells: Vec<MathNode>,
number: EqNumber,
labels: Vec<String>,
},
Hline,
Intertext(Box<MathNode>),
}
impl EnvRow {
#[must_use]
pub fn cells(cells: Vec<MathNode>) -> Self {
Self::Cells {
cells,
number: EqNumber::Default,
labels: Vec::new(),
}
}
fn gold(&self) -> String {
match self {
Self::Hline => "(hline)".into(),
Self::Intertext(n) => format!("(intertext {})", n.gold()),
Self::Cells {
cells,
number,
labels,
} => {
let mut s = String::from("(");
for (i, c) in cells.iter().enumerate() {
if i > 0 {
s.push(' ');
}
s.push_str(&c.gold());
}
match number {
EqNumber::Default => {}
EqNumber::Suppress => s.push_str(" (nonumber)"),
EqNumber::Tag { star: false, body } => {
s.push_str(&format!(" (tag {})", body.gold()));
}
EqNumber::Tag { star: true, body } => {
s.push_str(&format!(" (tagstar {})", body.gold()));
}
}
for lab in labels {
s.push_str(&format!(" (label {lab})"));
}
s.push(')');
s
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum IntegralKind {
Int,
Iint,
Iiint,
Oint,
Oiint,
}
impl IntegralKind {
fn gold(self) -> &'static str {
match self {
Self::Int => "int",
Self::Iint => "iint",
Self::Iiint => "iiint",
Self::Oint => "oint",
Self::Oiint => "oiint",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PhantomKind {
Full,
Vertical,
Horizontal,
}
impl PhantomKind {
fn gold(self) -> &'static str {
match self {
Self::Full => "phantom",
Self::Vertical => "vphantom",
Self::Horizontal => "hphantom",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Delimiter {
Empty,
Char(char),
Named(String),
}
impl Delimiter {
fn gold(&self) -> String {
match self {
Self::Empty => ".".into(),
Self::Char(c) => c.to_string(),
Self::Named(n) => {
if n == "{" || n == "}" || n == "|" {
format!("\\{n}")
} else {
n.clone()
}
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DelimSize {
Big,
Big2,
Bigg,
Bigg2,
}
impl DelimSize {
fn gold(self) -> &'static str {
match self {
Self::Big => "big",
Self::Big2 => "Big",
Self::Bigg => "bigg",
Self::Bigg2 => "Bigg",
}
}
#[must_use]
pub fn from_command(name: &str) -> Option<Self> {
match name {
"big" | "bigl" | "bigr" | "bigm" => Some(Self::Big),
"Big" | "Bigl" | "Bigr" | "Bigm" => Some(Self::Big2),
"bigg" | "biggl" | "biggr" | "biggm" => Some(Self::Bigg),
"Bigg" | "Biggl" | "Biggr" | "Biggm" => Some(Self::Bigg2),
_ => None,
}
}
#[must_use]
pub fn class_from_command(name: &str) -> Option<AtomKind> {
if name.ends_with('l') {
Some(AtomKind::Open)
} else if name.ends_with('r') {
Some(AtomKind::Close)
} else if name.ends_with('m') {
Some(AtomKind::Rel)
} else {
None
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MathNode {
Atom(char, AtomKind),
Fraction(Box<MathNode>, Box<MathNode>),
Radical(Option<Box<MathNode>>, Box<MathNode>),
Superscript(Box<MathNode>, Box<MathNode>),
Subscript(Box<MathNode>, Box<MathNode>),
SubSup(Box<MathNode>, Box<MathNode>, Box<MathNode>),
Delimited(Delimiter, Box<MathNode>, Delimiter),
SizedDelim(Delimiter, DelimSize, AtomKind),
Row(Vec<MathNode>),
Matrix(MatrixStyle, Vec<ColSpec>, Vec<EnvRow>),
Substack(Vec<MathNode>),
Ref(String),
Tag {
star: bool,
body: Box<MathNode>,
},
Label(String),
NoNumber,
Hline,
Intertext(Box<MathNode>),
Sum(Option<Box<MathNode>>, Option<Box<MathNode>>),
Integral(IntegralKind, Option<Box<MathNode>>, Option<Box<MathNode>>),
Product(Option<Box<MathNode>>, Option<Box<MathNode>>),
Limit(Option<Box<MathNode>>),
OverUnder(Box<MathNode>, Option<Box<MathNode>>, Option<Box<MathNode>>),
Accent(Box<MathNode>, AccentKind),
CancelTo(Box<MathNode>, Box<MathNode>),
Text(String, TextStyle),
Space(SpaceKind),
Operator(String, bool),
Symbol(String),
Color(Color, Box<MathNode>),
TextColor(Color, Box<MathNode>),
ColorBox(Color, Box<MathNode>),
FColorBox(Color, Color, Box<MathNode>),
Strut(Dim, Dim),
Phantom(PhantomKind, Box<MathNode>),
}
impl MathNode {
#[must_use]
pub fn gold(&self) -> String {
match self {
Self::Atom(c, k) => format!("(atom {} {})", k.gold(), quote_atom(*c)),
Self::Fraction(n, d) => format!("(frac {} {})", n.gold(), d.gold()),
Self::Radical(None, r) => format!("(sqrt {})", r.gold()),
Self::Radical(Some(i), r) => format!("(sqrtn {} {})", i.gold(), r.gold()),
Self::Superscript(b, e) => format!("(sup {} {})", b.gold(), e.gold()),
Self::Subscript(b, s) => format!("(sub {} {})", b.gold(), s.gold()),
Self::SubSup(b, s, e) => format!("(subsup {} {} {})", b.gold(), s.gold(), e.gold()),
Self::Delimited(l, b, r) => {
format!("(delim {} {} {})", l.gold(), b.gold(), r.gold())
}
Self::SizedDelim(d, sz, k) => {
format!("(big {} {} {})", sz.gold(), k.gold(), quote_delim(d))
}
Self::Row(items) => {
if items.is_empty() {
"(row)".into()
} else {
let mut s = String::from("(row");
for it in items {
s.push(' ');
s.push_str(&it.gold());
}
s.push(')');
s
}
}
Self::Matrix(style, spec, rows) => {
let mut s = format!("(matrix {}", style.gold());
if !spec.is_empty() {
s.push(' ');
for c in spec {
s.push(c.gold());
}
}
for row in rows {
s.push(' ');
s.push_str(&row.gold());
}
s.push(')');
s
}
Self::Substack(lines) => {
let mut s = String::from("(substack");
for ln in lines {
s.push(' ');
s.push_str(&ln.gold());
}
s.push(')');
s
}
Self::Ref(k) => format!("(ref {k})"),
Self::Tag { star: false, body } => format!("(tag {})", body.gold()),
Self::Tag { star: true, body } => format!("(tagstar {})", body.gold()),
Self::Label(k) => format!("(label {k})"),
Self::NoNumber => "(nonumber)".into(),
Self::Hline => "(hline)".into(),
Self::Intertext(n) => format!("(intertext {})", n.gold()),
Self::Sum(lo, hi) => format!("(sum {} {})", opt(lo), opt(hi)),
Self::Integral(k, lo, hi) => {
format!("({} {} {})", k.gold(), opt(lo), opt(hi))
}
Self::Product(lo, hi) => format!("(prod {} {})", opt(lo), opt(hi)),
Self::Limit(lo) => format!("(lim {})", opt(lo)),
Self::OverUnder(b, over, under) => {
format!("(overunder {} {} {})", b.gold(), opt(over), opt(under))
}
Self::Accent(b, a) => format!("(accent {} {})", a.gold(), b.gold()),
Self::CancelTo(v, e) => format!("(cancelto {} {})", v.gold(), e.gold()),
Self::Text(t, st) => format!("(text {} {})", st.gold(), quote_text(t)),
Self::Space(SpaceKind::Thin) => "(space thin)".into(),
Self::Space(SpaceKind::Medium) => "(space medium)".into(),
Self::Space(SpaceKind::Thick) => "(space thick)".into(),
Self::Space(SpaceKind::NegThin) => "(space negthin)".into(),
Self::Space(SpaceKind::Quad) => "(space quad)".into(),
Self::Space(SpaceKind::Qquad) => "(space qquad)".into(),
Self::Space(SpaceKind::ControlSpace) => "(space control)".into(),
Self::Space(SpaceKind::Hspace(d)) => format!("(space hspace {})", dim_gold(d)),
Self::Operator(name, false) => format!("(op {name})"),
Self::Operator(name, true) => format!("(op {name} limits)"),
Self::Symbol(name) => format!("(symbol {name})"),
Self::Color(c, b) => format!("(color {} {})", c.css_hex(), b.gold()),
Self::TextColor(c, b) => format!("(textcolor {} {})", c.css_hex(), b.gold()),
Self::ColorBox(c, b) => format!("(colorbox {} {})", c.css_hex(), b.gold()),
Self::FColorBox(border, fill, b) => {
format!(
"(fcolorbox {} {} {})",
border.css_hex(),
fill.css_hex(),
b.gold()
)
}
Self::Strut(h, d) => format!("(strut {} {})", dim_gold(h), dim_gold(d)),
Self::Phantom(k, b) => format!("({} {})", k.gold(), b.gold()),
}
}
}
fn opt(n: &Option<Box<MathNode>>) -> String {
match n {
None => "_".into(),
Some(x) => x.gold(),
}
}
fn quote_delim(d: &Delimiter) -> String {
match d {
Delimiter::Empty => ".".into(),
Delimiter::Char(c) => quote_atom(*c),
Delimiter::Named(n) => n.clone(),
}
}
fn quote_atom(c: char) -> String {
match c {
'"' => "'\"'".into(),
'\'' => "\"'\"".into(),
_ => format!("\"{c}\""),
}
}
fn quote_text(t: &str) -> String {
format!("\"{}\"", t.replace('\\', "\\\\").replace('"', "\\\""))
}
fn dim_gold(d: &Dim) -> String {
const RATIOS: [(i64, i64); 10] = [
(0, 1),
(1, 1),
(2, 1),
(1, 2),
(1, 18),
(2, 18),
(3, 18),
(7, 10),
(3, 10),
(1, 10),
];
for (n, den) in RATIOS {
if d.eq_dim(&Dim::ratio(n, den)) {
if den == 1 {
return n.to_string();
}
return format!("{n}/{den}");
}
}
for i in -64i64..65 {
if d.eq_dim(&Dim::from_i64(i)) {
return i.to_string();
}
}
d.to_dec_string()
}