From 3f973b0d8736d61540a52e655d5eea9a5bb4d88e Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Sat, 17 Oct 2026 03:07:11 +0000 Subject: [PATCH] Mirror the rotation when flipping a rotated shape Found while testing crates/engine/src/cmd/shape.rs:400-412 (flip; used by arrange.flipHorizontal / arrange.flipVertical) Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Sonnet 5.5 Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Sonnet 5.5 --- crates/engine/src/cmd/shape.rs | 2 ++ crates/engine/src/tests.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/crates/engine/src/cmd/shape.rs b/crates/engine/src/cmd/shape.rs index e3bf3920..51b1a680 100644 --- a/crates/engine/src/cmd/shape.rs +++ b/crates/engine/src/cmd/shape.rs @@ -406,6 +406,8 @@ fn flip(s: &mut Session, p: &Value) -> Result { } else { x.flip_h = !x.flip_h; } + // `Xfrm::affine` flips first and rotates second; a true mirror also negates the rotation. + x.rot = (360.0 - x.rot_norm()) % 360.0; } Ok(()) }) diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index a464fd8b..92d292bc 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -3623,3 +3623,34 @@ fn table_column_width_of_a_huge_column_is_ignored() { assert_eq!(table_of(&s, id).cols, vec![100.0, 100.0], "no column changes"); } } + +#[test] +fn flipping_a_rotated_shape_mirrors_its_outline() { + let corners = |s: &Session, id: deckcraft_model::ShapeId| { + let x = s.doc().unwrap().shape(id).unwrap().xfrm.unwrap(); + let a = x.affine(); + [(0.0, 0.0), (x.w, 0.0), (x.w, x.h), (0.0, x.h)].map(|(px, py)| { + let p = a * deckcraft_geom::Point::new(px, py); + ((p.x * 100.0).round() as i64, (p.y * 100.0).round() as i64) + }) + }; + let sorted = |mut c: [(i64, i64); 4]| { + c.sort_unstable(); + c + }; + for (cmd, horizontal) in [("arrange.flipHorizontal", true), ("arrange.flipVertical", false)] { + let mut s = session(); + let r = s.execute("shape.insert", &json!({"preset": "rect", "rect": [100, 100, 200, 50]})).unwrap(); + let id = deckcraft_model::ShapeId(r["id"].as_u64().unwrap() as u32); + s.execute("shape.rotate", &json!({"deg": 30})).unwrap(); + let before = corners(&s, id); + // Mirror about the box's centre line (centre is 200, 125). + let mirrored = before.map(|(x, y)| if horizontal { (40000 - x, y) } else { (x, 25000 - y) }); + s.execute(cmd, &json!({})).unwrap(); + assert_eq!(sorted(corners(&s, id)), sorted(mirrored), "{cmd} of a 30° rotated rectangle is not its mirror image"); + let rot = s.doc().unwrap().shape(id).unwrap().xfrm.unwrap().rot; + assert!((rot - 330.0).abs() < 1e-9, "{cmd} negates the rotation, got {rot}"); + s.execute(cmd, &json!({})).unwrap(); + assert_eq!(sorted(corners(&s, id)), sorted(before), "{cmd} twice restores the outline"); + } +}