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 01fa6be4b..d84a7b8fa 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..c9772c66f 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(); } @@ -296,10 +304,8 @@ impl MathRun { frame.translate(Point::with_y(ascent)); }; - 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 @@ -313,8 +319,21 @@ 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)); + if let MathFragment::Fractional(fr) = fragment { + if !empty { + let mut frame_prev = + std::mem::replace(&mut frame, Frame::soft(Size::zero())); + + finalize_frame(&mut frame_prev, x, ascent, descent); + items.push(InlineItem::Frame(frame_prev)); + empty = true; + + x = Abs::zero(); + ascent = Abs::zero(); + descent = Abs::zero(); + } + + items.push(InlineItem::Fractional(fr)); continue; } @@ -343,14 +362,13 @@ impl MathRun { ascent = Abs::zero(); descent = Abs::zero(); - space_is_visible = true; - if let Some(f_next) = iter.peek() { - if !is_space(f_next) { - items.push(InlineItem::Space(Abs::zero(), true)); + if iter.peek().map(is_space).is_some() { + while let Some(f_next) = iter.next_if(is_space) { + items.push(InlineItem::Absolute(f_next.width(), true)); } + } else { + items.push(InlineItem::Absolute(Abs::zero(), true)); } - } else { - space_is_visible = false; } } @@ -435,7 +453,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-fractional-inline.png b/tests/ref/math-spacing-fractional-inline.png new file mode 100644 index 000000000..7ca0a5c59 Binary files /dev/null and b/tests/ref/math-spacing-fractional-inline.png differ diff --git a/tests/ref/math-spacing-mixed-inline.png b/tests/ref/math-spacing-mixed-inline.png new file mode 100644 index 000000000..ed3be05e6 Binary files /dev/null and b/tests/ref/math-spacing-mixed-inline.png differ diff --git a/tests/ref/math-spacing-relative-inline.png b/tests/ref/math-spacing-relative-inline.png new file mode 100644 index 000000000..9d18bf2f3 Binary files /dev/null and b/tests/ref/math-spacing-relative-inline.png differ diff --git a/tests/ref/math-spacing-relative.png b/tests/ref/math-spacing-relative.png new file mode 100644 index 000000000..49201b9a7 Binary files /dev/null and b/tests/ref/math-spacing-relative.png differ diff --git a/tests/suite/math/spacing.typ b/tests/suite/math/spacing.typ index db8b905c6..464805c82 100644 --- a/tests/suite/math/spacing.typ +++ b/tests/suite/math/spacing.typ @@ -63,6 +63,46 @@ $#place(dx: 5em)[a] + b$ // Validate that ignorant elements are layouted #context test(counter("test").get(), (3,)) +--- math-spacing-relative --- +// Test relative spacing. +$ A #h(50%) B \ + A#block(width: 50%);B \ + A #block(width: 50%) B \ + A space #h(50%) space B $ + +--- math-spacing-relative-inline --- +// Test relative spacing in inline math. +#let mtext = text.with(font: "Libertinus Serif") +Hello#h(40%)world \ +Hello#box(width: 40%);world \ +Hello$#h(40%)$world \ +Hello$#box(width: 40%)$world \ +$mtext("Hello") #h(40%) mtext("world")$ \ +$mtext("Hello")#box(width: 40%);mtext("world")$ + +Hello #h(40%) world \ +Hello #box(width: 40%) world \ +Hello $#h(40%)$ world \ +Hello $#box(width: 40%)$ world \ +$mtext("Hello") #h(40%) space mtext("world")$ \ +$mtext("Hello") #box(width: 40%) mtext("world")$ + +--- math-spacing-fractional-inline --- +// Test fractional spacing in inline math. +Hello #h(1fr) world \ +Hello $#h(1fr)$ world + +x #h(1fr) y \ +$x #h(1fr) y$ + +Blah #h(1.5fr) long$#h(0.5fr) x - #h(1fr) y$ line. \ +Blah #h(1.5fr) long $#h(0.5fr) x - #h(1fr) y$ line. + +--- math-spacing-mixed-inline --- +// Test mixture of different kinds of spacing in inline math. +Some #h(30%) inline $x + #h(5%) y - #h(1fr) sum_(1 #h(1fr) 2) $ spacing #h(2fr) blah. +Long $(a #h(1fr) z) #h(1em, weak: true)$ #h(1%) $#h(0.5fr) sqrt(1 + #h(0.5fr) y)$. + --- issue-1052-math-number-spacing --- // Test spacing after numbers in math. $