Shape layers render their pixels clipped to the canvas. Moving an artboard (layer.artboard.set or layer.translate) re-rendered its shapes before fit_canvas grew the document, so a shape moved past the old edge came out missing or cut off until another shape edit refreshed it. fit_canvas now re-renders every shape whenever it changes the canvas size, which covers all its callers. Co-authored-by: Fabio Lussi <198260595+itrycodinganditsbad@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
0a7b8cab54
commit
14599472af
|
|
@ -188,7 +188,13 @@ pub(crate) fn fit_canvas(doc: &mut Document) {
|
|||
w = w.max(i64::from(a.rect.x1));
|
||||
h = h.max(i64::from(a.rect.y1));
|
||||
}
|
||||
doc.size = Size::new(w.clamp(1, 300_000) as u32, h.clamp(1, 300_000) as u32);
|
||||
let size = Size::new(w.clamp(1, 300_000) as u32, h.clamp(1, 300_000) as u32);
|
||||
if size != doc.size {
|
||||
doc.size = size;
|
||||
// Shape pixels are cut at the canvas: a board moved past the old edge rendered its
|
||||
// shapes against it, leaving them missing or clipped (#2389).
|
||||
crate::canvas_geom::refresh(doc, crate::canvas_geom::Refresh::Shapes);
|
||||
}
|
||||
}
|
||||
|
||||
fn next_artboard_name(doc: &Document) -> String {
|
||||
|
|
|
|||
|
|
@ -385,3 +385,44 @@ fn moving_an_artboard_past_the_canvas_grows_it() {
|
|||
s.undo();
|
||||
assert_eq!((doc(&s).size.width, doc(&s).size.height), (100, 80));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moving_an_artboard_past_the_canvas_keeps_its_shapes_whole() {
|
||||
// #2389: a shape's pixels are cut at the canvas; the board moved before the canvas grew,
|
||||
// so its shape rendered against the old edge and stayed missing or clipped.
|
||||
let shape_board = |s: &mut Session| {
|
||||
s.execute("file.new", json!({"width": 100, "height": 100, "background": "transparent"})).unwrap();
|
||||
let sh = s.execute("shape.create", json!({"kind": "rect", "rect": [10, 20, 30, 30], "fill": "#ff0000"})).unwrap()["layer"].as_u64().unwrap();
|
||||
s.execute("layer.new.artboardFromLayers", json!({})).unwrap();
|
||||
sh
|
||||
};
|
||||
for (cmd, p, size, red, not_red) in [
|
||||
("layer.artboard.set", json!({"x": 120}), (150, 100), [(121, 21), (130, 30), (149, 49)], None),
|
||||
("layer.translate", json!({"dx": 110, "dy": 0}), (150, 100), [(121, 21), (130, 30), (149, 49)], None),
|
||||
("layer.artboard.set", json!({"y": 120}), (100, 150), [(11, 121), (20, 130), (39, 149)], None),
|
||||
// Partly past the edge: the whole shape, not just the part inside the old canvas.
|
||||
("layer.artboard.set", json!({"x": 90}), (120, 100), [(91, 21), (95, 30), (110, 30)], Some((15, 30))),
|
||||
] {
|
||||
let mut s = Session::new();
|
||||
let sh = shape_board(&mut s);
|
||||
s.execute(cmd, p.clone()).unwrap();
|
||||
assert_eq!((doc(&s).size.width, doc(&s).size.height), size, "{cmd} {p}");
|
||||
for (x, y) in red {
|
||||
assert!(red_at(&s, x, y), "{cmd} {p}: ({x}, {y})");
|
||||
}
|
||||
if let Some((x, y)) = not_red {
|
||||
assert!(!red_at(&s, x, y), "{cmd} {p}: ({x}, {y}) left behind");
|
||||
}
|
||||
let info = s.execute("shape.info", json!({"layer": sh})).unwrap();
|
||||
assert_eq!(info["bounds"], json!([red[0].0 - 1, red[0].1 - 1, 30, 30]), "{cmd} {p}");
|
||||
// One undo step back to the original board; redo brings the whole shape back.
|
||||
s.undo();
|
||||
assert_eq!((doc(&s).size.width, doc(&s).size.height), (100, 100));
|
||||
assert!(red_at(&s, 20, 30), "{cmd} {p}: undo");
|
||||
s.redo();
|
||||
assert_eq!((doc(&s).size.width, doc(&s).size.height), size);
|
||||
for (x, y) in red {
|
||||
assert!(red_at(&s, x, y), "{cmd} {p}: redo ({x}, {y})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue