#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DisplayMode {
#[default]
Inline,
Block,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Length {
Em(f32),
Ex(f32),
Px(f32),
Pt(f32),
Percent(f32),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Direction {
#[default]
Ltr,
Rtl,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Color {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
impl Color {
pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
Color { r, g, b, a: 255 }
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct StyleOverrides {
pub display_style: Option<bool>,
pub script_level: Option<ScriptLevel>,
pub math_size: Option<Length>,
pub color: Option<Color>,
pub background: Option<Color>,
pub border: Option<Color>,
pub dir: Option<Direction>,
}
impl StyleOverrides {
pub fn is_empty(&self) -> bool {
*self == StyleOverrides::default()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TableCell {
pub content: Node,
pub row_span: u32,
pub col_span: u32,
}
impl TableCell {
pub fn new(content: Node) -> Self {
TableCell {
content,
row_span: 1,
col_span: 1,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ColumnAlign {
Left,
#[default]
Center,
Right,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Form {
Infix,
Prefix,
Postfix,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct OperatorAttrs {
pub form: Option<Form>,
pub lspace: Option<Length>,
pub rspace: Option<Length>,
pub stretchy: Option<bool>,
pub symmetric: Option<bool>,
pub largeop: Option<bool>,
pub movablelimits: Option<bool>,
pub minsize: Option<Length>,
pub maxsize: Option<Length>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScriptLevel {
Set(u8),
Add(i8),
}
#[derive(Debug, Clone, PartialEq)]
pub struct MathRoot {
pub display: DisplayMode,
pub displaystyle: Option<bool>,
pub children: Vec<Node>,
pub warnings: Vec<Warning>,
}
impl MathRoot {
pub fn has_arabic_text(&self) -> bool {
self.children.iter().any(node_has_arabic_text)
}
}
fn node_has_arabic_text(node: &Node) -> bool {
let is_arabic = |c: char| {
matches!(
u32::from(c),
0x0600..=0x06FF | 0x0750..=0x077F | 0x0870..=0x089F | 0x08A0..=0x08FF | 0xFB50..=0xFDFF | 0xFE70..=0xFEFF | 0x10E60..=0x10E7F | 0x10EC0..=0x10EFF | 0x1EE00..=0x1EEFF )
};
let any = |nodes: &[Node]| nodes.iter().any(node_has_arabic_text);
let boxed = |n: &Option<Box<Node>>| n.as_deref().is_some_and(node_has_arabic_text);
match node {
Node::Identifier(text) | Node::Number(text) | Node::Text(text) => {
text.chars().any(is_arabic)
}
Node::Operator { text, .. } => text.chars().any(is_arabic),
Node::Row(children)
| Node::Sqrt(children)
| Node::Phantom(children)
| Node::Styled { children, .. }
| Node::Padded { children, .. } => any(children),
Node::Frac { num, den, .. } => node_has_arabic_text(num) || node_has_arabic_text(den),
Node::Scripts { base, sub, sup } => node_has_arabic_text(base) || boxed(sub) || boxed(sup),
Node::MultiScripts { base, post, pre } => {
let pair = |p: &(Option<Node>, Option<Node>)| {
p.0.as_ref().is_some_and(node_has_arabic_text)
|| p.1.as_ref().is_some_and(node_has_arabic_text)
};
node_has_arabic_text(base) || post.iter().any(pair) || pre.iter().any(pair)
}
Node::Root { base, index } => node_has_arabic_text(base) || node_has_arabic_text(index),
Node::UnderOver {
base, under, over, ..
} => node_has_arabic_text(base) || boxed(under) || boxed(over),
Node::Table { rows, .. } => rows
.iter()
.flatten()
.any(|cell| node_has_arabic_text(&cell.content)),
Node::Space { .. } => false,
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Warning {
UnknownElement { element: String },
InvalidStructure { element: String, detail: String },
}
#[derive(Debug, Clone, PartialEq)]
pub enum Node {
Identifier(String),
Number(String),
Operator { text: String, attrs: OperatorAttrs },
Text(String),
Row(Vec<Node>),
Frac {
num: Box<Node>,
den: Box<Node>,
line_thickness: Option<Length>,
},
Scripts {
base: Box<Node>,
sub: Option<Box<Node>>,
sup: Option<Box<Node>>,
},
MultiScripts {
base: Box<Node>,
post: Vec<(Option<Node>, Option<Node>)>,
pre: Vec<(Option<Node>, Option<Node>)>,
},
Sqrt(Vec<Node>),
Root { base: Box<Node>, index: Box<Node> },
Space {
width: Option<Length>,
height: Option<Length>,
depth: Option<Length>,
},
Styled {
styles: StyleOverrides,
children: Vec<Node>,
},
Phantom(Vec<Node>),
UnderOver {
base: Box<Node>,
under: Option<Box<Node>>,
over: Option<Box<Node>>,
accent: Option<bool>,
accent_under: Option<bool>,
},
Table {
rows: Vec<Vec<TableCell>>,
column_align: Vec<ColumnAlign>,
},
Padded {
width: Option<Length>,
height: Option<Length>,
depth: Option<Length>,
lspace: Option<Length>,
voffset: Option<Length>,
children: Vec<Node>,
},
}