From d22ccebc9ca3184300ef412c8293a3a26ecda716 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 17 Oct 2026 00:05:54 -0700 Subject: [PATCH] Print: a Print Tiling origin on an artboard's right or bottom edge is measured from that artboard The Print Tiling tool's press and print.tiling.set without `artboard` decided which artboard a point is on with Rect::contains, which leaves out the right and bottom edges. A press exactly on an artboard's right or bottom edge picked no artboard when no other artboard holds that point, so the drag did not snap to that artboard's edges. An origin there was measured from the first printed artboard: with artboards at 0 to 300 pt and 300 to 600 pt, an origin on the second one's right edge was saved as x 600 instead of 300. Both now count the edges as Document::art_on_artboard does, within a millionth of a point. --- crates/engine/src/cmd/printtiling.rs | 17 ++++++++-- crates/engine/src/tests_printtiling.rs | 20 ++++++++++++ crates/tools/src/printtiling.rs | 43 ++++++++++++++++++++++++-- 3 files changed, 75 insertions(+), 5 deletions(-) diff --git a/crates/engine/src/cmd/printtiling.rs b/crates/engine/src/cmd/printtiling.rs index c4576985f..fd9f69d51 100644 --- a/crates/engine/src/cmd/printtiling.rs +++ b/crates/engine/src/cmd/printtiling.rs @@ -5,7 +5,7 @@ use serde_json::{Value, json}; use vectorcraft_doc::Document; -use vectorcraft_geom::Point; +use vectorcraft_geom::{Point, Rect}; use vectorcraft_pdf::{TileOrigin, TilingPage}; use super::fileio::pdf::pdf_error; @@ -30,7 +30,7 @@ pub fn specs() -> Vec { "Print Tiling", [], None, - "{origin: [x, y] (document point: the top-left corner of the first page's imageable area), artboard? (0-based: the artboard it is measured from; default the printed artboard containing origin, else the first; ignored with artboards: ignore)} | {reset: true} place the printed pages as the Print Tiling tool does, saved with the print settings (tileOrigin: {placed, x, y}, from the artboard's top-left corner; it replaces the placement, and tiles start there), as one undo step; reset → the placement places them again → {tileOrigin}", + "{origin: [x, y] (document point: the top-left corner of the first page's imageable area), artboard? (0-based: the artboard it is measured from; default the printed artboard containing origin, its edges included, else the first; ignored with artboards: ignore)} | {reset: true} place the printed pages as the Print Tiling tool does, saved with the print settings (tileOrigin: {placed, x, y}, from the artboard's top-left corner; it replaces the placement, and tiles start there), as one undo step; reset → the placement places them again → {tileOrigin}", has_doc, set_origin ), @@ -77,6 +77,17 @@ fn point(p: &Value, key: &str) -> Option { Some(Point::new(a.first()?.as_f64()?, a.get(1)?.as_f64()?)) } +/// The first of the print `regions` holding `at`: one that holds it inside or on its left or top +/// edge, else one that holds it on its right or bottom edge or within a millionth of a point +/// outside an edge (the edges `Document::art_on_artboard` counts). +fn region_holding(regions: &[(Option, Rect)], at: Point) -> Option<&(Option, Rect)> { + let holds = |r: &Rect| { + let r = r.inflate(1e-6, 1e-6); + r.x0 <= at.x && at.x <= r.x1 && r.y0 <= at.y && at.y <= r.y1 + }; + regions.iter().find(|(_, r)| r.contains(at)).or_else(|| regions.iter().find(|(_, r)| holds(r))) +} + fn set_origin(s: &mut Session, p: &Value) -> Result { let doc = &s.doc()?.doc; let current = settings(SET, doc, &json!({}))?; @@ -92,7 +103,7 @@ fn set_origin(s: &mut Session, p: &Value) -> Result { let i = v.as_u64().ok_or_else(|| bad(SET, "artboard must be a 0-based index"))? as usize; regions.iter().find(|(a, _)| a.is_none() || *a == Some(i)).ok_or_else(|| bad(SET, format!("artboard {i} doesn't print")))? } - None => regions.iter().find(|(_, r)| r.contains(at)).or(regions.first()).ok_or_else(|| bad(SET, "nothing prints"))?, + None => region_holding(®ions, at).or(regions.first()).ok_or_else(|| bad(SET, "nothing prints"))?, }; let corner = picked.1.origin(); TileOrigin { placed: true, x: at.x - corner.x, y: at.y - corner.y } diff --git a/crates/engine/src/tests_printtiling.rs b/crates/engine/src/tests_printtiling.rs index 3919e0ded..122acb3ab 100644 --- a/crates/engine/src/tests_printtiling.rs +++ b/crates/engine/src/tests_printtiling.rs @@ -147,6 +147,26 @@ fn tiles_start_at_the_tile_origin_and_cover_the_art() { assert!(s.execute("print.tiling.set", &json!({"origin": [1e9, 2]})).is_err()); } +#[test] +fn an_origin_on_an_artboards_right_or_bottom_edge_is_measured_from_that_artboard() { + // Artboard 0 is (0, 0) to (300, 200). Artboard 1 is (300, 0) to (600, 200), and its left edge is artboard 0's right edge. + let mut s = session(); + s.execute("artboard.new", &json!({"x": 300, "y": 0, "width": 300, "height": 200})).unwrap(); + let mut placed = |x: f64, y: f64| { + let o = s.execute("print.tiling.set", &json!({"origin": [x, y]})).unwrap()["tileOrigin"].clone(); + (o["x"].as_f64().unwrap(), o["y"].as_f64().unwrap()) + }; + assert_eq!(placed(600.0, 50.0), (300.0, 50.0), "artboard 1's right edge"); + assert_eq!(placed(450.0, 200.0), (150.0, 200.0), "artboard 1's bottom edge"); + assert_eq!(placed(600.0, 200.0), (300.0, 200.0), "artboard 1's bottom right corner"); + assert_eq!(placed(150.0, 200.0), (150.0, 200.0), "artboard 0's bottom edge"); + assert_eq!(placed(300.0, 50.0), (0.0, 50.0), "the shared edge: the artboard to its right"); + let (x, _) = placed(600.0 + 1e-9, 50.0); + assert!((x - 300.0).abs() < 1e-6, "roundoff past the edge: {x}"); + // Beyond every artboard the origin is measured from the first. + assert_eq!(placed(600.001, 50.0), (600.001, 50.0), "beyond the edge"); +} + #[test] fn settings_saved_before_the_tile_origin_still_load() { let mut s = session(); diff --git a/crates/tools/src/printtiling.rs b/crates/tools/src/printtiling.rs index ac1a1076a..ba9116b84 100644 --- a/crates/tools/src/printtiling.rs +++ b/crates/tools/src/printtiling.rs @@ -27,6 +27,18 @@ fn snap1(v: f64, edges: [f64; 2], tol: f64) -> f64 { edges.into_iter().filter(|e| (e - v).abs() <= tol).min_by(|a, b| (a - v).abs().total_cmp(&(b - v).abs())).unwrap_or(v) } +/// Index of the first artboard holding `p`: one that holds it inside or on its left or top edge, +/// else one that holds it on its right or bottom edge or within a millionth of a point outside an +/// edge (the edges `Document::art_on_artboard` counts). +fn artboard_holding(cx: &ToolContext, p: Point) -> Option { + let rects = || cx.doc.artboards.iter().map(|a| a.rect); + let holds = |r: Rect| { + let r = r.inflate(1e-6, 1e-6); + r.x0 <= p.x && p.x <= r.x1 && r.y0 <= p.y && p.y <= r.y1 + }; + rects().position(|r| r.contains(p)).or_else(|| rects().position(holds)) +} + impl Tool for PrintTilingTool { fn id(&self) -> &'static str { "printTiling" @@ -39,7 +51,7 @@ impl Tool for PrintTilingTool { fn pointer(&mut self, cx: &ToolContext, ev: &PointerEvent) -> Vec { match ev.kind { PointerKind::Down => { - let hit = cx.doc.artboards.iter().position(|a| a.rect.contains(ev.pos)); + let hit = artboard_holding(cx, ev.pos); self.drag = Some((hit, hit.and_then(|i| cx.doc.artboards.get(i)).map(|a| a.rect))); (self.began, self.last) = (false, ev.pos); vec![] @@ -98,7 +110,7 @@ impl Tool for PrintTilingTool { #[cfg(test)] mod tests { - use serde_json::json; + use serde_json::{Value, json}; use vectorcraft_doc::Selection; use super::*; @@ -134,4 +146,31 @@ mod tests { assert_eq!(t.key(&c, ToolKey::Escape, Mods::default()), vec![Action::Cancel]); assert_eq!(crate::create("printTiling").id(), "printTiling"); } + + #[test] + fn a_press_on_an_artboards_right_or_bottom_edge_drags_from_that_artboard() { + // Artboard 0 is (0, 0) to (500, 500). Artboard 1 is (500, 0) to (800, 200), and its left edge lies on artboard 0's right edge. + let (mut d, _) = doc_with_rect(); + let mut ab = d.artboards[0].clone(); + ab.rect = Rect::new(500.0, 0.0, 800.0, 200.0); + d.artboards.push(ab); + let (s, pd) = (Selection::default(), paint()); + let c = cx(&d, &s, &pd); + // What a drag to 3 pt up and left of a press at (x, y) previews: the corner snaps to the + // edges of the artboard the press picked. + let drag = |x: f64, y: f64| { + let mut t = PrintTilingTool::default(); + t.pointer(&c, &ev(PointerKind::Down, x, y)); + t.pointer(&c, &ev(PointerKind::Drag, x - 3.0, y - 3.0)).pop() + }; + let preview = + |origin: [f64; 2], artboard: Value| Some(Action::Preview("print.tiling.set".into(), json!({ "origin": origin, "artboard": artboard }))); + assert_eq!(drag(800.0, 100.0), preview([800.0, 97.0], json!(1)), "artboard 1's right edge"); + assert_eq!(drag(650.0, 200.0), preview([647.0, 200.0], json!(1)), "artboard 1's bottom edge"); + assert_eq!(drag(800.0, 200.0), preview([800.0, 200.0], json!(1)), "artboard 1's bottom right corner"); + assert_eq!(drag(300.0, 500.0), preview([297.0, 500.0], json!(0)), "artboard 0's bottom edge"); + assert_eq!(drag(500.0, 100.0), preview([500.0, 97.0], json!(1)), "the shared edge: the artboard to its right"); + assert_eq!(drag(800.0 + 1e-9, 100.0), preview([800.0, 97.0], json!(1)), "roundoff past the edge"); + assert_eq!(drag(800.001, 100.0), preview([800.001 - 3.0, 97.0], Value::Null), "beyond the edge"); + } }