mirror of
https://github.com/penpot/penpot.git
synced 2026-10-28 21:40:34 +00:00
🐛 Exclude inner shadows from WebGL export bounds (#12427)
Inner shadows were joined into extrect via the drop-shadow filter, so a Y offset grew the PNG/SVG export surface. Only drop shadows expand bounds, matching classic export. Closes #12386.
This commit is contained in:
parent
fd03ce2a63
commit
29cd50621d
@ -3,6 +3,6 @@ source: src/render/svg/tests.rs
|
||||
expression: svg
|
||||
---
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="123.78461" height="103.78461" viewBox="0 0 123.78461 103.78461"><defs><filter id="fx0" filterUnits="userSpaceOnUse" x="0" y="0" width="123.78461" height="103.78461" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="bg"/><feBlend mode="normal" in="SourceGraphic" in2="bg" result="shape"/><feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/><feOffset in="hardAlpha" dx="2" dy="3" result="off"/><feGaussianBlur in="off" stdDeviation="3.9641016" result="blurred"/><feComposite in="blurred" in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" result="shadow"/><feColorMatrix in="shadow" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7058824 0" result="colored"/><feBlend mode="normal" in="colored" in2="shape" result="inner0"/></filter></defs><g filter="url(#fx0)">
|
||||
<rect fill="#0080FF" transform="translate(9.8923 8.8923)" width="100" height="80"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="80" viewBox="0 0 100 80"><defs><filter id="fx0" filterUnits="userSpaceOnUse" x="0" y="0" width="100" height="80" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="bg"/><feBlend mode="normal" in="SourceGraphic" in2="bg" result="shape"/><feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/><feOffset in="hardAlpha" dx="2" dy="3" result="off"/><feGaussianBlur in="off" stdDeviation="3.9641016" result="blurred"/><feComposite in="blurred" in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" result="shadow"/><feColorMatrix in="shadow" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7058824 0" result="colored"/><feBlend mode="normal" in="colored" in2="shape" result="inner0"/></filter></defs><g filter="url(#fx0)">
|
||||
<rect fill="#0080FF" width="100" height="80"/>
|
||||
</g></svg>
|
||||
|
||||
@ -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<Item = &Shadow> {
|
||||
self.shadows.iter().rev().filter(|shadow| !shadow.hidden())
|
||||
}
|
||||
|
||||
pub fn to_path_transform(&self) -> Option<Matrix> {
|
||||
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());
|
||||
|
||||
@ -87,6 +87,10 @@ impl Shadow {
|
||||
}
|
||||
|
||||
pub fn get_drop_shadow_filter(&self) -> Option<ImageFilter> {
|
||||
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),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user