use crate::node::{Node, NodeKind, StackKind};
use crate::style::Style;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum AtomClass {
Ord,
Op,
Bin,
Rel,
Open,
Close,
Punct,
Inner,
}
pub const ATOM_CLASSES: [AtomClass; 8] = [
AtomClass::Ord,
AtomClass::Op,
AtomClass::Bin,
AtomClass::Rel,
AtomClass::Open,
AtomClass::Close,
AtomClass::Punct,
AtomClass::Inner,
];
impl AtomClass {
#[inline(always)]
#[must_use]
pub const fn index(self) -> usize {
match self {
Self::Ord => 0,
Self::Op => 1,
Self::Bin => 2,
Self::Rel => 3,
Self::Open => 4,
Self::Close => 5,
Self::Punct => 6,
Self::Inner => 7,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Spacing {
None,
Thin,
Med,
Thick,
}
impl Spacing {
#[inline(always)]
#[must_use]
pub const fn mu(self) -> i32 {
match self {
Self::None => 0,
Self::Thin => 3,
Self::Med => 4,
Self::Thick => 5,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PairSpacing {
Always(Spacing),
DisplayTextOnly(Spacing),
Impossible,
}
use PairSpacing::{Always as A, DisplayTextOnly as P, Impossible as X};
use Spacing::{Med, None as N0, Thick, Thin};
pub const SPACING_TABLE: [[PairSpacing; 8]; 8] = [
[
A(N0),
A(Thin),
P(Med),
P(Thick),
A(N0),
A(N0),
A(N0),
P(Thin),
],
[A(Thin), A(Thin), X, P(Thick), A(N0), A(N0), A(N0), P(Thin)],
[P(Med), P(Med), X, X, P(Med), X, X, P(Med)],
[
P(Thick),
P(Thick),
X,
A(N0),
P(Thick),
A(N0),
A(N0),
P(Thick),
],
[A(N0), A(N0), X, A(N0), A(N0), A(N0), A(N0), A(N0)],
[
A(N0),
A(Thin),
P(Med),
P(Thick),
A(N0),
A(N0),
A(N0),
P(Thin),
],
[
P(Thin),
P(Thin),
X,
P(Thin),
P(Thin),
P(Thin),
P(Thin),
P(Thin),
],
[
P(Thin),
A(Thin),
P(Med),
P(Thick),
P(Thin),
A(N0),
P(Thin),
P(Thin),
],
];
#[inline(always)]
#[must_use]
pub const fn pair_spacing(left: AtomClass, right: AtomClass) -> PairSpacing {
SPACING_TABLE[left.index()][right.index()]
}
#[inline(always)]
#[must_use]
pub const fn spacing_in_style(left: AtomClass, right: AtomClass, style: Style) -> Spacing {
match pair_spacing(left, right) {
PairSpacing::Always(s) => s,
PairSpacing::DisplayTextOnly(s) => {
if style.is_script() {
Spacing::None
} else {
s
}
}
PairSpacing::Impossible => Spacing::None,
}
}
#[must_use]
pub const fn char_class(ch: char) -> AtomClass {
match ch {
'+' | '−' | '-' | '∗' | '*' | '±' | '∓' | '×' | '⋅' | '÷' | '∘' | '∙' | '⊕' | '⊖' | '⊗'
| '⊘' | '⊙' | '∪' | '∩' | '∨' | '∧' | '∖' | '⋄' | '†' | '‡' | '⊎' | '⊔' | '⊓' | '≀'
| '⨿' | '⋆' | '◁' | '▷' => AtomClass::Bin,
'=' | '<' | '>' | ':' | '≤' | '≥' | '≠' | '≡' | '≈' | '∼' | '≃' | '≅' | '≐' | '∝' | '∈'
| '∋' | '∉' | '⊂' | '⊃' | '⊆' | '⊇' | '≪' | '≫' | '⊨' | '⊢' | '⊣' | '≍' | '∣' | '∥'
| '→' | '←' | '↔' | '⇒' | '⇐' | '⇔' | '⟶' | '⟵' | '⟷' | '⟹' | '⟸' | '⟺' | '↦' | '⟼'
| '↑' | '↓' | '↕' | '⇑' | '⇓' | '⇕' | '↗' | '↘' | '↙' | '↖' | '↪' | '↩' | '⇀' | '⇁'
| '↼' | '↽' | '⇌' => AtomClass::Rel,
'(' | '[' | '⟨' | '⌈' | '⌊' => AtomClass::Open,
')' | ']' | '⟩' | '⌉' | '⌋' | '!' | '?' => AtomClass::Close,
',' | ';' => AtomClass::Punct,
_ => AtomClass::Ord,
}
}
#[must_use]
pub fn intrinsic_class(node: &Node) -> Option<AtomClass> {
match &node.kind {
NodeKind::Symbol { class, .. } => Some(*class),
NodeKind::BigOp { .. } | NodeKind::OpName { .. } => Some(AtomClass::Op),
NodeKind::Scripts { base, .. } => base
.as_deref()
.map_or(Some(AtomClass::Ord), intrinsic_class),
NodeKind::Frac { .. } | NodeKind::LeftRight { .. } => Some(AtomClass::Inner),
NodeKind::SizedDelim { class, .. } => Some(*class),
NodeKind::Radical { .. }
| NodeKind::Accent { .. }
| NodeKind::List(_)
| NodeKind::Text { .. }
| NodeKind::TextRun { .. }
| NodeKind::TextStyled { .. }
| NodeKind::MathIsland { .. }
| NodeKind::AlignBlock { .. }
| NodeKind::Environment { .. } => Some(AtomClass::Ord),
NodeKind::MathFont { body, .. } | NodeKind::Phantom { body, .. } => intrinsic_class(body),
NodeKind::Stack { kind, base, .. } => match kind {
StackKind::Stackrel => Some(AtomClass::Rel),
StackKind::Overset | StackKind::Underset => intrinsic_class(base),
},
NodeKind::XArrow { .. } => Some(AtomClass::Rel),
NodeKind::Fragment(kind) => match kind {
crate::node::FragmentKind::UnmatchedClose
| crate::node::FragmentKind::RedundantMathShift => None,
crate::node::FragmentKind::StrayRight(_) => Some(AtomClass::Close),
},
NodeKind::StyleChange(_)
| NodeKind::SizeChange(_)
| NodeKind::AlignChange(_)
| NodeKind::LineSpacing(_)
| NodeKind::ColorChange(_)
| NodeKind::Space(_)
| NodeKind::Tie
| NodeKind::Linebreak
| NodeKind::AlignTab => None,
}
}
#[must_use]
pub fn classify_list(items: &[Node]) -> Vec<Option<AtomClass>> {
let mut classes: Vec<Option<AtomClass>> = items.iter().map(intrinsic_class).collect();
let mut prev_atom: Option<usize> = None;
for i in 0..classes.len() {
let Some(current) = classes[i] else { continue };
if current == AtomClass::Bin {
let degrade = match prev_atom {
None => true,
Some(p) => matches!(
classes[p],
Some(
AtomClass::Bin
| AtomClass::Op
| AtomClass::Rel
| AtomClass::Open
| AtomClass::Punct
)
),
};
if degrade {
classes[i] = Some(AtomClass::Ord);
}
} else if matches!(
current,
AtomClass::Rel | AtomClass::Close | AtomClass::Punct
) {
if let Some(p) = prev_atom {
if classes[p] == Some(AtomClass::Bin) {
classes[p] = Some(AtomClass::Ord);
}
}
}
prev_atom = Some(i);
}
if let Some(p) = prev_atom {
if classes[p] == Some(AtomClass::Bin) {
classes[p] = Some(AtomClass::Ord);
}
}
classes
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn spacing_table_diagonal_spot_checks() {
assert_eq!(pair_spacing(AtomClass::Ord, AtomClass::Op), A(Thin));
assert_eq!(pair_spacing(AtomClass::Bin, AtomClass::Bin), X);
assert_eq!(pair_spacing(AtomClass::Rel, AtomClass::Rel), A(N0));
assert_eq!(pair_spacing(AtomClass::Ord, AtomClass::Rel), P(Thick));
}
#[test]
fn script_styles_suppress_parenthesized_entries() {
assert_eq!(
spacing_in_style(AtomClass::Ord, AtomClass::Bin, Style::Text),
Med
);
assert_eq!(
spacing_in_style(AtomClass::Ord, AtomClass::Bin, Style::Script),
Spacing::None
);
assert_eq!(
spacing_in_style(AtomClass::Ord, AtomClass::Op, Style::ScriptScript),
Thin
);
}
#[test]
fn mu_values() {
assert_eq!(Spacing::Thin.mu(), 3);
assert_eq!(Spacing::Med.mu(), 4);
assert_eq!(Spacing::Thick.mu(), 5);
assert_eq!(Spacing::None.mu(), 0);
}
}