diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-01-23 15:03:10 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-01-23 15:23:52 +0100 |
| commit | 4653ffebb43d733a3cff873d0903c7d00aaeb499 (patch) | |
| tree | 6a97b2e6a6903b3198547d6f3d0a7e3d2eb023cd /library/src/math | |
| parent | 84c6c8b0e6b17996a603ec88b7490107154f38f3 (diff) | |
Math module
Diffstat (limited to 'library/src/math')
| -rw-r--r-- | library/src/math/accent.rs | 2 | ||||
| -rw-r--r-- | library/src/math/frac.rs | 5 | ||||
| -rw-r--r-- | library/src/math/lr.rs | 91 | ||||
| -rw-r--r-- | library/src/math/matrix.rs | 14 | ||||
| -rw-r--r-- | library/src/math/mod.rs | 77 | ||||
| -rw-r--r-- | library/src/math/op.rs | 131 | ||||
| -rw-r--r-- | library/src/math/root.rs | 2 | ||||
| -rw-r--r-- | library/src/math/spacing.rs | 18 |
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 { |
