From f9caddc6d3e8142646429aeb8b7914d34e16f916 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Fri, 22 Aug 2025 18:03:20 +0200 Subject: [PATCH] Compute width of shaped text on-demand (#6806) --- crates/typst-layout/src/inline/collect.rs | 2 +- crates/typst-layout/src/inline/line.rs | 4 ---- crates/typst-layout/src/inline/prepare.rs | 2 -- crates/typst-layout/src/inline/shaping.rs | 26 +++++++---------------- 4 files changed, 9 insertions(+), 25 deletions(-) diff --git a/crates/typst-layout/src/inline/collect.rs b/crates/typst-layout/src/inline/collect.rs index f19c1ab4f..a9c11793b 100644 --- a/crates/typst-layout/src/inline/collect.rs +++ b/crates/typst-layout/src/inline/collect.rs @@ -82,7 +82,7 @@ impl<'a> Item<'a> { /// The natural layouted width of the item. pub fn natural_width(&self) -> Abs { match self { - Self::Text(shaped) => shaped.width, + Self::Text(shaped) => shaped.width(), Self::Absolute(v, _) => *v, Self::Frame(frame) => frame.width(), Self::Fractional(_, _) | Self::Tag(_) => Abs::zero(), diff --git a/crates/typst-layout/src/inline/line.rs b/crates/typst-layout/src/inline/line.rs index f89795de6..97ec0a9d1 100644 --- a/crates/typst-layout/src/inline/line.rs +++ b/crates/typst-layout/src/inline/line.rs @@ -364,7 +364,6 @@ fn adjust_cj_at_line_start(p: &Preparation, items: &mut Items) { let glyph = shaped.glyphs.to_mut().first_mut().unwrap(); let shrink = glyph.shrinkability().0; glyph.shrink_left(shrink); - shaped.width -= shrink.at(glyph.size); } else if p.config.cjk_latin_spacing && glyph.is_cj_script() && glyph.x_offset > Em::zero() @@ -376,7 +375,6 @@ fn adjust_cj_at_line_start(p: &Preparation, items: &mut Items) { glyph.x_advance -= shrink; glyph.x_offset = Em::zero(); glyph.adjustability.shrinkability.0 = Em::zero(); - shaped.width -= shrink.at(glyph.size); } } @@ -394,7 +392,6 @@ fn adjust_cj_at_line_end(p: &Preparation, items: &mut Items) { let shrink = glyph.shrinkability().1; let punct = shaped.glyphs.to_mut().last_mut().unwrap(); punct.shrink_right(shrink); - shaped.width -= shrink.at(punct.size); } else if p.config.cjk_latin_spacing && glyph.is_cj_script() && (glyph.x_advance - glyph.x_offset) > Em::one() @@ -405,7 +402,6 @@ fn adjust_cj_at_line_end(p: &Preparation, items: &mut Items) { let glyph = shaped.glyphs.to_mut().last_mut().unwrap(); glyph.x_advance -= shrink; glyph.adjustability.shrinkability.1 = Em::zero(); - shaped.width -= shrink.at(glyph.size); } } diff --git a/crates/typst-layout/src/inline/prepare.rs b/crates/typst-layout/src/inline/prepare.rs index 1c876ca94..c8a731b5e 100644 --- a/crates/typst-layout/src/inline/prepare.rs +++ b/crates/typst-layout/src/inline/prepare.rs @@ -152,7 +152,6 @@ fn add_cjk_latin_spacing(items: &mut [(Range, Item)]) { // The spacing is default to 1/4 em, and can be shrunk to 1/8 em. glyph.x_advance += Em::new(0.25); glyph.adjustability.shrinkability.1 += Em::new(0.125); - text.width += Em::new(0.25).at(glyph.size); } // Case 2: Latin followed by a CJ character @@ -160,7 +159,6 @@ fn add_cjk_latin_spacing(items: &mut [(Range, Item)]) { glyph.x_advance += Em::new(0.25); glyph.x_offset += Em::new(0.25); glyph.adjustability.shrinkability.0 += Em::new(0.125); - text.width += Em::new(0.25).at(glyph.size); } prev = Some(glyph); diff --git a/crates/typst-layout/src/inline/shaping.rs b/crates/typst-layout/src/inline/shaping.rs index 66987b2ad..cad1a3b4b 100644 --- a/crates/typst-layout/src/inline/shaping.rs +++ b/crates/typst-layout/src/inline/shaping.rs @@ -47,8 +47,6 @@ pub struct ShapedText<'a> { pub styles: StyleChain<'a>, /// The font variant. pub variant: FontVariant, - /// The width of the text's bounding box. - pub width: Abs, /// The shaped glyphs. pub glyphs: Cow<'a, [ShapedGlyph]>, } @@ -214,7 +212,7 @@ impl<'a> ShapedText<'a> { extra_justification: Abs, ) -> Frame { let (top, bottom) = self.measure(engine); - let size = Size::new(self.width, top + bottom); + let size = Size::new(self.width(), top + bottom); let mut offset = Abs::zero(); let mut frame = Frame::soft(size); @@ -332,6 +330,12 @@ impl<'a> ShapedText<'a> { frame } + /// Computes the width of a run of glyphs relative to the font size, + /// accounting for their individual scaling factors and other font metrics. + pub fn width(&self) -> Abs { + self.glyphs.iter().map(|g| g.x_advance.at(g.size)).sum() + } + /// Measure the top and bottom extent of this text. pub fn measure(&self, engine: &Engine) -> (Abs, Abs) { let mut top = Abs::zero(); @@ -419,7 +423,6 @@ impl<'a> ShapedText<'a> { region: self.region, styles: self.styles, variant: self.variant, - width: glyphs_width(glyphs), glyphs: Cow::Borrowed(glyphs), } } else { @@ -437,12 +440,7 @@ impl<'a> ShapedText<'a> { /// Derive an empty text run with the same properties as this one. pub fn empty(&self) -> Self { - Self { - text: "", - width: Abs::zero(), - glyphs: Cow::Borrowed(&[]), - ..*self - } + Self { text: "", glyphs: Cow::Borrowed(&[]), ..*self } } /// Creates shaped text containing a hyphen. @@ -485,7 +483,6 @@ impl<'a> ShapedText<'a> { region: base.region, styles: base.styles, variant: base.variant, - width: x_advance.at(size), glyphs: Cow::Owned(vec![ShapedGlyph { font, glyph_id: glyph_id.0, @@ -694,17 +691,10 @@ fn shape<'a>( region, styles, variant: ctx.variant, - width: glyphs_width(&ctx.glyphs), glyphs: Cow::Owned(ctx.glyphs), } } -/// Computes the width of a run of glyphs relative to the font size, accounting -/// for their individual scaling factors and other font metrics. -fn glyphs_width(glyphs: &[ShapedGlyph]) -> Abs { - glyphs.iter().map(|g| g.x_advance.at(g.size)).sum() -} - /// Holds shaping results and metadata common to all shaped segments. struct ShapingContext<'a, 'v> { engine: &'a Engine<'v>,