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.
This commit is contained in:
dotCooCoo
2026-10-17 17:13:02 +07:00
committed by bflatastic
parent 24b6486ebd
commit d22ccebc9c
3 changed files with 75 additions and 5 deletions
+14 -3
View File
@@ -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<CommandSpec> {
"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<Point> {
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<usize>, Rect)], at: Point) -> Option<&(Option<usize>, 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<Value> {
let doc = &s.doc()?.doc;
let current = settings(SET, doc, &json!({}))?;
@@ -92,7 +103,7 @@ fn set_origin(s: &mut Session, p: &Value) -> Result<Value> {
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(&regions, 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 }
+20
View File
@@ -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();
+41 -2
View File
@@ -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<usize> {
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<Action> {
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");
}
}