diff --git a/render-wasm/src/render/svg/snapshots/render_wasm__render__svg__tests__exports_leaf_inner_shadow_as_svg_filter.snap b/render-wasm/src/render/svg/snapshots/render_wasm__render__svg__tests__exports_leaf_inner_shadow_as_svg_filter.snap index feb3c531dd..1127c671f0 100644 --- a/render-wasm/src/render/svg/snapshots/render_wasm__render__svg__tests__exports_leaf_inner_shadow_as_svg_filter.snap +++ b/render-wasm/src/render/svg/snapshots/render_wasm__render__svg__tests__exports_leaf_inner_shadow_as_svg_filter.snap @@ -3,6 +3,6 @@ source: src/render/svg/tests.rs expression: svg --- - - + + diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index e766b723d3..aaf4e55a49 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -1005,16 +1005,14 @@ impl Shape { let max_stroke = Stroke::max_bounds_width(self.strokes.iter(), self.is_open()); let mut rect = bounds.to_rect(); - for shadow in self.shadows_visible() { - if !shadow.hidden() { - if let Some(filter) = shadow.get_drop_shadow_filter() { - let mut source = rect; - if mitered && shadow.spread > 0.0 { - let tip = (MITER_LIMIT - 1.0) * (max_stroke + shadow.spread); - source.outset((tip, tip)); - } - rect.join(filter.compute_fast_bounds(source)); + for shadow in self.drop_shadows_visible() { + if let Some(filter) = shadow.get_drop_shadow_filter() { + let mut source = rect; + if mitered && shadow.spread > 0.0 { + let tip = (MITER_LIMIT - 1.0) * (max_stroke + shadow.spread); + source.outset((tip, tip)); } + rect.join(filter.compute_fast_bounds(source)); } } Bounds::from_rect(&rect) @@ -1514,10 +1512,6 @@ impl Shape { .filter(|shadow| shadow.style() == ShadowStyle::Inner && !shadow.hidden()) } - pub fn shadows_visible(&self) -> impl DoubleEndedIterator { - self.shadows.iter().rev().filter(|shadow| !shadow.hidden()) - } - pub fn to_path_transform(&self) -> Option { match self.shape_type { Type::Path(_) | Type::Bool(_) => { @@ -2571,6 +2565,35 @@ mod tests { assert!(extrect.bottom > 50.0 + 20.0); } + /// Inner shadows are drawn inside the shape; they must not inflate export + /// size (WebGL PNG surface uses `extrect`). Regression for #12386. + #[test] + fn leaf_extrect_ignores_inner_shadow_offset() { + let mut pool = ShapesPool::new(); + pool.initialize(1); + + let shape_id = Uuid::new_v4(); + { + let shape = pool.add_shape(shape_id); + shape.set_shape_type(Type::Rect(Rect::default())); + shape.set_selrect(0.0, 0.0, 200.0, 200.0); + shape.add_shadow(inner_shadow(0.0, (0.0, 1.0))); + } + + let shape = pool.get(&shape_id).expect("shape should exist"); + let baseline = { + let mut no_shadow = shape.clone(); + no_shadow.clear_shadows(); + no_shadow.calculate_extrect(&pool, 1.0) + }; + let extrect = shape.calculate_extrect(&pool, 1.0); + + assert_eq!(extrect.height(), baseline.height()); + assert_eq!(extrect.width(), baseline.width()); + assert_eq!(extrect.height(), 200.0); + assert_eq!(extrect.width(), 200.0); + } + #[test] fn test_apply_transform() { let mut shape = Shape::new(Uuid::new_v4()); diff --git a/render-wasm/src/shapes/shadows.rs b/render-wasm/src/shapes/shadows.rs index 11a0e13295..b13c6d76bf 100644 --- a/render-wasm/src/shapes/shadows.rs +++ b/render-wasm/src/shapes/shadows.rs @@ -87,6 +87,10 @@ impl Shadow { } pub fn get_drop_shadow_filter(&self) -> Option { + if self.style == ShadowStyle::Inner { + return None; + } + let sigma = radius_to_sigma(self.blur); let mut filter = image_filters::drop_shadow_only( (self.offset.0, self.offset.1),