Merge pull request #6927 from penpot/elenatorro-test-fix-text-shadows
🐛 Fix text shadows apply text opacity
|
@ -126,8 +126,8 @@ test("Renders a file with texts with images", async ({ page }) => {
|
||||||
await workspace.setupEmptyFile();
|
await workspace.setupEmptyFile();
|
||||||
await workspace.mockFileMediaAsset(
|
await workspace.mockFileMediaAsset(
|
||||||
[
|
[
|
||||||
"6bd7c17d-4f59-815e-8006-5e9765e0fabd",
|
"4f89252d-ebbc-813e-8006-8699e4170e17",
|
||||||
"6bd7c17d-4f59-815e-8006-5e97441071cc"
|
"4f89252d-ebbc-813e-8006-8699e4170e18"
|
||||||
],
|
],
|
||||||
"render-wasm/assets/pattern.png",
|
"render-wasm/assets/pattern.png",
|
||||||
);
|
);
|
||||||
|
|
Before Width: | Height: | Size: 512 KiB After Width: | Height: | Size: 508 KiB |
Before Width: | Height: | Size: 153 KiB After Width: | Height: | Size: 167 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 123 KiB |
|
@ -302,10 +302,12 @@ impl RenderState {
|
||||||
self.surfaces.reset(self.background_color);
|
self.surfaces.reset(self.background_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn get_canvas_at(&mut self, surface_id: SurfaceId) -> &skia::Canvas {
|
pub fn get_canvas_at(&mut self, surface_id: SurfaceId) -> &skia::Canvas {
|
||||||
self.surfaces.canvas(surface_id)
|
self.surfaces.canvas(surface_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn restore_canvas(&mut self, surface_id: SurfaceId) {
|
pub fn restore_canvas(&mut self, surface_id: SurfaceId) {
|
||||||
self.surfaces.canvas(surface_id).restore();
|
self.surfaces.canvas(surface_id).restore();
|
||||||
}
|
}
|
||||||
|
@ -462,13 +464,22 @@ impl RenderState {
|
||||||
let text_content = text_content.new_bounds(shape.selrect());
|
let text_content = text_content.new_bounds(shape.selrect());
|
||||||
let mut paragraphs = text_content.get_skia_paragraphs();
|
let mut paragraphs = text_content.get_skia_paragraphs();
|
||||||
|
|
||||||
|
if !shape.has_strokes() {
|
||||||
shadows::render_text_drop_shadows(self, &shape, &mut paragraphs, antialias);
|
shadows::render_text_drop_shadows(self, &shape, &mut paragraphs, antialias);
|
||||||
text::render(self, &shape, &mut paragraphs, None);
|
}
|
||||||
|
|
||||||
|
text::render(self, &shape, &mut paragraphs, None, None);
|
||||||
|
|
||||||
if shape.has_inner_strokes() {
|
if shape.has_inner_strokes() {
|
||||||
// Inner strokes paints need the text fill to apply correctly their blend modes
|
// Inner strokes paints need the text fill to apply correctly their blend modes
|
||||||
// (e.g., SrcATop, DstOver)
|
// (e.g., SrcATop, DstOver)
|
||||||
text::render(self, &shape, &mut paragraphs, Some(SurfaceId::Strokes));
|
text::render(
|
||||||
|
self,
|
||||||
|
&shape,
|
||||||
|
&mut paragraphs,
|
||||||
|
Some(SurfaceId::Strokes),
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for stroke in shape.strokes().rev() {
|
for stroke in shape.strokes().rev() {
|
||||||
|
@ -488,6 +499,7 @@ impl RenderState {
|
||||||
None,
|
None,
|
||||||
Some(&mut stroke_paragraphs),
|
Some(&mut stroke_paragraphs),
|
||||||
antialias,
|
antialias,
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
shadows::render_text_inner_shadows(
|
shadows::render_text_inner_shadows(
|
||||||
self,
|
self,
|
||||||
|
@ -531,7 +543,7 @@ impl RenderState {
|
||||||
|
|
||||||
for stroke in shape.strokes().rev() {
|
for stroke in shape.strokes().rev() {
|
||||||
shadows::render_stroke_drop_shadows(self, &shape, stroke, antialias);
|
shadows::render_stroke_drop_shadows(self, &shape, stroke, antialias);
|
||||||
strokes::render(self, &shape, stroke, None, None, None, antialias);
|
strokes::render(self, &shape, stroke, None, None, None, antialias, None);
|
||||||
shadows::render_stroke_inner_shadows(self, &shape, stroke, antialias);
|
shadows::render_stroke_inner_shadows(self, &shape, stroke, antialias);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ use crate::render::strokes;
|
||||||
use crate::render::text::{self};
|
use crate::render::text::{self};
|
||||||
use crate::shapes::{Shadow, Shape, Stroke, Type};
|
use crate::shapes::{Shadow, Shape, Stroke, Type};
|
||||||
use skia_safe::textlayout::ParagraphBuilder;
|
use skia_safe::textlayout::ParagraphBuilder;
|
||||||
use skia_safe::{canvas::SaveLayerRec, Paint, Path};
|
use skia_safe::{Paint, Path};
|
||||||
|
|
||||||
// Fill Shadows
|
// Fill Shadows
|
||||||
pub fn render_fill_drop_shadows(render_state: &mut RenderState, shape: &Shape, antialias: bool) {
|
pub fn render_fill_drop_shadows(render_state: &mut RenderState, shape: &Shape, antialias: bool) {
|
||||||
|
@ -59,6 +59,7 @@ pub fn render_stroke_drop_shadows(
|
||||||
filter.as_ref(),
|
filter.as_ref(),
|
||||||
None,
|
None,
|
||||||
antialias,
|
antialias,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,6 +82,7 @@ pub fn render_stroke_inner_shadows(
|
||||||
filter.as_ref(),
|
filter.as_ref(),
|
||||||
None,
|
None,
|
||||||
antialias,
|
antialias,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,7 +91,7 @@ pub fn render_stroke_inner_shadows(
|
||||||
pub fn render_text_drop_shadows(
|
pub fn render_text_drop_shadows(
|
||||||
render_state: &mut RenderState,
|
render_state: &mut RenderState,
|
||||||
shape: &Shape,
|
shape: &Shape,
|
||||||
paragraphs: &mut [ParagraphBuilder],
|
paragraphs: &mut [Vec<ParagraphBuilder>],
|
||||||
antialias: bool,
|
antialias: bool,
|
||||||
) {
|
) {
|
||||||
for shadow in shape.drop_shadows().rev().filter(|s| !s.hidden()) {
|
for shadow in shape.drop_shadows().rev().filter(|s| !s.hidden()) {
|
||||||
|
@ -124,29 +126,24 @@ pub fn render_text_drop_shadow(
|
||||||
render_state: &mut RenderState,
|
render_state: &mut RenderState,
|
||||||
shape: &Shape,
|
shape: &Shape,
|
||||||
shadow: &Shadow,
|
shadow: &Shadow,
|
||||||
paragraphs: &mut [ParagraphBuilder],
|
paragraphs: &mut [Vec<ParagraphBuilder>],
|
||||||
antialias: bool,
|
antialias: bool,
|
||||||
) {
|
) {
|
||||||
let paint = shadow.get_drop_shadow_paint(antialias);
|
let paint = shadow.get_drop_shadow_paint(antialias);
|
||||||
let mask_paint = paint.clone();
|
|
||||||
let mask = SaveLayerRec::default().paint(&mask_paint);
|
|
||||||
let canvas = render_state.get_canvas_at(SurfaceId::DropShadows);
|
|
||||||
canvas.save_layer(&mask);
|
|
||||||
|
|
||||||
text::render(
|
text::render(
|
||||||
render_state,
|
render_state,
|
||||||
shape,
|
shape,
|
||||||
paragraphs,
|
paragraphs,
|
||||||
Some(SurfaceId::DropShadows),
|
Some(SurfaceId::DropShadows),
|
||||||
|
Some(&paint),
|
||||||
);
|
);
|
||||||
|
|
||||||
render_state.restore_canvas(SurfaceId::DropShadows);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_text_inner_shadows(
|
pub fn render_text_inner_shadows(
|
||||||
render_state: &mut RenderState,
|
render_state: &mut RenderState,
|
||||||
shape: &Shape,
|
shape: &Shape,
|
||||||
paragraphs: &mut [ParagraphBuilder],
|
paragraphs: &mut [Vec<ParagraphBuilder>],
|
||||||
antialias: bool,
|
antialias: bool,
|
||||||
) {
|
) {
|
||||||
for shadow in shape.inner_shadows().rev().filter(|s| !s.hidden()) {
|
for shadow in shape.inner_shadows().rev().filter(|s| !s.hidden()) {
|
||||||
|
@ -158,24 +155,18 @@ pub fn render_text_inner_shadow(
|
||||||
render_state: &mut RenderState,
|
render_state: &mut RenderState,
|
||||||
shape: &Shape,
|
shape: &Shape,
|
||||||
shadow: &Shadow,
|
shadow: &Shadow,
|
||||||
paragraphs: &mut [ParagraphBuilder],
|
paragraphs: &mut [Vec<ParagraphBuilder>],
|
||||||
antialias: bool,
|
antialias: bool,
|
||||||
) {
|
) {
|
||||||
let paint = shadow.get_inner_shadow_paint(antialias);
|
let paint = shadow.get_inner_shadow_paint(antialias);
|
||||||
let mask_paint = paint.clone();
|
|
||||||
let mask = SaveLayerRec::default().paint(&mask_paint);
|
|
||||||
let canvas = render_state.get_canvas_at(SurfaceId::InnerShadows);
|
|
||||||
|
|
||||||
canvas.save_layer(&mask);
|
|
||||||
|
|
||||||
text::render(
|
text::render(
|
||||||
render_state,
|
render_state,
|
||||||
shape,
|
shape,
|
||||||
paragraphs,
|
paragraphs,
|
||||||
Some(SurfaceId::InnerShadows),
|
Some(SurfaceId::InnerShadows),
|
||||||
|
Some(&paint),
|
||||||
);
|
);
|
||||||
|
|
||||||
render_state.restore_canvas(SurfaceId::InnerShadows);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render text paths (unused)
|
// Render text paths (unused)
|
||||||
|
|
|
@ -479,14 +479,16 @@ fn draw_image_stroke_in_container(
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn render(
|
pub fn render(
|
||||||
render_state: &mut RenderState,
|
render_state: &mut RenderState,
|
||||||
shape: &Shape,
|
shape: &Shape,
|
||||||
stroke: &Stroke,
|
stroke: &Stroke,
|
||||||
surface_id: Option<SurfaceId>,
|
surface_id: Option<SurfaceId>,
|
||||||
shadow: Option<&ImageFilter>,
|
shadow: Option<&ImageFilter>,
|
||||||
paragraphs: Option<&mut [ParagraphBuilder]>,
|
paragraphs: Option<&mut Vec<Vec<ParagraphBuilder>>>,
|
||||||
antialias: bool,
|
antialias: bool,
|
||||||
|
paint: Option<&skia::Paint>,
|
||||||
) {
|
) {
|
||||||
let scale = render_state.get_scale();
|
let scale = render_state.get_scale();
|
||||||
let canvas = render_state
|
let canvas = render_state
|
||||||
|
@ -527,6 +529,7 @@ pub fn render(
|
||||||
shape,
|
shape,
|
||||||
paragraphs.expect("Text shapes should have paragraphs"),
|
paragraphs.expect("Text shapes should have paragraphs"),
|
||||||
Some(SurfaceId::Strokes),
|
Some(SurfaceId::Strokes),
|
||||||
|
paint,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
shape_type @ (Type::Path(_) | Type::Bool(_)) => {
|
shape_type @ (Type::Path(_) | Type::Bool(_)) => {
|
||||||
|
|
|
@ -1,38 +1,64 @@
|
||||||
use super::{RenderState, Shape, SurfaceId};
|
use super::{RenderState, Shape, SurfaceId};
|
||||||
use crate::shapes::VerticalAlign;
|
use crate::shapes::VerticalAlign;
|
||||||
|
use crate::utils::get_font_collection;
|
||||||
use skia_safe::{textlayout::ParagraphBuilder, FontMetrics, Paint, Path};
|
use skia_safe::{textlayout::ParagraphBuilder, FontMetrics, Paint, Path};
|
||||||
|
|
||||||
pub fn render(
|
pub fn render(
|
||||||
render_state: &mut RenderState,
|
render_state: &mut RenderState,
|
||||||
shape: &Shape,
|
shape: &Shape,
|
||||||
paragraphs: &mut [ParagraphBuilder],
|
paragraphs: &mut [Vec<ParagraphBuilder>],
|
||||||
surface_id: Option<SurfaceId>,
|
surface_id: Option<SurfaceId>,
|
||||||
|
paint: Option<&Paint>,
|
||||||
) {
|
) {
|
||||||
|
let fonts = get_font_collection();
|
||||||
let canvas = render_state
|
let canvas = render_state
|
||||||
.surfaces
|
.surfaces
|
||||||
.canvas(surface_id.unwrap_or(SurfaceId::Fills));
|
.canvas(surface_id.unwrap_or(SurfaceId::Fills));
|
||||||
|
|
||||||
let mut offset_y = 0.0;
|
|
||||||
let container_height = shape.selrect().height();
|
let container_height = shape.selrect().height();
|
||||||
|
|
||||||
for builder in paragraphs {
|
// Calculate total height for vertical alignment
|
||||||
let mut skia_paragraph = builder.build();
|
let total_content_height = calculate_all_paragraphs_height(paragraphs, shape.bounds().width());
|
||||||
skia_paragraph.layout(shape.bounds().width());
|
let mut global_offset_y = match shape.vertical_align() {
|
||||||
let paragraph_height: f32 = skia_paragraph.height();
|
VerticalAlign::Center => (container_height - total_content_height) / 2.0,
|
||||||
|
VerticalAlign::Bottom => container_height - total_content_height,
|
||||||
let paragraph_offset_y = match shape.vertical_align() {
|
|
||||||
VerticalAlign::Center => (container_height - paragraph_height) / 2.0,
|
|
||||||
VerticalAlign::Bottom => container_height - paragraph_height,
|
|
||||||
_ => 0.0,
|
_ => 0.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
offset_y += paragraph_offset_y;
|
for group in paragraphs {
|
||||||
|
let mut group_offset_y = global_offset_y;
|
||||||
|
let group_len = group.len();
|
||||||
|
|
||||||
let xy = (shape.selrect().x(), shape.selrect().y() + offset_y);
|
for (index, builder) in group.iter_mut().enumerate() {
|
||||||
|
let mut skia_paragraph = builder.build();
|
||||||
|
|
||||||
|
if paint.is_some() && index == 0 {
|
||||||
|
let text = builder.get_text().to_string();
|
||||||
|
let mut paragraph_builder =
|
||||||
|
ParagraphBuilder::new(&builder.get_paragraph_style(), fonts);
|
||||||
|
let mut text_style: skia_safe::Handle<_> = builder.peek_style();
|
||||||
|
let current_paint = text_style.foreground().clone();
|
||||||
|
let blend_mode = current_paint.as_blend_mode();
|
||||||
|
let mut new_paint = paint.unwrap().clone();
|
||||||
|
if blend_mode != Some(skia_safe::BlendMode::SrcIn) {
|
||||||
|
new_paint.set_stroke_width(current_paint.stroke_width());
|
||||||
|
new_paint.set_style(skia_safe::PaintStyle::StrokeAndFill);
|
||||||
|
}
|
||||||
|
new_paint.set_anti_alias(true);
|
||||||
|
text_style.set_foreground_paint(&new_paint);
|
||||||
|
paragraph_builder.reset();
|
||||||
|
paragraph_builder.push_style(&text_style);
|
||||||
|
paragraph_builder.add_text(&text);
|
||||||
|
skia_paragraph = paragraph_builder.build();
|
||||||
|
} else if paint.is_some() && index > 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
skia_paragraph.layout(shape.bounds().width());
|
||||||
|
|
||||||
|
let paragraph_height = skia_paragraph.height();
|
||||||
|
let xy = (shape.selrect().x(), shape.selrect().y() + group_offset_y);
|
||||||
skia_paragraph.paint(canvas, xy);
|
skia_paragraph.paint(canvas, xy);
|
||||||
|
|
||||||
offset_y += paragraph_height;
|
|
||||||
|
|
||||||
for line_metrics in skia_paragraph.get_line_metrics().iter() {
|
for line_metrics in skia_paragraph.get_line_metrics().iter() {
|
||||||
let style_metrics: Vec<_> = line_metrics
|
let style_metrics: Vec<_> = line_metrics
|
||||||
.get_style_metrics(line_metrics.start_index..line_metrics.end_index)
|
.get_style_metrics(line_metrics.start_index..line_metrics.end_index)
|
||||||
|
@ -43,6 +69,10 @@ pub fn render(
|
||||||
let total_line_width = line_metrics.width as f32;
|
let total_line_width = line_metrics.width as f32;
|
||||||
let total_chars = line_metrics.end_index - line_metrics.start_index;
|
let total_chars = line_metrics.end_index - line_metrics.start_index;
|
||||||
|
|
||||||
|
// Calculate line's actual start position considering text alignment
|
||||||
|
// let paragraph_width = shape.bounds().width();
|
||||||
|
let line_start_offset = line_metrics.left as f32;
|
||||||
|
|
||||||
// No text decoration for empty lines
|
// No text decoration for empty lines
|
||||||
if total_chars == 0 || style_metrics.is_empty() {
|
if total_chars == 0 || style_metrics.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
|
@ -61,13 +91,13 @@ pub fn render(
|
||||||
} else {
|
} else {
|
||||||
char_count as f32 * font_metrics.avg_char_width
|
char_count as f32 * font_metrics.avg_char_width
|
||||||
};
|
};
|
||||||
|
|
||||||
if text_style.decoration().ty
|
if text_style.decoration().ty
|
||||||
!= skia_safe::textlayout::TextDecoration::NO_DECORATION
|
!= skia_safe::textlayout::TextDecoration::NO_DECORATION
|
||||||
{
|
{
|
||||||
let decoration_type = text_style.decoration().ty;
|
let decoration_type = text_style.decoration().ty;
|
||||||
let text_left = xy.0 + current_x_offset;
|
let text_left = xy.0 + line_start_offset + current_x_offset;
|
||||||
let text_top = xy.1 + line_metrics.baseline as f32 - line_metrics.ascent as f32;
|
let text_top =
|
||||||
|
xy.1 + line_metrics.baseline as f32 - line_metrics.ascent as f32;
|
||||||
let text_width = segment_width;
|
let text_width = segment_width;
|
||||||
let line_height = line_metrics.height as f32;
|
let line_height = line_metrics.height as f32;
|
||||||
|
|
||||||
|
@ -85,10 +115,28 @@ pub fn render(
|
||||||
canvas.draw_rect(decoration_rect, &decoration_paint);
|
canvas.draw_rect(decoration_rect, &decoration_paint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
current_x_offset += segment_width;
|
current_x_offset += segment_width;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only increment group_offset_y for regular paragraphs (single element groups)
|
||||||
|
// For stroke groups (multiple elements), keep same offset for blending
|
||||||
|
if group_len == 1 {
|
||||||
|
group_offset_y += paragraph_height;
|
||||||
|
}
|
||||||
|
// For stroke groups (group_len > 1), don't increment group_offset_y within the group
|
||||||
|
// This ensures all stroke variants render at the same position for proper blending
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
global_offset_y += first_paragraph.height();
|
||||||
|
} else {
|
||||||
|
// For regular paragraphs, global_offset_y was already incremented inside the loop
|
||||||
|
global_offset_y = group_offset_y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,6 +172,36 @@ pub fn calculate_text_decoration_rect(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn calculate_total_paragraphs_height(paragraphs: &mut [ParagraphBuilder], width: f32) -> f32 {
|
||||||
|
paragraphs
|
||||||
|
.iter_mut()
|
||||||
|
.map(|p| {
|
||||||
|
let mut paragraph = p.build();
|
||||||
|
paragraph.layout(width);
|
||||||
|
paragraph.height()
|
||||||
|
})
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_all_paragraphs_height(
|
||||||
|
paragraph_groups: &mut [Vec<ParagraphBuilder>],
|
||||||
|
width: f32,
|
||||||
|
) -> f32 {
|
||||||
|
paragraph_groups
|
||||||
|
.iter_mut()
|
||||||
|
.map(|group| {
|
||||||
|
// For stroke groups, only count the first paragraph to avoid double-counting
|
||||||
|
if group.len() > 1 {
|
||||||
|
let mut paragraph = group[0].build();
|
||||||
|
paragraph.layout(width);
|
||||||
|
paragraph.height()
|
||||||
|
} else {
|
||||||
|
calculate_total_paragraphs_height(group, width)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
|
||||||
// Render text paths (unused)
|
// Render text paths (unused)
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn render_as_path(
|
pub fn render_as_path(
|
||||||
|
|
|
@ -966,6 +966,10 @@ impl Shape {
|
||||||
!self.fills.is_empty()
|
!self.fills.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has_strokes(&self) -> bool {
|
||||||
|
!self.strokes.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn has_inner_strokes(&self) -> bool {
|
pub fn has_inner_strokes(&self) -> bool {
|
||||||
self.strokes.iter().any(|s| s.kind == StrokeKind::Inner)
|
self.strokes.iter().any(|s| s.kind == StrokeKind::Inner)
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,15 +40,14 @@ pub struct TextContent {
|
||||||
pub grow_type: GrowType,
|
pub grow_type: GrowType,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_paragraphs_width(width: f32, paragraphs: &mut [ParagraphBuilder]) {
|
pub fn set_paragraphs_width(width: f32, paragraphs: &mut Vec<Vec<ParagraphBuilder>>) {
|
||||||
for p in paragraphs {
|
for group in paragraphs {
|
||||||
// We first set max so we can get the min_intrinsic_width (this is the min word size)
|
for p in group {
|
||||||
// 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();
|
let mut paragraph = p.build();
|
||||||
paragraph.layout(f32::MAX);
|
paragraph.layout(f32::MAX);
|
||||||
paragraph.layout(f32::max(width, paragraph.min_intrinsic_width().ceil()));
|
paragraph.layout(f32::max(width, paragraph.min_intrinsic_width().ceil()));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextContent {
|
impl TextContent {
|
||||||
|
@ -93,58 +92,60 @@ impl TextContent {
|
||||||
self.paragraphs.push(paragraph);
|
self.paragraphs.push(paragraph);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_paragraphs(&self) -> Vec<ParagraphBuilder> {
|
pub fn to_paragraphs(&self) -> Vec<Vec<ParagraphBuilder>> {
|
||||||
let fonts = get_font_collection();
|
let fonts = get_font_collection();
|
||||||
let fallback_fonts = get_fallback_fonts();
|
let fallback_fonts = get_fallback_fonts();
|
||||||
self.paragraphs
|
let mut paragraph_group = Vec::new();
|
||||||
.iter()
|
|
||||||
.map(|p| {
|
for paragraph in &self.paragraphs {
|
||||||
let paragraph_style = p.paragraph_to_style();
|
let paragraph_style = paragraph.paragraph_to_style();
|
||||||
let mut builder = ParagraphBuilder::new(¶graph_style, fonts);
|
let mut builder = ParagraphBuilder::new(¶graph_style, fonts);
|
||||||
for leaf in &p.children {
|
for leaf in ¶graph.children {
|
||||||
let text_style = leaf.to_style(p, &self.bounds, fallback_fonts);
|
let text_style = leaf.to_style(paragraph, &self.bounds, fallback_fonts);
|
||||||
let text = leaf.apply_text_transform();
|
let text = leaf.apply_text_transform();
|
||||||
builder.push_style(&text_style);
|
builder.push_style(&text_style);
|
||||||
builder.add_text(&text);
|
builder.add_text(&text);
|
||||||
builder.pop();
|
|
||||||
}
|
}
|
||||||
builder
|
paragraph_group.push(vec![builder]);
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_stroke_paragraphs(&self, stroke: &Stroke, bounds: &Rect) -> Vec<ParagraphBuilder> {
|
paragraph_group
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_stroke_paragraphs(
|
||||||
|
&self,
|
||||||
|
stroke: &Stroke,
|
||||||
|
bounds: &Rect,
|
||||||
|
) -> Vec<Vec<ParagraphBuilder>> {
|
||||||
let fallback_fonts = get_fallback_fonts();
|
let fallback_fonts = get_fallback_fonts();
|
||||||
let stroke_paints = get_text_stroke_paints(stroke, bounds);
|
let stroke_paints = get_text_stroke_paints(stroke, bounds);
|
||||||
let fonts = get_font_collection();
|
let fonts = get_font_collection();
|
||||||
|
let mut paragraph_group = Vec::new();
|
||||||
|
|
||||||
stroke_paints
|
for paragraph in &self.paragraphs {
|
||||||
.into_iter()
|
let mut stroke_paragraphs = Vec::new();
|
||||||
.flat_map(|stroke_paint| {
|
for stroke_paint in &stroke_paints {
|
||||||
self.paragraphs
|
|
||||||
.iter()
|
|
||||||
.map(|paragraph| {
|
|
||||||
let paragraph_style = paragraph.paragraph_to_style();
|
let paragraph_style = paragraph.paragraph_to_style();
|
||||||
let mut builder = ParagraphBuilder::new(¶graph_style, fonts);
|
let mut builder = ParagraphBuilder::new(¶graph_style, fonts);
|
||||||
for leaf in ¶graph.children {
|
for leaf in ¶graph.children {
|
||||||
let stroke_style =
|
let stroke_style =
|
||||||
leaf.to_stroke_style(paragraph, &stroke_paint, fallback_fonts);
|
leaf.to_stroke_style(paragraph, stroke_paint, fallback_fonts);
|
||||||
let text: String = leaf.apply_text_transform();
|
let text: String = leaf.apply_text_transform();
|
||||||
builder.push_style(&stroke_style);
|
builder.push_style(&stroke_style);
|
||||||
builder.add_text(&text);
|
builder.add_text(&text);
|
||||||
builder.pop();
|
|
||||||
}
|
}
|
||||||
builder
|
stroke_paragraphs.push(builder);
|
||||||
})
|
}
|
||||||
.collect::<Vec<_>>()
|
paragraph_group.push(stroke_paragraphs);
|
||||||
})
|
}
|
||||||
.collect()
|
|
||||||
|
paragraph_group
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn collect_paragraphs(
|
pub fn collect_paragraphs(
|
||||||
&self,
|
&self,
|
||||||
mut paragraphs: Vec<ParagraphBuilder>,
|
mut paragraphs: Vec<Vec<ParagraphBuilder>>,
|
||||||
) -> Vec<ParagraphBuilder> {
|
) -> Vec<Vec<ParagraphBuilder>> {
|
||||||
if self.grow_type() == GrowType::AutoWidth {
|
if self.grow_type() == GrowType::AutoWidth {
|
||||||
set_paragraphs_width(f32::MAX, &mut paragraphs);
|
set_paragraphs_width(f32::MAX, &mut paragraphs);
|
||||||
let max_width = auto_width(&mut paragraphs).ceil();
|
let max_width = auto_width(&mut paragraphs).ceil();
|
||||||
|
@ -155,7 +156,7 @@ impl TextContent {
|
||||||
paragraphs
|
paragraphs
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_skia_paragraphs(&self) -> Vec<ParagraphBuilder> {
|
pub fn get_skia_paragraphs(&self) -> Vec<Vec<ParagraphBuilder>> {
|
||||||
self.collect_paragraphs(self.to_paragraphs())
|
self.collect_paragraphs(self.to_paragraphs())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,7 +164,7 @@ impl TextContent {
|
||||||
&self,
|
&self,
|
||||||
stroke: &Stroke,
|
stroke: &Stroke,
|
||||||
bounds: &Rect,
|
bounds: &Rect,
|
||||||
) -> Vec<ParagraphBuilder> {
|
) -> Vec<Vec<ParagraphBuilder>> {
|
||||||
self.collect_paragraphs(self.to_stroke_paragraphs(stroke, bounds))
|
self.collect_paragraphs(self.to_stroke_paragraphs(stroke, bounds))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -637,28 +638,34 @@ impl From<&Vec<u8>> for RawTextData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn auto_width(paragraphs: &mut [ParagraphBuilder]) -> f32 {
|
pub fn auto_width(paragraphs: &mut [Vec<ParagraphBuilder>]) -> f32 {
|
||||||
paragraphs.iter_mut().fold(0.0, |auto_width, p| {
|
paragraphs.iter_mut().fold(0.0, |auto_width, p| {
|
||||||
let mut paragraph = p.build();
|
p.iter_mut().fold(auto_width, |auto_width, paragraph| {
|
||||||
|
let mut paragraph = paragraph.build();
|
||||||
paragraph.layout(f32::MAX);
|
paragraph.layout(f32::MAX);
|
||||||
f32::max(paragraph.max_intrinsic_width(), auto_width)
|
f32::max(paragraph.max_intrinsic_width(), auto_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: &mut [ParagraphBuilder], width: f32) -> f32 {
|
pub fn max_width(paragraphs: &mut [Vec<ParagraphBuilder>]) -> f32 {
|
||||||
|
paragraphs.iter_mut().fold(0.0, |max_width, p| {
|
||||||
|
p.iter_mut().fold(max_width, |max_width, paragraph| {
|
||||||
|
let mut paragraph = paragraph.build();
|
||||||
|
paragraph.layout(f32::MAX);
|
||||||
|
f32::max(paragraph.max_width(), max_width)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn auto_height(paragraphs: &mut [Vec<ParagraphBuilder>], width: f32) -> f32 {
|
||||||
paragraphs.iter_mut().fold(0.0, |auto_height, p| {
|
paragraphs.iter_mut().fold(0.0, |auto_height, p| {
|
||||||
let mut paragraph = p.build();
|
p.iter_mut().fold(auto_height, |auto_height, paragraph| {
|
||||||
|
let mut paragraph = paragraph.build();
|
||||||
paragraph.layout(width);
|
paragraph.layout(width);
|
||||||
auto_height + paragraph.height()
|
auto_height + paragraph.height()
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_text_stroke_paints(stroke: &Stroke, bounds: &Rect) -> Vec<Paint> {
|
fn get_text_stroke_paints(stroke: &Stroke, bounds: &Rect) -> Vec<Paint> {
|
||||||
|
|