From fe35df948cdc354cfe8defeb5cf9ae79c60acbfd Mon Sep 17 00:00:00 2001 From: Zach Berry <1112351+zberry@users.noreply.github.com> Date: Wed, 4 Nov 2026 20:09:40 -0600 Subject: [PATCH] Flip is refused on a SmartArt graphic instead of turning it into a plain group MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PowerPoint greys Flip out for a SmartArt graphic (SmartArt Design ▸ Right to Left is the way to mirror it), also inside a group, as ungrouping would hand the flip to the graphic. Before, a flip turned it into a plain group that the smartArt.* commands then refused. arrange.flipHorizontal and flipVertical reach the check through shape.flip. Split out of the SmartArt PR at review. Co-Authored-By: Claude Opus 5.5 --- crates/engine/src/cmd/shape/size.rs | 10 ++++++++++ crates/engine/src/tests.rs | 24 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/crates/engine/src/cmd/shape/size.rs b/crates/engine/src/cmd/shape/size.rs index af235e403..5a573c625 100644 --- a/crates/engine/src/cmd/shape/size.rs +++ b/crates/engine/src/cmd/shape/size.rs @@ -191,6 +191,16 @@ pub(super) fn rotate(s: &mut Session, p: &Value) -> Result { pub(super) fn flip(s: &mut Session, p: &Value) -> Result { let v = str_param(p, "axis").unwrap_or("horizontal").starts_with('v'); edit_geometry(s, p, "shape.flip", |sh| { + // PowerPoint greys Flip out for a SmartArt graphic (SmartArt Design > Right to Left mirrors it), + // also inside a group: ungrouping would hand the flip to the graphic. + let mut graphic = false; + deckcraft_model::walk(std::slice::from_ref(&*sh), &mut |x, _| graphic |= x.diagram.is_some()); + if graphic { + return Err(bad( + "shape.flip", + "Flip is not available for a SmartArt graphic (it would turn it into a plain group); use `smartArt.layout` to change how it is laid out", + )); + } if let Some(x) = sh.xfrm.as_mut() { if v { x.flip_v = !x.flip_v; diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 12fac5efc..2410a38bc 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -6509,3 +6509,27 @@ fn save_to_image_path_is_an_error_not_zip_bytes() { } let _ = std::fs::remove_dir_all(&dir); } + +#[test] +fn flip_is_refused_on_a_smartart_graphic_and_leaves_it_a_graphic() { + let mut s = session(); + s.execute("slide.new", &json!({"layout": "blank"})).unwrap(); + s.execute("insert.textBox", &json!({"text": "Goals\nRevenue"})).unwrap(); + let gid = s.execute("smartArt.fromText", &json!({"kind": "blockList"})).unwrap()["id"].as_u64().unwrap(); + for cmd in ["arrange.flipHorizontal", "arrange.flipVertical", "shape.flip"] { + let e = s.execute(cmd, &json!({"ids": [gid]})).unwrap_err().to_string(); + assert!(e.contains("SmartArt") && e.contains("smartArt.layout"), "{cmd}: {e}"); + } + // Grouped with another shape it cannot be flipped either: ungrouping would hand it the flip. + let other = s.execute("insert.textBox", &json!({"text": "x"})).unwrap()["id"].as_u64().unwrap(); + let group = s.execute("arrange.group", &json!({"ids": [gid, other]})).unwrap()["id"].as_u64().unwrap(); + let e = s.execute("shape.flip", &json!({"ids": [group]})).unwrap_err().to_string(); + assert!(e.contains("SmartArt"), "{e}"); + s.execute("arrange.ungroup", &json!({"ids": [group]})).unwrap(); + // Still a SmartArt graphic: smartArt commands keep working. + s.execute("smartArt.layout", &json!({"id": gid, "kind": "cycle"})).unwrap(); + // Ordinary shapes still flip. + let b = s.execute("insert.textBox", &json!({"text": "x"})).unwrap(); + let id = b["id"].as_u64().unwrap(); + assert_eq!(s.execute("arrange.flipHorizontal", &json!({"ids": [id]})).unwrap(), json!(1)); +}