PDF export: warn when a PDF/X file leaves out the document's bleed

Choosing a PDF/X standard without a PDF/X preset writes a file with no bleed
(BleedBox = TrimBox) even when Document Setup sets one, and the export gave no
warning about it. The export now warns when a PDF/X file has no bleed on a side
where the document's bleed has one. The warning comes ahead of the flattening
note, because the status bar shows only the first warning. The code adds a
warning and does not change what is written.

These cases do not warn: the PDF/X presets, Use document bleed settings, a
nonzero bleed on every side the document sets, a document with no bleed, files
that aren't PDF/X files, and Export for Screens rows whose artboards Include
Bleed grows.

The warning has a row in every catalog. The Summary's warning and error rows in
the Save PDF and Print dialogs now go through the catalog, so they show its
translations.
This commit is contained in:
dotCooCoo
2026-11-08 11:42:47 +07:00
committed by bflatastic
parent 500f74b700
commit a8a1c82a43
39 changed files with 165 additions and 9 deletions
File diff suppressed because one or more lines are too long
+4 -1
View File
@@ -1019,11 +1019,14 @@ fn encode_and_write(export: ScreenExport, writer: &mut dyn FnMut(&str, &[u8]) ->
let mut out = Output::default();
// The encoders' warnings for every file, each once.
let mut warnings: Vec<String> = Vec::new();
// Include Bleed: a grown artboard already holds the bleed. Assets, layers and a selection are
// written on a page of their own bounds, which it doesn't grow.
let bleed_on_page = o.include_bleed && o.assets.is_none() && o.layers.is_none() && o.selection.is_none();
let mut write = |doc: &Document, format: &str, options: &Value| -> Result<Vec<u8>> {
// The steps are the files: an encoder's own steps (artboards, pages) are not reported.
let (bytes, notes) = vectorcraft_doc::cancel::quiet(|| encode_with_warnings(doc, format, options))?;
for w in notes {
if !warnings.contains(&w) {
if !(bleed_on_page && w == super::pdf::BLEED_LEFT_OUT) && !warnings.contains(&w) {
warnings.push(w);
}
}
@@ -90,6 +90,55 @@ fn choosing_a_pdfx_standard_sets_its_version_and_turns_off_what_it_forbids() {
}
}
/// A PDF/X file without the bleed the document sets exports as asked and warns; the presets and
/// options that take the document's bleed, and files that aren't PDF/X files, don't warn.
#[test]
fn a_pdfx_file_that_leaves_out_the_document_bleed_warns() {
let left_out = |w: &Vec<String>| w.iter().any(|w| w == super::pdf::BLEED_LEFT_OUT);
let mut s = session();
s.execute("document.setup", &json!({"bleed": 6})).unwrap();
for standard in ["pdfX1a", "pdfX3", "pdfX4"] {
let (_, warnings) = pdf(&mut s, json!({"standard": standard}));
// First: the status bar shows the first warning, and PDF/X-1a and PDF/X-3 add the flattening note.
assert_eq!(warnings.first().map(String::as_str), Some(super::pdf::BLEED_LEFT_OUT), "{standard}: {warnings:?}");
// The page has no bleed, as asked.
let v = s.execute("document.exportPdf", &json!({"standard": standard})).unwrap();
let info = s.execute("document.pdfInfo", &json!({"name": "x.pdf", "dataBase64": v["dataBase64"]})).unwrap();
let boxes = &info["pageInfo"][0]["boxes"];
assert!(boxes["media"].is_array() && boxes["media"] == boxes["trim"] && boxes["media"] == boxes["bleed"], "{standard}: {info}");
}
let v = s.execute("document.export", &json!({"format": "pdf", "standard": "pdfX4"})).unwrap();
assert!(v["warnings"].as_array().unwrap().iter().any(|w| w == super::pdf::BLEED_LEFT_OUT), "{v}");
// A side the settings leave out warns, and sides they all keep don't.
let (_, warnings) = pdf(&mut s, json!({"standard": "pdfX4", "bleed": {"top": 6, "bottom": 6, "left": 6}}));
assert!(left_out(&warnings), "{warnings:?}");
for p in [
json!({"preset": "PDF/X-1a:2001"}),
json!({"preset": "PDF/X-3:2002"}),
json!({"preset": "PDF/X-4:2010"}),
json!({"standard": "pdfX4", "bleed": {"useDocument": true}}),
json!({"standard": "pdfX4", "bleed": {"top": 1, "bottom": 1, "left": 1, "right": 1}}),
json!({}),
json!({"standard": "pdfA2b"}),
] {
let (_, warnings) = pdf(&mut s, p.clone());
assert!(!left_out(&warnings), "{p}: {warnings:?}");
}
// The dialog's Summary exports in memory, so it lists the warning too.
let v = s.execute("document.pdfSettings", &json!({"standard": "pdfX4", "includeDocument": true})).unwrap();
assert!(v["warnings"].as_array().unwrap().iter().any(|w| w == super::pdf::BLEED_LEFT_OUT), "{v}");
// A bleed on the top alone: only that side counts.
s.execute("document.setup", &json!({"bleed": [6, 0, 0, 0]})).unwrap();
let (_, warnings) = pdf(&mut s, json!({"standard": "pdfX4", "bleed": {"top": 1}}));
assert!(!left_out(&warnings), "{warnings:?}");
let (_, warnings) = pdf(&mut s, json!({"standard": "pdfX4", "bleed": {"bottom": 1}}));
assert!(left_out(&warnings), "{warnings:?}");
// A document without a bleed has nothing to leave out.
s.execute("document.setup", &json!({"bleed": 0})).unwrap();
let (_, warnings) = pdf(&mut s, json!({"standard": "pdfX4"}));
assert!(!left_out(&warnings), "{warnings:?}");
}
/// The ink amounts (0..1) `doc` shows at `p` in its images (the topmost image there).
fn image_inks_at(doc: &vectorcraft_doc::Document, p: vectorcraft_geom::Point) -> Option<[f32; 4]> {
fn visit(n: &vectorcraft_doc::Node, out: &mut Vec<vectorcraft_doc::ImageObject>) {
@@ -526,6 +526,20 @@ fn include_bleed_grows_each_artboard() {
assert_eq!(size(&files(&r)[0].1), (40, 30), "without it, the artboard alone");
}
/// Include Bleed puts the bleed on a PDF/X row's page, so the row doesn't warn that it leaves the bleed out.
#[test]
fn include_bleed_pdfx_rows_do_not_warn_about_the_bleed() {
let mut s = session(1);
s.execute("document.setup", &json!({"bleed": 6})).unwrap();
let warned = |r: &Value| r["warnings"].as_array().unwrap().iter().any(|w| w == super::pdf::BLEED_LEFT_OUT);
let row = json!([{"format": "pdf", "standard": "pdfX4"}]);
assert!(warned(&export(&mut s, json!({"formats": row}))));
assert!(!warned(&export(&mut s, json!({"includeBleed": true, "formats": row}))));
// A selection is written on a page of its own bounds, which Include Bleed doesn't grow.
s.execute("shape.rectangle", &json!({"x": 5, "y": 5, "width": 10, "height": 10})).unwrap();
assert!(warned(&export(&mut s, json!({"includeBleed": true, "selection": "one", "formats": row}))));
}
#[test]
fn png_8_rows_write_indexed_colour() {
let mut s = session(1);