summaryrefslogtreecommitdiff
path: root/library/src/math
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-01-23 15:03:10 +0100
committerLaurenz <laurmaedje@gmail.com>2023-01-23 15:23:52 +0100
commit4653ffebb43d733a3cff873d0903c7d00aaeb499 (patch)
tree6a97b2e6a6903b3198547d6f3d0a7e3d2eb023cd /library/src/math
parent84c6c8b0e6b17996a603ec88b7490107154f38f3 (diff)
Math module
Diffstat (limited to 'library/src/math')
-rw-r--r--library/src/math/accent.rs2
-rw-r--r--library/src/math/frac.rs5
-rw-r--r--library/src/math/lr.rs91
-rw-r--r--library/src/math/matrix.rs14
-rw-r--r--library/src/math/mod.rs77
-rw-r--r--library/src/math/op.rs131
-rw-r--r--library/src/math/root.rs2
-rw-r--r--library/src/math/spacing.rs18
8 files changed, 190 insertions, 150 deletions
diff --git a/library/src/math/accent.rs b/library/src/math/accent.rs
index b8c31c19..a15a6020 100644
--- a/library/src/math/accent.rs
+++ b/library/src/math/accent.rs
@@ -133,7 +133,7 @@ fn attachment(ctx: &MathContext, id: GlyphId, italics_correction: Abs) -> Abs {
/// Extract a single character from content.
fn extract(accent: &Content) -> Option<char> {
- let atom = accent.to::<MathNode>()?.body.to::<AtomNode>()?;
+ let atom = accent.to::<FormulaNode>()?.body.to::<AtomNode>()?;
let mut chars = atom.0.chars();
let c = chars.next().filter(|_| chars.next().is_none())?;
Some(combining(c))
diff --git a/library/src/math/frac.rs b/library/src/math/frac.rs
index 50a68fea..e945473c 100644
--- a/library/src/math/frac.rs
+++ b/library/src/math/frac.rs
@@ -98,6 +98,7 @@ fn layout(
denom: &Content,
binom: bool,
) -> SourceResult<()> {
+ let short_fall = DELIM_SHORT_FALL.scaled(ctx);
let axis = scaled!(ctx, axis_height);
let thickness = scaled!(ctx, fraction_rule_thickness);
let shift_up = scaled!(
@@ -149,9 +150,9 @@ fn layout(
frame.push_frame(denom_pos, denom);
if binom {
- ctx.push(GlyphFragment::new(ctx, '('));
+ ctx.push(GlyphFragment::new(ctx, '(').stretch_vertical(ctx, height, short_fall));
ctx.push(frame);
- ctx.push(GlyphFragment::new(ctx, ')'));
+ ctx.push(GlyphFragment::new(ctx, ')').stretch_vertical(ctx, height, short_fall));
} else {
frame.push(
line_pos,
diff --git a/library/src/math/lr.rs b/library/src/math/lr.rs
index 9cfc6e5f..89d12380 100644
--- a/library/src/math/lr.rs
+++ b/library/src/math/lr.rs
@@ -1,7 +1,7 @@
use super::*;
/// How much less high scaled delimiters can be than what they wrap.
-const DELIM_SHORT_FALL: Em = Em::new(0.1);
+pub(super) const DELIM_SHORT_FALL: Em = Em::new(0.1);
/// # Left-Right
/// Scales delimiters.
@@ -62,7 +62,7 @@ impl LayoutMath for LrNode {
}
let MathFragment::Glyph(glyph) = *fragment else { continue };
- let short_fall = DELIM_SHORT_FALL.at(glyph.font_size);
+ let short_fall = DELIM_SHORT_FALL.scaled(ctx);
*fragment = MathFragment::Variant(
glyph.stretch_vertical(ctx, height, short_fall),
);
@@ -76,3 +76,90 @@ impl LayoutMath for LrNode {
Ok(())
}
}
+
+/// # Floor
+/// Floor an expression.
+///
+/// ## Example
+/// ```
+/// $ floor(x/2) $
+/// ```
+///
+/// ## Parameters
+/// - body: Content (positional, required)
+/// The expression to floor.
+///
+/// ## Category
+/// math
+#[func]
+pub fn floor(args: &mut Args) -> SourceResult<Value> {
+ delimited(args, '⌊', '⌋')
+}
+
+/// # Ceil
+/// Ceil an expression.
+///
+/// ## Example
+/// ```
+/// $ ceil(x/2) $
+/// ```
+///
+/// ## Parameters
+/// - body: Content (positional, required)
+/// The expression to ceil.
+///
+/// ## Category
+/// math
+#[func]
+pub fn ceil(args: &mut Args) -> SourceResult<Value> {
+ delimited(args, '⌈', '⌉')
+}
+
+/// # Abs
+/// Take the absolute value of an expression.
+///
+/// ## Example
+/// ```
+/// $ abs(x/2) $
+/// ```
+///
+/// ## Parameters
+/// - body: Content (positional, required)
+/// The expression to take the absolute value of.
+///
+/// ## Category
+/// math
+#[func]
+pub fn abs(args: &mut Args) -> SourceResult<Value> {
+ delimited(args, '|', '|')
+}
+
+/// # Norm
+/// Take the norm of an expression.
+///
+/// ## Example
+/// ```
+/// $ norm(x/2) $
+/// ```
+///
+/// ## Parameters
+/// - body: Content (positional, required)
+/// The expression to take the norm of.
+///
+/// ## Category
+/// math
+#[func]
+pub fn norm(args: &mut Args) -> SourceResult<Value> {
+ delimited(args, '‖', '‖')
+}
+
+fn delimited(args: &mut Args, left: char, right: char) -> SourceResult<Value> {
+ Ok(Value::Content(
+ LrNode(Content::sequence(vec![
+ AtomNode(left.into()).pack(),
+ args.expect::<Content>("body")?,
+ AtomNode(right.into()).pack(),
+ ]))
+ .pack(),
+ ))
+}
diff --git a/library/src/math/matrix.rs b/library/src/math/matrix.rs
index aa21e9cf..9472e989 100644
--- a/library/src/math/matrix.rs
+++ b/library/src/math/matrix.rs
@@ -161,6 +161,7 @@ fn layout(
) -> SourceResult<()> {
let axis = scaled!(ctx, axis_height);
let gap = ROW_GAP.scaled(ctx);
+ let short_fall = DELIM_SHORT_FALL.scaled(ctx);
ctx.style(ctx.style.for_denominator());
let mut rows = vec![];
@@ -169,17 +170,20 @@ fn layout(
}
ctx.unstyle();
- if let Some(left) = left {
- ctx.push(GlyphFragment::new(ctx, left));
- }
-
let mut frame = stack(ctx, rows, align, gap, 0);
+ let height = frame.height();
frame.set_baseline(frame.height() / 2.0 + axis);
+ if let Some(left) = left {
+ ctx.push(GlyphFragment::new(ctx, left).stretch_vertical(ctx, height, short_fall));
+ }
+
ctx.push(frame);
if let Some(right) = right {
- ctx.push(GlyphFragment::new(ctx, right));
+ ctx.push(
+ GlyphFragment::new(ctx, right).stretch_vertical(ctx, height, short_fall),
+ );
}
Ok(())
diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs
index 44b52e96..0de440be 100644
--- a/library/src/math/mod.rs
+++ b/library/src/math/mod.rs
@@ -33,7 +33,7 @@ pub use self::style::*;
use ttf_parser::GlyphId;
use ttf_parser::Rect;
use typst::font::Font;
-use typst::model::{Guard, Scope, SequenceNode};
+use typst::model::{Guard, Module, Scope, SequenceNode};
use unicode_math_class::MathClass;
use self::ctx::*;
@@ -48,38 +48,38 @@ use crate::text::TextNode;
use crate::text::TextSize;
use crate::text::{families, variant, FallbackList, FontFamily, SpaceNode, SymbolNode};
-/// Hook up all math definitions.
-pub fn define(scope: &mut Scope) {
- scope.def_func::<MathNode>("math");
- scope.def_func::<LrNode>("lr");
- scope.def_func::<AccentNode>("accent");
- scope.def_func::<FracNode>("frac");
- scope.def_func::<BinomNode>("binom");
- scope.def_func::<ScriptNode>("script");
- scope.def_func::<SqrtNode>("sqrt");
- scope.def_func::<RootNode>("root");
- scope.def_func::<FloorNode>("floor");
- scope.def_func::<CeilNode>("ceil");
- scope.def_func::<VecNode>("vec");
- scope.def_func::<CasesNode>("cases");
- scope.def_func::<UnderbraceNode>("underbrace");
- scope.def_func::<OverbraceNode>("overbrace");
- scope.def_func::<BoldNode>("bold");
- scope.def_func::<ItalicNode>("italic");
- scope.def_func::<SerifNode>("serif");
- scope.def_func::<SansNode>("sans");
- scope.def_func::<CalNode>("cal");
- scope.def_func::<FrakNode>("frak");
- scope.def_func::<MonoNode>("mono");
- scope.def_func::<BbNode>("bb");
- scope.define("thin", HNode::strong(THIN).pack());
- scope.define("med", HNode::strong(MEDIUM).pack());
- scope.define("thick", HNode::strong(THICK).pack());
- scope.define("quad", HNode::strong(QUAD).pack());
- define_operators(scope);
+/// Create a module with all math definitions.
+pub fn module() -> Module {
+ let mut math = Scope::deduplicating();
+ math.def_func::<FormulaNode>("formula");
+ math.def_func::<LrNode>("lr");
+ math.def_func::<FloorFunc>("floor");
+ math.def_func::<CeilFunc>("ceil");
+ math.def_func::<AbsFunc>("abs");
+ math.def_func::<AccentNode>("accent");
+ math.def_func::<FracNode>("frac");
+ math.def_func::<BinomNode>("binom");
+ math.def_func::<ScriptNode>("script");
+ math.def_func::<SqrtNode>("sqrt");
+ math.def_func::<RootNode>("root");
+ math.def_func::<VecNode>("vec");
+ math.def_func::<CasesNode>("cases");
+ math.def_func::<UnderbraceNode>("underbrace");
+ math.def_func::<OverbraceNode>("overbrace");
+ math.def_func::<BoldNode>("bold");
+ math.def_func::<ItalicNode>("italic");
+ math.def_func::<SerifNode>("serif");
+ math.def_func::<SansNode>("sans");
+ math.def_func::<CalNode>("cal");
+ math.def_func::<FrakNode>("frak");
+ math.def_func::<MonoNode>("mono");
+ math.def_func::<BbNode>("bb");
+ define_spacings(&mut math);
+ define_operators(&mut math);
+ Module::new("math").with_scope(math)
}
-/// # Math
+/// # Formula
/// A mathematical formula.
///
/// ## Syntax
@@ -131,7 +131,7 @@ pub fn define(scope: &mut Scope) {
#[func]
#[capable(Show, Finalize, Layout, Inline, LayoutMath)]
#[derive(Debug, Clone, Hash)]
-pub struct MathNode {
+pub struct FormulaNode {
/// Whether the formula is displayed as a separate block.
pub block: bool,
/// The content of the formula.
@@ -139,7 +139,7 @@ pub struct MathNode {
}
#[node]
-impl MathNode {
+impl FormulaNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
let body = args.expect("body")?;
let block = args.named("block")?.unwrap_or(false);
@@ -148,13 +148,14 @@ impl MathNode {
fn field(&self, name: &str) -> Option<Value> {
match name {
+ "body" => Some(Value::Content(self.body.clone())),
"block" => Some(Value::Bool(self.block)),
_ => None,
}
}
}
-impl Show for MathNode {
+impl Show for FormulaNode {
fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult<Content> {
let mut realized = self.clone().pack().guarded(Guard::Base(NodeId::of::<Self>()));
if self.block {
@@ -164,7 +165,7 @@ impl Show for MathNode {
}
}
-impl Finalize for MathNode {
+impl Finalize for FormulaNode {
fn finalize(&self, realized: Content) -> Content {
realized.styled(
TextNode::FAMILY,
@@ -173,7 +174,7 @@ impl Finalize for MathNode {
}
}
-impl Layout for MathNode {
+impl Layout for FormulaNode {
fn layout(
&self,
vt: &mut Vt,
@@ -200,14 +201,14 @@ impl Layout for MathNode {
}
}
-impl Inline for MathNode {}
+impl Inline for FormulaNode {}
#[capability]
trait LayoutMath {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()>;
}
-impl LayoutMath for MathNode {
+impl LayoutMath for FormulaNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
self.body.layout_math(ctx)
}
diff --git a/library/src/math/op.rs b/library/src/math/op.rs
index aef0d41b..a7d29166 100644
--- a/library/src/math/op.rs
+++ b/library/src/math/op.rs
@@ -25,12 +25,6 @@ pub struct OpNode {
pub limits: bool,
}
-impl OpNode {
- fn new(text: impl Into<EcoString>, limits: bool) -> Self {
- Self { text: text.into(), limits }
- }
-}
-
#[node]
impl OpNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
@@ -55,96 +49,41 @@ impl LayoutMath for OpNode {
}
/// 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<Content> {
- Ok(Self(args.expect("body")?).pack())
- }
+pub(super) fn define_operators(math: &mut Scope) {
+ math.define("arccos", op("arccos", false));
+ math.define("arcsin", op("arcsin", false));
+ math.define("arctan", op("arctan", false));
+ math.define("arg", op("arg", false));
+ math.define("cos", op("cos", false));
+ math.define("cosh", op("cosh", false));
+ math.define("cot", op("cot", false));
+ math.define("coth", op("coth", false));
+ math.define("csc", op("csc", false));
+ math.define("deg", op("deg", false));
+ math.define("det", op("det", true));
+ math.define("dim", op("dim", false));
+ math.define("exp", op("exp", false));
+ math.define("gcd", op("gcd", true));
+ math.define("hom", op("hom", false));
+ math.define("inf", op("inf", true));
+ math.define("ker", op("ker", false));
+ math.define("lg", op("lg", false));
+ math.define("lim", op("lim", true));
+ math.define("ln", op("ln", false));
+ math.define("log", op("log", false));
+ math.define("max", op("max", true));
+ math.define("min", op("min", true));
+ math.define("Pr", op("Pr", true));
+ math.define("sec", op("sec", false));
+ math.define("sin", op("sin", false));
+ math.define("sinh", op("sinh", false));
+ math.define("sup", op("sup", true));
+ math.define("tan", op("tan", false));
+ math.define("tanh", op("tanh", false));
+ math.define("liminf", op("lim inf", true));
+ math.define("limsup", op("lim sup", true));
}
-/// # 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<Content> {
- Ok(Self(args.expect("body")?).pack())
- }
+fn op(name: &str, limits: bool) -> Content {
+ OpNode { text: name.into(), limits }.pack()
}
diff --git a/library/src/math/root.rs b/library/src/math/root.rs
index f5c5b2b8..2efe4e07 100644
--- a/library/src/math/root.rs
+++ b/library/src/math/root.rs
@@ -155,7 +155,7 @@ fn layout(
/// Select a precomposed radical, if the font has it.
fn precomposed(ctx: &MathContext, index: Option<&Content>, target: Abs) -> Option<Frame> {
- let node = index?.to::<MathNode>()?.body.to::<AtomNode>()?;
+ let node = index?.to::<FormulaNode>()?.body.to::<AtomNode>()?;
let c = match node.0.as_str() {
"3" => '∛',
"4" => '∜',
diff --git a/library/src/math/spacing.rs b/library/src/math/spacing.rs
index 0f613309..d5a7603d 100644
--- a/library/src/math/spacing.rs
+++ b/library/src/math/spacing.rs
@@ -1,10 +1,18 @@
use super::*;
-pub(super) const ZERO: Em = Em::zero();
-pub(super) const THIN: Em = Em::new(1.0 / 6.0);
-pub(super) const MEDIUM: Em = Em::new(2.0 / 9.0);
-pub(super) const THICK: Em = Em::new(5.0 / 18.0);
-pub(super) const QUAD: Em = Em::new(1.0);
+const ZERO: Em = Em::zero();
+const THIN: Em = Em::new(1.0 / 6.0);
+const MEDIUM: Em = Em::new(2.0 / 9.0);
+const THICK: Em = Em::new(5.0 / 18.0);
+const QUAD: Em = Em::new(1.0);
+
+/// Hook up all spacings.
+pub(super) fn define_spacings(math: &mut Scope) {
+ math.define("thin", HNode::strong(THIN).pack());
+ math.define("med", HNode::strong(MEDIUM).pack());
+ math.define("thick", HNode::strong(THICK).pack());
+ math.define("quad", HNode::strong(QUAD).pack());
+}
/// Determine the spacing between two fragments in a given style.
pub(super) fn spacing(left: MathClass, right: MathClass, style: MathStyle) -> Em {