summaryrefslogtreecommitdiff
path: root/library/src
diff options
context:
space:
mode:
Diffstat (limited to 'library/src')
-rw-r--r--library/src/math/accent.rs112
-rw-r--r--library/src/math/atom.rs48
-rw-r--r--library/src/math/frac.rs102
-rw-r--r--library/src/math/group.rs71
-rw-r--r--library/src/math/mod.rs447
-rw-r--r--library/src/math/root.rs38
-rw-r--r--library/src/math/script.rs70
7 files changed, 453 insertions, 435 deletions
diff --git a/library/src/math/accent.rs b/library/src/math/accent.rs
new file mode 100644
index 00000000..bf40a332
--- /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<Content> {
+ let base = args.expect("base")?;
+ let Spanned { v, span } = args.expect::<Spanned<Content>>("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<Result<char, &'static str>> {
+ let MathNode { children, .. } = content.to::<MathNode>()?;
+ let [child] = children.as_slice() else { return None };
+ let c = if let Some(atom) = child.to::<AtomNode>() {
+ let mut chars = atom.0.chars();
+ chars.next().filter(|_| chars.next().is_none())?
+ } else if let Some(symbol) = child.to::<SymbolNode>() {
+ 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 00000000..14246147
--- /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<Content> {
+ 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 00000000..f3edf852
--- /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<Content> {
+ 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<Content> {
+ 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 00000000..4a55e0e8
--- /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<Content> {
+ 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<Content> {
+ 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 52cddc9f..ce70f1de 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<Content> {
- 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<Content> {
- let base = args.expect("base")?;
- let Spanned { v, span } = args.expect::<Spanned<Content>>("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<Result<char, &'static str>> {
- let MathNode { children, .. } = content.to::<MathNode>()?;
- let [child] = children.as_slice() else { return None };
- let c = if let Some(atom) = child.to::<AtomNode>() {
- let mut chars = atom.0.chars();
- chars.next().filter(|_| chars.next().is_none())?
- } else if let Some(symbol) = child.to::<SymbolNode>() {
- 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<Content> {
- 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<Content> {
- 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<Content>,
- /// The superscript.
- pub sup: Option<Content>,
-}
-
-#[node]
-impl ScriptNode {
- fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
- 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<Content> {
- 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<Content> {
- 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<Content> {
- 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 00000000..90664afa
--- /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<Content> {
+ 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 00000000..c0255a2e
--- /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<Content>,
+ /// The superscript.
+ pub sup: Option<Content>,
+}
+
+#[node]
+impl ScriptNode {
+ fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
+ 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(())
+ }
+}