diff --git a/frontend/src/app/render_wasm/serializers.cljs b/frontend/src/app/render_wasm/serializers.cljs index 32c5e20854..cd5feb080f 100644 --- a/frontend/src/app/render_wasm/serializers.cljs +++ b/frontend/src/app/render_wasm/serializers.cljs @@ -295,7 +295,8 @@ "uppercase" 1 "lowercase" 2 "capitalize" 3 - nil)) + nil 0 + 0)) (defn translate-text-decoration [text-decoration] @@ -304,13 +305,15 @@ "underline" 1 "line-through" 2 "overline" 3 - nil)) + nil 0 + 0)) (defn translate-text-direction [text-direction] (case text-direction "ltr" 0 "rtl" 1 + nil 0 0)) (defn translate-font-style diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index a836135063..8773e7527f 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -511,7 +511,7 @@ impl RenderState { }); let text_content = text_content.new_bounds(shape.selrect()); - let mut paragraphs = text_content.get_skia_paragraphs( + let mut paragraphs = text_content.to_paragraphs( shape.image_filter(1.).as_ref(), shape.mask_filter(1.).as_ref(), ); @@ -524,7 +524,7 @@ impl RenderState { text::render(self, &shape, &mut paragraphs, None, None); for stroke in shape.visible_strokes().rev() { - let mut stroke_paragraphs = text_content.get_skia_stroke_paragraphs( + let mut stroke_paragraphs = text_content.to_stroke_paragraphs( stroke, &shape.selrect(), shape.image_filter(1.).as_ref(), diff --git a/render-wasm/src/render/text.rs b/render-wasm/src/render/text.rs index ff33d0cab1..cf597fbf8d 100644 --- a/render-wasm/src/render/text.rs +++ b/render-wasm/src/render/text.rs @@ -14,10 +14,17 @@ pub fn render( let canvas = render_state .surfaces .canvas(surface_id.unwrap_or(SurfaceId::Fills)); - let container_height = shape.selrect().height(); - // Calculate total height for vertical alignment - let total_content_height = calculate_all_paragraphs_height(paragraphs, shape.bounds().width()); + // Width + let paragraph_width = if let crate::shapes::Type::Text(text_content) = &shape.shape_type { + text_content.get_width() + } else { + shape.width() + }; + + // Height + let container_height = shape.selrect().height(); + let total_content_height = calculate_all_paragraphs_height(paragraphs, paragraph_width); let mut global_offset_y = match shape.vertical_align() { VerticalAlign::Center => (container_height - total_content_height) / 2.0, VerticalAlign::Bottom => container_height - total_content_height, @@ -48,8 +55,7 @@ pub fn render( continue; } - skia_paragraph.layout(shape.bounds().width()); - + skia_paragraph.layout(paragraph_width); let paragraph_height = skia_paragraph.height(); let xy = (shape.selrect().x(), shape.selrect().y() + group_offset_y); skia_paragraph.paint(canvas, xy); @@ -126,7 +132,7 @@ pub fn render( // For stroke groups (multiple elements), increment global_offset_y once per group if group_len > 1 { let mut first_paragraph = group[0].build(); - first_paragraph.layout(shape.bounds().width()); + first_paragraph.layout(paragraph_width); global_offset_y += first_paragraph.height(); } else { // For regular paragraphs, global_offset_y was already incremented inside the loop diff --git a/render-wasm/src/shapes/modifiers.rs b/render-wasm/src/shapes/modifiers.rs index 6941245d78..e90b8e2d0c 100644 --- a/render-wasm/src/shapes/modifiers.rs +++ b/render-wasm/src/shapes/modifiers.rs @@ -10,8 +10,8 @@ use crate::math::bools; use crate::math::{self as math, identitish, Bounds, Matrix, Point}; use crate::shapes::{ - auto_height, set_paragraphs_width, ConstraintH, ConstraintV, Frame, Group, GrowType, Layout, - Modifier, Shape, StructureEntry, TransformEntry, Type, + auto_height, ConstraintH, ConstraintV, Frame, Group, GrowType, Layout, Modifier, Shape, + StructureEntry, TransformEntry, Type, }; use crate::state::ShapesPool; use crate::state::State; @@ -197,18 +197,34 @@ fn propagate_transform( let mut transform = entry.transform; if let Type::Text(content) = &shape.shape_type { - if content.grow_type() == GrowType::AutoHeight { - let mut paragraphs = content.get_skia_paragraphs(None, None); - set_paragraphs_width(shape_bounds_after.width(), &mut paragraphs); - let height = auto_height(&mut paragraphs, shape_bounds_after.width()); - let resize_transform = math::resize_matrix( - &shape_bounds_after, - &shape_bounds_after, - shape_bounds_after.width(), - height, - ); - shape_bounds_after = shape_bounds_after.transform(&resize_transform); - transform.post_concat(&resize_transform); + match content.grow_type() { + GrowType::AutoHeight => { + let paragraph_width = shape_bounds_after.width(); + let mut paragraphs = content.to_paragraphs(None, None); + let height = auto_height(&mut paragraphs, paragraph_width); + let resize_transform = math::resize_matrix( + &shape_bounds_after, + &shape_bounds_after, + shape_bounds_after.width(), + height, + ); + shape_bounds_after = shape_bounds_after.transform(&resize_transform); + transform.post_concat(&resize_transform); + } + GrowType::AutoWidth => { + let paragraph_width = content.get_width(); + let mut paragraphs = content.to_paragraphs(None, None); + let height = auto_height(&mut paragraphs, paragraph_width); + let resize_transform = math::resize_matrix( + &shape_bounds_after, + &shape_bounds_after, + paragraph_width, + height, + ); + shape_bounds_after = shape_bounds_after.transform(&resize_transform); + transform.post_concat(&resize_transform); + } + GrowType::Fixed => {} } } diff --git a/render-wasm/src/shapes/text.rs b/render-wasm/src/shapes/text.rs index 536c340f35..10d53023be 100644 --- a/render-wasm/src/shapes/text.rs +++ b/render-wasm/src/shapes/text.rs @@ -41,14 +41,28 @@ pub struct TextContent { pub grow_type: GrowType, } -pub fn set_paragraphs_width(width: f32, paragraphs: &mut Vec>) { - for group in paragraphs { - for p in group { - let mut paragraph = p.build(); - paragraph.layout(f32::MAX); - paragraph.layout(f32::max(width, paragraph.min_intrinsic_width().ceil())); - } - } +pub fn build_paragraphs_with_width( + paragraphs: &mut [Vec], + width: f32, +) -> Vec> { + paragraphs + .iter_mut() + .map(|builders| { + builders + .iter_mut() + .map(|builder| { + let mut paragraph = builder.build(); + // For auto-width, always layout with infinite width first to get intrinsic width + paragraph.layout(f32::MAX); + let intrinsic_width = paragraph.max_intrinsic_width().ceil(); + // Use the larger of the requested width or intrinsic width to prevent line breaks + let final_width = f32::max(width, intrinsic_width); + paragraph.layout(final_width); + paragraph + }) + .collect() + }) + .collect() } impl TextContent { @@ -177,43 +191,14 @@ impl TextContent { paragraph_group } - pub fn collect_paragraphs( - &self, - mut paragraphs: Vec>, - ) -> Vec> { + pub fn get_width(&self) -> f32 { if self.grow_type() == GrowType::AutoWidth { - set_paragraphs_width(f32::MAX, &mut paragraphs); - let max_width = auto_width(&mut paragraphs, self.width()).ceil(); - set_paragraphs_width(max_width, &mut paragraphs); + let temp_paragraphs = self.to_paragraphs(None, None); + let mut temp_paragraphs = temp_paragraphs; + auto_width(&mut temp_paragraphs, f32::MAX).ceil() } else { - set_paragraphs_width(self.width(), &mut paragraphs); + self.width() } - paragraphs - } - - pub fn get_skia_paragraphs( - &self, - blur: Option<&ImageFilter>, - blur_mask: Option<&MaskFilter>, - ) -> Vec> { - self.collect_paragraphs(self.to_paragraphs(blur, blur_mask)) - } - - pub fn get_skia_stroke_paragraphs( - &self, - stroke: &Stroke, - bounds: &Rect, - blur: Option<&ImageFilter>, - blur_mask: Option<&MaskFilter>, - count_inner_strokes: usize, - ) -> Vec> { - self.collect_paragraphs(self.to_stroke_paragraphs( - stroke, - bounds, - blur, - blur_mask, - count_inner_strokes, - )) } pub fn grow_type(&self) -> GrowType { @@ -225,9 +210,10 @@ impl TextContent { } pub fn visual_bounds(&self) -> (f32, f32) { + let paragraph_width = self.get_width(); let mut paragraphs = self.to_paragraphs(None, None); - let height = auto_height(&mut paragraphs, self.width()); - (self.width(), height) + let paragraph_height = auto_height(&mut paragraphs, paragraph_width); + (paragraph_width, paragraph_height) } pub fn transform(&mut self, transform: &Matrix) { @@ -728,19 +714,7 @@ pub fn get_built_paragraphs( paragraphs: &mut [Vec], width: f32, ) -> Vec> { - paragraphs - .iter_mut() - .map(|builders| { - builders - .iter_mut() - .map(|builder_handle| { - let mut paragraph = builder_handle.build(); - paragraph.layout(width); - paragraph - }) - .collect() - }) - .collect() + build_paragraphs_with_width(paragraphs, width) } pub fn auto_width(paragraphs: &mut [Vec], width: f32) -> f32 { @@ -754,15 +728,6 @@ pub fn auto_width(paragraphs: &mut [Vec], width: f32) -> f32 { }) } -pub fn max_width(paragraphs: &mut [Vec], width: f32) -> f32 { - let built_paragraphs = get_built_paragraphs(paragraphs, width); - - built_paragraphs - .iter() - .flatten() - .fold(0.0, |max_width, p| f32::max(p.max_width(), max_width)) -} - pub fn auto_height(paragraphs: &mut [Vec], width: f32) -> f32 { paragraphs.iter_mut().fold(0.0, |auto_height, p| { p.iter_mut().fold(auto_height, |auto_height, paragraph| { diff --git a/render-wasm/src/shapes/text_paths.rs b/render-wasm/src/shapes/text_paths.rs index c7e14d6070..11c987055e 100644 --- a/render-wasm/src/shapes/text_paths.rs +++ b/render-wasm/src/shapes/text_paths.rs @@ -1,7 +1,6 @@ use crate::shapes::text::TextContent; use skia_safe::{ - self as skia, textlayout::Paragraph as SkiaParagraph, textlayout::ParagraphBuilder, - FontMetrics, Point, Rect, TextBlob, + self as skia, textlayout::Paragraph as SkiaParagraph, FontMetrics, Point, Rect, TextBlob, }; use std::ops::Deref; @@ -17,16 +16,11 @@ impl TextPaths { Self(content) } - pub fn get_skia_paragraphs(&self) -> Vec> { - let paragraphs = self.to_paragraphs(None, None); - self.collect_paragraphs(paragraphs) - } - pub fn get_paths(&self, antialias: bool) -> Vec<(skia::Path, skia::Paint)> { let mut paths = Vec::new(); let mut offset_y = self.bounds.y(); - let mut paragraphs = self.get_skia_paragraphs(); + let mut paragraphs = self.to_paragraphs(None, None); for paragraphs in paragraphs.iter_mut() { for paragraph_builder in paragraphs.iter_mut() { diff --git a/render-wasm/src/wasm/text.rs b/render-wasm/src/wasm/text.rs index f581d06933..06096bf0e8 100644 --- a/render-wasm/src/wasm/text.rs +++ b/render-wasm/src/wasm/text.rs @@ -1,5 +1,5 @@ use crate::mem; -use crate::shapes::{auto_height, auto_width, max_width, GrowType, RawTextData, Type}; +use crate::shapes::{auto_height, build_paragraphs_with_width, GrowType, RawTextData, Type}; use crate::STATE; use crate::{with_current_shape, with_current_shape_mut}; @@ -43,19 +43,27 @@ pub extern "C" fn get_text_dimensions() -> *mut u8 { height = shape.selrect.height(); if let Type::Text(content) = &shape.shape_type { - let mut paragraphs = content.get_skia_paragraphs( - shape.image_filter(1.).as_ref(), - shape.mask_filter(1.).as_ref(), - ); - m_width = max_width(&mut paragraphs, width); + // 1. Reset Paragraphs + let paragraph_width = content.get_width(); + let mut paragraphs = content.to_paragraphs(None, None); + let built_paragraphs = build_paragraphs_with_width(&mut paragraphs, paragraph_width); + // 2. Max Width Calculation + m_width = built_paragraphs + .iter() + .flatten() + .fold(0.0, |max_width, p| f32::max(p.max_width(), max_width)); + + // 3. Width and Height Calculation match content.grow_type() { GrowType::AutoHeight => { - height = auto_height(&mut paragraphs, width).ceil(); + let mut paragraph_height = content.to_paragraphs(None, None); + height = auto_height(&mut paragraph_height, paragraph_width).ceil(); } GrowType::AutoWidth => { - width = auto_width(&mut paragraphs, width).ceil(); - height = auto_height(&mut paragraphs, width).ceil(); + width = paragraph_width; + let mut paragraph_height = content.to_paragraphs(None, None); + height = auto_height(&mut paragraph_height, paragraph_width).ceil(); } GrowType::Fixed => {} }