//! File → Print to a PostScript file: `file.print {format: "postscript"}` writes the job the PDF //! print lays out as DSC-conforming PostScript. use serde_json::{Value, json}; use super::*; /// A document of `artboards` 300 × 200 artboards with a rectangle on the first. fn session(artboards: usize) -> Session { let mut s = Session::new(); s.execute("file.new", &json!({"width": 300, "height": 200, "artboards": artboards})).unwrap(); s.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 100, "height": 50})).unwrap(); s } /// `file.print {format: "postscript", …p}` → (the PostScript, the answer). fn print_ps(s: &mut Session, mut p: Value) -> (String, Value) { p["format"] = json!("postscript"); let v = s.execute("file.print", &p).unwrap_or_else(|e| panic!("{p}: {e}")); let bytes = vectorcraft_format::base64_decode(v["dataBase64"].as_str().unwrap()).unwrap(); (String::from_utf8(bytes).unwrap(), v) } /// The values of DSC comment `key` (`%%Key:`), in order. fn dsc(ps: &str, key: &str) -> Vec { let prefix = format!("%%{key}:"); ps.lines().filter_map(|l| l.strip_prefix(&prefix)).map(|v| v.trim().to_string()).collect() } #[test] fn pages_equals_the_page_count() { let mut s = session(3); let (ps, v) = print_ps(&mut s, json!({})); assert_eq!((v["pages"].clone(), v["format"].clone()), (json!(3), json!("postscript"))); assert!(ps.starts_with("%!PS-Adobe-3.0\n") && ps.ends_with("%%EOF\n")); assert_eq!(dsc(&ps, "Pages"), ["3"]); assert_eq!(dsc(&ps, "Page").len(), 3); assert_eq!(dsc(&ps, "LanguageLevel"), ["3"]); // Copies, a range of artboards, and the level. let (ps, v) = print_ps(&mut s, json!({"level": 2, "settings": {"copies": 2, "artboards": "range", "range": "1-2"}})); assert_eq!((v["pages"].clone(), dsc(&ps, "Pages")), (json!(4), vec!["4".to_string()])); assert_eq!(dsc(&ps, "Page").len(), 4); assert_eq!(dsc(&ps, "LanguageLevel"), ["2"]); // Letter paper, turned to the wide artboards. assert_eq!(dsc(&ps, "PageBoundingBox")[0], "0 0 792 612"); } #[test] fn marks_and_separations_are_written() { let mut s = session(1); let (ps, _) = print_ps(&mut s, json!({"settings": {"marks": {"trim": true, "registration": true}}})); // Marks print in Registration: on every ink. assert!(ps.contains("/All /DeviceCMYK"), "{ps}"); let (none, _) = print_ps(&mut s, json!({})); assert!(!none.contains("/All /DeviceCMYK")); // Separations: a page per ink with its screen. let (ps, v) = print_ps(&mut s, json!({"settings": {"output": {"mode": "separations", "inks": [{"name": "Cyan", "frequency": 120, "angle": 30}]}}})); assert_eq!(v["pages"], 4); assert_eq!(dsc(&ps, "PlateColor"), ["Cyan", "Magenta", "Yellow", "Black"]); assert!(ps.contains("120 30 {dup mul exch dup mul add 1 exch sub} setscreen")); // PostScript writes the screens, so no warning says they are left to the device. assert!(!v["warnings"].as_array().unwrap().iter().any(|w| w.as_str().unwrap().contains("screens")), "{v}"); // Negatives and a fixed flatness. let (ps, _) = print_ps(&mut s, json!({"settings": {"output": {"image": "negative"}, "graphics": {"autoFlatness": false, "flatness": 4}}})); assert!(ps.contains("{1 exch sub} settransfer") && ps.contains("4 setflat")); } #[test] fn a_ps_path_prints_postscript_and_pdf_stays_the_default() { let mut s = session(1); let v = s.execute("file.print", &json!({})).unwrap(); assert_eq!(v["format"], "pdf"); let path = std::env::temp_dir().join(format!("vc-printps-{}.ps", std::process::id())); let v = s.execute("file.print", &json!({"path": path.to_string_lossy()})).unwrap(); assert_eq!(v["format"], "postscript"); let bytes = std::fs::read(&path).unwrap(); assert!(bytes.starts_with(b"%!PS-Adobe-3.0")); let _ = std::fs::remove_file(&path); // The file reads back: the rectangle on the first page. let back = vectorcraft_eps::import(&bytes).unwrap(); assert!(!back.document.layers[0].children().unwrap().is_empty()); for bad in [json!({"format": "ps2"}), json!({"format": "postscript", "level": 1}), json!({"format": "postscript", "flattenerPreset": "nope"})] { assert!(matches!(s.execute("file.print", &bad), Err(EngineError::BadParams { .. })), "{bad}"); } } #[test] fn transparency_is_flattened_for_postscript() { let mut s = session(1); s.execute("transparency.set", &json!({"opacity": 50})).unwrap(); let (ps, v) = print_ps(&mut s, json!({})); assert!(v["warnings"].as_array().unwrap().iter().any(|w| w.as_str().unwrap().contains("flattened")), "{v}"); assert!(!v["warnings"].as_array().unwrap().iter().any(|w| w.as_str().unwrap().contains("opaque: EPS")), "{v}"); assert_eq!(dsc(&ps, "Pages"), ["1"]); }