From b3b6b14eb42d78095193a0ab212722f345974900 Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Thu, 15 Oct 2026 13:18:59 +0200 Subject: [PATCH] Anti-alias rectangular frame clips on fractional device edges Fixes #428 Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Opus 5.5 --- crates/render/src/draw.rs | 18 ++++++++++++++---- crates/render/src/tests.rs | 25 +++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/crates/render/src/draw.rs b/crates/render/src/draw.rs index be839b27..e4425938 100644 --- a/crates/render/src/draw.rs +++ b/crates/render/src/draw.rs @@ -665,13 +665,17 @@ fn draw_children_clipped(ctx: &Ctx, pix: &mut Pixmap, n: &Node, xf: Affine, dept // layers behind the frame. let Some(mut layer) = pix.clone_rect(ir) else { return }; let lxf = Affine::translate((-f64::from(lx), -f64::from(ly))) * xf; - let rect_clip = n.corners.uniform() == Some(0.0) && is_axis_aligned(xf); - // The clip (a rectangular one is the layer's own box) is known before the children draw, so - // their backdrops are clipped the way the layer will be. + let square_aligned = n.corners.uniform() == Some(0.0) && is_axis_aligned(xf); + let rect_clip = square_aligned && on_whole_pixels(dev); + // The clip (a rectangular one on whole pixels is the layer's own box) is known before the + // children draw, so their backdrops are clipped the way the layer will be. An axis-aligned + // square frame with fractional edges gets an anti-aliased mask of its box so its edge pixels + // are only partly covered. let mask = if rect_clip { None } else { - clip.and_then(ts_path).and_then(|cp| { + let own_box = (square_aligned && clip.is_none()).then(|| n.local_rect().to_path(0.1)); + clip.or(own_box.as_ref()).and_then(ts_path).and_then(|cp| { let mut m = Mask::new(ir.width(), ir.height())?; m.fill_path(&cp, FillRule::Winding, true, ts_transform(lxf)); Some(m) @@ -695,3 +699,9 @@ fn is_axis_aligned(xf: Affine) -> bool { fn fract_ok(_v: f64) -> bool { true } + +/// Whether all four edges of the device rectangle `r` lie on whole pixels, up to the float noise +/// `snap_px` forgives. +fn on_whole_pixels(r: Rect) -> bool { + [r.x0, r.y0, r.x1, r.y1].into_iter().all(|v| (snap_px(v) - v.round()).abs() < 1e-9) +} diff --git a/crates/render/src/tests.rs b/crates/render/src/tests.rs index 167cc742..3596fb6d 100644 --- a/crates/render/src/tests.rs +++ b/crates/render/src/tests.rs @@ -299,3 +299,28 @@ fn inner_shadow_reaches_every_edge() { assert!(px(&pix, 11, 25)[0] < 235, "{:?}", px(&pix, 11, 25)); assert!(px(&pix, 35, 30)[0] > 245); } + +#[test] +fn rect_clip_frame_matches_same_rect_fill_on_fractional_edges() { + let white = Options { background: Some(Color::WHITE), ..Default::default() }; + for (x, y, w, h) in [(0.5, 0.5, 10.0, 10.0), (0.0, 0.0, 10.5, 10.5), (0.0, 0.0, 10.0, 10.0)] { + let mut clipped = figcraft_doc::Document::new("clipped"); + let p = clipped.first_page().unwrap(); + let f = clipped.create(p, NodeKind::Frame, "F", x, y, w, h).unwrap(); + clipped.get_mut(f).unwrap().fills.clear(); + clipped.get_mut(f).unwrap().clip = true; + let c = clipped.create(f, NodeKind::Rectangle, "C", -5.0, -5.0, w + 10.0, h + 10.0).unwrap(); + clipped.get_mut(c).unwrap().fills = vec![Paint::solid(Color::BLACK)]; + + let mut direct = figcraft_doc::Document::new("direct"); + let dp = direct.first_page().unwrap(); + let r = direct.create(dp, NodeKind::Rectangle, "R", x, y, w, h).unwrap(); + direct.get_mut(r).unwrap().fills = vec![Paint::solid(Color::BLACK)]; + + let a = render_view(&clipped, p, &view(12, 12), &white).unwrap(); + let e = render_view(&direct, dp, &view(12, 12), &white).unwrap(); + for (px_x, px_y) in [(0, 0), (0, 5), (5, 0), (5, 5), (10, 5), (5, 10), (10, 10), (11, 11)] { + assert_eq!(px(&a, px_x, px_y), px(&e, px_x, px_y), "frame {x},{y} {w}x{h} at ({px_x},{px_y})"); + } + } +}