diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 5066370c0b..1fad158a07 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -460,37 +460,44 @@ impl RenderState { }); let text_content = text_content.new_bounds(shape.selrect()); - let paragraphs = text_content.get_skia_paragraphs(self.fonts.font_collection()); + let mut paragraphs = text_content.get_skia_paragraphs(); - shadows::render_text_drop_shadows(self, &shape, ¶graphs, antialias); - text::render(self, &shape, ¶graphs, None); + shadows::render_text_drop_shadows(self, &shape, &mut paragraphs, antialias); + text::render(self, &shape, &mut paragraphs, None); if shape.has_inner_strokes() { // Inner strokes paints need the text fill to apply correctly their blend modes // (e.g., SrcATop, DstOver) - text::render(self, &shape, ¶graphs, Some(SurfaceId::Strokes)); + text::render(self, &shape, &mut paragraphs, Some(SurfaceId::Strokes)); } for stroke in shape.strokes().rev() { - let stroke_paragraphs = text_content.get_skia_stroke_paragraphs( - stroke, - &shape.selrect(), - self.fonts.font_collection(), + let mut stroke_paragraphs = + text_content.get_skia_stroke_paragraphs(stroke, &shape.selrect()); + shadows::render_text_drop_shadows( + self, + &shape, + &mut stroke_paragraphs, + antialias, ); - shadows::render_text_drop_shadows(self, &shape, &stroke_paragraphs, antialias); strokes::render( self, &shape, stroke, None, None, - Some(&stroke_paragraphs), + Some(&mut stroke_paragraphs), + antialias, + ); + shadows::render_text_inner_shadows( + self, + &shape, + &mut stroke_paragraphs, antialias, ); - shadows::render_text_inner_shadows(self, &shape, &stroke_paragraphs, antialias); } - shadows::render_text_inner_shadows(self, &shape, ¶graphs, antialias); + shadows::render_text_inner_shadows(self, &shape, &mut paragraphs, antialias); } _ => { let surface_ids = SurfaceId::Strokes as u32 diff --git a/render-wasm/src/render/shadows.rs b/render-wasm/src/render/shadows.rs index c59b9507af..a8dd60183b 100644 --- a/render-wasm/src/render/shadows.rs +++ b/render-wasm/src/render/shadows.rs @@ -2,7 +2,8 @@ use super::{RenderState, SurfaceId}; use crate::render::strokes; use crate::render::text::{self}; use crate::shapes::{Shadow, Shape, Stroke, Type}; -use skia_safe::{canvas::SaveLayerRec, textlayout::Paragraph, Paint, Path}; +use skia_safe::textlayout::ParagraphBuilder; +use skia_safe::{canvas::SaveLayerRec, Paint, Path}; // Fill Shadows pub fn render_fill_drop_shadows(render_state: &mut RenderState, shape: &Shape, antialias: bool) { @@ -88,7 +89,7 @@ pub fn render_stroke_inner_shadows( pub fn render_text_drop_shadows( render_state: &mut RenderState, shape: &Shape, - paragraphs: &[Vec], + paragraphs: &mut [ParagraphBuilder], antialias: bool, ) { for shadow in shape.drop_shadows().rev().filter(|s| !s.hidden()) { @@ -123,7 +124,7 @@ pub fn render_text_drop_shadow( render_state: &mut RenderState, shape: &Shape, shadow: &Shadow, - paragraphs: &[Vec], + paragraphs: &mut [ParagraphBuilder], antialias: bool, ) { let paint = shadow.get_drop_shadow_paint(antialias); @@ -145,7 +146,7 @@ pub fn render_text_drop_shadow( pub fn render_text_inner_shadows( render_state: &mut RenderState, shape: &Shape, - paragraphs: &[Vec], + paragraphs: &mut [ParagraphBuilder], antialias: bool, ) { for shadow in shape.inner_shadows().rev().filter(|s| !s.hidden()) { @@ -157,7 +158,7 @@ pub fn render_text_inner_shadow( render_state: &mut RenderState, shape: &Shape, shadow: &Shadow, - paragraphs: &[Vec], + paragraphs: &mut [ParagraphBuilder], antialias: bool, ) { let paint = shadow.get_inner_shadow_paint(antialias); diff --git a/render-wasm/src/render/strokes.rs b/render-wasm/src/render/strokes.rs index 87c5f1bbb1..eee72a9d1a 100644 --- a/render-wasm/src/render/strokes.rs +++ b/render-wasm/src/render/strokes.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use crate::math::{Matrix, Point, Rect}; use crate::shapes::{Corners, Fill, ImageFill, Path, Shape, Stroke, StrokeCap, StrokeKind, Type}; -use skia_safe::{self as skia, textlayout::Paragraph, ImageFilter, RRect}; +use skia_safe::{self as skia, textlayout::ParagraphBuilder, ImageFilter, RRect}; use super::{RenderState, SurfaceId}; use crate::render::text::{self}; @@ -485,7 +485,7 @@ pub fn render( stroke: &Stroke, surface_id: Option, shadow: Option<&ImageFilter>, - paragraphs: Option<&[Vec]>, + paragraphs: Option<&mut [ParagraphBuilder]>, antialias: bool, ) { let scale = render_state.get_scale(); diff --git a/render-wasm/src/render/text.rs b/render-wasm/src/render/text.rs index cb53e3c906..ef47725e62 100644 --- a/render-wasm/src/render/text.rs +++ b/render-wasm/src/render/text.rs @@ -1,11 +1,11 @@ use super::{RenderState, Shape, SurfaceId}; use crate::shapes::VerticalAlign; -use skia_safe::{textlayout::Paragraph, FontMetrics, Paint, Path}; +use skia_safe::{textlayout::ParagraphBuilder, FontMetrics, Paint, Path}; pub fn render( render_state: &mut RenderState, shape: &Shape, - paragraphs: &[Vec], + paragraphs: &mut [ParagraphBuilder], surface_id: Option, ) { let canvas = render_state @@ -14,78 +14,70 @@ pub fn render( let container_height = shape.selrect().height(); - for group in paragraphs { - let total_paragraphs_height: f32 = group.iter().map(|p| p.height()).sum(); + for builder in paragraphs { + let mut skia_paragraph = builder.build(); + skia_paragraph.layout(shape.bounds().width()); + let paragraph_height: f32 = skia_paragraph.height(); - let mut offset_y = match shape.vertical_align() { - VerticalAlign::Center => (container_height - total_paragraphs_height) / 2.0, - VerticalAlign::Bottom => container_height - total_paragraphs_height, + let offset_y = match shape.vertical_align() { + VerticalAlign::Center => (container_height - paragraph_height) / 2.0, + VerticalAlign::Bottom => container_height - paragraph_height, _ => 0.0, }; - let mut offset_lines_y = offset_y; + let xy = (shape.selrect().x(), shape.selrect().y() + offset_y); + skia_paragraph.paint(canvas, xy); - for skia_paragraph in group { - let xy = (shape.selrect().x(), shape.selrect().y() + offset_y); - skia_paragraph.paint(canvas, xy); - offset_y += skia_paragraph.height(); - } + for line_metrics in skia_paragraph.get_line_metrics().iter() { + let style_metrics: Vec<_> = line_metrics + .get_style_metrics(line_metrics.start_index..line_metrics.end_index) + .into_iter() + .collect(); - for skia_paragraph in group { - let xy = (shape.selrect().x(), shape.selrect().y() + offset_lines_y); + let mut current_x_offset = 0.0; + let total_line_width = line_metrics.width as f32; + let total_chars = line_metrics.end_index - line_metrics.start_index; - for line_metrics in skia_paragraph.get_line_metrics().iter() { - let style_metrics: Vec<_> = line_metrics - .get_style_metrics(line_metrics.start_index..line_metrics.end_index) - .into_iter() - .collect(); + for (i, (index, style_metric)) in style_metrics.iter().enumerate() { + let text_style = style_metric.text_style; + let font_metrics = style_metric.font_metrics; + let next_index = style_metrics + .get(i + 1) + .map(|(next_i, _)| *next_i) + .unwrap_or(line_metrics.end_index); + let char_count = next_index - index; + let segment_width = if total_chars > 0 { + (char_count as f32 / total_chars as f32) * total_line_width + } else { + char_count as f32 * font_metrics.avg_char_width + }; - let mut current_x_offset = 0.0; - let total_line_width = line_metrics.width as f32; - let total_chars = line_metrics.end_index - line_metrics.start_index; + if text_style.decoration().ty + != skia_safe::textlayout::TextDecoration::NO_DECORATION + { + let decoration_type = text_style.decoration().ty; + let text_left = xy.0 + current_x_offset; + let text_top = xy.1 + line_metrics.baseline as f32 - line_metrics.ascent as f32; + let text_width = segment_width; + let line_height = line_metrics.height as f32; - for (i, (index, style_metric)) in style_metrics.iter().enumerate() { - let text_style = style_metric.text_style; - let font_metrics = style_metric.font_metrics; - let next_index = style_metrics - .get(i + 1) - .map(|(next_i, _)| *next_i) - .unwrap_or(line_metrics.end_index); - let char_count = next_index - index; - let segment_width = if total_chars > 0 { - (char_count as f32 / total_chars as f32) * total_line_width - } else { - char_count as f32 * font_metrics.avg_char_width - }; + let r = calculate_text_decoration_rect( + decoration_type, + font_metrics, + text_left, + text_top, + text_width, + line_height, + ); - if text_style.decoration().ty - != skia_safe::textlayout::TextDecoration::NO_DECORATION - { - let decoration_type = text_style.decoration().ty; - let text_left = xy.0 + current_x_offset; - let text_top = - xy.1 + line_metrics.baseline as f32 - line_metrics.ascent as f32; - let text_width = segment_width; - let line_height = line_metrics.height as f32; - - let r = calculate_text_decoration_rect( - decoration_type, - font_metrics, - text_left, - text_top, - text_width, - line_height, - ); - if let Some(decoration_rect) = r { - let decoration_paint = text_style.foreground().clone(); - canvas.draw_rect(decoration_rect, &decoration_paint); - } + if let Some(decoration_rect) = r { + let decoration_paint = text_style.foreground(); + canvas.draw_rect(decoration_rect, &decoration_paint); } - - current_x_offset += segment_width; } + + current_x_offset += segment_width; } - offset_lines_y += skia_paragraph.height(); } } } diff --git a/render-wasm/src/shapes/modifiers.rs b/render-wasm/src/shapes/modifiers.rs index 464e728626..0e0c59bd7a 100644 --- a/render-wasm/src/shapes/modifiers.rs +++ b/render-wasm/src/shapes/modifiers.rs @@ -164,8 +164,6 @@ fn propagate_transform( }; let shapes = &state.shapes; - let font_col = state.render_state.fonts.font_collection(); - let shape_bounds_before = bounds.find(shape); let mut shape_bounds_after = shape_bounds_before.transform(&entry.transform); @@ -173,9 +171,9 @@ fn propagate_transform( if let Type::Text(content) = &shape.shape_type { if content.grow_type() == GrowType::AutoHeight { - let mut paragraphs = content.get_skia_paragraphs(font_col); + let mut paragraphs = content.get_skia_paragraphs(); set_paragraphs_width(shape_bounds_after.width(), &mut paragraphs); - let height = auto_height(¶graphs); + let height = auto_height(&mut paragraphs, shape_bounds_after.width()); let resize_transform = math::resize_matrix( &shape_bounds_after, &shape_bounds_after, diff --git a/render-wasm/src/shapes/text.rs b/render-wasm/src/shapes/text.rs index d4141b1d05..081a9c151c 100644 --- a/render-wasm/src/shapes/text.rs +++ b/render-wasm/src/shapes/text.rs @@ -5,13 +5,13 @@ use crate::{ use skia_safe::{ self as skia, paint::Paint, - textlayout::{FontCollection, ParagraphBuilder, ParagraphStyle}, + textlayout::{ParagraphBuilder, ParagraphStyle}, }; use std::collections::HashSet; use super::FontFamily; use crate::shapes::{self, merge_fills, set_paint_fill, Stroke, StrokeKind}; -use crate::utils::{get_fallback_fonts, uuid_from_u32}; +use crate::utils::{get_fallback_fonts, get_font_collection, uuid_from_u32}; use crate::wasm::fills::parse_fills_from_bytes; use crate::Uuid; @@ -40,15 +40,14 @@ pub struct TextContent { pub grow_type: GrowType, } -pub fn set_paragraphs_width(width: f32, paragraphs: &mut Vec>) { - for group in paragraphs { - for paragraph in group { - // We first set max so we can get the min_intrinsic_width (this is the min word size) - // then after we set either the real with or the min. - // This is done this way so the words are not break into lines. - paragraph.layout(f32::MAX); - paragraph.layout(f32::max(width, paragraph.min_intrinsic_width().ceil())); - } +pub fn set_paragraphs_width(width: f32, paragraphs: &mut [ParagraphBuilder]) { + for p in paragraphs { + // We first set max so we can get the min_intrinsic_width (this is the min word size) + // then after we set either the real with or the min. + // This is done this way so the words are not break into lines. + let mut paragraph = p.build(); + paragraph.layout(f32::MAX); + paragraph.layout(f32::max(width, paragraph.min_intrinsic_width().ceil())); } } @@ -94,67 +93,61 @@ impl TextContent { self.paragraphs.push(paragraph); } - pub fn to_paragraphs(&self, fonts: &FontCollection) -> Vec> { + pub fn to_paragraphs(&self) -> Vec { + let fonts = get_font_collection(); let fallback_fonts = get_fallback_fonts(); - let mut paragraph_group = Vec::new(); - let paragraphs = self - .paragraphs + self.paragraphs .iter() .map(|p| { let paragraph_style = p.paragraph_to_style(); let mut builder = ParagraphBuilder::new(¶graph_style, fonts); for leaf in &p.children { - let text_style = leaf.to_style(p, &self.bounds, fallback_fonts); // FIXME + let text_style = leaf.to_style(p, &self.bounds, fallback_fonts); let text = leaf.apply_text_transform(); builder.push_style(&text_style); builder.add_text(&text); builder.pop(); } - builder.build() + builder }) - .collect(); - paragraph_group.push(paragraphs); - paragraph_group + .collect() } - pub fn to_stroke_paragraphs( - &self, - stroke: &Stroke, - bounds: &Rect, - fonts: &FontCollection, - ) -> Vec> { + pub fn to_stroke_paragraphs(&self, stroke: &Stroke, bounds: &Rect) -> Vec { let fallback_fonts = get_fallback_fonts(); - let mut paragraph_group = Vec::new(); let stroke_paints = get_text_stroke_paints(stroke, bounds); + let fonts = get_font_collection(); - for stroke_paint in stroke_paints { - let mut stroke_paragraphs = 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 stroke_style = - leaf.to_stroke_style(paragraph, &stroke_paint, fallback_fonts); - let text: String = leaf.apply_text_transform(); - builder.push_style(&stroke_style); - builder.add_text(&text); - builder.pop(); - } - let p = builder.build(); - stroke_paragraphs.push(p); - } - paragraph_group.push(stroke_paragraphs); - } - paragraph_group + stroke_paints + .into_iter() + .flat_map(|stroke_paint| { + self.paragraphs + .iter() + .map(|paragraph| { + let paragraph_style = paragraph.paragraph_to_style(); + let mut builder = ParagraphBuilder::new(¶graph_style, fonts); + for leaf in ¶graph.children { + let stroke_style = + leaf.to_stroke_style(paragraph, &stroke_paint, fallback_fonts); + let text: String = leaf.apply_text_transform(); + builder.push_style(&stroke_style); + builder.add_text(&text); + builder.pop(); + } + builder + }) + .collect::>() + }) + .collect() } pub fn collect_paragraphs( &self, - mut paragraphs: Vec>, - ) -> Vec> { + mut paragraphs: Vec, + ) -> Vec { if self.grow_type() == GrowType::AutoWidth { set_paragraphs_width(f32::MAX, &mut paragraphs); - let max_width = auto_width(¶graphs).ceil(); + let max_width = auto_width(&mut paragraphs).ceil(); set_paragraphs_width(max_width, &mut paragraphs); } else { set_paragraphs_width(self.width(), &mut paragraphs); @@ -162,20 +155,16 @@ impl TextContent { paragraphs } - pub fn get_skia_paragraphs( - &self, - fonts: &FontCollection, - ) -> Vec> { - self.collect_paragraphs(self.to_paragraphs(fonts)) + pub fn get_skia_paragraphs(&self) -> Vec { + self.collect_paragraphs(self.to_paragraphs()) } pub fn get_skia_stroke_paragraphs( &self, stroke: &Stroke, bounds: &Rect, - fonts: &FontCollection, - ) -> Vec> { - self.collect_paragraphs(self.to_stroke_paragraphs(stroke, bounds, fonts)) + ) -> Vec { + self.collect_paragraphs(self.to_stroke_paragraphs(stroke, bounds)) } pub fn grow_type(&self) -> GrowType { @@ -627,24 +616,28 @@ impl From<&Vec> for RawTextData { } } -pub fn auto_width(paragraphs: &[Vec]) -> f32 { - paragraphs.iter().flatten().fold(0.0, |auto_width, p| { - f32::max(p.max_intrinsic_width(), auto_width) +pub fn auto_width(paragraphs: &mut [ParagraphBuilder]) -> f32 { + paragraphs.iter_mut().fold(0.0, |auto_width, p| { + let mut paragraph = p.build(); + paragraph.layout(f32::MAX); + f32::max(paragraph.max_intrinsic_width(), auto_width) }) } -pub fn max_width(paragraphs: &[Vec]) -> f32 { - paragraphs - .iter() - .flatten() - .fold(0.0, |max_width, p| f32::max(p.max_width(), max_width)) +pub fn max_width(paragraphs: &mut [ParagraphBuilder]) -> f32 { + paragraphs.iter_mut().fold(0.0, |max_width, p| { + let mut paragraph = p.build(); + paragraph.layout(f32::MAX); + f32::max(paragraph.max_width(), max_width) + }) } -pub fn auto_height(paragraphs: &[Vec]) -> f32 { - paragraphs - .iter() - .flatten() - .fold(0.0, |auto_height, p| auto_height + p.height()) +pub fn auto_height(paragraphs: &mut [ParagraphBuilder], width: f32) -> f32 { + paragraphs.iter_mut().fold(0.0, |auto_height, p| { + let mut paragraph = p.build(); + paragraph.layout(width); + auto_height + paragraph.height() + }) } fn get_text_stroke_paints(stroke: &Stroke, bounds: &Rect) -> Vec { diff --git a/render-wasm/src/state.rs b/render-wasm/src/state.rs index 5dffb986eb..72d08f0ba8 100644 --- a/render-wasm/src/state.rs +++ b/render-wasm/src/state.rs @@ -1,4 +1,4 @@ -use skia_safe::{self as skia, Path, Point}; +use skia_safe::{self as skia, textlayout::FontCollection, Path, Point}; use std::collections::HashMap; mod shapes_pool; @@ -167,6 +167,10 @@ impl State { .rebuild_modifier_tiles(&self.shapes, &self.modifiers); } + pub fn font_collection(&self) -> &FontCollection { + self.render_state.fonts().font_collection() + } + pub fn get_grid_coords(&self, pos_x: f32, pos_y: f32) -> Option<(i32, i32)> { let shape = self.current_shape()?; let bounds = shape.bounds(); diff --git a/render-wasm/src/utils.rs b/render-wasm/src/utils.rs index c0d0fc4a54..fb39a9bf6d 100644 --- a/render-wasm/src/utils.rs +++ b/render-wasm/src/utils.rs @@ -1,3 +1,4 @@ +use crate::skia::textlayout::FontCollection; use crate::skia::Image; use crate::uuid::Uuid; use crate::with_state_mut; @@ -31,3 +32,7 @@ pub fn get_image(image_id: &Uuid) -> Option<&Image> { pub fn get_fallback_fonts() -> &'static HashSet { with_state_mut!(state, { state.render_state().fonts().get_fallback() }) } + +pub fn get_font_collection() -> &'static FontCollection { + with_state_mut!(state, { state.font_collection() }) +} diff --git a/render-wasm/src/wasm/text.rs b/render-wasm/src/wasm/text.rs index 74f9dd7795..b0705f71dc 100644 --- a/render-wasm/src/wasm/text.rs +++ b/render-wasm/src/wasm/text.rs @@ -2,7 +2,7 @@ use crate::mem; use crate::shapes::{auto_height, auto_width, max_width, GrowType, RawTextData, Type}; use crate::STATE; -use crate::{with_current_shape, with_current_shape_mut, with_state_mut}; +use crate::{with_current_shape, with_current_shape_mut}; #[no_mangle] pub extern "C" fn clear_shape_text() { @@ -35,11 +35,6 @@ pub extern "C" fn set_shape_grow_type(grow_type: u8) { #[no_mangle] pub extern "C" fn get_text_dimensions() -> *mut u8 { - let font_col; - with_state_mut!(state, { - font_col = state.render_state.fonts.font_collection(); - }); - let mut width = 0.01; let mut height = 0.01; let mut m_width = 0.01; @@ -49,16 +44,16 @@ pub extern "C" fn get_text_dimensions() -> *mut u8 { height = shape.selrect.height(); if let Type::Text(content) = &shape.shape_type { - let paragraphs = content.get_skia_paragraphs(font_col); - m_width = max_width(¶graphs); + let mut paragraphs = content.get_skia_paragraphs(); + m_width = max_width(&mut paragraphs); match content.grow_type() { GrowType::AutoHeight => { - height = auto_height(¶graphs).ceil(); + height = auto_height(&mut paragraphs, width).ceil(); } GrowType::AutoWidth => { - width = auto_width(¶graphs).ceil(); - height = auto_height(¶graphs).ceil(); + width = auto_width(&mut paragraphs).ceil(); + height = auto_height(&mut paragraphs, width).ceil(); } GrowType::Fixed => {} }