Files
vectorcraft/crates/engine/src/tests_printps.rs
T
dotCooCoo 1c30cbf88c Print: Print and Print Preview warn when type prints in the fallback font
PDF, EPS, EMF/WMF and raster exports, and SVG exports that outline or
embed fonts, warn when type is set in a font family that isn't
available, but file.print and print.preview gave no such warning. A job
in a missing family printed in Source Sans 3 with no warning, and the
Print dialog's Summary showed none.

Both commands now add the exports' warning for the type that prints:
visible type that fits its frame or path, on the layers that Print
Layers picks, where what it paints (its effects applied) lands on the
paper of an artboard or tile that prints, inside its clipping paths.
The type in the symbols, inline graphics, patterns, scatter brushes,
opacity masks and placed documents of the art that prints counts too.
file.print reads the fonts before raster effects and Print as Bitmap
turn type into images, for PDF and PostScript output.

Docs: docs/mcp.md (Print), docs/target-app-parity.md (Fonts).
2026-10-16 02:26:13 +07:00

114 lines
5.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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<String> {
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"]);
}
#[test]
fn type_in_a_missing_family_is_named_when_its_artboard_prints() {
let mut s = session(2);
let r = s.doc().unwrap().doc.artboards[1].rect;
s.execute("text.create", &json!({"x": r.x0 + 10.0, "y": r.y0 + 50.0, "text": "Ab", "font": "Nonexistent Sans Probe"})).unwrap();
let fallback = |v: &Value| v["warnings"].as_array().unwrap().iter().filter(|w| w.as_str().unwrap().contains("fallback font")).count();
let (_, v) = print_ps(&mut s, json!({}));
assert_eq!(fallback(&v), 1, "{v}");
assert!(v["warnings"].to_string().contains("Nonexistent Sans Probe"), "{v}");
let (_, v) = print_ps(&mut s, json!({"settings": {"artboards": "range", "range": "1"}}));
assert_eq!(fallback(&v), 0, "{v}");
// Off the paper: 100 pt wide paper at 100% shows the middle of the 300 pt wide artboard.
let paper = json!({"artboards": "range", "range": "2", "media": "custom", "width": 100, "height": 300, "autoRotate": false});
let (_, v) = print_ps(&mut s, json!({ "settings": paper }));
assert_eq!(fallback(&v), 0, "{v}");
}