From e460da1ce7436242e9c356a23b97d6a474085544 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 3 Jan 2023 12:40:14 +0100 Subject: [PATCH] Split up math library --- library/src/math/accent.rs | 112 ++++++++++ library/src/math/atom.rs | 48 ++++ library/src/math/frac.rs | 102 +++++++++ library/src/math/group.rs | 71 ++++++ library/src/math/mod.rs | 447 +------------------------------------ library/src/math/root.rs | 38 ++++ library/src/math/script.rs | 70 ++++++ 7 files changed, 453 insertions(+), 435 deletions(-) create mode 100644 library/src/math/accent.rs create mode 100644 library/src/math/atom.rs create mode 100644 library/src/math/frac.rs create mode 100644 library/src/math/group.rs create mode 100644 library/src/math/root.rs create mode 100644 library/src/math/script.rs diff --git a/library/src/math/accent.rs b/library/src/math/accent.rs new file mode 100644 index 000000000..bf40a332d --- /dev/null +++ b/library/src/math/accent.rs @@ -0,0 +1,112 @@ +use super::*; + +/// # Accent +/// An accented node. +/// +/// ## Example +/// ``` +/// $acc(a, ->) != acc(a, ~)$ \ +/// $acc(a, `) = acc(a, grave)$ +/// ``` +/// +/// ## Parameters +/// - base: Content (positional, required) +/// The base to which the accent is applied. +/// May consist of multiple letters. +/// +/// ### Example +/// ``` +/// $acc(A B C, ->)$ +/// ``` +/// +/// - accent: Content (positional, required) +/// The accent to apply to the base. +/// +/// Supported accents include: +/// - Grave: `` ` `` +/// - Acute: `´` +/// - Circumflex: `^` +/// - Tilde: `~` +/// - Macron: `¯` +/// - Overline: `‾` +/// - Breve: `˘` +/// - Dot: `.` +/// - Diaeresis: `¨` +/// - Caron: `ˇ` +/// - Arrow: `→` +/// +/// ## Category +/// math +#[func] +#[capable(Texify)] +#[derive(Debug, Hash)] +pub struct AccNode { + /// The accent base. + pub base: Content, + /// The Unicode accent character. + pub accent: char, +} + +#[node] +impl AccNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + let base = args.expect("base")?; + let Spanned { v, span } = args.expect::>("accent")?; + let accent = match extract(&v) { + Some(Ok(c)) => c, + Some(Err(msg)) => bail!(span, "{}", msg), + None => bail!(span, "not an accent"), + }; + Ok(Self { base, accent }.pack()) + } +} + +#[rustfmt::skip] +fn extract(content: &Content) -> Option> { + let MathNode { children, .. } = content.to::()?; + let [child] = children.as_slice() else { return None }; + let c = if let Some(atom) = child.to::() { + let mut chars = atom.0.chars(); + chars.next().filter(|_| chars.next().is_none())? + } else if let Some(symbol) = child.to::() { + match symmie::get(&symbol.0) { + Some(c) => c, + None => return Some(Err("unknown symbol")), + } + } else { + return None; + }; + + Some(Ok(match c { + '`' | '\u{300}' => '\u{300}', // Grave + '´' | '\u{301}' => '\u{301}', // Acute + '^' | '\u{302}' => '\u{302}', // Circumflex + '~' | '\u{223C}' | '\u{303}' => '\u{303}', // Tilde + '¯' | '\u{304}' => '\u{304}', // Macron + '‾' | '\u{305}' => '\u{305}', // Overline + '˘' | '\u{306}' => '\u{306}', // Breve + '.' | '\u{22C5}' | '\u{307}' => '\u{307}', // Dot + '¨' | '\u{308}' => '\u{308}', // Diaeresis + 'ˇ' | '\u{30C}' => '\u{30C}', // Caron + '→' | '\u{20D7}' => '\u{20D7}', // Arrow + _ => return None, + })) +} + +impl Texify for AccNode { + fn texify(&self, t: &mut Texifier) -> SourceResult<()> { + if let Some(sym) = unicode_math::SYMBOLS.iter().find(|sym| { + sym.codepoint == self.accent + && sym.atom_type == unicode_math::AtomType::Accent + }) { + t.push_str("\\"); + t.push_str(sym.name); + t.push_str("{"); + self.base.texify(t)?; + t.push_str("}"); + } else { + self.base.texify(t)?; + } + Ok(()) + } +} diff --git a/library/src/math/atom.rs b/library/src/math/atom.rs new file mode 100644 index 000000000..142461474 --- /dev/null +++ b/library/src/math/atom.rs @@ -0,0 +1,48 @@ +use super::*; + +/// # Atom +/// An atom in a math formula: `x`, `+`, `12`. +/// +/// ## Parameters +/// - text: EcoString (positional, required) +/// The atom's text. +/// +/// ## Category +/// math +#[func] +#[capable(Texify)] +#[derive(Debug, Hash)] +pub struct AtomNode(pub EcoString); + +#[node] +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(()) + } +} diff --git a/library/src/math/frac.rs b/library/src/math/frac.rs new file mode 100644 index 000000000..f3edf852f --- /dev/null +++ b/library/src/math/frac.rs @@ -0,0 +1,102 @@ +use super::*; + +/// # Fraction +/// A mathematical fraction. +/// +/// ## Syntax +/// This function also has dedicated syntax: Use a slash to turn neighbouring +/// expressions into a fraction. Multiple atoms can be grouped into a single +/// expression using round grouping parenthesis. Such parentheses are removed +/// from the output, but you can nest multiple to force them. +/// +/// ## Example +/// ``` +/// $ 1/2 < (x+1)/2 $ +/// $ ((x+1)) / 2 = frac(a, b) $ +/// ``` +/// +/// ## Parameters +/// - num: Content (positional, required) +/// The fraction's numerator. +/// +/// - denom: Content (positional, required) +/// The fraction's denominator. +/// +/// ## Category +/// math +#[func] +#[capable(Texify)] +#[derive(Debug, Hash)] +pub struct FracNode { + /// The numerator. + pub num: Content, + /// The denominator. + pub denom: Content, +} + +#[node] +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(()) + } +} + +/// # Binomial +/// A binomial expression. +/// +/// ## Example +/// ``` +/// $ binom(n, k) $ +/// ``` +/// +/// ## Parameters +/// - upper: Content (positional, required) +/// The binomial's upper index. +/// +/// - lower: Content (positional, required) +/// The binomial's lower index. +/// +/// ## Category +/// math +#[func] +#[capable(Texify)] +#[derive(Debug, Hash)] +pub struct BinomNode { + /// The upper index. + pub upper: Content, + /// The lower index. + pub lower: Content, +} + +#[node] +impl BinomNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + let upper = args.expect("upper index")?; + let lower = args.expect("lower index")?; + Ok(Self { upper, lower }.pack()) + } +} + +impl Texify for BinomNode { + fn texify(&self, t: &mut Texifier) -> SourceResult<()> { + t.push_str("\\binom{"); + self.upper.texify(t)?; + t.push_str("}{"); + self.lower.texify(t)?; + t.push_str("}"); + Ok(()) + } +} diff --git a/library/src/math/group.rs b/library/src/math/group.rs new file mode 100644 index 000000000..4a55e0e8f --- /dev/null +++ b/library/src/math/group.rs @@ -0,0 +1,71 @@ +use super::*; + +/// # Floor +/// A floored expression. +/// +/// ## Example +/// ``` +/// $ floor(x/2) $ +/// ``` +/// +/// ## Parameters +/// - body: Content (positional, required) +/// The expression to floor. +/// +/// ## Category +/// math +#[func] +#[capable(Texify)] +#[derive(Debug, Hash)] +pub struct FloorNode(pub Content); + +#[node] +impl FloorNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + Ok(Self(args.expect("body")?).pack()) + } +} + +impl Texify for FloorNode { + fn texify(&self, t: &mut Texifier) -> SourceResult<()> { + t.push_str("\\left\\lfloor "); + self.0.texify(t)?; + t.push_str("\\right\\rfloor "); + Ok(()) + } +} + +/// # Ceil +/// A ceiled expression. +/// +/// ## Example +/// ``` +/// $ ceil(x/2) $ +/// ``` +/// +/// ## Parameters +/// - body: Content (positional, required) +/// The expression to ceil. +/// +/// ## Category +/// math +#[func] +#[capable(Texify)] +#[derive(Debug, Hash)] +pub struct CeilNode(pub Content); + +#[node] +impl CeilNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + Ok(Self(args.expect("body")?).pack()) + } +} + +impl Texify for CeilNode { + fn texify(&self, t: &mut Texifier) -> SourceResult<()> { + t.push_str("\\left\\lceil "); + self.0.texify(t)?; + t.push_str("\\right\\rceil "); + Ok(()) + } +} diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index 52cddc9f7..ce70f1de3 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -1,10 +1,22 @@ //! Mathematical formulas. +mod accent; +mod atom; +mod frac; +mod group; mod matrix; +mod root; +mod script; mod style; mod tex; +pub use self::accent::*; +pub use self::atom::*; +pub use self::frac::*; +pub use self::group::*; pub use self::matrix::*; +pub use self::root::*; +pub use self::script::*; pub use self::style::*; use typst::model::{Guard, SequenceNode}; @@ -297,334 +309,6 @@ impl Texify for Content { } } -/// # Atom -/// An atom in a math formula: `x`, `+`, `12`. -/// -/// ## Parameters -/// - text: EcoString (positional, required) -/// The atom's text. -/// -/// ## Category -/// math -#[func] -#[capable(Texify)] -#[derive(Debug, Hash)] -pub struct AtomNode(pub EcoString); - -#[node] -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(()) - } -} - -/// # Accent -/// An accented node. -/// -/// ## Example -/// ``` -/// $acc(a, ->) != acc(a, ~)$ \ -/// $acc(a, `) = acc(a, grave)$ -/// ``` -/// -/// ## Parameters -/// - base: Content (positional, required) -/// The base to which the accent is applied. -/// May consist of multiple letters. -/// -/// ### Example -/// ``` -/// $acc(A B C, ->)$ -/// ``` -/// -/// - accent: Content (positional, required) -/// The accent to apply to the base. -/// -/// Supported accents include: -/// - Grave: `` ` `` -/// - Acute: `´` -/// - Circumflex: `^` -/// - Tilde: `~` -/// - Macron: `¯` -/// - Overline: `‾` -/// - Breve: `˘` -/// - Dot: `.` -/// - Diaeresis: `¨` -/// - Caron: `ˇ` -/// - Arrow: `→` -/// -/// ## Category -/// math -#[func] -#[capable(Texify)] -#[derive(Debug, Hash)] -pub struct AccNode { - /// The accent base. - pub base: Content, - /// The Unicode accent character. - pub accent: char, -} - -#[node] -impl AccNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult { - let base = args.expect("base")?; - let Spanned { v, span } = args.expect::>("accent")?; - let accent = match extract(&v) { - Some(Ok(c)) => c, - Some(Err(msg)) => bail!(span, "{}", msg), - None => bail!(span, "not an accent"), - }; - Ok(Self { base, accent }.pack()) - } -} - -#[rustfmt::skip] -fn extract(content: &Content) -> Option> { - let MathNode { children, .. } = content.to::()?; - let [child] = children.as_slice() else { return None }; - let c = if let Some(atom) = child.to::() { - let mut chars = atom.0.chars(); - chars.next().filter(|_| chars.next().is_none())? - } else if let Some(symbol) = child.to::() { - match symmie::get(&symbol.0) { - Some(c) => c, - None => return Some(Err("unknown symbol")), - } - } else { - return None; - }; - - Some(Ok(match c { - '`' | '\u{300}' => '\u{300}', // Grave - '´' | '\u{301}' => '\u{301}', // Acute - '^' | '\u{302}' => '\u{302}', // Circumflex - '~' | '\u{223C}' | '\u{303}' => '\u{303}', // Tilde - '¯' | '\u{304}' => '\u{304}', // Macron - '‾' | '\u{305}' => '\u{305}', // Overline - '˘' | '\u{306}' => '\u{306}', // Breve - '.' | '\u{22C5}' | '\u{307}' => '\u{307}', // Dot - '¨' | '\u{308}' => '\u{308}', // Diaeresis - 'ˇ' | '\u{30C}' => '\u{30C}', // Caron - '→' | '\u{20D7}' => '\u{20D7}', // Arrow - _ => return None, - })) -} - -impl Texify for AccNode { - fn texify(&self, t: &mut Texifier) -> SourceResult<()> { - if let Some(sym) = unicode_math::SYMBOLS.iter().find(|sym| { - sym.codepoint == self.accent - && sym.atom_type == unicode_math::AtomType::Accent - }) { - t.push_str("\\"); - t.push_str(sym.name); - t.push_str("{"); - self.base.texify(t)?; - t.push_str("}"); - } else { - self.base.texify(t)?; - } - Ok(()) - } -} - -/// # Fraction -/// A mathematical fraction. -/// -/// ## Syntax -/// This function also has dedicated syntax: Use a slash to turn neighbouring -/// expressions into a fraction. Multiple atoms can be grouped into a single -/// expression using round grouping parenthesis. Such parentheses are removed -/// from the output, but you can nest multiple to force them. -/// -/// ## Example -/// ``` -/// $ 1/2 < (x+1)/2 $ -/// $ ((x+1)) / 2 = frac(a, b) $ -/// ``` -/// -/// ## Parameters -/// - num: Content (positional, required) -/// The fraction's numerator. -/// -/// - denom: Content (positional, required) -/// The fraction's denominator. -/// -/// ## Category -/// math -#[func] -#[capable(Texify)] -#[derive(Debug, Hash)] -pub struct FracNode { - /// The numerator. - pub num: Content, - /// The denominator. - pub denom: Content, -} - -#[node] -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(()) - } -} - -/// # Binomial -/// A binomial expression. -/// -/// ## Example -/// ``` -/// $ binom(n, k) $ -/// ``` -/// -/// ## Parameters -/// - upper: Content (positional, required) -/// The binomial's upper index. -/// -/// - lower: Content (positional, required) -/// The binomial's lower index. -/// -/// ## Category -/// math -#[func] -#[capable(Texify)] -#[derive(Debug, Hash)] -pub struct BinomNode { - /// The upper index. - pub upper: Content, - /// The lower index. - pub lower: Content, -} - -#[node] -impl BinomNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult { - let upper = args.expect("upper index")?; - let lower = args.expect("lower index")?; - Ok(Self { upper, lower }.pack()) - } -} - -impl Texify for BinomNode { - fn texify(&self, t: &mut Texifier) -> SourceResult<()> { - t.push_str("\\binom{"); - self.upper.texify(t)?; - t.push_str("}{"); - self.lower.texify(t)?; - t.push_str("}"); - Ok(()) - } -} - -/// # Script -/// A mathematical sub- and/or superscript. -/// -/// _Note:_ In the future, this might be unified with the [sub](@sub) and -/// [super](@super) functions that handle sub- and superscripts in text. -/// -/// ## Syntax -/// This function also has dedicated syntax: Use the underscore (`_`) to -/// indicate a subscript and the circumflex (`^`) to indicate a superscript. -/// -/// ## Example -/// ``` -/// $ a_i = 2^(1+i) $ -/// ``` -/// -/// ## Parameters -/// - base: Content (positional, required) -/// The base to which the applies the sub- and/or superscript. -/// -/// - sub: Content (named) -/// The subscript. -/// -/// - sup: Content (named) -/// The superscript. -/// -/// ## Category -/// math -#[func] -#[capable(Texify)] -#[derive(Debug, Hash)] -pub struct ScriptNode { - /// The base. - pub base: Content, - /// The subscript. - pub sub: Option, - /// The superscript. - pub sup: Option, -} - -#[node] -impl ScriptNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult { - let base = args.expect("base")?; - let sub = args.named("sub")?; - let sup = args.named("sup")?; - Ok(Self { base, sub, sup }.pack()) - } -} - -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(()) - } -} - /// # Alignment Point /// A math alignment point: `&`, `&&`. /// @@ -651,110 +335,3 @@ impl Texify for AlignPointNode { Ok(()) } } - -/// # Square Root -/// A square root. -/// -/// _Note:_ Non-square roots are not yet supported. -/// -/// ## Example -/// ``` -/// $ sqrt(x^2) = x = sqrt(x)^2 $ -/// ``` -/// -/// ## Parameters -/// - body: Content (positional, required) -/// The expression to take the square root of. -/// -/// ## Category -/// math -#[func] -#[capable(Texify)] -#[derive(Debug, Hash)] -pub struct SqrtNode(pub Content); - -#[node] -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(t)?; - t.push_str("}"); - Ok(()) - } -} - -/// # Floor -/// A floored expression. -/// -/// ## Example -/// ``` -/// $ floor(x/2) $ -/// ``` -/// -/// ## Parameters -/// - body: Content (positional, required) -/// The expression to floor. -/// -/// ## Category -/// math -#[func] -#[capable(Texify)] -#[derive(Debug, Hash)] -pub struct FloorNode(pub Content); - -#[node] -impl FloorNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult { - Ok(Self(args.expect("body")?).pack()) - } -} - -impl Texify for FloorNode { - fn texify(&self, t: &mut Texifier) -> SourceResult<()> { - t.push_str("\\left\\lfloor "); - self.0.texify(t)?; - t.push_str("\\right\\rfloor "); - Ok(()) - } -} - -/// # Ceil -/// A ceiled expression. -/// -/// ## Example -/// ``` -/// $ ceil(x/2) $ -/// ``` -/// -/// ## Parameters -/// - body: Content (positional, required) -/// The expression to ceil. -/// -/// ## Category -/// math -#[func] -#[capable(Texify)] -#[derive(Debug, Hash)] -pub struct CeilNode(pub Content); - -#[node] -impl CeilNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult { - Ok(Self(args.expect("body")?).pack()) - } -} - -impl Texify for CeilNode { - fn texify(&self, t: &mut Texifier) -> SourceResult<()> { - t.push_str("\\left\\lceil "); - self.0.texify(t)?; - t.push_str("\\right\\rceil "); - Ok(()) - } -} diff --git a/library/src/math/root.rs b/library/src/math/root.rs new file mode 100644 index 000000000..90664afa7 --- /dev/null +++ b/library/src/math/root.rs @@ -0,0 +1,38 @@ +use super::*; + +/// # Square Root +/// A square root. +/// +/// _Note:_ Non-square roots are not yet supported. +/// +/// ## Example +/// ``` +/// $ sqrt(x^2) = x = sqrt(x)^2 $ +/// ``` +/// +/// ## Parameters +/// - body: Content (positional, required) +/// The expression to take the square root of. +/// +/// ## Category +/// math +#[func] +#[capable(Texify)] +#[derive(Debug, Hash)] +pub struct SqrtNode(pub Content); + +#[node] +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(t)?; + t.push_str("}"); + Ok(()) + } +} diff --git a/library/src/math/script.rs b/library/src/math/script.rs new file mode 100644 index 000000000..c0255a2ee --- /dev/null +++ b/library/src/math/script.rs @@ -0,0 +1,70 @@ +use super::*; + +/// # Script +/// A mathematical sub- and/or superscript. +/// +/// _Note:_ In the future, this might be unified with the [sub](@sub) and +/// [super](@super) functions that handle sub- and superscripts in text. +/// +/// ## Syntax +/// This function also has dedicated syntax: Use the underscore (`_`) to +/// indicate a subscript and the circumflex (`^`) to indicate a superscript. +/// +/// ## Example +/// ``` +/// $ a_i = 2^(1+i) $ +/// ``` +/// +/// ## Parameters +/// - base: Content (positional, required) +/// The base to which the applies the sub- and/or superscript. +/// +/// - sub: Content (named) +/// The subscript. +/// +/// - sup: Content (named) +/// The superscript. +/// +/// ## Category +/// math +#[func] +#[capable(Texify)] +#[derive(Debug, Hash)] +pub struct ScriptNode { + /// The base. + pub base: Content, + /// The subscript. + pub sub: Option, + /// The superscript. + pub sup: Option, +} + +#[node] +impl ScriptNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + let base = args.expect("base")?; + let sub = args.named("sub")?; + let sup = args.named("sup")?; + Ok(Self { base, sub, sup }.pack()) + } +} + +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(()) + } +}