From cd9600170b00bd1127868195260014620c8a9e92 Mon Sep 17 00:00:00 2001 From: mkorje Date: Fri, 30 May 2025 19:22:26 +1000 Subject: [PATCH] Add fractional spacing inline item --- crates/typst-layout/src/inline/collect.rs | 5 +++- crates/typst-layout/src/math/fragment.rs | 10 ++++---- crates/typst-layout/src/math/lr.rs | 2 +- crates/typst-layout/src/math/mod.rs | 14 +++++++---- crates/typst-layout/src/math/run.rs | 22 +++++++++++------ crates/typst-library/src/layout/container.rs | 4 +++- tests/ref/math-spacing-relative-inline.png | Bin 0 -> 3110 bytes tests/ref/math-spacing-relative.png | Bin 0 -> 577 bytes tests/suite/math/spacing.typ | 24 +++++++++++++++++++ 9 files changed, 63 insertions(+), 18 deletions(-) create mode 100644 tests/ref/math-spacing-relative-inline.png create mode 100644 tests/ref/math-spacing-relative.png diff --git a/crates/typst-layout/src/inline/collect.rs b/crates/typst-layout/src/inline/collect.rs index 5a1b7b4fc..e8dd35dcf 100644 --- a/crates/typst-layout/src/inline/collect.rs +++ b/crates/typst-layout/src/inline/collect.rs @@ -201,9 +201,12 @@ pub fn collect<'a>( for item in elem.layout(engine, locator.next(&elem.span()), styles, region)? { match item { - InlineItem::Space(space, weak) => { + InlineItem::Absolute(space, weak) => { collector.push_item(Item::Absolute(space, weak)); } + InlineItem::Fractional(fr) => { + collector.push_item(Item::Fractional(fr, None)); + } InlineItem::Frame(mut frame) => { frame.modify(&FrameModifiers::get_in(styles)); apply_baseline_shift(&mut frame, styles); diff --git a/crates/typst-layout/src/math/fragment.rs b/crates/typst-layout/src/math/fragment.rs index 59858a9cb..c565bce51 100644 --- a/crates/typst-layout/src/math/fragment.rs +++ b/crates/typst-layout/src/math/fragment.rs @@ -7,7 +7,7 @@ use ttf_parser::{GlyphId, Rect}; use typst_library::foundations::StyleChain; use typst_library::introspection::Tag; use typst_library::layout::{ - Abs, Axis, Corner, Em, Frame, FrameItem, Point, Size, VAlignment, + Abs, Axis, Corner, Em, Fr, Frame, FrameItem, Point, Size, VAlignment, }; use typst_library::math::{EquationElem, MathSize}; use typst_library::text::{Font, Glyph, Lang, Region, TextElem, TextItem}; @@ -24,7 +24,8 @@ pub enum MathFragment { Glyph(GlyphFragment), Variant(VariantFragment), Frame(FrameFragment), - Spacing(Abs, bool), + Absolute(Abs, bool), + Fractional(Fr), Space(Abs), Linebreak, Align, @@ -41,7 +42,7 @@ impl MathFragment { Self::Glyph(glyph) => glyph.width, Self::Variant(variant) => variant.frame.width(), Self::Frame(fragment) => fragment.frame.width(), - Self::Spacing(amount, _) => *amount, + Self::Absolute(amount, _) => *amount, Self::Space(amount) => *amount, _ => Abs::zero(), } @@ -87,7 +88,8 @@ impl MathFragment { Self::Glyph(glyph) => glyph.class, Self::Variant(variant) => variant.class, Self::Frame(fragment) => fragment.class, - Self::Spacing(_, _) => MathClass::Space, + Self::Absolute(_, _) => MathClass::Space, + Self::Fractional(_) => MathClass::Space, Self::Space(_) => MathClass::Space, Self::Linebreak => MathClass::Space, Self::Align => MathClass::Special, diff --git a/crates/typst-layout/src/math/lr.rs b/crates/typst-layout/src/math/lr.rs index bf8235411..98064ad3f 100644 --- a/crates/typst-layout/src/math/lr.rs +++ b/crates/typst-layout/src/math/lr.rs @@ -75,7 +75,7 @@ pub fn layout_lr( fragments.retain(|fragment| { let discard = (index == start_idx + 1 && opening_exists || index + 2 == end_idx && closing_exists) - && matches!(fragment, MathFragment::Spacing(_, true)); + && matches!(fragment, MathFragment::Absolute(_, true)); index += 1; !discard }); diff --git a/crates/typst-layout/src/math/mod.rs b/crates/typst-layout/src/math/mod.rs index 708a4443d..f82ab4c32 100644 --- a/crates/typst-layout/src/math/mod.rs +++ b/crates/typst-layout/src/math/mod.rs @@ -635,11 +635,17 @@ fn layout_h( ctx: &mut MathContext, styles: StyleChain, ) -> SourceResult<()> { - if let Spacing::Rel(rel) = elem.amount { - if rel.rel.is_zero() { - ctx.push(MathFragment::Spacing(rel.abs.resolve(styles), elem.weak(styles))); - } + if elem.amount.is_zero() { + return Ok(()); } + + ctx.push(match elem.amount { + Spacing::Fr(fr) => MathFragment::Fractional(fr), + Spacing::Rel(rel) => MathFragment::Absolute( + rel.resolve(styles).relative_to(ctx.region.size.x), + elem.weak(styles), + ), + }); Ok(()) } diff --git a/crates/typst-layout/src/math/run.rs b/crates/typst-layout/src/math/run.rs index 4ec76c253..cc46c4288 100644 --- a/crates/typst-layout/src/math/run.rs +++ b/crates/typst-layout/src/math/run.rs @@ -33,14 +33,14 @@ impl MathRun { } // Explicit spacing disables automatic spacing. - MathFragment::Spacing(width, weak) => { + MathFragment::Absolute(width, weak) => { last = None; space = None; if weak { match resolved.last_mut() { None => continue, - Some(MathFragment::Spacing(prev, true)) => { + Some(MathFragment::Absolute(prev, true)) => { *prev = (*prev).max(width); continue; } @@ -52,6 +52,14 @@ impl MathRun { continue; } + // Same as explicit spacing that isn't weak. + MathFragment::Fractional(_) => { + last = None; + space = None; + resolved.push(fragment); + continue; + } + // Alignment points are resolved later. MathFragment::Align => { resolved.push(fragment); @@ -99,7 +107,7 @@ impl MathRun { resolved.push(fragment); } - if let Some(MathFragment::Spacing(_, true)) = resolved.last() { + if let Some(MathFragment::Absolute(_, true)) = resolved.last() { resolved.pop(); } @@ -299,7 +307,7 @@ impl MathRun { let mut space_is_visible = false; let is_space = |f: &MathFragment| { - matches!(f, MathFragment::Space(_) | MathFragment::Spacing(_, _)) + matches!(f, MathFragment::Space(_) | MathFragment::Absolute(_, _)) }; let is_line_break_opportunity = |class, next_fragment| match class { // Don't split when two relations are in a row or when preceding a @@ -314,7 +322,7 @@ impl MathRun { let mut iter = self.0.into_iter().peekable(); while let Some(fragment) = iter.next() { if space_is_visible && is_space(&fragment) { - items.push(InlineItem::Space(fragment.width(), true)); + items.push(InlineItem::Absolute(fragment.width(), true)); continue; } @@ -346,7 +354,7 @@ impl MathRun { space_is_visible = true; if let Some(f_next) = iter.peek() { if !is_space(f_next) { - items.push(InlineItem::Space(Abs::zero(), true)); + items.push(InlineItem::Absolute(Abs::zero(), true)); } } } else { @@ -435,7 +443,7 @@ fn spacing( let resolve = |v: Em, size_ref: &MathFragment| -> Option { let width = size_ref.font_size().map_or(Abs::zero(), |size| v.at(size)); - Some(MathFragment::Spacing(width, false)) + Some(MathFragment::Absolute(width, false)) }; let script = |f: &MathFragment| f.math_size().is_some_and(|s| s <= MathSize::Script); diff --git a/crates/typst-library/src/layout/container.rs b/crates/typst-library/src/layout/container.rs index 725f177b7..d88ad2c7b 100644 --- a/crates/typst-library/src/layout/container.rs +++ b/crates/typst-library/src/layout/container.rs @@ -174,7 +174,9 @@ impl Packed { #[derive(Debug, Clone)] pub enum InlineItem { /// Absolute spacing between other items, and whether it is weak. - Space(Abs, bool), + Absolute(Abs, bool), + /// Fractional spacing between other items. + Fractional(Fr), /// Layouted inline-level content. Frame(Frame), } diff --git a/tests/ref/math-spacing-relative-inline.png b/tests/ref/math-spacing-relative-inline.png new file mode 100644 index 0000000000000000000000000000000000000000..9d18bf2f3633acef927f2dd04bcb8764cb6e8a54 GIT binary patch literal 3110 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 ArT_o{ literal 0 HcmV?d00001 diff --git a/tests/ref/math-spacing-relative.png b/tests/ref/math-spacing-relative.png new file mode 100644 index 0000000000000000000000000000000000000000..49201b9a74289dcc5042fc94fe01188d38339995 GIT binary patch literal 577 zcmV-H0>1r;P)qG)aFLD$ed?=C^-=M$2L@B1Hk-QHRJr?L!eU;`U?)!;J=2s!oV z0?qB9!;#ISD7AT@Frvd5e`y~|Wdo#g6FU4PUfGJ$j;C$87BW0v5jf^WXd(|@mguk# zLgx_*-iF?M6<)k`jg^}HGq}#mYY0C)I<3MfYl`L1Rub3E#$vHqb1nm~Rd~ul$#I;2 zgEM<3B=<^gYfD)g9S-dqMCpzXir?z+TvvHFO8MiN7bnqSAJxqWWwt$kRP;fIhiU_- zsu3DK0mBDnINc20Og?IJb~;n^m3A