Bound save_layer rects in the vector export path

This commit is contained in:
Elena Torro 2026-08-05 13:45:50 +02:00
parent 25a470bf64
commit 6ec7e47197
3 changed files with 123 additions and 29 deletions

View File

@ -329,15 +329,21 @@ fn render_text_on_canvas(
layer_opacity: Option<f32>,
overlay_emoji: bool,
) {
let layer_bounds = shape.layer_bounds();
if let Some(blur_filter) = blur {
let mut blur_paint = Paint::default();
blur_paint.set_image_filter(blur_filter.clone());
let blur_layer = SaveLayerRec::default().paint(&blur_paint);
let blur_layer = SaveLayerRec::default()
.bounds(&layer_bounds)
.paint(&blur_paint);
canvas.save_layer(&blur_layer);
}
if let Some(shadow_paint) = shadow {
let layer_rec = SaveLayerRec::default().paint(shadow_paint);
let layer_rec = SaveLayerRec::default()
.bounds(&layer_bounds)
.paint(shadow_paint);
canvas.save_layer(&layer_rec);
draw_text(
canvas,
@ -351,7 +357,9 @@ fn render_text_on_canvas(
if let Some(erode) = skia_safe::image_filters::erode((eps, eps), None, None) {
let mut layer_paint = Paint::default();
layer_paint.set_image_filter(erode);
let layer_rec = SaveLayerRec::default().paint(&layer_paint);
let layer_rec = SaveLayerRec::default()
.bounds(&layer_bounds)
.paint(&layer_paint);
canvas.save_layer(&layer_rec);
draw_text(
canvas,
@ -582,7 +590,10 @@ fn draw_decoration_stroke(
skia::BlendMode::SrcOut
};
canvas.save_layer(&SaveLayerRec::default());
let outset = stroke_paint.stroke_width().max(0.0);
let layer_bounds = bar.with_outset((outset, outset));
canvas.save_layer(&SaveLayerRec::default().bounds(&layer_bounds));
let mut mask_paint = Paint::default();
mask_paint.set_color(skia::Color::BLACK);
mask_paint.set_anti_alias(true);
@ -590,7 +601,11 @@ fn draw_decoration_stroke(
let mut blend_paint = Paint::default();
blend_paint.set_blend_mode(blend);
canvas.save_layer(&SaveLayerRec::default().paint(&blend_paint));
canvas.save_layer(
&SaveLayerRec::default()
.bounds(&layer_bounds)
.paint(&blend_paint),
);
canvas.draw_rect(bar, stroke_paint);
canvas.restore();
canvas.restore();
@ -705,7 +720,12 @@ pub fn render_emoji_overlay(
if let Some(blur_filter) = blur {
let mut blur_paint = Paint::default();
blur_paint.set_image_filter(blur_filter.clone());
canvas.save_layer(&SaveLayerRec::default().paint(&blur_paint));
let layer_bounds = shape.layer_bounds();
canvas.save_layer(
&SaveLayerRec::default()
.bounds(&layer_bounds)
.paint(&blur_paint),
);
}
for (emoji_para, deco_para) in emoji_layout
@ -728,13 +748,17 @@ fn draw_text(
layer_opacity: Option<f32>,
overlay_emoji: bool,
) {
let layer_bounds = shape.layer_bounds();
if let Some(opacity) = layer_opacity {
let mut opacity_paint = Paint::default();
opacity_paint.set_alpha_f(opacity);
let layer_rec = SaveLayerRec::default().paint(&opacity_paint);
let layer_rec = SaveLayerRec::default()
.bounds(&layer_bounds)
.paint(&opacity_paint);
canvas.save_layer(&layer_rec);
} else {
canvas.save_layer(&SaveLayerRec::default());
canvas.save_layer(&SaveLayerRec::default().bounds(&layer_bounds));
}
paint_text_with_emoji_overlay(canvas, shape, paragraph_builder_groups, overlay_emoji);
@ -759,27 +783,41 @@ fn render_masked_stroke_on_canvas(
blur: Option<&ImageFilter>,
layer_opacity: Option<f32>,
) {
let layer_bounds = shape.layer_bounds();
if let Some(blur_filter) = blur {
let mut blur_paint = Paint::default();
blur_paint.set_image_filter(blur_filter.clone());
canvas.save_layer(&SaveLayerRec::default().paint(&blur_paint));
canvas.save_layer(
&SaveLayerRec::default()
.bounds(&layer_bounds)
.paint(&blur_paint),
);
}
if let Some(opacity) = layer_opacity {
let mut opacity_paint = Paint::default();
opacity_paint.set_alpha_f(opacity);
canvas.save_layer(&SaveLayerRec::default().paint(&opacity_paint));
canvas.save_layer(
&SaveLayerRec::default()
.bounds(&layer_bounds)
.paint(&opacity_paint),
);
}
canvas.save_layer(&SaveLayerRec::default());
canvas.save_layer(&SaveLayerRec::default().bounds(&layer_bounds));
canvas.save_layer(&SaveLayerRec::default());
canvas.save_layer(&SaveLayerRec::default().bounds(&layer_bounds));
paint_text(canvas, shape, mask_builders);
let mut stroke_paint = Paint::default();
stroke_paint.set_blend_mode(stroke_mask_blend);
canvas.save_layer(&SaveLayerRec::default().paint(&stroke_paint));
canvas.save_layer(
&SaveLayerRec::default()
.bounds(&layer_bounds)
.paint(&stroke_paint),
);
paint_text(canvas, shape, stroke_builders);
@ -789,7 +827,11 @@ fn render_masked_stroke_on_canvas(
if let Some(fill_builders) = fill_builders {
let mut dst_over_paint = Paint::default();
dst_over_paint.set_blend_mode(skia::BlendMode::DstOver);
canvas.save_layer(&SaveLayerRec::default().paint(&dst_over_paint));
canvas.save_layer(
&SaveLayerRec::default()
.bounds(&layer_bounds)
.paint(&dst_over_paint),
);
paint_text(canvas, shape, fill_builders);

View File

@ -79,11 +79,14 @@ impl ShapeRenderer for VectorRenderer<'_> {
}
fn draw_drop_shadows(&mut self, shape: &Shape) -> Result<()> {
let layer_bounds = shape.layer_bounds();
for shadow in shape.drop_shadows_visible() {
if let Some(filter) = shadow.get_drop_shadow_filter() {
let mut paint = Paint::default();
paint.set_image_filter(filter);
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint);
let layer_rec = skia::canvas::SaveLayerRec::default()
.bounds(&layer_bounds)
.paint(&paint);
self.canvas.save_layer(&layer_rec);
let mut fill_paint = Paint::default();
fill_paint.set_anti_alias(true);
@ -99,10 +102,14 @@ impl ShapeRenderer for VectorRenderer<'_> {
if !shape.has_fills() {
return Ok(());
}
let layer_bounds = shape.layer_bounds();
for shadow in shape.inner_shadows_visible() {
let paint = shadow.get_inner_shadow_paint(true, shape.image_filter(1.).as_ref());
self.canvas
.save_layer(&skia::canvas::SaveLayerRec::default().paint(&paint));
self.canvas.save_layer(
&skia::canvas::SaveLayerRec::default()
.bounds(&layer_bounds)
.paint(&paint),
);
let mut fill_paint = Paint::default();
fill_paint.set_anti_alias(true);
fill_paint.set_color(skia::Color::BLACK);
@ -161,9 +168,13 @@ impl ShapeRenderer for VectorRenderer<'_> {
})
.collect();
let layer_bounds = shape.layer_bounds();
for shadow_paint in &drop_shadows {
self.canvas
.save_layer(&skia::canvas::SaveLayerRec::default().paint(shadow_paint));
self.canvas.save_layer(
&skia::canvas::SaveLayerRec::default()
.bounds(&layer_bounds)
.paint(shadow_paint),
);
text::render_overlay_emoji(
self.canvas,
@ -331,7 +342,10 @@ impl ShapeRenderer for VectorRenderer<'_> {
if let Some(filter) = skia::image_filters::blur((sigma, sigma), None, None, None) {
let mut paint = Paint::default();
paint.set_image_filter(filter);
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint);
let layer_bounds = shape.layer_bounds();
let layer_rec = skia::canvas::SaveLayerRec::default()
.bounds(&layer_bounds)
.paint(&paint);
self.canvas.save_layer(&layer_rec);
true
} else {
@ -715,7 +729,10 @@ fn render_group(
}
}
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint);
let layer_bounds = element.extrect(tree, scale);
let layer_rec = skia::canvas::SaveLayerRec::default()
.bounds(&layer_bounds)
.paint(&paint);
canvas.save_layer(&layer_rec);
}
@ -726,7 +743,12 @@ fn render_group(
// as content, then re-draw the mask silhouette (the group's first child)
// with DstIn to clip everything to it.
let paint = Paint::default();
canvas.save_layer(&skia::canvas::SaveLayerRec::default().paint(&paint));
let subtree_bounds = element.extrect(tree, scale);
canvas.save_layer(
&skia::canvas::SaveLayerRec::default()
.bounds(&subtree_bounds)
.paint(&paint),
);
for child_id in &children {
render_tree_inner(shared, canvas, child_id, tree, scale, opts)?;
@ -735,7 +757,11 @@ fn render_group(
if let Some(mask_id) = element.mask_id() {
let mut mask_paint = Paint::default();
mask_paint.set_blend_mode(skia::BlendMode::DstIn);
canvas.save_layer(&skia::canvas::SaveLayerRec::default().paint(&mask_paint));
canvas.save_layer(
&skia::canvas::SaveLayerRec::default()
.bounds(&subtree_bounds)
.paint(&mask_paint),
);
render_tree_inner(shared, canvas, mask_id, tree, scale, opts)?;
canvas.restore(); // mask layer
}
@ -797,7 +823,10 @@ fn render_frame(
}
}
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint);
let layer_bounds = element.extrect(tree, scale);
let layer_rec = skia::canvas::SaveLayerRec::default()
.bounds(&layer_bounds)
.paint(&paint);
canvas.save_layer(&layer_rec);
}
@ -857,13 +886,18 @@ fn render_container_drop_shadows(
draw_fills: bool,
opts: &TreeOpts,
) -> Result<()> {
let subtree_bounds = element.extrect(tree, scale);
for shadow in element.drop_shadows_visible() {
let Some(filter) = shadow.get_drop_shadow_filter() else {
continue;
};
let mut paint = Paint::default();
paint.set_image_filter(filter);
canvas.save_layer(&skia::canvas::SaveLayerRec::default().paint(&paint));
canvas.save_layer(
&skia::canvas::SaveLayerRec::default()
.bounds(&subtree_bounds)
.paint(&paint),
);
if draw_fills && !element.fills.is_empty() {
let mut renderer = VectorRenderer::new(canvas, shared, scale);
@ -902,7 +936,10 @@ fn render_leaf(
let mut paint = Paint::default();
paint.set_blend_mode(element.blend_mode().into());
paint.set_alpha_f(element.opacity());
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint);
let layer_bounds = element.layer_bounds();
let layer_rec = skia::canvas::SaveLayerRec::default()
.bounds(&layer_bounds)
.paint(&paint);
canvas.save_layer(&layer_rec);
}
@ -1101,7 +1138,8 @@ fn draw_stroke_kind_aware(canvas: &Canvas, shape: &Shape, stroke: &Stroke, paint
}
StrokeKind::Outer => {
canvas.save();
canvas.save_layer(&skia::canvas::SaveLayerRec::default());
let layer_bounds = shape.layer_bounds();
canvas.save_layer(&skia::canvas::SaveLayerRec::default().bounds(&layer_bounds));
draw_shape_geometry(canvas, shape, paint);
let mut clear_paint = Paint::default();
clear_paint.set_blend_mode(skia::BlendMode::Clear);
@ -1134,7 +1172,8 @@ fn draw_image_stroke(
let container = shape.selrect;
canvas.save();
canvas.save_layer(&skia::canvas::SaveLayerRec::default());
let layer_bounds = shape.layer_bounds();
canvas.save_layer(&skia::canvas::SaveLayerRec::default().bounds(&layer_bounds));
// Opaque stroke silhouette; the SrcIn image draw below fills it.
draw_stroke_geometry(canvas, scale, shape, stroke, true);

View File

@ -1070,7 +1070,7 @@ impl Shape {
extrect
}
fn calculate_extrect_uncached(&self, shapes_pool: ShapesPoolRef, scale: f32) -> math::Rect {
fn own_extrect_bounds(&self) -> Bounds {
let shape = self;
let max_stroke = Stroke::max_bounds_width(shape.strokes.iter(), shape.is_open());
@ -1096,6 +1096,19 @@ impl Shape {
bounds = self.apply_stroke_bounds(bounds, max_stroke);
bounds = self.apply_shadow_bounds(bounds);
bounds = self.apply_blur_bounds(bounds);
bounds
}
/// Bound for a `SaveLayerRec` wrapping this shape's own drawing, in
/// untransformed space (callers concatenate [`Self::centered_transform`]
/// first). Includes shadow/blur margins, so it is also a valid input bound
/// for a layer whose paint carries an image filter.
pub fn layer_bounds(&self) -> math::Rect {
self.own_extrect_bounds().to_rect()
}
fn calculate_extrect_uncached(&self, shapes_pool: ShapesPoolRef, scale: f32) -> math::Rect {
let mut bounds = self.own_extrect_bounds();
bounds = self.apply_children_bounds(bounds, shapes_pool, scale);
bounds = self.apply_children_blur(bounds, shapes_pool);