diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-11-03 11:44:53 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-11-03 13:35:39 +0100 |
| commit | 37a7afddfaffd44cb9bc013c9506599267e08983 (patch) | |
| tree | 20e7d62d3c5418baff01a21d0406b91bf3096214 /library/src/math/mod.rs | |
| parent | 56342bd972a13ffe21beaf2b87ab7eb1597704b4 (diff) | |
Split crates
Diffstat (limited to 'library/src/math/mod.rs')
| -rw-r--r-- | library/src/math/mod.rs | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs new file mode 100644 index 00000000..e46ba040 --- /dev/null +++ b/library/src/math/mod.rs @@ -0,0 +1,188 @@ +//! Mathematical formulas. + +mod tex; + +use std::fmt::Write; + +use self::tex::{layout_tex, Texify}; +use crate::layout::BlockSpacing; +use crate::prelude::*; +use crate::text::FontFamily; + +/// A piece of a mathematical formula. +#[derive(Debug, Clone, Hash)] +pub struct MathNode { + /// The pieces of the formula. + pub children: Vec<Content>, + /// Whether the formula is display-level. + pub display: bool, +} + +#[node(Show, LayoutInline, Texify)] +impl MathNode { + /// The math font family. + #[property(referenced)] + pub const FAMILY: FontFamily = FontFamily::new("NewComputerModernMath"); + /// The spacing above display math. + #[property(resolve, shorthand(around))] + pub const ABOVE: Option<BlockSpacing> = Some(Ratio::one().into()); + /// The spacing below display math. + #[property(resolve, shorthand(around))] + pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into()); +} + +impl Show for MathNode { + fn unguard_parts(&self, _: Selector) -> Content { + self.clone().pack() + } + + fn field(&self, _: &str) -> Option<Value> { + None + } + + fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> { + Ok(if self.display { + self.clone().pack().aligned(Axes::with_x(Some(Align::Center.into()))) + } else { + self.clone().pack() + }) + } + + fn finalize( + &self, + _: Tracked<dyn World>, + styles: StyleChain, + realized: Content, + ) -> SourceResult<Content> { + Ok(if self.display { + realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW)) + } else { + realized + }) + } +} + +impl LayoutInline for MathNode { + fn layout_inline( + &self, + world: Tracked<dyn World>, + _: &Regions, + styles: StyleChain, + ) -> SourceResult<Vec<Frame>> { + Ok(vec![layout_tex( + &self.texify(), + self.display, + world, + styles, + )?]) + } +} + +impl Texify for MathNode { + fn texify(&self) -> EcoString { + self.children.iter().map(Texify::texify).collect() + } +} + +/// An atom in a math formula: `x`, `+`, `12`. +#[derive(Debug, Hash)] +pub struct AtomNode(pub EcoString); + +#[node(Texify)] +impl AtomNode {} + +impl Texify for AtomNode { + fn texify(&self) -> EcoString { + self.0.chars().map(escape_char).collect() + } +} + +/// 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 {} + +impl Texify for FracNode { + fn texify(&self) -> EcoString { + format_eco!( + "\\frac{{{}}}{{{}}}", + unparen(self.num.texify()), + unparen(self.denom.texify()) + ) + } +} + +/// 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<Content>, + /// The superscript. + pub sup: Option<Content>, +} + +#[node(Texify)] +impl ScriptNode {} + +impl Texify for ScriptNode { + fn texify(&self) -> EcoString { + let mut tex = self.base.texify(); + + if let Some(sub) = &self.sub { + write!(tex, "_{{{}}}", unparen(sub.texify())).unwrap(); + } + + if let Some(sup) = &self.sup { + write!(tex, "^{{{}}}", unparen(sup.texify())).unwrap(); + } + + tex + } +} + +/// A math alignment indicator: `&`, `&&`. +#[derive(Debug, Hash)] +pub struct AlignNode(pub usize); + +#[node(Texify)] +impl AlignNode {} + +impl Texify for AlignNode { + fn texify(&self) -> EcoString { + EcoString::new() + } +} + +/// Escape a char for TeX usage. +#[rustfmt::skip] +fn escape_char(c: char) -> EcoString { + match c { + '{' | '}' | '%' | '&' | '$' | '#' => format_eco!(" \\{c} "), + 'a' ..= 'z' | 'A' ..= 'Z' | '0' ..= '9' | 'Α' ..= 'Ω' | 'α' ..= 'ω' | + '*' | '+' | '-' | '[' | '(' | ']' | ')' | '?' | '!' | '=' | '<' | '>' | + ':' | ',' | ';' | '|' | '/' | '@' | '.' | '"' => c.into(), + c => unicode_math::SYMBOLS + .iter() + .find(|sym| sym.codepoint == c) + .map(|sym| format_eco!("\\{} ", sym.name)) + .unwrap_or_default(), + } +} + +/// Trim grouping parenthesis≤. +fn unparen(s: EcoString) -> EcoString { + if s.starts_with('(') && s.ends_with(')') { + s[1 .. s.len() - 1].into() + } else { + s + } +} |
