diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 756d23336e..8a9a5fab30 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -7,6 +7,7 @@ mod performance; mod render; mod shapes; mod state; +mod textlayout; mod tiles; mod utils; mod uuid; diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index f4e48e05a7..8ab8338cbd 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -24,6 +24,9 @@ pub use surfaces::{SurfaceId, Surfaces}; use crate::performance; use crate::shapes::{Blur, BlurType, Corners, Fill, Shape, StructureEntry, Type}; use crate::state::ShapesPool; +use crate::textlayout::{ + paragraph_builder_group_from_text, stroke_paragraph_builder_group_from_text, +}; use crate::tiles::{self, PendingTiles, TileRect}; use crate::uuid::Uuid; use crate::view::Viewbox; @@ -541,13 +544,18 @@ impl RenderState { let inner_shadows = shape.inner_shadow_paints(); let blur_filter = shape.image_filter(1.); let blur_mask = shape.mask_filter(1.); - let mut paragraphs = - text_content.to_paragraphs(blur_filter.as_ref(), blur_mask.as_ref(), None); + let mut paragraphs = paragraph_builder_group_from_text( + &text_content, + blur_filter.as_ref(), + blur_mask.as_ref(), + None, + ); // Render all drop shadows if there are no visible strokes if !shape.has_visible_strokes() && !drop_shadows.is_empty() { for drop_shadow in &drop_shadows { - let mut paragraphs_with_drop_shadows = text_content.to_paragraphs( + let mut paragraphs_with_drop_shadows = paragraph_builder_group_from_text( + &text_content, blur_filter.as_ref(), blur_mask.as_ref(), Some(drop_shadow), @@ -564,8 +572,9 @@ impl RenderState { text::render(self, &shape, &mut paragraphs, None); for stroke in shape.visible_strokes().rev() { for drop_shadow in &drop_shadows { - let mut stroke_paragraphs_with_drop_shadows = text_content - .to_stroke_paragraphs( + let mut stroke_paragraphs_with_drop_shadows = + stroke_paragraph_builder_group_from_text( + &text_content, stroke, &shape.selrect(), blur_filter.as_ref(), @@ -580,7 +589,8 @@ impl RenderState { ); } - let mut stroke_paragraphs = text_content.to_stroke_paragraphs( + let mut stroke_paragraphs = stroke_paragraph_builder_group_from_text( + &text_content, stroke, &shape.selrect(), blur_filter.as_ref(), @@ -600,8 +610,9 @@ impl RenderState { ); for inner_shadow in &inner_shadows { - let mut stroke_paragraphs_with_inner_shadows = text_content - .to_stroke_paragraphs( + let mut stroke_paragraphs_with_inner_shadows = + stroke_paragraph_builder_group_from_text( + &text_content, stroke, &shape.selrect(), blur_filter.as_ref(), @@ -618,7 +629,8 @@ impl RenderState { } for inner_shadow in &inner_shadows { - let mut paragraphs_with_inner_shadows = text_content.to_paragraphs( + let mut paragraphs_with_inner_shadows = paragraph_builder_group_from_text( + &text_content, blur_filter.as_ref(), blur_mask.as_ref(), Some(inner_shadow), diff --git a/render-wasm/src/render/text.rs b/render-wasm/src/render/text.rs index 593dd008ff..25e4a58c76 100644 --- a/render-wasm/src/render/text.rs +++ b/render-wasm/src/render/text.rs @@ -19,7 +19,7 @@ pub fn render( // Width let paragraph_width = if let crate::shapes::Type::Text(text_content) = &shape.shape_type { - text_content.get_width() + text_content.width() } else { shape.width() }; diff --git a/render-wasm/src/shapes/modifiers.rs b/render-wasm/src/shapes/modifiers.rs index 4c8e00682f..a74f201c3e 100644 --- a/render-wasm/src/shapes/modifiers.rs +++ b/render-wasm/src/shapes/modifiers.rs @@ -1,20 +1,20 @@ use std::collections::{HashMap, HashSet, VecDeque}; -pub mod common; + mod constraints; mod flex_layout; + +pub mod common; pub mod grid_layout; +use crate::math::{self as math, bools, identitish, Bounds, Matrix, Point}; use common::GetBounds; -use crate::math::bools; -use crate::math::{self as math, identitish, Bounds, Matrix, Point}; - use crate::shapes::{ - auto_height, ConstraintH, ConstraintV, Frame, Group, GrowType, Layout, Modifier, Shape, - StructureEntry, TransformEntry, Type, + ConstraintH, ConstraintV, Frame, Group, GrowType, Layout, Modifier, Shape, StructureEntry, + TransformEntry, Type, }; -use crate::state::ShapesPool; -use crate::state::State; +use crate::state::{ShapesPool, State}; +use crate::textlayout::{auto_height, paragraph_builder_group_from_text}; use crate::uuid::Uuid; #[allow(clippy::too_many_arguments)] @@ -200,7 +200,7 @@ fn propagate_transform( match content.grow_type() { GrowType::AutoHeight => { let paragraph_width = shape_bounds_after.width(); - let mut paragraphs = content.to_paragraphs(None, None, None); + let mut paragraphs = paragraph_builder_group_from_text(content, None, None, None); let height = auto_height(&mut paragraphs, paragraph_width); let resize_transform = math::resize_matrix( &shape_bounds_after, @@ -212,8 +212,8 @@ fn propagate_transform( transform.post_concat(&resize_transform); } GrowType::AutoWidth => { - let paragraph_width = content.get_width(); - let mut paragraphs = content.to_paragraphs(None, None, None); + let paragraph_width = content.width(); + let mut paragraphs = paragraph_builder_group_from_text(content, None, None, None); let height = auto_height(&mut paragraphs, paragraph_width); let resize_transform = math::resize_matrix( &shape_bounds_after, diff --git a/render-wasm/src/shapes/text.rs b/render-wasm/src/shapes/text.rs index 825a3c24e7..35fb4b42a8 100644 --- a/render-wasm/src/shapes/text.rs +++ b/render-wasm/src/shapes/text.rs @@ -1,19 +1,16 @@ use crate::{ math::{Matrix, Rect}, - render::{default_font, filters::compose_filters, DEFAULT_EMOJI_FONT}, + render::{default_font, DEFAULT_EMOJI_FONT}, + textlayout::paragraph_builder_group_from_text, }; -use skia_safe::{ - self as skia, - paint::Paint, - textlayout::{ParagraphBuilder, ParagraphStyle}, - ImageFilter, MaskFilter, -}; +use skia_safe::{self as skia, paint::Paint, textlayout::ParagraphStyle, ImageFilter, MaskFilter}; use std::collections::HashSet; use super::FontFamily; -use crate::shapes::{self, merge_fills, set_paint_fill, Stroke, StrokeKind}; -use crate::utils::{get_fallback_fonts, get_font_collection, uuid_from_u32}; +use crate::shapes::{self, merge_fills}; +use crate::textlayout::{auto_height, auto_width}; +use crate::utils::uuid_from_u32; use crate::wasm::fills::parse_fills_from_bytes; use crate::Uuid; @@ -42,30 +39,6 @@ pub struct TextContent { pub grow_type: GrowType, } -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 { pub fn new(bounds: Rect, grow_type: GrowType) -> Self { Self { @@ -85,13 +58,12 @@ impl TextContent { } } - pub fn set_xywh(&mut self, x: f32, y: f32, w: f32, h: f32) { - self.bounds = Rect::from_xywh(x, y, w, h); + pub fn bounds(&self) -> Rect { + self.bounds } - #[allow(dead_code)] - pub fn width(&self) -> f32 { - self.bounds.width() + pub fn set_xywh(&mut self, x: f32, y: f32, w: f32, h: f32) { + self.bounds = Rect::from_xywh(x, y, w, h); } #[allow(dead_code)] @@ -108,106 +80,17 @@ impl TextContent { self.paragraphs.push(paragraph); } - pub fn to_paragraphs( - &self, - blur: Option<&ImageFilter>, - blur_mask: Option<&MaskFilter>, - shadow: Option<&Paint>, - ) -> Vec> { - let fonts = get_font_collection(); - let fallback_fonts = get_fallback_fonts(); - let mut paragraph_group = Vec::new(); - - for paragraph in &self.paragraphs { - let paragraph_style = paragraph.paragraph_to_style(); - let mut builder = ParagraphBuilder::new(¶graph_style, fonts); - for leaf in ¶graph.children { - let text_style = - leaf.to_style(&self.bounds, fallback_fonts, blur, blur_mask, shadow); - let text = leaf.apply_text_transform(); - builder.push_style(&text_style); - builder.add_text(&text); - } - paragraph_group.push(vec![builder]); - } - - paragraph_group + pub fn paragraphs(&self) -> &[Paragraph] { + &self.paragraphs } - pub fn to_stroke_paragraphs( - &self, - stroke: &Stroke, - bounds: &Rect, - blur: Option<&ImageFilter>, - blur_mask: Option<&MaskFilter>, - shadow: Option<&Paint>, - count_inner_strokes: usize, - ) -> Vec> { - let fallback_fonts = get_fallback_fonts(); - let fonts = get_font_collection(); - let mut paragraph_group = Vec::new(); - - for paragraph in &self.paragraphs { - let mut stroke_paragraphs_map: std::collections::HashMap = - std::collections::HashMap::new(); - - for leaf in paragraph.children.iter() { - let mut text_paint = merge_fills(&leaf.fills, *bounds); - if let Some(blur_mask) = blur_mask { - text_paint.set_mask_filter(blur_mask.clone()); - } - - let stroke_paints = if shadow.is_some() { - get_text_stroke_paints_with_shadows( - stroke, - blur, - blur_mask, - shadow, - leaf.is_transparent(), - ) - } else { - get_text_stroke_paints( - stroke, - bounds, - &text_paint, - blur, - blur_mask, - count_inner_strokes, - ) - }; - - let text: String = leaf.apply_text_transform(); - - for (paint_idx, stroke_paint) in stroke_paints.iter().enumerate() { - let builder = stroke_paragraphs_map.entry(paint_idx).or_insert_with(|| { - let paragraph_style = paragraph.paragraph_to_style(); - ParagraphBuilder::new(¶graph_style, fonts) - }); - let stroke_paint = stroke_paint.clone(); - let stroke_style = - leaf.to_stroke_style(&stroke_paint, fallback_fonts, blur, blur_mask, None); - builder.push_style(&stroke_style); - builder.add_text(&text); - } - } - - let stroke_paragraphs: Vec = (0..stroke_paragraphs_map.len()) - .map(|i| stroke_paragraphs_map.remove(&i).unwrap()) - .collect(); - - paragraph_group.push(stroke_paragraphs); - } - - paragraph_group - } - - pub fn get_width(&self) -> f32 { + pub fn width(&self) -> f32 { if self.grow_type() == GrowType::AutoWidth { - let temp_paragraphs = self.to_paragraphs(None, None, None); + let temp_paragraphs = paragraph_builder_group_from_text(self, None, None, None); let mut temp_paragraphs = temp_paragraphs; auto_width(&mut temp_paragraphs, f32::MAX).ceil() } else { - self.width() + self.bounds.width() } } @@ -220,8 +103,8 @@ impl TextContent { } pub fn visual_bounds(&self) -> (f32, f32) { - let paragraph_width = self.get_width(); - let mut paragraphs = self.to_paragraphs(None, None, None); + let paragraph_width = self.width(); + let mut paragraphs = paragraph_builder_group_from_text(self, None, None, None); let paragraph_height = auto_height(&mut paragraphs, paragraph_width); (paragraph_width, paragraph_height) } @@ -311,8 +194,7 @@ impl Paragraph { self.children = children; } - #[allow(dead_code)] - pub fn get_children(&self) -> &Vec { + pub fn children(&self) -> &[TextLeaf] { &self.children } @@ -418,6 +300,10 @@ impl TextLeaf { } } + pub fn fills(&self) -> &[shapes::Fill] { + &self.fills + } + pub fn to_style( &self, content_bounds: &Rect, @@ -741,211 +627,3 @@ impl From<&Vec> for RawTextData { Self { paragraph } } } - -pub fn get_built_paragraphs( - paragraphs: &mut [Vec], - width: f32, -) -> Vec> { - build_paragraphs_with_width(paragraphs, width) -} - -pub fn auto_width(paragraphs: &mut [Vec], width: f32) -> f32 { - let built_paragraphs = get_built_paragraphs(paragraphs, width); - - built_paragraphs - .iter() - .flatten() - .fold(0.0, |auto_width, p| { - f32::max(p.max_intrinsic_width(), auto_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| { - let mut paragraph = paragraph.build(); - paragraph.layout(width); - auto_height + paragraph.height() - }) - }) -} - -fn get_text_stroke_paints_with_shadows( - stroke: &Stroke, - blur: Option<&ImageFilter>, - blur_mask: Option<&MaskFilter>, - shadow: Option<&Paint>, - is_transparent: bool, -) -> Vec { - let mut paints = Vec::new(); - - match stroke.kind { - StrokeKind::Inner => { - let mut paint = skia_safe::Paint::default(); - paint.set_style(skia::PaintStyle::Fill); - paint.set_anti_alias(true); - - if let Some(blur) = blur { - paint.set_image_filter(blur.clone()); - } - - if let Some(shadow) = shadow { - paint.set_image_filter(shadow.image_filter()); - } - - paints.push(paint.clone()); - - if is_transparent { - let image_filter = skia_safe::image_filters::erode( - (stroke.width, stroke.width), - paint.image_filter(), - None, - ); - paint.set_image_filter(image_filter); - paint.set_blend_mode(skia::BlendMode::DstOut); - paints.push(paint.clone()); - } - } - StrokeKind::Center => { - let mut paint = skia_safe::Paint::default(); - paint.set_anti_alias(true); - paint.set_stroke_width(stroke.width); - - if let Some(blur) = blur { - paint.set_image_filter(blur.clone()); - } - - if let Some(shadow) = shadow { - paint.set_image_filter(shadow.image_filter()); - } - - if is_transparent { - paint.set_style(skia::PaintStyle::Stroke); - } else { - paint.set_style(skia::PaintStyle::StrokeAndFill); - } - - paints.push(paint); - } - StrokeKind::Outer => { - let mut paint = skia_safe::Paint::default(); - paint.set_style(skia::PaintStyle::StrokeAndFill); - paint.set_anti_alias(true); - paint.set_stroke_width(stroke.width * 2.0); - - if let Some(blur_mask) = blur_mask { - paint.set_mask_filter(blur_mask.clone()); - } - - if let Some(shadow) = shadow { - paint.set_image_filter(shadow.image_filter()); - } - - paints.push(paint.clone()); - - if is_transparent { - let image_filter = skia_safe::image_filters::erode( - (stroke.width, stroke.width), - paint.image_filter(), - None, - ); - paint.set_image_filter(image_filter); - paint.set_blend_mode(skia::BlendMode::DstOut); - paints.push(paint.clone()); - } - } - } - paints -} - -fn get_text_stroke_paints( - stroke: &Stroke, - bounds: &Rect, - text_paint: &Paint, - blur: Option<&ImageFilter>, - blur_mask: Option<&MaskFilter>, - count_inner_strokes: usize, -) -> Vec { - let mut paints = Vec::new(); - - match stroke.kind { - StrokeKind::Inner => { - let shader = text_paint.shader(); - let mut is_opaque = true; - - if shader.is_some() { - is_opaque = shader.unwrap().is_opaque(); - } - - if is_opaque && count_inner_strokes == 1 { - let mut paint = text_paint.clone(); - paint.set_style(skia::PaintStyle::Fill); - paint.set_anti_alias(true); - if let Some(blur) = blur { - paint.set_image_filter(blur.clone()); - } - paints.push(paint); - let mut paint = skia::Paint::default(); - paint.set_style(skia::PaintStyle::Stroke); - paint.set_blend_mode(skia::BlendMode::SrcIn); - paint.set_anti_alias(true); - paint.set_stroke_width(stroke.width * 2.0); - set_paint_fill(&mut paint, &stroke.fill, bounds); - if let Some(blur) = blur { - paint.set_image_filter(blur.clone()); - } - paints.push(paint); - } else { - let mut paint = text_paint.clone(); - paint.set_style(skia::PaintStyle::Fill); - paint.set_anti_alias(false); - set_paint_fill(&mut paint, &stroke.fill, bounds); - paints.push(paint); - - let mut paint = skia::Paint::default(); - let image_filter = - skia_safe::image_filters::erode((stroke.width, stroke.width), None, None); - - let filter = compose_filters(blur, image_filter.as_ref()); - paint.set_image_filter(filter); - paint.set_anti_alias(false); - paint.set_blend_mode(skia::BlendMode::DstOut); - paints.push(paint); - } - } - StrokeKind::Center => { - let mut paint = skia::Paint::default(); - paint.set_style(skia::PaintStyle::Stroke); - paint.set_anti_alias(true); - paint.set_stroke_width(stroke.width); - - set_paint_fill(&mut paint, &stroke.fill, bounds); - if let Some(blur) = blur { - paint.set_image_filter(blur.clone()); - } - - paints.push(paint); - } - StrokeKind::Outer => { - let mut paint = skia::Paint::default(); - paint.set_style(skia::PaintStyle::Stroke); - paint.set_blend_mode(skia::BlendMode::DstOver); - paint.set_anti_alias(true); - paint.set_stroke_width(stroke.width * 2.0); - set_paint_fill(&mut paint, &stroke.fill, bounds); - if let Some(blur_mask) = blur_mask { - paint.set_mask_filter(blur_mask.clone()); - } - paints.push(paint); - - let mut paint = skia::Paint::default(); - paint.set_style(skia::PaintStyle::Fill); - paint.set_blend_mode(skia::BlendMode::Clear); - paint.set_color(skia::Color::TRANSPARENT); - paint.set_anti_alias(true); - paints.push(paint); - } - } - - paints -} diff --git a/render-wasm/src/shapes/text_paths.rs b/render-wasm/src/shapes/text_paths.rs index 9c12b86a83..0cf874cdb2 100644 --- a/render-wasm/src/shapes/text_paths.rs +++ b/render-wasm/src/shapes/text_paths.rs @@ -1,4 +1,4 @@ -use crate::shapes::text::TextContent; +use crate::{shapes::text::TextContent, textlayout::paragraph_builder_group_from_text}; use skia_safe::{ self as skia, textlayout::Paragraph as SkiaParagraph, FontMetrics, Point, Rect, TextBlob, }; @@ -20,7 +20,7 @@ impl TextPaths { let mut paths = Vec::new(); let mut offset_y = self.bounds.y(); - let mut paragraphs = self.to_paragraphs(None, None, None); + let mut paragraphs = paragraph_builder_group_from_text(&self.0, None, None, None); for paragraphs in paragraphs.iter_mut() { for paragraph_builder in paragraphs.iter_mut() { diff --git a/render-wasm/src/textlayout.rs b/render-wasm/src/textlayout.rs new file mode 100644 index 0000000000..fc69b4cc6e --- /dev/null +++ b/render-wasm/src/textlayout.rs @@ -0,0 +1,339 @@ +use skia_safe::{self as skia, textlayout::ParagraphBuilder, ImageFilter, MaskFilter, Paint, Rect}; + +use crate::{ + render::filters::compose_filters, + shapes::{merge_fills, set_paint_fill, Stroke, StrokeKind, TextContent}, + utils::{get_fallback_fonts, get_font_collection}, +}; + +pub fn auto_width(paragraphs: &mut [Vec], width: f32) -> f32 { + let built_paragraphs = get_built_paragraphs(paragraphs, width); + + built_paragraphs + .iter() + .flatten() + .fold(0.0, |auto_width, p| { + f32::max(p.max_intrinsic_width(), auto_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| { + let mut paragraph = paragraph.build(); + paragraph.layout(width); + auto_height + paragraph.height() + }) + }) +} + +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() +} + +type ParagraphBuilderGroup = Vec; + +pub fn paragraph_builder_group_from_text( + text_content: &TextContent, + blur: Option<&ImageFilter>, + blur_mask: Option<&MaskFilter>, + shadow: Option<&Paint>, +) -> Vec { + let fonts = get_font_collection(); + let fallback_fonts = get_fallback_fonts(); + let mut paragraph_group = Vec::new(); + + for paragraph in text_content.paragraphs() { + let paragraph_style = paragraph.paragraph_to_style(); + let mut builder = ParagraphBuilder::new(¶graph_style, fonts); + for leaf in paragraph.children() { + let text_style = leaf.to_style( + &text_content.bounds(), + fallback_fonts, + blur, + blur_mask, + shadow, + ); + let text = leaf.apply_text_transform(); + builder.push_style(&text_style); + builder.add_text(&text); + } + paragraph_group.push(vec![builder]); + } + + paragraph_group +} + +pub fn stroke_paragraph_builder_group_from_text( + text_content: &TextContent, + stroke: &Stroke, + bounds: &Rect, + blur: Option<&ImageFilter>, + blur_mask: Option<&MaskFilter>, + shadow: Option<&Paint>, + count_inner_strokes: usize, +) -> Vec { + let fallback_fonts = get_fallback_fonts(); + let fonts = get_font_collection(); + let mut paragraph_group = Vec::new(); + + for paragraph in text_content.paragraphs() { + let mut stroke_paragraphs_map: std::collections::HashMap = + std::collections::HashMap::new(); + + for leaf in paragraph.children().iter() { + let mut text_paint = merge_fills(leaf.fills(), *bounds); + if let Some(blur_mask) = blur_mask { + text_paint.set_mask_filter(blur_mask.clone()); + } + + let stroke_paints = if shadow.is_some() { + get_text_stroke_paints_with_shadows( + stroke, + blur, + blur_mask, + shadow, + leaf.is_transparent(), + ) + } else { + get_text_stroke_paints( + stroke, + bounds, + &text_paint, + blur, + blur_mask, + count_inner_strokes, + ) + }; + + let text: String = leaf.apply_text_transform(); + + for (paint_idx, stroke_paint) in stroke_paints.iter().enumerate() { + let builder = stroke_paragraphs_map.entry(paint_idx).or_insert_with(|| { + let paragraph_style = paragraph.paragraph_to_style(); + ParagraphBuilder::new(¶graph_style, fonts) + }); + let stroke_paint = stroke_paint.clone(); + let stroke_style = + leaf.to_stroke_style(&stroke_paint, fallback_fonts, blur, blur_mask, None); + builder.push_style(&stroke_style); + builder.add_text(&text); + } + } + + let stroke_paragraphs: Vec = (0..stroke_paragraphs_map.len()) + .map(|i| stroke_paragraphs_map.remove(&i).unwrap()) + .collect(); + + paragraph_group.push(stroke_paragraphs); + } + + paragraph_group +} + +fn get_built_paragraphs( + paragraphs: &mut [Vec], + width: f32, +) -> Vec> { + build_paragraphs_with_width(paragraphs, width) +} + +fn get_text_stroke_paints_with_shadows( + stroke: &Stroke, + blur: Option<&ImageFilter>, + blur_mask: Option<&MaskFilter>, + shadow: Option<&Paint>, + is_transparent: bool, +) -> Vec { + let mut paints = Vec::new(); + + match stroke.kind { + StrokeKind::Inner => { + let mut paint = Paint::default(); + paint.set_style(skia::PaintStyle::Fill); + paint.set_anti_alias(true); + + if let Some(blur) = blur { + paint.set_image_filter(blur.clone()); + } + + if let Some(shadow) = shadow { + paint.set_image_filter(shadow.image_filter()); + } + + paints.push(paint.clone()); + + if is_transparent { + let image_filter = skia_safe::image_filters::erode( + (stroke.width, stroke.width), + paint.image_filter(), + None, + ); + paint.set_image_filter(image_filter); + paint.set_blend_mode(skia::BlendMode::DstOut); + paints.push(paint.clone()); + } + } + StrokeKind::Center => { + let mut paint = skia_safe::Paint::default(); + paint.set_anti_alias(true); + paint.set_stroke_width(stroke.width); + + if let Some(blur) = blur { + paint.set_image_filter(blur.clone()); + } + + if let Some(shadow) = shadow { + paint.set_image_filter(shadow.image_filter()); + } + + if is_transparent { + paint.set_style(skia::PaintStyle::Stroke); + } else { + paint.set_style(skia::PaintStyle::StrokeAndFill); + } + + paints.push(paint); + } + StrokeKind::Outer => { + let mut paint = skia_safe::Paint::default(); + paint.set_style(skia::PaintStyle::StrokeAndFill); + paint.set_anti_alias(true); + paint.set_stroke_width(stroke.width * 2.0); + + if let Some(blur_mask) = blur_mask { + paint.set_mask_filter(blur_mask.clone()); + } + + if let Some(shadow) = shadow { + paint.set_image_filter(shadow.image_filter()); + } + + paints.push(paint.clone()); + + if is_transparent { + let image_filter = skia_safe::image_filters::erode( + (stroke.width, stroke.width), + paint.image_filter(), + None, + ); + paint.set_image_filter(image_filter); + paint.set_blend_mode(skia::BlendMode::DstOut); + paints.push(paint.clone()); + } + } + } + paints +} + +fn get_text_stroke_paints( + stroke: &Stroke, + bounds: &Rect, + text_paint: &Paint, + blur: Option<&ImageFilter>, + blur_mask: Option<&MaskFilter>, + count_inner_strokes: usize, +) -> Vec { + let mut paints = Vec::new(); + + match stroke.kind { + StrokeKind::Inner => { + let shader = text_paint.shader(); + let mut is_opaque = true; + + if shader.is_some() { + is_opaque = shader.unwrap().is_opaque(); + } + + if is_opaque && count_inner_strokes == 1 { + let mut paint = text_paint.clone(); + paint.set_style(skia::PaintStyle::Fill); + paint.set_anti_alias(true); + if let Some(blur) = blur { + paint.set_image_filter(blur.clone()); + } + paints.push(paint); + let mut paint = skia::Paint::default(); + paint.set_style(skia::PaintStyle::Stroke); + paint.set_blend_mode(skia::BlendMode::SrcIn); + paint.set_anti_alias(true); + paint.set_stroke_width(stroke.width * 2.0); + set_paint_fill(&mut paint, &stroke.fill, bounds); + if let Some(blur) = blur { + paint.set_image_filter(blur.clone()); + } + paints.push(paint); + } else { + let mut paint = text_paint.clone(); + paint.set_style(skia::PaintStyle::Fill); + paint.set_anti_alias(false); + set_paint_fill(&mut paint, &stroke.fill, bounds); + paints.push(paint); + + let mut paint = skia::Paint::default(); + let image_filter = + skia_safe::image_filters::erode((stroke.width, stroke.width), None, None); + + let filter = compose_filters(blur, image_filter.as_ref()); + paint.set_image_filter(filter); + paint.set_anti_alias(false); + paint.set_blend_mode(skia::BlendMode::DstOut); + paints.push(paint); + } + } + StrokeKind::Center => { + let mut paint = skia::Paint::default(); + paint.set_style(skia::PaintStyle::Stroke); + paint.set_anti_alias(true); + paint.set_stroke_width(stroke.width); + + set_paint_fill(&mut paint, &stroke.fill, bounds); + if let Some(blur) = blur { + paint.set_image_filter(blur.clone()); + } + + paints.push(paint); + } + StrokeKind::Outer => { + let mut paint = skia::Paint::default(); + paint.set_style(skia::PaintStyle::Stroke); + paint.set_blend_mode(skia::BlendMode::DstOver); + paint.set_anti_alias(true); + paint.set_stroke_width(stroke.width * 2.0); + set_paint_fill(&mut paint, &stroke.fill, bounds); + if let Some(blur_mask) = blur_mask { + paint.set_mask_filter(blur_mask.clone()); + } + paints.push(paint); + + let mut paint = skia::Paint::default(); + paint.set_style(skia::PaintStyle::Fill); + paint.set_blend_mode(skia::BlendMode::Clear); + paint.set_color(skia::Color::TRANSPARENT); + paint.set_anti_alias(true); + paints.push(paint); + } + } + + paints +} diff --git a/render-wasm/src/wasm/text.rs b/render-wasm/src/wasm/text.rs index 28976c66ff..e6cd77ded5 100644 --- a/render-wasm/src/wasm/text.rs +++ b/render-wasm/src/wasm/text.rs @@ -1,8 +1,9 @@ use crate::mem; -use crate::shapes::{auto_height, build_paragraphs_with_width, GrowType, RawTextData, Type}; - -use crate::STATE; -use crate::{with_current_shape, with_current_shape_mut}; +use crate::shapes::{GrowType, RawTextData, Type}; +use crate::textlayout::{ + auto_height, build_paragraphs_with_width, paragraph_builder_group_from_text, +}; +use crate::{with_current_shape, with_current_shape_mut, STATE}; #[no_mangle] pub extern "C" fn clear_shape_text() { @@ -44,8 +45,8 @@ pub extern "C" fn get_text_dimensions() -> *mut u8 { if let Type::Text(content) = &shape.shape_type { // 1. Reset Paragraphs - let paragraph_width = content.get_width(); - let mut paragraphs = content.to_paragraphs(None, None, None); + let paragraph_width = content.width(); + let mut paragraphs = paragraph_builder_group_from_text(content, None, None, None); let built_paragraphs = build_paragraphs_with_width(&mut paragraphs, paragraph_width); // 2. Max Width Calculation @@ -57,12 +58,14 @@ pub extern "C" fn get_text_dimensions() -> *mut u8 { // 3. Width and Height Calculation match content.grow_type() { GrowType::AutoHeight => { - let mut paragraph_height = content.to_paragraphs(None, None, None); + let mut paragraph_height = + paragraph_builder_group_from_text(content, None, None, None); height = auto_height(&mut paragraph_height, paragraph_width).ceil(); } GrowType::AutoWidth => { width = paragraph_width; - let mut paragraph_height = content.to_paragraphs(None, None, None); + let mut paragraph_height = + paragraph_builder_group_from_text(content, None, None, None); height = auto_height(&mut paragraph_height, paragraph_width).ceil(); } GrowType::Fixed => {}