use mathml_renderer::{
arena::Arena,
ast::Node,
attribute::{MathSpacing, OpAttrs, RowAttr, Style, TextTransform},
symbol::{self, MathMLOperator, OrdCategory, OrdLike, Rel, RelCategory},
};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum Class {
#[default]
Default = 0,
Operator,
BinaryOp,
Relation,
Open,
Close,
Punctuation,
Inner,
End,
}
#[derive(Debug, Clone, Copy)]
pub enum ParenType {
Left = 1,
Right,
Middle,
}
#[derive(Debug, Clone, Copy)]
pub enum MathVariant {
Normal,
Transform(TextTransform),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Stretchy {
Always = 1,
PrePostfix,
Never,
AlwaysAsymmetric,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DelimiterSpacing {
Zero,
InfixRelation,
Relation,
Other,
}
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub struct StretchableOp {
op: MathMLOperator,
pub stretchy: Stretchy,
pub spacing: DelimiterSpacing,
}
impl StretchableOp {
#[inline]
pub const fn as_op(self) -> MathMLOperator {
self.op
}
pub const fn from_ord(ord: OrdLike) -> Option<Self> {
let (stretchy, spacing) = match ord.category() {
OrdCategory::F | OrdCategory::G => (Stretchy::Always, DelimiterSpacing::Zero),
OrdCategory::FGandForceDefault => {
(Stretchy::PrePostfix, DelimiterSpacing::InfixRelation)
}
OrdCategory::K => (Stretchy::Never, DelimiterSpacing::Zero),
OrdCategory::KButUsedToBeB => (Stretchy::Never, DelimiterSpacing::Other),
OrdCategory::D | OrdCategory::E | OrdCategory::I | OrdCategory::IK => {
return None;
}
};
Some(StretchableOp {
op: ord.as_op(),
stretchy,
spacing,
})
}
pub const fn from_rel(rel: Rel) -> Option<Self> {
match rel.category() {
RelCategory::A => Some(StretchableOp {
op: rel.as_op(),
stretchy: Stretchy::AlwaysAsymmetric,
spacing: DelimiterSpacing::Relation,
}),
RelCategory::Default => None,
}
}
}
pub fn fenced<'arena>(
arena: &'arena Arena,
mut content: Vec<&'arena Node<'arena>>,
open: Option<StretchableOp>,
close: Option<StretchableOp>,
style: Option<Style>,
) -> Node<'arena> {
fn to_operator(delim: Option<StretchableOp>) -> Node<'static> {
if let Some(op) = delim {
let attrs = if matches!(op.stretchy, Stretchy::Never) {
OpAttrs::STRETCHY_TRUE
} else {
OpAttrs::empty()
};
let (left, right) = if matches!(
op.spacing,
DelimiterSpacing::Relation | DelimiterSpacing::Other
) {
(Some(MathSpacing::Zero), Some(MathSpacing::Zero))
} else {
(None, None)
};
Node::Operator {
op: op.as_op(),
attrs,
size: None,
left,
right,
}
} else {
Node::Operator {
op: const { symbol::INVISIBLE_SEPARATOR.as_op() },
attrs: OpAttrs::empty(),
size: None,
left: None,
right: None,
}
}
}
let open = arena.push(to_operator(open));
let close = arena.push(to_operator(close));
content.insert(0, open);
content.push(close);
let nodes = arena.push_slice(&content);
Node::Row {
nodes,
attr: style.map(RowAttr::Style),
}
}
#[cfg(test)]
mod tests {
use super::{MathVariant, TextTransform};
#[test]
fn size_test() {
assert_eq!(
std::mem::size_of::<MathVariant>(),
std::mem::size_of::<TextTransform>()
);
assert_eq!(
std::mem::size_of::<Option<MathVariant>>(),
std::mem::size_of::<TextTransform>()
);
}
}