Merge branch 'pr-513' into batch/t5

This commit is contained in:
bflatastic
2026-10-16 01:35:39 +07:00
2 changed files with 39 additions and 4 deletions
+14 -4
View File
@@ -735,13 +735,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)
@@ -782,3 +786,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)
}
+25
View File
@@ -330,3 +330,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})");
}
}
}