//! View → Show Print Tiling (`view.printTiling`, `print.tiling`) and the Print Tiling tool //! (`print.tiling.set`): the pages drawn are print.preview's, and where the tool puts them is //! saved with the print settings. use serde_json::{Value, json}; use vectorcraft_tools::{PointerEvent, PointerKind}; use super::*; use crate::tooling::ViewInfo; /// A 300 × 200 document with a rectangle. fn session() -> Session { let mut s = Session::new(); s.execute("file.new", &json!({"width": 300, "height": 200})).unwrap(); s.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 100, "height": 50})).unwrap(); s } fn rect(v: &Value) -> [f64; 4] { let a: Vec = v.as_array().unwrap().iter().map(|x| x.as_f64().unwrap()).collect(); [a[0], a[1], a[2], a[3]] } fn close(a: [f64; 4], b: [f64; 4]) -> bool { a.iter().zip(b).all(|(x, y)| (x - y).abs() < 1e-6) } /// `t` (a print.preview transform) applied to `(x, y)`. fn apply(t: &Value, (x, y): (f64, f64)) -> (f64, f64) { let c: Vec = t.as_array().unwrap().iter().map(|v| v.as_f64().unwrap()).collect(); (c[0] * x + c[2] * y + c[4], c[1] * x + c[3] * y + c[5]) } fn tiling(s: &mut Session, settings: Value) -> Value { s.execute("print.tiling", &json!({ "settings": settings })).unwrap() } #[test] fn show_print_tiling_is_a_per_document_view_toggle() { let mut s = session(); let undo = s.doc().unwrap().history.undo.len(); assert_eq!(s.execute("view.printTiling", &json!({})).unwrap()["on"], true); assert!(s.doc().unwrap().print_tiling); assert_eq!(s.execute("view.printTiling", &json!({})).unwrap()["on"], false); assert_eq!(s.execute("view.printTiling", &json!({"on": true})).unwrap()["on"], true); assert_eq!(s.doc().unwrap().history.undo.len(), undo, "a view toggle is no undo step"); s.execute("file.new", &json!({"width": 100, "height": 100})).unwrap(); assert!(!s.doc().unwrap().print_tiling, "each document has its own"); } #[test] fn tiling_pages_are_print_preview_tiles() { let mut s = session(); let set = json!({"scaling": "tileImageable", "scale": {"width": 600, "height": 600}, "margin": 18, "overlap": 9, "tileRange": "1-2, 5"}); let pv = s.execute("print.preview", &json!({ "settings": set })).unwrap(); let grid = &pv["tiles"][0]; let tiles = grid["tiles"].as_array().unwrap(); let pages = tiling(&mut s, set.clone())["pages"].as_array().unwrap().clone(); assert!(tiles.len() > 4); assert_eq!(pages.len(), tiles.len(), "one page per tile"); for (i, (p, t)) in pages.iter().zip(tiles).enumerate() { assert_eq!(p["number"], i + 1); assert!(close(rect(&p["imageable"]), rect(t)), "tiles are imageable areas"); let printed = grid["printed"].as_array().unwrap().contains(&json!(i + 1)); assert_eq!(p["printed"], printed); // The paper reaches the margin (18 pt on paper, 3 pt of the document at 600%) further. let (pg, im) = (rect(&p["page"]), rect(&p["imageable"])); assert!((im[0] - pg[0] - 3.0).abs() < 1e-6 && (pg[3] - im[3] - 3.0).abs() < 1e-6); } // Full pages: the tiles are the paper, the imageable area inside it. let full = json!({"scaling": "tileFull", "scale": {"width": 400, "height": 400}, "margin": 18}); let pv = s.execute("print.preview", &json!({ "settings": full })).unwrap(); let pages = tiling(&mut s, full)["pages"].as_array().unwrap().clone(); assert_eq!(pages.len(), pv["tiles"][0]["tiles"].as_array().unwrap().len()); assert!(close(rect(&pages[0]["page"]), rect(&pv["tiles"][0]["tiles"][0]))); } #[test] fn untiled_pages_come_from_the_sheets_once_per_page() { let mut s = session(); s.execute("artboard.new", &json!({"x": 400, "y": 0, "width": 300, "height": 200})).unwrap(); // Letter turned landscape for the wide artboards, centred on each. let set = json!({"output": {"mode": "separations"}, "margin": 36}); let pv = s.execute("print.preview", &json!({ "settings": set })).unwrap(); assert!(pv["sheets"].as_array().unwrap().len() >= 8, "a page per ink"); let pages = tiling(&mut s, set)["pages"].as_array().unwrap().clone(); assert_eq!(pages.len(), 2, "each page once, not once per ink"); for (p, cx) in pages.iter().zip([150.0, 550.0]) { let pg = rect(&p["page"]); assert!(close(pg, [cx - 396.0, 100.0 - 306.0, cx + 396.0, 100.0 + 306.0]), "{pg:?}"); assert!(close(rect(&p["imageable"]), [pg[0] + 36.0, pg[1] + 36.0, pg[2] - 36.0, pg[3] - 36.0])); } } #[test] fn the_tool_puts_the_first_page_where_it_is_dragged_as_one_undo_step() { let mut s = session(); let v = ViewInfo::default(); s.select_tool("printTiling", v).unwrap(); let undo = s.doc().unwrap().history.undo.len(); s.pointer(&PointerEvent::new(PointerKind::Down, 50.0, 50.0), v).unwrap(); s.pointer(&PointerEvent::new(PointerKind::Drag, 45.0, 40.0), v).unwrap(); s.pointer(&PointerEvent::new(PointerKind::Drag, 40.0, 30.0), v).unwrap(); s.pointer(&PointerEvent::new(PointerKind::Up, 40.0, 30.0), v).unwrap(); let st = s.doc().unwrap(); assert_eq!(st.history.undo.len(), undo + 1); assert_eq!(st.history.undo.last().unwrap().label, "Print Tiling"); let set = s.execute("print.setup", &json!({})).unwrap()["settings"].clone(); assert_eq!(set["tileOrigin"], json!({"placed": true, "x": 40.0, "y": 30.0})); // The page's imageable corner is the point dragged to, on the paper and on the canvas. let pv = s.execute("print.preview", &json!({"settings": {"margin": 12}})).unwrap(); let (x, y) = apply(&pv["sheets"][0]["transform"], (40.0, 30.0)); assert!((x - 12.0).abs() < 1e-6 && (y - 12.0).abs() < 1e-6, "({x}, {y})"); let page = &tiling(&mut s, json!({"margin": 12}))["pages"][0]; assert!((rect(&page["imageable"])[0] - 40.0).abs() < 1e-6 && (rect(&page["imageable"])[1] - 30.0).abs() < 1e-6); // Saved in the native file. let doc = &s.doc().unwrap().doc; assert_eq!(vectorcraft_format::load(&vectorcraft_format::save(doc, false)).unwrap().print_setup, doc.print_setup); // A double click puts the pages back where the placement puts them. s.pointer(&PointerEvent::new(PointerKind::DoubleClick, 10.0, 10.0), v).unwrap(); assert_eq!(s.execute("print.setup", &json!({})).unwrap()["settings"]["tileOrigin"]["placed"], false); s.execute("edit.undo", &json!({})).unwrap(); assert_eq!(s.execute("print.setup", &json!({})).unwrap()["settings"]["tileOrigin"]["placed"], true); s.execute("edit.undo", &json!({})).unwrap(); assert!(s.doc().unwrap().doc.print_setup.is_none()); } #[test] fn tiles_start_at_the_tile_origin_and_cover_the_art() { let mut s = session(); let tiles = json!({"scaling": "tileImageable", "scale": {"width": 400, "height": 400}}); s.execute("print.setup", &json!({ "settings": tiles })).unwrap(); let before = tiling(&mut s, json!({}))["pages"].as_array().unwrap().len(); s.execute("print.tiling.set", &json!({"origin": [60, 35]})).unwrap(); let pages = tiling(&mut s, json!({}))["pages"].as_array().unwrap().clone(); let pv = s.execute("print.preview", &json!({})).unwrap(); assert_eq!(pages.len(), pv["tiles"][0]["tiles"].as_array().unwrap().len(), "tile count equals print.preview"); assert_eq!(pages.len(), pv["sheets"].as_array().unwrap().len()); assert!(pages.len() > before, "the grid moved off the corner takes more tiles"); let rects: Vec<[f64; 4]> = pages.iter().map(|p| rect(&p["imageable"])).collect(); assert!(rects.iter().any(|r| (r[0] - 60.0).abs() < 1e-6 && (r[1] - 35.0).abs() < 1e-6), "a tile starts at the origin"); let all = rects.iter().fold([f64::MAX, f64::MAX, f64::MIN, f64::MIN], |a, r| [a[0].min(r[0]), a[1].min(r[1]), a[2].max(r[2]), a[3].max(r[3])]); assert!(all[0] <= 0.0 && all[1] <= 0.0 && all[2] >= 300.0 && all[3] >= 200.0, "the tiles cover the artboard: {all:?}"); // Bad input is refused. assert!(s.execute("print.tiling.set", &json!({})).is_err()); assert!(s.execute("print.tiling.set", &json!({"origin": [1, 2], "artboard": 7})).is_err()); assert!(s.execute("print.tiling.set", &json!({"origin": [1e9, 2]})).is_err()); } #[test] fn settings_saved_before_the_tile_origin_still_load() { let mut s = session(); let mut old = serde_json::to_value(vectorcraft_pdf::PrintSettings::default()).unwrap(); old.as_object_mut().unwrap().remove("tileOrigin"); old["copies"] = json!(2); let mut doc = (*s.doc().unwrap().doc).clone(); doc.print_setup = Some(old); s.doc_mut().unwrap().doc = std::sync::Arc::new(doc); let set = s.execute("print.setup", &json!({})).unwrap()["settings"].clone(); assert_eq!((set["copies"].clone(), set["tileOrigin"]["placed"].clone()), (json!(2), json!(false))); assert_eq!(tiling(&mut s, json!({}))["pages"].as_array().unwrap().len(), 1); }