diff --git a/crates/render/src/draw.rs b/crates/render/src/draw.rs index c2ab8d095..f46222570 100644 --- a/crates/render/src/draw.rs +++ b/crates/render/src/draw.rs @@ -550,14 +550,15 @@ pub fn effect_margin(n: &Node) -> f64 { layer + others } -/// Everything node `id` can paint, in page coordinates: its paint bounds (glyphs of overflowing -/// text included) grown by its effects' margin. What a change to the node can affect on screen. +/// Everything node `id` can paint, in page coordinates: its paint bounds, effects and glyphs of +/// overflowing text included. What a change to the node can affect on screen. Its effects' margin +/// (what they read from outside, see [`effect_margin`]) isn't added again: drawing the area +/// takes in what lies that far off, as culling does. pub fn paint_bounds(doc: &Document, id: NodeId) -> Option { let n = doc.node(id)?; - let m = effect_margin(n).abs(); let world = doc.world_transform(id); let local = local_paint_bounds(doc, n, world, 0, None); - Some(figcraft_geom::transform_rect_bbox(world, local).inflate(m, m)) + Some(figcraft_geom::transform_rect_bbox(world, local)) } /// The local → page map of a layer drawn at `xf` (local → device): the view's scale taken out. diff --git a/crates/render/src/retained_tests.rs b/crates/render/src/retained_tests.rs index 5060d1cab..dd93abc72 100644 --- a/crates/render/src/retained_tests.rs +++ b/crates/render/src/retained_tests.rs @@ -188,9 +188,9 @@ fn edits_under_and_beside_effects_match() { let beside = edit(&across, |d| d.get_mut(s.rect).unwrap().transform = Affine::translate((10.0, 110.0))); assert!(partial(check(&mut r, &beside, s.page, &v))); // A child of a group with a drop shadow: the shadow moves too, past the group's box with the - // child (a view large enough that this stays a part of it). + // child. let g1 = s.doc.find_by_name("G1", None)[0]; - let v = View { origin: (260.0, 0.0), scale: 1.0, width: 400, height: 300 }; + let v = View { origin: (260.0, 0.0), scale: 1.0, width: 300, height: 220 }; check(&mut r, &beside, s.page, &v); let shadowed = edit(&beside, |d| d.get_mut(g1).unwrap().transform = Affine::translate((30.0, 20.0))); assert!(partial(check(&mut r, &shadowed, s.page, &v))); @@ -448,6 +448,36 @@ fn effect_margins_too_large_to_add_stay_large() { assert_eq!(m, 2.0 * crate::draw::MAX_REACH); } +#[test] +fn a_moved_layer_renders_where_its_effects_paint_and_no_further() { + fonts(); + let shadow = |dy: f64, r: f64| Effect { + kind: EffectKind::DropShadow(Shadow { color: Color::BLACK, offset: (0.0, dy), radius: r, ..Shadow::default() }), + ..Effect::drop_shadow() + }; + let blur = Effect { kind: EffectKind::LayerBlur(figcraft_doc::Blur::uniform(30.0)), ..Effect::layer_blur() }; + for (why, effects) in [("a shadow falling 40", vec![shadow(40.0, 8.0)]), ("a blur of 30", vec![blur])] { + let mut d = Document::new("moved"); + let p = d.first_page().unwrap(); + let id = d.create(p, NodeKind::Rectangle, "R", 300.0, 200.0, 120.0, 120.0).unwrap(); + solid(d.node_mut(id).unwrap(), "3366FF"); + d.node_mut(id).unwrap().effects = effects; + let before = Arc::new(d); + let after = edit(&before, |d| d.get_mut(id).unwrap().transform = Affine::translate((315.0, 210.0))); + let v = View { origin: (0.0, 0.0), scale: 1.0, width: 1000, height: 800 }; + let mut r = Retained::default(); + check(&mut r, &before, p, &v); + // Where it painted and where it paints now, a pixel round each for rounding out; a quarter + // more, as layers grow by a shadow's reach on every side, not only towards its offset. + let area = |doc: &Document| doc.world_render_bounds(id).map_or(0.0, |b| (b.width() + 2.0) * (b.height() + 2.0)); + let most = (area(&before) + area(&after)) * 1.25; + match check(&mut r, &after, p, &v) { + Work::Partial { pixels } => assert!(pixels as f64 <= most, "{why}: {pixels} pixels rendered, where it paints is {most}"), + w => panic!("{why}: {w:?}"), + } + } +} + /// Every channel of `a` within `tol` of the same channel of `b` from row `rows_down` on. fn assert_within(a: &Pixmap, b: &Pixmap, rows_down: u32, tol: u8, what: &str) { assert_eq!(a.width(), b.width(), "{what}: widths");