diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index 34eace04f..ae6604055 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -7,9 +7,11 @@ mod align; mod atom; mod braced; mod frac; -mod group; +mod fragment; mod matrix; +mod op; mod root; +mod row; mod script; mod spacing; mod stretch; @@ -54,6 +56,8 @@ pub fn define(scope: &mut Scope) { scope.def_func::("script"); scope.def_func::("sqrt"); scope.def_func::("root"); + scope.def_func::("floor"); + scope.def_func::("ceil"); scope.def_func::("vec"); scope.def_func::("cases"); scope.def_func::("underbrace"); @@ -70,6 +74,7 @@ pub fn define(scope: &mut Scope) { scope.define("med", HNode::strong(MEDIUM).pack()); scope.define("thick", HNode::strong(THICK).pack()); scope.define("quad", HNode::strong(QUAD).pack()); + define_operators(scope); } /// # Math diff --git a/library/src/math/op.rs b/library/src/math/op.rs new file mode 100644 index 000000000..aef0d41b2 --- /dev/null +++ b/library/src/math/op.rs @@ -0,0 +1,150 @@ +use typst::model::Scope; + +use super::*; + +/// # Text Operator +/// A text operator in a math formula. +/// +/// ## Parameters +/// - text: EcoString (positional, required) +/// The operator's text. +/// - limits: bool (named) +/// Whether the operator should display sub- and superscripts as limits. +/// +/// Defaults to `true`. +/// +/// ## Category +/// math +#[func] +#[capable(LayoutMath)] +#[derive(Debug, Hash)] +pub struct OpNode { + /// The operator's text. + pub text: EcoString, + /// Whether the operator should display sub- and superscripts as limits. + pub limits: bool, +} + +impl OpNode { + fn new(text: impl Into, limits: bool) -> Self { + Self { text: text.into(), limits } + } +} + +#[node] +impl OpNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + Ok(Self { + text: args.expect("text")?, + limits: args.named("limits")?.unwrap_or(true), + } + .pack()) + } +} + +impl LayoutMath for OpNode { + fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { + let frame = ctx.layout_non_math(&TextNode(self.text.clone()).pack())?; + ctx.push(FrameFragment { + frame, + class: MathClass::Large, + limits: self.limits, + }); + Ok(()) + } +} + +/// Hook up all operators. +pub fn define_operators(scope: &mut Scope) { + let mut define = |name: &str, limits| { + scope.define(name, OpNode { text: name.into(), limits }.pack()); + }; + + // These have the same name in code and display. + define("arccos", false); + define("arcsin", false); + define("arctan", false); + define("arg", false); + define("cos", false); + define("cosh", false); + define("cot", false); + define("coth", false); + define("csc", false); + define("deg", false); + define("det", true); + define("dim", false); + define("exp", false); + define("gcd", true); + define("hom", false); + define("inf", true); + define("ker", false); + define("lg", false); + define("lim", true); + define("ln", false); + define("log", false); + define("max", true); + define("min", true); + define("Pr", true); + define("sec", false); + define("sin", false); + define("sinh", false); + define("sup", true); + define("tan", false); + define("tanh", false); + + // These have an extra thin space. + scope.define("liminf", OpNode::new("lim inf", true).pack()); + scope.define("limsup", OpNode::new("lim sup", true).pack()); +} + +/// # Floor +/// A floored expression. +/// +/// ## Example +/// ``` +/// $ floor(x/2) $ +/// ``` +/// +/// ## Parameters +/// - body: Content (positional, required) +/// The expression to floor. +/// +/// ## Category +/// math +#[func] +#[capable] +#[derive(Debug, Hash)] +pub struct FloorNode(pub Content); + +#[node] +impl FloorNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + Ok(Self(args.expect("body")?).pack()) + } +} + +/// # Ceil +/// A ceiled expression. +/// +/// ## Example +/// ``` +/// $ ceil(x/2) $ +/// ``` +/// +/// ## Parameters +/// - body: Content (positional, required) +/// The expression to ceil. +/// +/// ## Category +/// math +#[func] +#[capable] +#[derive(Debug, Hash)] +pub struct CeilNode(pub Content); + +#[node] +impl CeilNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + Ok(Self(args.expect("body")?).pack()) + } +}