diff --git a/render-wasm/src/shapes/modifiers.rs b/render-wasm/src/shapes/modifiers.rs index 895cf890c2..32752cbbc3 100644 --- a/render-wasm/src/shapes/modifiers.rs +++ b/render-wasm/src/shapes/modifiers.rs @@ -167,6 +167,7 @@ fn set_pixel_precision(transform: &mut Matrix, bounds: &mut Bounds) { } } +#[allow(clippy::too_many_arguments)] fn propagate_transform( entry: TransformEntry, pixel_precision: bool, @@ -175,6 +176,7 @@ fn propagate_transform( bounds: &mut HashMap, modifiers: &mut HashMap, reflown: &mut HashSet, + reflowed_shapes: &mut HashSet, ) -> Result<()> { let Some(shape) = state.shapes.get(&entry.id) else { return Ok(()); @@ -190,7 +192,11 @@ fn propagate_transform( if !is_close_to(shape_bounds_before.width(), shape_bounds_after.width()) || !is_close_to(shape_bounds_before.height(), shape_bounds_after.height()) { - if let Type::Text(text_content) = &mut shape.shape_type.clone() { + if let Type::Text(text_content) = &shape.shape_type { + let width_changed = + !is_close_to(shape_bounds_before.width(), shape_bounds_after.width()); + let height_changed = + !is_close_to(shape_bounds_before.height(), shape_bounds_after.height()); let resized_selrect = math::Rect::from_xywh( shape.selrect.left(), shape.selrect.top(), @@ -199,12 +205,15 @@ fn propagate_transform( ); match text_content.grow_type() { GrowType::AutoHeight => { - // For auto-height, always update layout when width changes - // because the new width affects how text wraps - let width_changed = - !is_close_to(shape_bounds_before.width(), shape_bounds_after.width()); - if width_changed || text_content.needs_update_layout() { - text_content.update_layout(resized_selrect); + let height_before = text_content.size.height; + let new_height = if width_changed { + let mut clone = text_content.clone(); + clone.update_layout(resized_selrect); + clone.size.height + } else { + height_before + }; + if !is_close_to(height_before, new_height) && reflowed_shapes.insert(shape.id) { entries.push_back(Modifier::reflow(shape.id, false)); if let Some(parent_id) = shape.parent_id { @@ -215,31 +224,28 @@ fn propagate_transform( } } } - let height = text_content.size.height; let resize_transform = math::resize_matrix( &shape_bounds_after, &shape_bounds_after, shape_bounds_after.width(), - height, + new_height, ); shape_bounds_after = shape_bounds_after.transform(&resize_transform); transform.post_concat(&resize_transform); } GrowType::AutoWidth => { - // For auto-width, always update layout when height changes - // because the new height affects how text flows - let height_changed = - !is_close_to(shape_bounds_before.height(), shape_bounds_after.height()); - if height_changed || text_content.needs_update_layout() { - text_content.update_layout(resized_selrect); - } - let width = text_content.width(); - let height = text_content.size.height; + let (new_width, new_height) = if height_changed { + let mut clone = text_content.clone(); + clone.update_layout(resized_selrect); + (clone.width(), clone.size.height) + } else { + (text_content.width(), text_content.size.height) + }; let resize_transform = math::resize_matrix( &shape_bounds_after, &shape_bounds_after, - width, - height, + new_width, + new_height, ); shape_bounds_after = shape_bounds_after.transform(&resize_transform); transform.post_concat(&resize_transform); @@ -404,6 +410,7 @@ pub fn propagate_modifiers( // In order for loop to eventualy finish, we limit the flex reflow to just // one (the reflown set). while !entries.is_empty() { + let mut reflowed_shapes = HashSet::::new(); while let Some(modifier) = entries.pop_front() { match modifier { Modifier::Transform(entry, pixel) => propagate_transform( @@ -414,6 +421,7 @@ pub fn propagate_modifiers( &mut bounds, &mut modifiers, &mut reflown, + &mut reflowed_shapes, )?, Modifier::Reflow(id, force_reflow) => { if force_reflow { diff --git a/render-wasm/src/shapes/text.rs b/render-wasm/src/shapes/text.rs index 98e0a4e6c9..e3a1037175 100644 --- a/render-wasm/src/shapes/text.rs +++ b/render-wasm/src/shapes/text.rs @@ -333,13 +333,26 @@ pub fn calculate_normalized_line_height( normalized_line_height } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, Clone)] pub struct TextContent { pub paragraphs: Vec, pub bounds: Rect, pub grow_type: GrowType, pub size: TextContentSize, pub layout: TextContentLayout, + content_version: u64, + layout_version: u64, + layout_width: Option, +} + +impl PartialEq for TextContent { + fn eq(&self, other: &Self) -> bool { + self.paragraphs == other.paragraphs + && self.bounds == other.bounds + && self.grow_type == other.grow_type + && self.size == other.size + && self.layout == other.layout + } } impl TextContent { @@ -350,6 +363,9 @@ impl TextContent { grow_type, size: TextContentSize::default(), layout: TextContentLayout::new(), + content_version: 0, + layout_version: 0, + layout_width: None, } } @@ -362,6 +378,9 @@ impl TextContent { grow_type, size: TextContentSize::new_with_size(bounds.width(), bounds.height()), layout: TextContentLayout::new(), + content_version: 0, + layout_version: 0, + layout_width: None, } } @@ -385,6 +404,7 @@ impl TextContent { pub fn add_paragraph(&mut self, paragraph: Paragraph) { self.paragraphs.push(paragraph); + self.content_version = self.content_version.wrapping_add(1); } pub fn paragraphs(&self) -> &[Paragraph] { @@ -392,6 +412,7 @@ impl TextContent { } pub fn paragraphs_mut(&mut self) -> &mut Vec { + self.content_version = self.content_version.wrapping_add(1); &mut self.paragraphs } @@ -408,7 +429,10 @@ impl TextContent { } pub fn set_grow_type(&mut self, grow_type: GrowType) { - self.grow_type = grow_type; + if self.grow_type != grow_type { + self.grow_type = grow_type; + self.content_version = self.content_version.wrapping_add(1); + } } /// Compute a tight text rect from laid-out Skia paragraphs using glyph @@ -891,6 +915,15 @@ impl TextContent { } pub fn update_layout(&mut self, selrect: Rect) -> TextContentSize { + if !self.layout.needs_update() + && self.layout_version == self.content_version + && self + .layout_width + .is_some_and(|w| (w - selrect.width()).abs() < f32::EPSILON) + { + return self.size; + } + self.size.set_size(selrect.width(), selrect.height()); match self.grow_type() { @@ -915,6 +948,9 @@ impl TextContent { self.size.max_width = placeholder_width; } + self.layout_version = self.content_version; + self.layout_width = Some(selrect.width()); + self.size } @@ -1048,6 +1084,9 @@ impl Default for TextContent { grow_type: GrowType::Fixed, size: TextContentSize::default(), layout: TextContentLayout::new(), + content_version: 0, + layout_version: 0, + layout_width: None, } } }