From 64efd76b76a1fe4ca14e652a80337f33eb22e066 Mon Sep 17 00:00:00 2001 From: mkorje Date: Fri, 25 Apr 2025 22:14:57 +1000 Subject: [PATCH 1/3] Allow a function as an argument to `size` in `stretch` and `lr` Previously there was always a short fall when scaling delimiters, even if the user requested a specific size. This is no longer the case; the short fall is only present in the default for `lr` (`x => x - 0.1em`), and is the reason for the updated tests - the size of the delimiters is now actually what was specified in the size argument. This also makes the default for `lr` much clearer to the user. A slight hack was used by exploiting the `name` property in the `func` attribute macro so that the default value in the docs for `lr.size` would clearly show what the default function was (instead of just its name `default_lr_size` which is meaningless and inaccessible to the user). --- crates/typst-layout/src/math/attach.rs | 18 +-- crates/typst-layout/src/math/fragment.rs | 2 +- crates/typst-layout/src/math/lr.rs | 32 ++--- crates/typst-layout/src/math/stretch.rs | 55 ++++---- crates/typst-library/src/math/attach.rs | 29 ---- crates/typst-library/src/math/lr.rs | 69 +++++++--- crates/typst-library/src/math/mod.rs | 2 + crates/typst-library/src/math/stretch.rs | 125 ++++++++++++++++++ .../math-attach-scripts-extended-shapes.png | Bin 1057 -> 1035 bytes .../ref/math-lr-mid-size-nested-equation.png | Bin 900 -> 906 bytes tests/ref/math-lr-mid-size.png | Bin 2210 -> 2223 bytes tests/ref/math-lr-size-function.png | Bin 0 -> 603 bytes tests/ref/math-lr-size.png | Bin 663 -> 685 bytes tests/ref/math-stretch-function.png | Bin 0 -> 291 bytes tests/suite/math/delimited.typ | 5 + tests/suite/math/stretch.typ | 6 + 16 files changed, 232 insertions(+), 111 deletions(-) create mode 100644 crates/typst-library/src/math/stretch.rs create mode 100644 tests/ref/math-lr-size-function.png create mode 100644 tests/ref/math-stretch-function.png diff --git a/crates/typst-layout/src/math/attach.rs b/crates/typst-layout/src/math/attach.rs index 90aad941e..5d923a58d 100644 --- a/crates/typst-layout/src/math/attach.rs +++ b/crates/typst-layout/src/math/attach.rs @@ -1,8 +1,9 @@ use typst_library::diag::SourceResult; use typst_library::foundations::{Packed, StyleChain, SymbolElem}; -use typst_library::layout::{Abs, Axis, Corner, Frame, Point, Rel, Size}; +use typst_library::layout::{Abs, Axis, Corner, Frame, Point, Size}; use typst_library::math::{ AttachElem, EquationElem, LimitsElem, PrimesElem, ScriptsElem, StretchElem, + StretchSize, }; use typst_utils::OptionExt; @@ -63,16 +64,9 @@ pub fn layout_attach( let t = layout!(t, sup_style_chain)?; let b = layout!(b, sub_style_chain)?; if let Some(stretch) = stretch { - let relative_to_width = measure!(t, width).max(measure!(b, width)); - stretch_fragment( - ctx, - styles, - &mut base, - Some(Axis::X), - Some(relative_to_width), - stretch, - Abs::zero(), - ); + let relative_to = measure!(t, width).max(measure!(b, width)); + let width = stretch.resolve(ctx.engine, styles, relative_to)?; + stretch_fragment(ctx, styles, &mut base, Axis::X, width); } let fragments = [ @@ -155,7 +149,7 @@ pub fn layout_limits( } /// Get the size to stretch the base to. -fn stretch_size(styles: StyleChain, elem: &Packed) -> Option> { +fn stretch_size(styles: StyleChain, elem: &Packed) -> Option { // Extract from an EquationElem. let mut base = &elem.base; while let Some(equation) = base.to_packed::() { diff --git a/crates/typst-layout/src/math/fragment.rs b/crates/typst-layout/src/math/fragment.rs index 01fa6be4b..e15fe0d07 100644 --- a/crates/typst-layout/src/math/fragment.rs +++ b/crates/typst-layout/src/math/fragment.rs @@ -307,7 +307,7 @@ impl GlyphFragment { } /// Apply GSUB substitutions. - fn adjust_glyph_index(ctx: &MathContext, id: GlyphId) -> GlyphId { + pub fn adjust_glyph_index(ctx: &MathContext, id: GlyphId) -> GlyphId { if let Some(glyphwise_tables) = &ctx.glyphwise_tables { glyphwise_tables.iter().fold(id, |id, table| table.apply(id)) } else { diff --git a/crates/typst-layout/src/math/lr.rs b/crates/typst-layout/src/math/lr.rs index bf8235411..e2e0eb8ba 100644 --- a/crates/typst-layout/src/math/lr.rs +++ b/crates/typst-layout/src/math/lr.rs @@ -1,11 +1,11 @@ use typst_library::diag::SourceResult; use typst_library::foundations::{Packed, StyleChain}; -use typst_library::layout::{Abs, Axis, Rel}; +use typst_library::layout::{Abs, Axis}; use typst_library::math::{EquationElem, LrElem, MidElem}; use typst_utils::SliceExt; use unicode_math_class::MathClass; -use super::{stretch_fragment, MathContext, MathFragment, DELIM_SHORT_FALL}; +use super::{stretch_fragment, MathContext, MathFragment}; /// Lays out an [`LrElem`]. #[typst_macros::time(name = "math.lr", span = elem.span())] @@ -22,7 +22,7 @@ pub fn layout_lr( // Extract implicit LrElem. if let Some(lr) = body.to_packed::() { - if lr.size(styles).is_one() { + if lr.size(styles).is_lr_default() { body = &lr.body; } } @@ -41,14 +41,14 @@ pub fn layout_lr( .unwrap_or_default(); let relative_to = 2.0 * max_extent; - let height = elem.size(styles); + let height = elem.size(styles).resolve(ctx.engine, styles, relative_to)?; // Scale up fragments at both ends. match inner_fragments { - [one] => scale(ctx, styles, one, relative_to, height, None), + [one] => scale(ctx, styles, one, height, None), [first, .., last] => { - scale(ctx, styles, first, relative_to, height, Some(MathClass::Opening)); - scale(ctx, styles, last, relative_to, height, Some(MathClass::Closing)); + scale(ctx, styles, first, height, Some(MathClass::Opening)); + scale(ctx, styles, last, height, Some(MathClass::Closing)); } _ => {} } @@ -58,7 +58,7 @@ pub fn layout_lr( if let MathFragment::Variant(ref mut variant) = fragment { if variant.mid_stretched == Some(false) { variant.mid_stretched = Some(true); - scale(ctx, styles, fragment, relative_to, height, Some(MathClass::Large)); + scale(ctx, styles, fragment, height, Some(MathClass::Large)); } } } @@ -119,26 +119,14 @@ fn scale( ctx: &mut MathContext, styles: StyleChain, fragment: &mut MathFragment, - relative_to: Abs, - height: Rel, + height: Abs, apply: Option, ) { if matches!( fragment.class(), MathClass::Opening | MathClass::Closing | MathClass::Fence ) { - // This unwrap doesn't really matter. If it is None, then the fragment - // won't be stretchable anyways. - let short_fall = DELIM_SHORT_FALL.at(fragment.font_size().unwrap_or_default()); - stretch_fragment( - ctx, - styles, - fragment, - Some(Axis::Y), - Some(relative_to), - height, - short_fall, - ); + stretch_fragment(ctx, styles, fragment, Axis::Y, height); if let Some(class) = apply { fragment.set_class(class); diff --git a/crates/typst-layout/src/math/stretch.rs b/crates/typst-layout/src/math/stretch.rs index 40f76da59..c8725fe02 100644 --- a/crates/typst-layout/src/math/stretch.rs +++ b/crates/typst-layout/src/math/stretch.rs @@ -2,7 +2,7 @@ use ttf_parser::math::{GlyphAssembly, GlyphConstruction, GlyphPart}; use ttf_parser::LazyArray16; use typst_library::diag::{warning, SourceResult}; use typst_library::foundations::{Packed, StyleChain}; -use typst_library::layout::{Abs, Axis, Frame, Point, Rel, Size}; +use typst_library::layout::{Abs, Axis, Frame, Point, Size}; use typst_library::math::StretchElem; use typst_utils::Get; @@ -23,15 +23,13 @@ pub fn layout_stretch( styles: StyleChain, ) -> SourceResult<()> { let mut fragment = ctx.layout_into_fragment(&elem.body, styles)?; - stretch_fragment( - ctx, - styles, - &mut fragment, - None, - None, - elem.size(styles), - Abs::zero(), - ); + + if let Some(axis) = stretch_axis(ctx, &fragment) { + let relative_to = fragment.size().get(axis); + let target = elem.size(styles).resolve(ctx.engine, styles, relative_to)?; + stretch_fragment(ctx, styles, &mut fragment, axis, target); + } + ctx.push(fragment); Ok(()) } @@ -41,10 +39,8 @@ pub fn stretch_fragment( ctx: &mut MathContext, styles: StyleChain, fragment: &mut MathFragment, - axis: Option, - relative_to: Option, - stretch: Rel, - short_fall: Abs, + axis: Axis, + target: Abs, ) { let glyph = match fragment { MathFragment::Glyph(glyph) => glyph.clone(), @@ -56,20 +52,12 @@ pub fn stretch_fragment( // Return if we attempt to stretch along an axis which isn't stretchable, // so that the original fragment isn't modified. - let Some(stretch_axis) = stretch_axis(ctx, &glyph) else { return }; - let axis = axis.unwrap_or(stretch_axis); + let Some(stretch_axis) = stretch_axis(ctx, fragment) else { return }; if axis != stretch_axis { return; } - let relative_to_size = relative_to.unwrap_or_else(|| fragment.size().get(axis)); - - let mut variant = stretch_glyph( - ctx, - glyph, - stretch.relative_to(relative_to_size) - short_fall, - axis, - ); + let mut variant = stretch_glyph(ctx, glyph, target, axis); if axis == Axis::Y { variant.align_on_axis(ctx, delimiter_alignment(variant.c)); @@ -80,17 +68,26 @@ pub fn stretch_fragment( /// Return whether the glyph is stretchable and if it is, along which axis it /// can be stretched. -fn stretch_axis(ctx: &mut MathContext, base: &GlyphFragment) -> Option { - let base_id = base.id; +fn stretch_axis(ctx: &mut MathContext, fragment: &MathFragment) -> Option { + let (id, span) = match fragment { + MathFragment::Glyph(glyph) => (glyph.id, glyph.span), + MathFragment::Variant(variant) => { + let id = ctx.ttf.glyph_index(variant.c).unwrap_or_default(); + let id = GlyphFragment::adjust_glyph_index(ctx, id); + (id, variant.span) + } + _ => return None, + }; + let vertical = ctx .table .variants - .and_then(|variants| variants.vertical_constructions.get(base_id)) + .and_then(|variants| variants.vertical_constructions.get(id)) .map(|_| Axis::Y); let horizontal = ctx .table .variants - .and_then(|variants| variants.horizontal_constructions.get(base_id)) + .and_then(|variants| variants.horizontal_constructions.get(id)) .map(|_| Axis::X); match (vertical, horizontal) { @@ -101,7 +98,7 @@ fn stretch_axis(ctx: &mut MathContext, base: &GlyphFragment) -> Option { // vertical and horizontal constructions. So for the time being, we // will assume that a glyph cannot have both. ctx.engine.sink.warn(warning!( - base.span, + span, "glyph has both vertical and horizontal constructions"; hint: "this is probably a font bug"; hint: "please file an issue at https://github.com/typst/typst/issues" diff --git a/crates/typst-library/src/math/attach.rs b/crates/typst-library/src/math/attach.rs index d526aba57..c367b6f4f 100644 --- a/crates/typst-library/src/math/attach.rs +++ b/crates/typst-library/src/math/attach.rs @@ -1,5 +1,4 @@ use crate::foundations::{elem, Content, Packed}; -use crate::layout::{Length, Rel}; use crate::math::{EquationElem, Mathy}; /// A base with optional attachments. @@ -128,31 +127,3 @@ pub struct LimitsElem { #[default(true)] pub inline: bool, } - -/// Stretches a glyph. -/// -/// This function can also be used to automatically stretch the base of an -/// attachment, so that it fits the top and bottom attachments. -/// -/// Note that only some glyphs can be stretched, and which ones can depend on -/// the math font being used. However, most math fonts are the same in this -/// regard. -/// -/// ```example -/// $ H stretch(=)^"define" U + p V $ -/// $ f : X stretch(->>, size: #150%)_"surjective" Y $ -/// $ x stretch(harpoons.ltrb, size: #3em) y -/// stretch(\[, size: #150%) z $ -/// ``` -#[elem(Mathy)] -pub struct StretchElem { - /// The glyph to stretch. - #[required] - pub body: Content, - - /// The size to stretch to, relative to the maximum size of the glyph and - /// its attachments. - #[resolve] - #[default(Rel::one())] - pub size: Rel, -} diff --git a/crates/typst-library/src/math/lr.rs b/crates/typst-library/src/math/lr.rs index 7558717af..6da348832 100644 --- a/crates/typst-library/src/math/lr.rs +++ b/crates/typst-library/src/math/lr.rs @@ -1,6 +1,16 @@ -use crate::foundations::{elem, func, Content, NativeElement, SymbolElem}; -use crate::layout::{Length, Rel}; -use crate::math::Mathy; +use crate::foundations::{elem, func, Content, NativeElement, NativeFunc, SymbolElem}; +use crate::layout::{Em, Length, Ratio, Rel}; +use crate::math::{Mathy, StretchSize}; + +const DELIM_SHORT_FALL: Em = Em::new(-0.1); + +#[func(name = "x => x - 0.1em")] +pub const fn default_lr_size(base: Length) -> Rel { + Rel { + rel: Ratio::zero(), + abs: Length { abs: base.abs, em: DELIM_SHORT_FALL }, + } +} /// Scales delimiters. /// @@ -8,10 +18,13 @@ use crate::math::Mathy; /// unmatched delimiters and to control the delimiter scaling more precisely. #[elem(title = "Left/Right", Mathy)] pub struct LrElem { - /// The size of the brackets, relative to the height of the wrapped content. - #[resolve] - #[default(Rel::one())] - pub size: Rel, + /// The size of the delimiters, relative to the height of the wrapped + /// content. + /// + /// See the [stretch documentation]($math.stretch.size) for more + /// information on sizes. + #[default(::data().into())] + pub size: StretchSize, /// The delimited content, including the delimiters. #[required] @@ -43,9 +56,13 @@ pub struct MidElem { /// ``` #[func] pub fn floor( - /// The size of the brackets, relative to the height of the wrapped content. + /// The size of the delimiters, relative to the height of the wrapped + /// content. + /// + /// See the [stretch documentation]($math.stretch.size) for more + /// information on sizes. #[named] - size: Option>, + size: Option, /// The expression to floor. body: Content, ) -> Content { @@ -59,9 +76,13 @@ pub fn floor( /// ``` #[func] pub fn ceil( - /// The size of the brackets, relative to the height of the wrapped content. + /// The size of the delimiters, relative to the height of the wrapped + /// content. + /// + /// See the [stretch documentation]($math.stretch.size) for more + /// information on sizes. #[named] - size: Option>, + size: Option, /// The expression to ceil. body: Content, ) -> Content { @@ -75,9 +96,13 @@ pub fn ceil( /// ``` #[func] pub fn round( - /// The size of the brackets, relative to the height of the wrapped content. + /// The size of the delimiters, relative to the height of the wrapped + /// content. + /// + /// See the [stretch documentation]($math.stretch.size) for more + /// information on sizes. #[named] - size: Option>, + size: Option, /// The expression to round. body: Content, ) -> Content { @@ -91,9 +116,13 @@ pub fn round( /// ``` #[func] pub fn abs( - /// The size of the brackets, relative to the height of the wrapped content. + /// The size of the delimiters, relative to the height of the wrapped + /// content. + /// + /// See the [stretch documentation]($math.stretch.size) for more + /// information on sizes. #[named] - size: Option>, + size: Option, /// The expression to take the absolute value of. body: Content, ) -> Content { @@ -107,9 +136,13 @@ pub fn abs( /// ``` #[func] pub fn norm( - /// The size of the brackets, relative to the height of the wrapped content. + /// The size of the delimiters, relative to the height of the wrapped + /// content. + /// + /// See the [stretch documentation]($math.stretch.size) for more + /// information on sizes. #[named] - size: Option>, + size: Option, /// The expression to take the norm of. body: Content, ) -> Content { @@ -120,7 +153,7 @@ fn delimited( body: Content, left: char, right: char, - size: Option>, + size: Option, ) -> Content { let span = body.span(); let mut elem = LrElem::new(Content::sequence([ diff --git a/crates/typst-library/src/math/mod.rs b/crates/typst-library/src/math/mod.rs index 2e6d42b13..4f4bbb03b 100644 --- a/crates/typst-library/src/math/mod.rs +++ b/crates/typst-library/src/math/mod.rs @@ -9,6 +9,7 @@ mod lr; mod matrix; mod op; mod root; +mod stretch; mod style; mod underover; @@ -21,6 +22,7 @@ pub use self::lr::*; pub use self::matrix::*; pub use self::op::*; pub use self::root::*; +pub use self::stretch::*; pub use self::style::*; pub use self::underover::*; diff --git a/crates/typst-library/src/math/stretch.rs b/crates/typst-library/src/math/stretch.rs new file mode 100644 index 000000000..899234fa9 --- /dev/null +++ b/crates/typst-library/src/math/stretch.rs @@ -0,0 +1,125 @@ +use comemo::Track; + +use crate::diag::{At, SourceResult}; +use crate::engine::Engine; +use crate::foundations::{ + cast, elem, Content, Context, Func, NativeFunc, NativeFuncData, Resolve, StyleChain, +}; +use crate::layout::{Abs, Rel}; +use crate::math::{default_lr_size, Mathy}; + +/// Stretches a glyph. +/// +/// This function can also be used to automatically stretch the base of an +/// attachment, so that it fits the top and bottom attachments. +/// +/// Note that only some glyphs can be stretched, and which ones can depend on +/// the math font being used. However, most math fonts are the same in this +/// regard. +/// +/// ```example +/// $ H stretch(=)^"define" U + p V $ +/// $ f : X stretch(->>, size: #150%)_"surjective" Y $ +/// $ x stretch(harpoons.ltrb, size: #3em) y +/// stretch(\[, size: #150%) z $ +/// ``` +#[elem(Mathy)] +pub struct StretchElem { + /// The glyph to stretch. + #[required] + pub body: Content, + + /// The size to stretch to, relative to the maximum size of the glyph and + /// its attachments. + /// + /// This value can be given as a [relative length]($relative), or a + /// [function]($function) that receives the size of the glyph as a + /// parameter (an absolute length) and should return a (relative) length. + /// For example, `{x => x * 80%}` would be equivalent to just specifying `{80%}`. + /// + /// Note that the sizes of glyphs in math fonts come about in two ways: + /// + /// - First, there are pre-made variants at specific sizes. This means you + /// will see discrete jumps in the stretched glyph's size as you increase + /// the size parameter. It is up to the font how many pre-made variants + /// there are and what their sizes are. + /// + /// - Then, if the pre-made variants are all too small, a glyph of the + /// desired size is assembled from parts. The stretched glyph's size will + /// now be the exact size requested. + /// + /// It could be the case that only one of the above exist for a glyph in + /// the font. + /// + /// The value given here is really a minimum (but if there is no assembly + /// for the glyph, this minimum may not be reached), so the actual size of + /// the stretched glyph may not match what you specified. + /// + /// ```example + /// #for i in range(0, 15) { + /// $stretch(\[, size: #(10pt + i * 2pt))$ + /// } + /// + /// #set math.stretch(size: x => x + 0.5em) + /// $x stretch(=)^"def" y$ + /// ``` + #[default(Rel::one().into())] + pub size: StretchSize, +} + +/// How to size a stretched glyph. +#[derive(Debug, Clone, PartialEq, Hash)] +pub enum StretchSize { + /// Sized by the specified length. + Rel(Rel), + /// Resolve the size for the given base size through the specified + /// function. + Func(Func), +} + +impl StretchSize { + /// Resolve the stretch size given the base size. + pub fn resolve( + &self, + engine: &mut Engine, + styles: StyleChain, + base: Abs, + ) -> SourceResult { + Ok(match self { + Self::Rel(rel) => *rel, + Self::Func(func) => func + .call(engine, Context::new(None, Some(styles)).track(), [base])? + .cast() + .at(func.span())?, + } + .resolve(styles) + .relative_to(base)) + } + + /// Whether the size is the default used by `LrElem`. + pub fn is_lr_default(self) -> bool { + self == ::data().into() + } +} + +impl From for StretchSize { + fn from(rel: Rel) -> Self { + Self::Rel(rel) + } +} + +impl From<&'static NativeFuncData> for StretchSize { + fn from(data: &'static NativeFuncData) -> Self { + Self::Func(Func::from(data)) + } +} + +cast! { + StretchSize, + self => match self { + Self::Rel(v) => v.into_value(), + Self::Func(v) => v.into_value(), + }, + v: Rel => Self::Rel(v), + v: Func => Self::Func(v), +} diff --git a/tests/ref/math-attach-scripts-extended-shapes.png b/tests/ref/math-attach-scripts-extended-shapes.png index c99d3c7fdbf1ece5e07dc52ebaf6ae8359dcfd37..16bad11579963f26d2b27205a01275d7dc79dae0 100644 GIT binary patch delta 1026 zcmV+d1pWJ=2#W}i7k?}W00000ix4C4000BhNkll5~K;Du|O)X-R}3?c}hIK{Oes?%iaRlN*N? z)>77S=z)F>?=$Etg`{sy%7uI`nkR35$v3~`eVVj~*e8b$!hiqcu%mvT!V9P47jD-F z0C-e22_(laoH~ZgYcj+)oB_gs0j7`tfo#=|%>5K+VJEWLMMh71QRqDbO7qw5#~Ajy zo+Ill2>>8{1;4ffh?VEW8n6?p?);mQ`b&Z!0{}ZbI-M!%MH_ zK0@QnQez@sV_iT+c&aq}R?lQOoaWyQPnKuacTb}wXMc6z7Bxe1I1SwdIZJTKteG{x zyhbYST%hYZbQ}p}`TGni!ojxbwjsh{y<;;RZ2h}+7_^+ieid6A;#_N?eHiWy_H{D+ z9SH=#ffuIR1dS%?6jA}PU#FgbU#IhasRH|qd`_nFAyyEc<#A4oBb*IB_$juq(>wwm zpg$u3fPXa|TVt6E4^XGW*i{yi&qDbfBP_FRpt;l&TlfmuYw`N7{_K0P+fb^3P!few zyu$7UWIo6GW;kz#|LA2D;Rty%g{bsbB}&2OG&SMrMFjgg5hCZ5){;SK`Lc@e9VxdJ z!Rusqjuo&-Ic)$87QEsBMYz89rn>NiulYv=J%2=6DD#0H#(jor035H|0Z)HYkutpT z2~X99d7d9c)-r*DG>Xjpwi}+bD&{)g@&i!C(7V5bU{3;?D#xM;uUghSp0N03rwLgK zxO=>}pCPmMAslxDDT+BRUL=A!J^Jg^ke9W5lELcg31o@z%b6Q%CwB`cZq7|2_z(Za zPJdO!(T80L^BeCIrUBQnz|sj|uAm@UBfma~um+{E!|ZgWHu^9}>CPQR5F4+2NKMV? zt{W>g+-0XPEYXLj&eQa@6@Y>Yv6b~(ighC>!#}@KIJ>=Zeigt2`I8X1fH&6xPDs&* z3Bn4p(4T-|nowx_k?k>I<0i$NGaFeh`g?&Q7zh9y^&y$3q6#ZLasuet%T2{x;Pc73 zx!K{CcZ#{dWjEcx93Qh%#3j{X!Q*6i^DhW{oc+q2`z)C=0G#`whd~_)@%nvyfpmlo wu$7>RS6J#EAuMaWJTBo#_JjXG4#NB5AFBkl%0aBLIRF3v07*qoM6N<$g5$X7;Q#;t delta 1049 zcmV+!1m^pT2%!j&7k?@U00000T}i<5000B%Nkl2JqPVzZ7IXTTa#6ETC0DnG!_Z(i+*6t6?NYUQG znYR#Cr#=Us(IIE*MUm&8jgY3BRlw)v2PJ%^3xFq?|1fcOla2Aa&07`M&%+wbq%ns4QSWnZM?{2w)zzqqd>rgtU5A1#E6fHxg`} zXSXKJ8v?-A8EhlueD1!}t`6>4Z@Pw5l^b?v41cg*{dAtLK>B^Ssi&u_@%tmCA+W~P zp$=y2n-0tg*6i$r#O!RF3@4(jZ6Z@F-EY^pvoCm#Wx z3Z7T_refZOIRJawIldy*&3yo(@#T86RKwCIjN5s3^An1Bf4`^EXyV^FdURyv3(h?j z?~>YXV*Y_o_vq5*KTV1cfCV%3{Yd;C!PK=kAm>Zr!v0)S9WCyIi}?Y(AN>!ldlJf6 T;%)5K00000NkvXXu0mjf`#=RV diff --git a/tests/ref/math-lr-mid-size-nested-equation.png b/tests/ref/math-lr-mid-size-nested-equation.png index df0106689f081bd938addaf1acf938687152625f..5fd22ae34a132c23614bfe8dc5cfc0e2adb8df9c 100644 GIT binary patch delta 883 zcmV-(1C0EH2Z{%fB!5v!L_t(|+U?g}NK;_|#_?_{y67SZiLL|<5(2FtEXyph$j?f{ zh$zDn6_ZRwtjx*~BelT7a%uTRkYy@tW|$UY7KoOi)BH$nO~pt}O}FWJg7>VQckZ2g zCqZ4D|HYo&jPv7s!It5FY?-W*RkBJ}$*RAo>TicsDd@DV|9?uAmNWyY+b~uA3PNVA z9(=u4g1^znn#EO$7=Wp2yB=ZyauUFmCN5$G+=W%C70J3TwPPbR`dAN zw_saWaL*enDi;9&hS!5D_c7S2cpAnOnM+mmc*73W{14Rz8V2$C95E2kW|vW+7rT| zN~|?gIe%oUVw%b5+yOuVW1i&r%CrTlYLK+PW~$=DWO{?Gt9CYnq;@Z)Roaz$vJ>lI zp8q_VLI)kC(yh{xABD43Sx#oEGgob?rnW;Y_vQ^+^?%$AVh4Lzs)6dQ1C;8$16Znr zS~87oYt2-ym1G73t*PdsRUjz`OU}`%)G&yBI64Koz2q|&{_iMNo)4L6KhRC>(vf2* z;5<&LbSwHn^3hmP`6)q~KARsON~?ly!B~fz0j7kLB>+%=CEv4xQbi|%lob!D@y%+o zMcMfq)PIz!Wevne-MZN-T|_O}2YPgP(kjP#h_#1~S)40fK9G`2sdki-`E>wv-@Oa~ zhRcB?^^~f>3u3-~V5>0U0l<*FAIM(!R#N>~J_)gqPK*1hbDsi3K>;Hsm9V;-d0(Bc z0~)t9K8X0YbtoOLQDW8RlO_3et)u`L6S+;`}Ss!LIJUr7Ggn1J*pvJ@KiWWCO`u^w#xS< zLu{39RylYxBdyuqQm8)u=4`w}L=*u7ZTD7__o7QA&`EUD&E;lgG80#)l% zjmaD_0~@K!KZ84=Oa(3#y4oOB2s%xZ{NGtt$tqbTt7O%Z`wiS1Nm==3uyOzZ002ov JPDHLkV1i{fqX_^2 delta 877 zcmV-z1CsoT2ZRTZB!5duL_t(|+U?g{NK}EGA((Gy! z5oK6XVUnqcm01~Lq$XHcE-g(7vP^}|4AVl)1k*BfnwQkpRE*Trbeo=E@c$on{?q^5 z|42{|=lk;8%Q&B%8*B#t$7X0MO{J+cm8SZOs`h#)<$%px`+u)gv5_NCN`}3n*N_rI ztPtys9^&;rF-@(~1p{1F-3@;mkP;4|Orb$N5H6q~Wonggu@lwlT5@?kAk=j|#QT20 z)fL?Q)`@DV1OVL&A(U4i#M32ASJko=DpZ5Zz^gJ1N*~4`ceETpu3;Lghv8~e#Ye#D zWgwU`1|heIe1D3&BcK|W98@Q=_$r&;OqZNc$X$*H&{izLp(q1rHbte}t1bQls)!N? zmB%N(D!7`A#R~v*kn<+T*T+s!711^OIH(@)C)0YsxoUkiU87eft1{2Fk{wt8$99dA z$#keGy6QNiU|PiH4qyy2ZjB0HTncq9X_G6O`KzAN+ zu#8b_)&a0Z?EsP%yi-&^XAXkq-#F>MYTT|v*B-wfyGokd%)PHJlmZni z%VG2i8dX#wk#xWL!I=u{YC)3#MOC&Ov|pTOP=85QHMs%&XOQ<6-Ix#g8C3gjPd)?i z-&dzUPgXtMO}8XQL^ZExx~jk|XR6UWGwe0eiH;}JRyZc0>U0OKMYU>mD__NLUpcrH zp5dtU$3RO;QmL{nfg06PEBKY(hfF3Z$GJj+%HsP2YNQiXmEkRW$21TKoJ2^@uJO_#EZL{zwV#X3P%`^;oF2aUi* z#=I{OPRcbvNQI_4C}o1pRH^-U)>N8GQ)wzqHSPWY6A4O~_iB**00000NkvXXu0mjf DRdS$< diff --git a/tests/ref/math-lr-mid-size.png b/tests/ref/math-lr-mid-size.png index 12b4c0868d133a99f8973511888564a83dafb329..c0e191a153b6e88f189abfae1694d2f17af2a421 100644 GIT binary patch delta 2211 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 delta 2198 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 literal 0 HcmV?d00001 diff --git a/tests/ref/math-lr-size.png b/tests/ref/math-lr-size.png index 09d2442152852b6e5e4e503605d178d0316980f5..f3259ac3c5d0e6dbb6fa15263d3bbadb03f45d34 100644 GIT binary patch delta 661 zcmV;G0&4x21+4{;B!6^CL_t(|+U?i>OA~P%$8i(t=YFXEf__>*6a+!gFP2dK5ClQ8 z3JV#;W{uIP7VL+iBhpPY);CN0MX=^bB`xRNWKpH8+fH?MvHU?T+qt z++Fv>XJkKk&)?pk`{BX8xz7(*ga1^G49l<#%kaO07q9kO7k}0d&JEb3pvzuPSVG|R z-EPl8qzG?bA%Y7NMh`%z!G$q!T63LRg)r|h@4)9ETeX9MvqiQ7qG;gsx z2*CK97{+Mr4Y0-g9t_;%zp!d^Y+Y=2f&aJN2Mc4QusXcMTc~IAq6)tL0JAc2x0TK z{Z#vQZhx)a)Gi56-iEO8Zc_Nm$4VJV7)=nS&Jn`gGLVMdBjER;2i3!$RY7SOT_N5< zyafbsN_BP)_%=E9v=}Xyhn0mtH8mz7l8ZNc3j{D@yzdOBr^+QLm>^PkzGEivW3b!` vEb9t;{NoAGE#L3Y$^T9=EWL_t(|+GF@XK!9P?;!%r7EgrR)b{5|)pWAb1AT0h-`mXyn zF-AQ@<3Fb~sI&g(Y$w9rspzhobB!#EyTkUwy!`(Qip;(AC@kt%9>EZ*`9hYDcW=fi zm5V9#I|ozjK?m6uhihdO0YP50ySCP9ycW;diN%PNkK|f>gnu;;=K2;#ycP#wGh+1; zvV*#rX$y>h_#_^S-vwba;zb167XNb+gXfZecr4!33MBq6a`^$_?wXdk6-1knpSYfh zy8OeJxVBD*$J4>RaAm2u;6D zy2T*Res@7>cBH_=^4E=f*qUZFKhQ*uFDJjv^4K#TE$Pbbmiih{=8 z&5QjHz8(XDcR-8j*}T2ld*;%|bwKd^e>Yt%-g{!_I|%F1tYtq(`%a@4k6Jux@d&pV Y0L@qOW1fjjkpKVy07*qoM6N<$f@48II{*Lx diff --git a/tests/ref/math-stretch-function.png b/tests/ref/math-stretch-function.png new file mode 100644 index 0000000000000000000000000000000000000000..8e1daa46ce75ce760822f770c986b6667088e28b GIT binary patch literal 291 zcmV+;0o?wHP)MSv+CXV)|NqVxTOZO*4z%4wS_e17q>O_M-!1@j=D5wzkZ+ob0y1 zwu1Dwj8-7`7+r$;%+!I3V?q`Wv+Q)?$-r2Af5j~X=kMA7+y7!%{F){fFYGydWY>l5 zn>YS`wdcyzt`8Ic-8}N(?DB1|Xb9WBy zEq>d7c=_42+YaabeQ|up)s~-i|I2?gKAZuPTuMWWcdq)sdGowIYc}qDaq#ZHweM!$ py1ehs002ovPDHLkV1kUKl9vDg literal 0 HcmV?d00001 diff --git a/tests/suite/math/delimited.typ b/tests/suite/math/delimited.typ index 794ffd8aa..06e3e0d20 100644 --- a/tests/suite/math/delimited.typ +++ b/tests/suite/math/delimited.typ @@ -34,6 +34,11 @@ $ lr(a/b\]) = a = lr(\{a/b) $ $ lr(]sum_(x=1)^n x], size: #70%) < lr((1, 2), size: #200%) $ +--- math-lr-size-function --- +// Test using a function as an argument to size. +#set math.lr(size: x => if x > 10pt { 1em } else { 4 * x }) +$ (a) (1/2) $ + --- math-lr-shorthands --- // Test predefined delimiter pairings. $floor(x/2), ceil(x/2), abs(x), norm(x)$ diff --git a/tests/suite/math/stretch.typ b/tests/suite/math/stretch.typ index d145f72a1..f5ee05af6 100644 --- a/tests/suite/math/stretch.typ +++ b/tests/suite/math/stretch.typ @@ -91,3 +91,9 @@ $ body^"text" $ } $body^"long text"$ } + +--- math-stretch-function --- +// Test using a function as an argument to size. +$stretch(<-, size: #(x => x - 0.5em))_"function"$ +#set math.stretch(size: x => x + 0.5em) +$stretch(|) |$ From ba8ea4249ce3304a35dee7b117b29554b8b64ba0 Mon Sep 17 00:00:00 2001 From: mkorje Date: Fri, 25 Apr 2025 22:36:21 +1000 Subject: [PATCH 2/3] Allow a function as an argument to size in `accent` The short fall is now only applied in the default for `accent` (`x => x - 0.5em`). This is the reason for the updated tests. --- crates/typst-layout/src/math/accent.rs | 12 +++------ crates/typst-library/src/math/accent.rs | 31 ++++++++++++++++++----- tests/ref/math-accent-sized-function.png | Bin 0 -> 417 bytes tests/ref/math-accent-sized-script.png | Bin 331 -> 355 bytes tests/suite/math/accent.typ | 10 ++++++-- 5 files changed, 35 insertions(+), 18 deletions(-) create mode 100644 tests/ref/math-accent-sized-function.png diff --git a/crates/typst-layout/src/math/accent.rs b/crates/typst-layout/src/math/accent.rs index 301606466..7be32b1a8 100644 --- a/crates/typst-layout/src/math/accent.rs +++ b/crates/typst-layout/src/math/accent.rs @@ -1,13 +1,10 @@ use typst_library::diag::SourceResult; use typst_library::foundations::{Packed, StyleChain}; -use typst_library::layout::{Em, Frame, Point, Size}; +use typst_library::layout::{Frame, Point, Size}; use typst_library::math::AccentElem; use super::{style_cramped, FrameFragment, GlyphFragment, MathContext, MathFragment}; -/// How much the accent can be shorter than the base. -const ACCENT_SHORT_FALL: Em = Em::new(0.5); - /// Lays out an [`AccentElem`]. #[typst_macros::time(name = "math.accent", span = elem.span())] pub fn layout_accent( @@ -42,11 +39,8 @@ pub fn layout_accent( } } - // Forcing the accent to be at least as large as the base makes it too - // wide in many case. - let width = elem.size(styles).relative_to(base.width()); - let short_fall = ACCENT_SHORT_FALL.at(glyph.font_size); - let variant = glyph.stretch_horizontal(ctx, width - short_fall); + let width = elem.size(styles).resolve(ctx.engine, styles, base.width())?; + let variant = glyph.stretch_horizontal(ctx, width); let accent = variant.frame; let accent_attach = variant.accent_attach.0; diff --git a/crates/typst-library/src/math/accent.rs b/crates/typst-library/src/math/accent.rs index f2c9168c2..7f2ff87a9 100644 --- a/crates/typst-library/src/math/accent.rs +++ b/crates/typst-library/src/math/accent.rs @@ -1,7 +1,19 @@ use crate::diag::bail; -use crate::foundations::{cast, elem, func, Content, NativeElement, SymbolElem}; -use crate::layout::{Length, Rel}; -use crate::math::Mathy; +use crate::foundations::{ + cast, elem, func, Content, NativeElement, NativeFunc, SymbolElem, +}; +use crate::layout::{Em, Length, Ratio, Rel}; +use crate::math::{Mathy, StretchSize}; + +const ACCENT_SHORT_FALL: Em = Em::new(-0.5); + +#[func(name = "x => x - 0.5em")] +const fn default_accent_size(base: Length) -> Rel { + Rel { + rel: Ratio::zero(), + abs: Length { abs: base.abs, em: ACCENT_SHORT_FALL }, + } +} /// Attaches an accent to a base. /// @@ -52,12 +64,14 @@ pub struct AccentElem { /// The size of the accent, relative to the width of the base. /// + /// See the [stretch documentation]($math.stretch.size) for more + /// information on sizes. + /// /// ```example /// $dash(A, size: #150%)$ /// ``` - #[resolve] - #[default(Rel::one())] - pub size: Rel, + #[default(::data().into())] + pub size: StretchSize, /// Whether to remove the dot on top of lowercase i and j when adding a top /// accent. @@ -129,8 +143,11 @@ macro_rules! accents { /// The base to which the accent is applied. base: Content, /// The size of the accent, relative to the width of the base. + /// + /// See the [stretch documentation]($math.stretch.size) for + /// more information on sizes. #[named] - size: Option>, + size: Option, /// Whether to remove the dot on top of lowercase i and j when /// adding a top accent. #[named] diff --git a/tests/ref/math-accent-sized-function.png b/tests/ref/math-accent-sized-function.png new file mode 100644 index 0000000000000000000000000000000000000000..9bd52a6e72aa76c5c5d481bc93fecec24637de1b GIT binary patch literal 417 zcmV;S0bc%zP)adD!gD3XJNhl2?xdCJ3T2enOEEf1Grd7SoJN!nA| z+FCTNv9X7upX|+F@ul_q`~$DP-^1rq#vi3jgEhFs;h4!}iUD~Ka4&sv(PrH-z^d^= z5`F~Wqrhi|gdptK10nrjoNRx~ks!QO1<;%VMt6~e4-=KZYZHL(Sq{!yXm|m~Fpz0L z4&K@_H~L`24(t!Y-Mb9zJA>Y1(4~Me8|pM7u)Ez&sC&Gyu6Kg7WpS;0PwVQEgX4rNGzi;5P{eC|FDW8NT z+)miS>sr~&T4u5=7Ot#}4EhfZ-q%&!ZqDqtWN@R1P`D7daDT{!hKFXaz{{9V5RM}j zwrEkG&Au%dhVQYi6notneyjF_ig1`21)h#+bQ-`ppixZ_+>?W_2>=ffEp}O&{*2ZE zK;7NmN$6r=vjbSeEmG;cjZ;kHRSo9vqlkf115jJV%{iasPvc}i1i|T12<5>k!PoPg zs=wk^HsEE6BS8eg&V;g@0Umonn_5`h!Yk$nOF6j{1%m?s8ck{K+3I?;qZi~YN?5`Y aZqW}GA9G2VYDcL60000h5s+Up^?QWvi|>{{sd^} ztN;H$J!skT=qXg<^Vk3XA5Q+imsW}EU+J`&pd@|Q>;L~V)qVYoVG^rq|Ns9z?Z8bM zS&T@wXV4X$p(#%R@(10XN6$yA#Zik#EgrQP3jhGnEO*GWH diff --git a/tests/suite/math/accent.typ b/tests/suite/math/accent.typ index 2239d8975..9a6a4e6c6 100644 --- a/tests/suite/math/accent.typ +++ b/tests/suite/math/accent.typ @@ -30,12 +30,18 @@ $ tilde(integral), tilde(integral)_a^b, tilde(integral_a^b) $ --- math-accent-sized --- // Test accent size. -$tilde(sum), tilde(sum, size: #50%), accent(H, hat, size: #200%)$ +$tilde(sum), tilde(sum, size: #25%), accent(H, hat, size: #125%)$ --- math-accent-sized-script --- // Test accent size in script size. $tilde(U, size: #1.1em), x^tilde(U, size: #1.1em), sscript(tilde(U, size: #1.1em))$ +--- math-accent-sized-function --- +// Test accent size with a function. +$dash(A) arrow(I) hat(L)$ \ +#set math.accent(size: x => x - 0.1em) +$dash(A) arrow(I) hat(L)$ + --- math-accent-dotless --- // Test dotless glyph variants. #let test(c) = $grave(#c), acute(sans(#c)), hat(frak(#c)), tilde(mono(#c)), @@ -79,7 +85,7 @@ $ accent(integral, \u{20EC}), accent(integral, \u{20EC})_a^b, accent(integral_a^ --- math-accent-bottom-sized --- // Test bottom accent size. -$accent(sum, \u{0330}), accent(sum, \u{0330}, size: #50%), accent(H, \u{032D}, size: #200%)$ +$accent(sum, \u{0330}), accent(sum, \u{0330}, size: #25%), accent(H, \u{032D}, size: #125%)$ --- math-accent-nested --- // Test nested top and bottom accents. From 189e2f444cdba351d483be7385238c584d76fdb8 Mon Sep 17 00:00:00 2001 From: mkorje Date: Sun, 27 Apr 2025 21:04:39 +1000 Subject: [PATCH 3/3] Add `delim-size` parameter to `mat`, `vec`, and `cases` Takes either a function or a relative length, just like with `lr`, `stretch`, and `accent` which was changed in the previous two commits. The short fall was changed in the first commit, so no test updates here. The default is now much clearer to the user: `x => x * 1.1 - 0.1em`. --- crates/typst-layout/src/math/frac.rs | 7 ++-- crates/typst-layout/src/math/mat.rs | 44 +++++++++++++++++------- crates/typst-layout/src/math/shared.rs | 5 +-- crates/typst-library/src/math/lr.rs | 2 +- crates/typst-library/src/math/matrix.rs | 41 +++++++++++++++++++--- tests/ref/math-cases-delim-size.png | Bin 0 -> 1005 bytes tests/ref/math-mat-delim-size.png | Bin 0 -> 1327 bytes tests/ref/math-vec-delim-size.png | Bin 0 -> 1340 bytes tests/suite/math/cases.typ | 6 ++++ tests/suite/math/mat.typ | 8 ++++- tests/suite/math/vec.typ | 6 ++++ 11 files changed, 92 insertions(+), 27 deletions(-) create mode 100644 tests/ref/math-cases-delim-size.png create mode 100644 tests/ref/math-mat-delim-size.png create mode 100644 tests/ref/math-vec-delim-size.png diff --git a/crates/typst-layout/src/math/frac.rs b/crates/typst-layout/src/math/frac.rs index 2567349d0..172cdf7ad 100644 --- a/crates/typst-layout/src/math/frac.rs +++ b/crates/typst-layout/src/math/frac.rs @@ -1,14 +1,13 @@ use typst_library::diag::SourceResult; use typst_library::foundations::{Content, Packed, Resolve, StyleChain, SymbolElem}; use typst_library::layout::{Em, Frame, FrameItem, Point, Size}; -use typst_library::math::{BinomElem, FracElem}; +use typst_library::math::{BinomElem, FracElem, DELIM_SHORT_FALL}; use typst_library::text::TextElem; use typst_library::visualize::{FixedStroke, Geometry}; use typst_syntax::Span; use super::{ - style_for_denominator, style_for_numerator, FrameFragment, GlyphFragment, - MathContext, DELIM_SHORT_FALL, + style_for_denominator, style_for_numerator, FrameFragment, GlyphFragment, MathContext, }; const FRAC_AROUND: Em = Em::new(0.1); @@ -49,7 +48,7 @@ fn layout_frac_like( binom: bool, span: Span, ) -> SourceResult<()> { - let short_fall = DELIM_SHORT_FALL.resolve(styles); + let short_fall = DELIM_SHORT_FALL.abs().resolve(styles); let axis = scaled!(ctx, styles, axis_height); let thickness = scaled!(ctx, styles, fraction_rule_thickness); let shift_up = scaled!( diff --git a/crates/typst-layout/src/math/mat.rs b/crates/typst-layout/src/math/mat.rs index e509cecc7..3d10fde57 100644 --- a/crates/typst-layout/src/math/mat.rs +++ b/crates/typst-layout/src/math/mat.rs @@ -1,19 +1,20 @@ use typst_library::diag::{bail, warning, SourceResult}; use typst_library::foundations::{Content, Packed, Resolve, StyleChain}; use typst_library::layout::{ - Abs, Axes, Em, FixedAlignment, Frame, FrameItem, Point, Ratio, Rel, Size, + Abs, Axes, Em, FixedAlignment, Frame, FrameItem, Point, Rel, Size, +}; +use typst_library::math::{ + Augment, AugmentOffsets, CasesElem, MatElem, StretchSize, VecElem, }; -use typst_library::math::{Augment, AugmentOffsets, CasesElem, MatElem, VecElem}; use typst_library::text::TextElem; use typst_library::visualize::{FillRule, FixedStroke, Geometry, LineCap, Shape}; use typst_syntax::Span; use super::{ alignments, delimiter_alignment, style_for_denominator, AlignmentResult, - FrameFragment, GlyphFragment, LeftRightAlternator, MathContext, DELIM_SHORT_FALL, + FrameFragment, GlyphFragment, LeftRightAlternator, MathContext, }; -const VERTICAL_PADDING: Ratio = Ratio::new(0.1); const DEFAULT_STROKE_THICKNESS: Em = Em::new(0.05); /// Lays out a [`VecElem`]. @@ -39,7 +40,15 @@ pub fn layout_vec( )?; let delim = elem.delim(styles); - layout_delimiters(ctx, styles, frame, delim.open(), delim.close(), span) + layout_delimiters( + ctx, + styles, + frame, + elem.delim_size(styles), + delim.open(), + delim.close(), + span, + ) } /// Lays out a [`CasesElem`]. @@ -67,7 +76,7 @@ pub fn layout_cases( let delim = elem.delim(styles); let (open, close) = if elem.reverse(styles) { (None, delim.close()) } else { (delim.open(), None) }; - layout_delimiters(ctx, styles, frame, open, close, span) + layout_delimiters(ctx, styles, frame, elem.delim_size(styles), open, close, span) } /// Lays out a [`MatElem`]. @@ -125,7 +134,15 @@ pub fn layout_mat( )?; let delim = elem.delim(styles); - layout_delimiters(ctx, styles, frame, delim.open(), delim.close(), span) + layout_delimiters( + ctx, + styles, + frame, + elem.delim_size(styles), + delim.open(), + delim.close(), + span, + ) } /// Layout the inner contents of a matrix, vector, or cases. @@ -302,19 +319,20 @@ fn layout_delimiters( ctx: &mut MathContext, styles: StyleChain, mut frame: Frame, + size: StretchSize, left: Option, right: Option, span: Span, ) -> SourceResult<()> { - let short_fall = DELIM_SHORT_FALL.resolve(styles); let axis = scaled!(ctx, styles, axis_height); let height = frame.height(); - let target = height + VERTICAL_PADDING.of(height); frame.set_baseline(height / 2.0 + axis); + let target = size.resolve(ctx.engine, styles, height)?; + if let Some(left) = left { - let mut left = GlyphFragment::new(ctx, styles, left, span) - .stretch_vertical(ctx, target - short_fall); + let mut left = + GlyphFragment::new(ctx, styles, left, span).stretch_vertical(ctx, target); left.align_on_axis(ctx, delimiter_alignment(left.c)); ctx.push(left); } @@ -322,8 +340,8 @@ fn layout_delimiters( ctx.push(FrameFragment::new(styles, frame)); if let Some(right) = right { - let mut right = GlyphFragment::new(ctx, styles, right, span) - .stretch_vertical(ctx, target - short_fall); + let mut right = + GlyphFragment::new(ctx, styles, right, span).stretch_vertical(ctx, target); right.align_on_axis(ctx, delimiter_alignment(right.c)); ctx.push(right); } diff --git a/crates/typst-layout/src/math/shared.rs b/crates/typst-layout/src/math/shared.rs index 600c130d4..77d1ab4a8 100644 --- a/crates/typst-layout/src/math/shared.rs +++ b/crates/typst-layout/src/math/shared.rs @@ -1,6 +1,6 @@ use ttf_parser::math::MathValue; use typst_library::foundations::{Style, StyleChain}; -use typst_library::layout::{Abs, Em, FixedAlignment, Frame, Point, Size, VAlignment}; +use typst_library::layout::{Abs, FixedAlignment, Frame, Point, Size, VAlignment}; use typst_library::math::{EquationElem, MathSize}; use typst_utils::LazyHash; @@ -28,9 +28,6 @@ macro_rules! percent { }; } -/// How much less high scaled delimiters can be than what they wrap. -pub const DELIM_SHORT_FALL: Em = Em::new(0.1); - /// Converts some unit to an absolute length with the current font & font size. pub trait Scaled { fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs; diff --git a/crates/typst-library/src/math/lr.rs b/crates/typst-library/src/math/lr.rs index 6da348832..34745bd24 100644 --- a/crates/typst-library/src/math/lr.rs +++ b/crates/typst-library/src/math/lr.rs @@ -2,7 +2,7 @@ use crate::foundations::{elem, func, Content, NativeElement, NativeFunc, SymbolE use crate::layout::{Em, Length, Ratio, Rel}; use crate::math::{Mathy, StretchSize}; -const DELIM_SHORT_FALL: Em = Em::new(-0.1); +pub const DELIM_SHORT_FALL: Em = Em::new(-0.1); #[func(name = "x => x - 0.1em")] pub const fn default_lr_size(base: Length) -> Rel { diff --git a/crates/typst-library/src/math/matrix.rs b/crates/typst-library/src/math/matrix.rs index b6c4654ed..8ab19040c 100644 --- a/crates/typst-library/src/math/matrix.rs +++ b/crates/typst-library/src/math/matrix.rs @@ -5,15 +5,27 @@ use unicode_math_class::MathClass; use crate::diag::{bail, At, HintedStrResult, StrResult}; use crate::foundations::{ - array, cast, dict, elem, Array, Content, Dict, Fold, NoneValue, Resolve, Smart, - StyleChain, Symbol, Value, + array, cast, dict, elem, func, Array, Content, Dict, Fold, NativeFunc, NoneValue, + Resolve, Smart, StyleChain, Symbol, Value, }; -use crate::layout::{Abs, Em, HAlignment, Length, Rel}; -use crate::math::Mathy; +use crate::layout::{Abs, Em, HAlignment, Length, Ratio, Rel}; +use crate::math::{Mathy, StretchSize, DELIM_SHORT_FALL}; use crate::visualize::Stroke; const DEFAULT_ROW_GAP: Em = Em::new(0.2); const DEFAULT_COL_GAP: Em = Em::new(0.5); +const VERTICAL_PADDING: Ratio = Ratio::new(1.1); + +#[func(name = "x => x * 1.1 - 0.1em")] +const fn default_mat_size(base: Length) -> Rel { + Rel { + rel: Ratio::zero(), + abs: Length { + abs: Abs::raw(base.abs.to_raw() * VERTICAL_PADDING.get()), + em: DELIM_SHORT_FALL, + }, + } +} /// A column vector. /// @@ -40,6 +52,13 @@ pub struct VecElem { #[default(DelimiterPair::PAREN)] pub delim: DelimiterPair, + /// The size of the delimiters, relative to the elements' total height. + /// + /// See the [stretch documentation]($math.stretch.size) for more + /// information on sizes. + #[default(::data().into())] + pub delim_size: StretchSize, + /// The horizontal alignment that each element should have. /// /// ```example @@ -101,6 +120,13 @@ pub struct MatElem { #[default(DelimiterPair::PAREN)] pub delim: DelimiterPair, + /// The size of the delimiters, relative to the cells' total height. + /// + /// See the [stretch documentation]($math.stretch.size) for more + /// information on sizes. + #[default(::data().into())] + pub delim_size: StretchSize, + /// The horizontal alignment that each cell should have. /// /// ```example @@ -244,6 +270,13 @@ pub struct CasesElem { #[default(DelimiterPair::BRACE)] pub delim: DelimiterPair, + /// The size of the delimiters, relative to the branches' total height. + /// + /// See the [stretch documentation]($math.stretch.size) for more + /// information on sizes. + #[default(::data().into())] + pub delim_size: StretchSize, + /// Whether the direction of cases should be reversed. /// /// ```example diff --git a/tests/ref/math-cases-delim-size.png b/tests/ref/math-cases-delim-size.png new file mode 100644 index 0000000000000000000000000000000000000000..92231d6915396867ab906a7d7afa70f0f6173d40 GIT binary patch literal 1005 zcmVjz3W6@sgSLbbmSwSKm(6sM zGNLP4pn1a-O%cfrS6kC|-Dw^7HaqUluXASBZNeUUIlqCLefa*)`N8sG_slsn3w!AUUg8b`QM6nOMX{yvdZX~vk^7@HxH3|_JB z4KanUR)EDp2kYw8M%_A^x9`BAyG&uX6SAKvV2VtOoKi>2P!AlaWW2_oGvZ1u%kcsD za>^3z{;lI|Vc~+Pu5Nc~8$erXx7rS#505d0rIXDKRdnDs^R)10H!ZQ#jZGCy;rAAc zrI!w}6H8i|O|zb~yHNJAJlCrZymMvj10?w%fC~G$Fw&8!|4kyUBtP+s!yK=+8N+EpW1wJv>_rb8e-0H-wO3 z_2L>o%SUyA^E;j8IIwKXygovzG%ZB3F)b~vnK>-Y*G$@0;XQLz=V>O}pQFLAvOLLg z;AkPB!C9IO+q%|?2gCW>n$y$#4G$h(Od@>sQf`D)$8Oq++^@lp<1Iy=HJ02;Ed8Tg4r}I+5ab<|-ind*mHLNJ->V1Jl|*ge)s$S^VxmQ zxz9N}tH4cKrGYiD2G+nX0Xx1~z?g}4HTZDXa`wakugn?rg)}m-ArB;-ujjcL%h~t? z5qRFm36hrlgq6B46eK0L(ty)S(SIv8GS?ecYX2E#4Tchc#Q=cw^OJJEZT3|kJh|4%kB-$2F}HN` z?Su8|gS%dk&rgq_JJ(JCuk|ykgH2xYd21{B_~Y_+%MI1RmEQ8%qXoU=MS1&%S#@yS z2Kl`1XY>{idFx&2qF-!&PTqRvkb%pJOE_@j77%4N6o~m+vFc#mCNc6;;3S})D&)4> z908G3TOIYJ7_W{<^O_6}?At+pjROFFm$sS%2hX#X7Tj$z-{Qa(n*n95s2u>5XS(WO z{qyp9Ly?RAP6fN<^X4n)ty|@-SC#5uqqltC)`D)^C~seCR~_86Lq2c3iEdvl$j`I8 zs)I+K0bX;D&58N!INwgJ1J%KfHB0aSBTkRy^ z3t4T$0H&Agpua87ER_jtLBGLlXn?%Y%yAlU3;N&@^h+}EuY%`jH8r?6QwfHyFaT7p(5 zHYNDSq;wAa7F&`IwH~-Z34TWa4t#(uNpU?9RuXVX5qgzp9rLI({u*~1N(*Y}uJK11 z=mTG)A3nu{D;zs-U!ensmZ2AAqbFwZU}!%~lEPYhWtxLIGG*5hsn{(K}2Y(X_}a_h8d@txNb}ef<(1~NXT$e3=>1-G6Y?aTR;o0 z8z3yo2pa@iU?BzUz=|S7Nd`KCh};(hcK7T)o%cQL+4C}fJnuH+ocF{3_dNeEzj@Ej zoSivq;D0)6B(Wrx#FE%9;wuFh(@Ay~-`ve$LfFPX+Dzeh=I37!L}`=}29@-7O! z3K%Ml09cHgp(Cy+LK&lF!|cuQ^*}ruliqBMM$-{{8+kEgtNGe@+A4;xnrVpZj(|-( zGr; zll!`xC@qhT6YY^S(Z$}CYc}CUA(!tlqv6}vL>F&KA@`@M`5W_x%8lPtko%@)l-jLiTjeFX*i%jJ zE*&U`9LP3DF1k2aLGBLMQ5x1VL>r$Zx;P~>e(OjEt$d|Re8}F_TfPu(E(v!2lQ`RWP`CQV_seQNU7#)AX8QinYVnnD z@M@zi2E(4Kq!{Hb8ADI}QYyx|{R0Ru9~X#c3J%|=Cr(Ia!_*6nUTAaG*~DX`5K&A| zJo>^EK>xd-*^ch*2R3m=K7^j2Cx$McYXCK8BL4v>->J8XRo%B{DvJ*pZ+EL{iDCX1 yjsT0AslWpzql1SGK}mc){n!ubys literal 0 HcmV?d00001 diff --git a/tests/suite/math/cases.typ b/tests/suite/math/cases.typ index 306c1ae80..3765561ea 100644 --- a/tests/suite/math/cases.typ +++ b/tests/suite/math/cases.typ @@ -16,6 +16,12 @@ $ x = cases(1, 2) $ #set math.cases(delim: sym.angle.l) $ cases(a, b, c) $ +--- math-cases-delim-size --- +// Test setting delimiter size. +$ cases(reverse: #true, 1, 2, 3) cases(delim-size: #100%, 1, 2, 3) $ +#set math.cases(delim-size: x => calc.max(x - 5pt, x * 0.901)) +$ cases(1, 2) cases(1, 2, 3, 4) $ + --- math-cases-linebreaks --- // Warning: 40-49 linebreaks are ignored in branches // Hint: 40-49 use commas instead to separate each line diff --git a/tests/suite/math/mat.typ b/tests/suite/math/mat.typ index 80f190605..cde0eae98 100644 --- a/tests/suite/math/mat.typ +++ b/tests/suite/math/mat.typ @@ -54,6 +54,12 @@ $ a + mat(delim: #none, 1, 2; 3, 4) + b $ $ mat(1, 2; 3, 4; delim: "[") $, ) +--- math-mat-delim-size --- +// Test setting delimiter size. +$ mat(c; c; c) mat(delim-size: #100%, c; c; c) $ +#set math.mat(delim-size: x => calc.max(x - 5pt, x * 0.901)) +$ mat(delim: "[", f; f; f; f) mat(delim: ||, x; x; x; x) $ + --- math-mat-spread --- // Test argument spreading in matrix. $ mat(..#range(1, 5).chunks(2)) @@ -263,7 +269,7 @@ $ mat(a; b; c) mat(a \ b \ c) $ --- math-mat-vec-cases-unity --- // Test that matrices, vectors, and cases are all laid out the same. $ mat(z_(n_p); a^2) - vec(z_(n_p), a^2) + vec(z_(n_p), a^2) cases(reverse: #true, delim: \(, z_(n_p), a^2) cases(delim: \(, z_(n_p), a^2) $ diff --git a/tests/suite/math/vec.typ b/tests/suite/math/vec.typ index e5ee409ec..e976ca645 100644 --- a/tests/suite/math/vec.typ +++ b/tests/suite/math/vec.typ @@ -50,6 +50,12 @@ $ vec(1, 2) $ // Error: 22-33 invalid delimiter: "%" #set math.vec(delim: (none, "%")) +--- math-vec-delim-size --- +// Test setting delimiter size. +$ vec(1, 2, 3) vec(delim-size: #100%, 1, 2, 3) $ +#set math.vec(delim-size: x => calc.max(x - 5pt, x * 0.901)) +$ vec(delim: "{", 1, 2, 3) vec(delim: "[", 1, 2, 3) $ + --- math-vec-linebreaks --- // Warning: 20-29 linebreaks are ignored in elements // Hint: 20-29 use commas instead to separate each line