From 2e03e178204ac9cc483140d3b8d9e5683a46911e Mon Sep 17 00:00:00 2001 From: Nigel Ihza Firdausy <106657905+Ni-zav@users.noreply.github.com> Date: Sat, 10 Oct 2026 15:52:05 +0700 Subject: [PATCH] fix(layout): reject unsupported spread-view rotation angles Reject non-quarter-turn or non-integer view angles instead of silently truncating or defaulting them. Add a command-level regression test for invalid inputs, valid quarter turns, and clearing the rotation. --- crates/engine/src/cmd/layout.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/cmd/layout.rs b/crates/engine/src/cmd/layout.rs index bd532079..bf967483 100644 --- a/crates/engine/src/cmd/layout.rs +++ b/crates/engine/src/cmd/layout.rs @@ -105,7 +105,13 @@ pub fn specs() -> Vec { has_doc, |s, p| { let si = p.get("spread").and_then(Value::as_u64).unwrap_or(0) as usize; - let angle = p.get("angle").and_then(Value::as_i64).unwrap_or(90); + let angle = match p.get("angle") { + None => 90, + Some(v) => v.as_i64().ok_or_else(|| bad("layout.rotateSpreadView", "angle must be an integer multiple of 90 degrees"))?, + }; + if angle % 90 != 0 { + return Err(bad("layout.rotateSpreadView", "angle must be an integer multiple of 90 degrees")); + } s.edit(|d, _| { let sp = d.spreads.get_mut(si).ok_or_else(|| bad("layout.rotateSpreadView", "no such spread"))?; let pg = std::sync::Arc::make_mut(sp).pages.first_mut().ok_or_else(|| bad("layout.rotateSpreadView", "empty spread"))?; @@ -921,3 +927,27 @@ mod adjust_tests { assert_eq!(b, b2); } } + +#[cfg(test)] +mod spread_rotation_input_tests { + use serde_json::json; + + #[test] + fn quarter_turns_work_and_other_angles_are_rejected_without_mutation() { + let mut s = crate::Session::new(); + s.execute("file.new", &json!({})).unwrap(); + let rotation = |s: &crate::Session| s.doc().unwrap().doc.spreads[0].pages[0].view_rotation; + for bad in [json!(45), json!(-45), json!(91), json!("90"), json!(0.5)] { + assert!(s.execute("layout.rotateSpreadView", &json!({"angle": bad})).is_err(), "{bad}"); + assert_eq!(rotation(&s), 0, "invalid input must not change the view"); + } + s.execute("layout.rotateSpreadView", &json!({"angle": 90})).unwrap(); + assert_eq!(rotation(&s), 1); + s.execute("layout.rotateSpreadView", &json!({"angle": -90})).unwrap(); + assert_eq!(rotation(&s), 0); + s.execute("layout.rotateSpreadView", &json!({"angle": 180})).unwrap(); + assert_eq!(rotation(&s), 2); + s.execute("layout.rotateSpreadView", &json!({"angle": 0})).unwrap(); + assert_eq!(rotation(&s), 0, "zero resets the spread view"); + } +}