Merge pull request #6927 from penpot/elenatorro-test-fix-text-shadows

🐛 Fix text shadows apply text opacity
This commit is contained in:
Aitor Moreno 2025-07-23 06:59:28 +02:00 committed by GitHub
commit 3e3be95420
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 1234 additions and 501 deletions

File diff suppressed because it is too large Load diff

View file

@ -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",
); );

Binary file not shown.

Before

Width:  |  Height:  |  Size: 512 KiB

After

Width:  |  Height:  |  Size: 508 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 167 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Before After
Before After

View file

@ -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();
shadows::render_text_drop_shadows(self, &shape, &mut paragraphs, antialias); if !shape.has_strokes() {
text::render(self, &shape, &mut paragraphs, None); shadows::render_text_drop_shadows(self, &shape, &mut paragraphs, antialias);
}
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);
} }

View file

@ -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)

View file

@ -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(_)) => {

View file

@ -1,93 +1,141 @@
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,
_ => 0.0,
};
let paragraph_offset_y = match shape.vertical_align() { for group in paragraphs {
VerticalAlign::Center => (container_height - paragraph_height) / 2.0, let mut group_offset_y = global_offset_y;
VerticalAlign::Bottom => container_height - paragraph_height, let group_len = group.len();
_ => 0.0,
};
offset_y += paragraph_offset_y; for (index, builder) in group.iter_mut().enumerate() {
let mut skia_paragraph = builder.build();
let xy = (shape.selrect().x(), shape.selrect().y() + offset_y); if paint.is_some() && index == 0 {
skia_paragraph.paint(canvas, xy); let text = builder.get_text().to_string();
let mut paragraph_builder =
offset_y += paragraph_height; ParagraphBuilder::new(&builder.get_paragraph_style(), fonts);
let mut text_style: skia_safe::Handle<_> = builder.peek_style();
for line_metrics in skia_paragraph.get_line_metrics().iter() { let current_paint = text_style.foreground().clone();
let style_metrics: Vec<_> = line_metrics let blend_mode = current_paint.as_blend_mode();
.get_style_metrics(line_metrics.start_index..line_metrics.end_index) let mut new_paint = paint.unwrap().clone();
.into_iter() if blend_mode != Some(skia_safe::BlendMode::SrcIn) {
.collect(); new_paint.set_stroke_width(current_paint.stroke_width());
new_paint.set_style(skia_safe::PaintStyle::StrokeAndFill);
let mut current_x_offset = 0.0; }
let total_line_width = line_metrics.width as f32; new_paint.set_anti_alias(true);
let total_chars = line_metrics.end_index - line_metrics.start_index; text_style.set_foreground_paint(&new_paint);
paragraph_builder.reset();
// No text decoration for empty lines paragraph_builder.push_style(&text_style);
if total_chars == 0 || style_metrics.is_empty() { paragraph_builder.add_text(&text);
skia_paragraph = paragraph_builder.build();
} else if paint.is_some() && index > 0 {
continue; continue;
} }
for (i, (index, style_metric)) in style_metrics.iter().enumerate() { skia_paragraph.layout(shape.bounds().width());
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
};
if text_style.decoration().ty let paragraph_height = skia_paragraph.height();
!= skia_safe::textlayout::TextDecoration::NO_DECORATION let xy = (shape.selrect().x(), shape.selrect().y() + group_offset_y);
{ skia_paragraph.paint(canvas, xy);
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( for line_metrics in skia_paragraph.get_line_metrics().iter() {
decoration_type, let style_metrics: Vec<_> = line_metrics
font_metrics, .get_style_metrics(line_metrics.start_index..line_metrics.end_index)
text_left, .into_iter()
text_top, .collect();
text_width,
line_height,
);
if let Some(decoration_rect) = r { let mut current_x_offset = 0.0;
let decoration_paint = text_style.foreground(); let total_line_width = line_metrics.width as f32;
canvas.draw_rect(decoration_rect, &decoration_paint); 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
if total_chars == 0 || style_metrics.is_empty() {
continue;
} }
current_x_offset += segment_width; 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
};
if text_style.decoration().ty
!= skia_safe::textlayout::TextDecoration::NO_DECORATION
{
let decoration_type = text_style.decoration().ty;
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_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();
canvas.draw_rect(decoration_rect, &decoration_paint);
}
}
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(

View file

@ -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)
} }

View file

@ -40,14 +40,13 @@ 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. let mut paragraph = p.build();
// This is done this way so the words are not break into lines. paragraph.layout(f32::MAX);
let mut paragraph = p.build(); paragraph.layout(f32::max(width, paragraph.min_intrinsic_width().ceil()));
paragraph.layout(f32::MAX); }
paragraph.layout(f32::max(width, paragraph.min_intrinsic_width().ceil()));
} }
} }
@ -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(&paragraph_style, fonts); let mut builder = ParagraphBuilder::new(&paragraph_style, fonts);
for leaf in &p.children { for leaf in &paragraph.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(); }
} paragraph_group.push(vec![builder]);
builder }
})
.collect() paragraph_group
} }
pub fn to_stroke_paragraphs(&self, stroke: &Stroke, bounds: &Rect) -> Vec<ParagraphBuilder> { 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 let paragraph_style = paragraph.paragraph_to_style();
.iter() let mut builder = ParagraphBuilder::new(&paragraph_style, fonts);
.map(|paragraph| { for leaf in &paragraph.children {
let paragraph_style = paragraph.paragraph_to_style(); let stroke_style =
let mut builder = ParagraphBuilder::new(&paragraph_style, fonts); leaf.to_stroke_style(paragraph, stroke_paint, fallback_fonts);
for leaf in &paragraph.children { let text: String = leaf.apply_text_transform();
let stroke_style = builder.push_style(&stroke_style);
leaf.to_stroke_style(paragraph, &stroke_paint, fallback_fonts); builder.add_text(&text);
let text: String = leaf.apply_text_transform(); }
builder.push_style(&stroke_style); stroke_paragraphs.push(builder);
builder.add_text(&text); }
builder.pop(); paragraph_group.push(stroke_paragraphs);
} }
builder
}) paragraph_group
.collect::<Vec<_>>()
})
.collect()
} }
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,27 +638,33 @@ 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| {
paragraph.layout(f32::MAX); let mut paragraph = paragraph.build();
f32::max(paragraph.max_intrinsic_width(), auto_width) paragraph.layout(f32::MAX);
f32::max(paragraph.max_intrinsic_width(), auto_width)
})
}) })
} }
pub fn max_width(paragraphs: &mut [ParagraphBuilder]) -> f32 { pub fn max_width(paragraphs: &mut [Vec<ParagraphBuilder>]) -> f32 {
paragraphs.iter_mut().fold(0.0, |max_width, p| { paragraphs.iter_mut().fold(0.0, |max_width, p| {
let mut paragraph = p.build(); p.iter_mut().fold(max_width, |max_width, paragraph| {
paragraph.layout(f32::MAX); let mut paragraph = paragraph.build();
f32::max(paragraph.max_width(), max_width) paragraph.layout(f32::MAX);
f32::max(paragraph.max_width(), max_width)
})
}) })
} }
pub fn auto_height(paragraphs: &mut [ParagraphBuilder], width: f32) -> f32 { 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| {
paragraph.layout(width); let mut paragraph = paragraph.build();
auto_height + paragraph.height() paragraph.layout(width);
auto_height + paragraph.height()
})
}) })
} }