summaryrefslogtreecommitdiff
path: root/library/src/math/mod.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-11-03 11:44:53 +0100
committerLaurenz <laurmaedje@gmail.com>2022-11-03 13:35:39 +0100
commit37a7afddfaffd44cb9bc013c9506599267e08983 (patch)
tree20e7d62d3c5418baff01a21d0406b91bf3096214 /library/src/math/mod.rs
parent56342bd972a13ffe21beaf2b87ab7eb1597704b4 (diff)
Split crates
Diffstat (limited to 'library/src/math/mod.rs')
-rw-r--r--library/src/math/mod.rs188
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
+ }
+}