From 6b192f58ce802b9d54e87e040d4bf5242bd5d747 Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Sun, 18 Oct 2026 10:41:20 +0200 Subject: [PATCH] Clamp negative margins before checking they leave text room (#1941) Found while testing crates/engine/src/cmd/page.rs:392 (margins) and :424 (margins_fit) Co-authored-by: Claude Haiku 5.5 --- crates/engine/src/cmd/page.rs | 14 ++++++++------ crates/engine/src/tests.rs | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/crates/engine/src/cmd/page.rs b/crates/engine/src/cmd/page.rs index a521fc6103..6fdfa31abe 100644 --- a/crates/engine/src/cmd/page.rs +++ b/crates/engine/src/cmd/page.rs @@ -474,7 +474,9 @@ fn margins(s: &mut Session, v: &Value) -> CmdResult { ) } }; - let gutter = p::f32(v, "gutter"); + // Negative margins count as zero before the fit check, so one can't offset another. + let (t, b, l, r) = (t.max(0.0), b.max(0.0), l.max(0.0), r.max(0.0)); + let gutter = p::f32(v, "gutter").map(|g| g.max(0.0)); let cur = sect(s); if !margins_fit((cur.page_w, cur.page_h), (t, b, l, r), gutter.unwrap_or(cur.gutter)) { return Err(CmdError::Params("margins leave no room for text".into())); @@ -485,12 +487,12 @@ fn margins(s: &mut Session, v: &Value) -> CmdResult { s.doc.settings.mirror_margins = on; } with_sect(s, |x| { - x.margin_top = t.max(0.0); - x.margin_bottom = b.max(0.0); - x.margin_left = l.max(0.0); - x.margin_right = r.max(0.0); + x.margin_top = t; + x.margin_bottom = b; + x.margin_left = l; + x.margin_right = r; if let Some(g) = gutter { - x.gutter = g.max(0.0); + x.gutter = g; } }) } diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 72aaa9cee3..e2a86ec6e7 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -1935,6 +1935,20 @@ fn layout_size_refuses_a_page_its_margins_dont_fit() { assert_eq!((s.doc.last_section.page_w, s.doc.last_section.page_h), (180.0, 180.0), "exactly 36 pt of room is enough"); } +/// A negative margin (or gutter) counts as zero, so it can't offset an oversized one past the +/// text-room check: `layout.margins` refuses and changes nothing. +#[test] +fn layout_margins_negative_cannot_hide_an_oversized_margin() { + let mut s = s(); + run(&mut s, "layout.margins", json!({"preset": "normal"})); + let before = s.doc.last_section.clone(); + for v in [json!({"top": 2000, "bottom": -2000}), json!({"left": 2000, "right": -2000}), json!({"left": 600, "right": 0, "gutter": -600})] { + let e = s.run("layout.margins", &v).unwrap_err().to_string(); + assert!(e.contains("margins leave no room for text"), "{v}: {e}"); + assert_eq!(s.doc.last_section, before, "{v} changes nothing"); + } +} + /// #1433: the smallest page there can be (0.1" wide, no margins, set straight through Page /// Setup's section) still lays out and exports text, a table and two columns. #[test]