//! Mathematical formulas. mod tex; use typst::model::{Guard, SequenceNode}; use unicode_segmentation::UnicodeSegmentation; use self::tex::layout_tex; use crate::prelude::*; use crate::text::{FontFamily, LinebreakNode, SpaceNode, SymbolNode, TextNode}; /// A piece of a mathematical formula. #[derive(Debug, Clone, Hash)] pub struct MathNode { /// The pieces of the formula. pub children: Vec, /// Whether the formula is display-level. pub display: bool, } #[node(Show, Layout, Inline, Texify)] impl MathNode { fn field(&self, name: &str) -> Option { match name { "display" => Some(Value::Bool(self.display)), _ => None, } } } impl Show for MathNode { fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult { let mut map = StyleMap::new(); map.set_family(FontFamily::new("NewComputerModernMath"), styles); let mut realized = self .clone() .pack() .guarded(Guard::Base(NodeId::of::())) .styled_with_map(map); if self.display { realized = realized.aligned(Axes::with_x(Some(Align::Center.into()))) } Ok(realized) } } impl Layout for MathNode { fn layout( &self, vt: &mut Vt, styles: StyleChain, _: &Regions, ) -> SourceResult { let mut t = Texifier::new(); self.texify(&mut t)?; layout_tex(vt, &t.finish(), self.display, styles) } } impl Inline for MathNode {} /// Turn a math node into TeX math code. #[capability] trait Texify { /// Perform the conversion. fn texify(&self, t: &mut Texifier) -> SourceResult<()>; /// Texify the node, but trim parentheses.. fn texify_unparen(&self, t: &mut Texifier) -> SourceResult<()> { let s = { let mut sub = Texifier::new(); self.texify(&mut sub)?; sub.finish() }; let unparened = if s.starts_with("\\left(") && s.ends_with("\\right)") { s[6..s.len() - 7].into() } else { s }; t.push_str(&unparened); Ok(()) } } /// Builds the TeX representation of the formula. struct Texifier { tex: EcoString, support: bool, space: bool, } impl Texifier { /// Create a new texifier. fn new() -> Self { Self { tex: EcoString::new(), support: false, space: false, } } /// Finish texifier and return the TeX string. fn finish(self) -> EcoString { self.tex } /// Push a weak space. fn push_space(&mut self) { self.space = !self.tex.is_empty(); } /// Mark this position as supportive. This allows a space before or after /// to exist. fn support(&mut self) { self.support = true; } /// Flush a space. fn flush(&mut self) { if self.space && self.support { self.tex.push_str("\\ "); } self.space = false; self.support = false; } /// Push a string. fn push_str(&mut self, s: &str) { self.flush(); self.tex.push_str(s); } /// Escape and push a char for TeX usage. #[rustfmt::skip] fn push_escaped(&mut self, c: char) { self.flush(); match c { ' ' => self.tex.push_str("\\ "), '%' | '&' | '$' | '#' => { self.tex.push('\\'); self.tex.push(c); self.tex.push(' '); } '{' => self.tex.push_str("\\left\\{"), '}' => self.tex.push_str("\\right\\}"), '[' | '(' => { self.tex.push_str("\\left"); self.tex.push(c); } ']' | ')' => { self.tex.push_str("\\right"); self.tex.push(c); } 'a' ..= 'z' | 'A' ..= 'Z' | '0' ..= '9' | 'Α' ..= 'Ω' | 'α' ..= 'ω' | '*' | '+' | '-' | '?' | '!' | '=' | '<' | '>' | ':' | ',' | ';' | '|' | '/' | '@' | '.' | '"' => self.tex.push(c), c => { if let Some(sym) = unicode_math::SYMBOLS .iter() .find(|sym| sym.codepoint == c) { self.tex.push('\\'); self.tex.push_str(sym.name); self.tex.push(' '); } } } } } impl Texify for MathNode { fn texify(&self, t: &mut Texifier) -> SourceResult<()> { for child in &self.children { child.texify(t)?; } Ok(()) } } impl Texify for Content { fn texify(&self, t: &mut Texifier) -> SourceResult<()> { if self.is::() { t.push_space(); return Ok(()); } if self.is::() { t.push_str("\\"); return Ok(()); } if let Some(node) = self.to::() { if let Some(c) = symmie::get(&node.0) { t.push_escaped(c); return Ok(()); } else if let Some(span) = self.span() { bail!(span, "unknown symbol"); } } if let Some(node) = self.to::() { t.support(); t.push_str("\\mathrm{"); for c in node.0.chars() { t.push_escaped(c); } t.push_str("}"); t.support(); return Ok(()); } if let Some(node) = self.to::() { for child in &node.0 { child.texify(t)?; } return Ok(()); } if let Some(node) = self.with::() { return node.texify(t); } if let Some(span) = self.span() { bail!(span, "not allowed here"); } Ok(()) } } /// An atom in a math formula: `x`, `+`, `12`. #[derive(Debug, Hash)] pub struct AtomNode(pub EcoString); #[node(Texify)] impl AtomNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("text")?).pack()) } } impl Texify for AtomNode { fn texify(&self, t: &mut Texifier) -> SourceResult<()> { let multi = self.0.graphemes(true).count() > 1; if multi { t.push_str("\\mathrm{"); } for c in self.0.chars() { let supportive = c == '|'; if supportive { t.support(); } t.push_escaped(c); if supportive { t.support(); } } if multi { t.push_str("}"); } Ok(()) } } /// A fraction in a mathematical formula. #[derive(Debug, Hash)] pub struct FracNode { /// The numerator. pub num: Content, /// The denominator. pub denom: Content, } #[node(Texify)] impl FracNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { let num = args.expect("numerator")?; let denom = args.expect("denominator")?; Ok(Self { num, denom }.pack()) } } impl Texify for FracNode { fn texify(&self, t: &mut Texifier) -> SourceResult<()> { t.push_str("\\frac{"); self.num.texify_unparen(t)?; t.push_str("}{"); self.denom.texify_unparen(t)?; t.push_str("}"); Ok(()) } } /// A sub- and/or superscript in a mathematical formula. #[derive(Debug, Hash)] pub struct ScriptNode { /// The base. pub base: Content, /// The subscript. pub sub: Option, /// The superscript. pub sup: Option, } #[node(Texify)] impl ScriptNode {} impl Texify for ScriptNode { fn texify(&self, t: &mut Texifier) -> SourceResult<()> { self.base.texify(t)?; if let Some(sub) = &self.sub { t.push_str("_{"); sub.texify_unparen(t)?; t.push_str("}"); } if let Some(sup) = &self.sup { t.push_str("^{"); sup.texify_unparen(t)?; t.push_str("}"); } Ok(()) } } /// A math alignment indicator: `&`, `&&`. #[derive(Debug, Hash)] pub struct AlignNode(pub usize); #[node(Texify)] impl AlignNode {} impl Texify for AlignNode { fn texify(&self, _: &mut Texifier) -> SourceResult<()> { Ok(()) } } /// A square root node. #[derive(Debug, Hash)] pub struct SqrtNode(Content); #[node(Texify)] impl SqrtNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("body")?).pack()) } } impl Texify for SqrtNode { fn texify(&self, t: &mut Texifier) -> SourceResult<()> { t.push_str("\\sqrt{"); self.0.texify_unparen(t)?; t.push_str("}"); Ok(()) } }