From e950ec56eb9e735688419a4b750ae5a93470176c Mon Sep 17 00:00:00 2001 From: Elena Torro Date: Tue, 5 May 2026 14:17:00 +0200 Subject: [PATCH] :zap: Reduce per-render text layout work --- render-wasm/src/render.rs | 27 +++++++++++++++++++-------- render-wasm/src/shapes/text.rs | 16 +++++++++------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 897fb733af..1b384e235d 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -26,7 +26,7 @@ use crate::error::{Error, Result}; use crate::performance; use crate::shapes::{ all_with_ancestors, radius_to_sigma, Blur, BlurType, Corners, Fill, Shadow, Shape, SolidColor, - Stroke, StrokeKind, Type, + Stroke, StrokeKind, TextContent, Type, }; use crate::state::{ShapesPoolMutRef, ShapesPoolRef}; use crate::tiles::{self, PendingTiles, TileRect}; @@ -1229,12 +1229,23 @@ impl RenderState { } } - Type::Text(text_content) => { + Type::Text(stored_text_content) => { self.surfaces.apply_mut(surface_ids, |s| { s.canvas().concat(&matrix); }); - let text_content = text_content.new_bounds(shape.selrect()); + // Skip the paragraph-cloning `new_bounds` when shape size is unchanged. + let selrect = shape.selrect(); + let stored_bounds = stored_text_content.bounds(); + let bounds_match = (stored_bounds.width() - selrect.width()).abs() < 0.01 + && (stored_bounds.height() - selrect.height()).abs() < 0.01; + let rebound_text_content = if bounds_match { + None + } else { + Some(stored_text_content.new_bounds(selrect)) + }; + let text_content: &TextContent = + rebound_text_content.as_ref().unwrap_or(stored_text_content); let count_inner_strokes = shape.count_visible_inner_strokes(); // Erode the main text fill by 1px when there are inner strokes, to avoid a visible seam at the glyph edge. let text_fill_inset = (count_inner_strokes > 0).then(|| 1.0 / self.get_scale()); @@ -1248,7 +1259,7 @@ impl RenderState { .rev() .map(|stroke| { text::stroke_paragraph_builder_group_from_text( - &text_content, + text_content, stroke, &shape.selrect(), None, @@ -1324,7 +1335,7 @@ impl RenderState { .rev() .map(|stroke| { text::stroke_paragraph_builder_group_from_text( - &text_content, + text_content, stroke, &shape.selrect(), Some(true), @@ -1357,7 +1368,7 @@ impl RenderState { &parent_shadows, &blur_filter, &stroke_kinds, - &text_content, + text_content, )?; } } else { @@ -1401,7 +1412,7 @@ impl RenderState { &drop_shadows, &blur_filter, &stroke_kinds, - &text_content, + text_content, )?; // 4. Stroke fills @@ -1453,7 +1464,7 @@ impl RenderState { &inner_shadows, &blur_filter, &stroke_kinds, - &text_content, + text_content, )?; // 6. Fill Inner shadows diff --git a/render-wasm/src/shapes/text.rs b/render-wasm/src/shapes/text.rs index 541747229a..1a99000692 100644 --- a/render-wasm/src/shapes/text.rs +++ b/render-wasm/src/shapes/text.rs @@ -1424,11 +1424,14 @@ pub fn calculate_text_layout_data( let mut previous_line_height = text_content.normalized_line_height(); let text_paragraphs = text_content.paragraphs(); - // 1. Calculate paragraph heights + // 1. Build + layout each paragraph once, recording heights as we go. let mut paragraph_heights: Vec = Vec::new(); + let mut built_groups: Vec> = + Vec::with_capacity(paragraph_builder_groups.len()); for paragraph_builder_group in paragraph_builder_groups.iter_mut() { let group_len = paragraph_builder_group.len(); let mut paragraph_offset_y = previous_line_height; + let mut group_paragraphs: Vec = Vec::with_capacity(group_len); for (builder_index, paragraph_builder) in paragraph_builder_group.iter_mut().enumerate() { let mut skia_paragraph = paragraph_builder.build(); skia_paragraph.layout(text_width); @@ -1442,11 +1445,13 @@ pub fn calculate_text_layout_data( if builder_index == 0 { paragraph_heights.push(skia_paragraph.height()); } + group_paragraphs.push(skia_paragraph); } previous_line_height = paragraph_offset_y; + built_groups.push(group_paragraphs); } - // 2. Calculate vertical offset and build paragraphs with positions + // 2. Position each built paragraph using the heights from step 1. let total_text_height: f32 = paragraph_heights.iter().sum(); let vertical_offset = match shape.vertical_align() { VerticalAlign::Center => (selrect_height - total_text_height) / 2.0, @@ -1455,12 +1460,9 @@ pub fn calculate_text_layout_data( }; let mut paragraph_layouts: Vec = Vec::new(); let mut y_accum = base_y + vertical_offset; - for (i, paragraph_builder_group) in paragraph_builder_groups.iter_mut().enumerate() { + for (i, group_paragraphs) in built_groups.into_iter().enumerate() { // For each paragraph in the group (e.g., fill, stroke, etc.) - for paragraph_builder in paragraph_builder_group.iter_mut() { - let mut skia_paragraph = paragraph_builder.build(); - skia_paragraph.layout(text_width); - + for skia_paragraph in group_paragraphs.into_iter() { let spans = if let Some(text_para) = text_paragraphs.get(i) { text_para.children().to_vec() } else {