From 09f9ce50e78fff7e68a8fef133bf2d09baf11e06 Mon Sep 17 00:00:00 2001 From: skylar taylor-barrick Date: Sat, 24 Oct 2026 15:37:14 -0600 Subject: [PATCH] Package: name the placed-PDF layer choices its IDML copy cannot carry A package's IDML copy links the placed files, so a per-placement Object Layer Options choice cannot travel in it, while the packaged DesignCraft document keeps the choice. The report now says so instead of leaving the two copies to differ silently. --- crates/engine/src/cmd/package.rs | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/crates/engine/src/cmd/package.rs b/crates/engine/src/cmd/package.rs index ffc69b39e..316d5dedc 100644 --- a/crates/engine/src/cmd/package.rs +++ b/crates/engine/src/cmd/package.rs @@ -427,12 +427,19 @@ fn package(s: &mut Session, p: &Value) -> Result { .iter() .map(|f| format!(" {} {}{}\n", f.family, f.style, copy_font(&root, f, &fonts_dir, &mut copied, &mut taken, &mut files))) .collect(); + // The IDML copy links the placed files, so a per-placement PDF layer choice cannot travel in + // it (an external-only IDML export rejects one for the same reason). Say so in the report + // instead of leaving a silent difference between the package's copies. + let mut idml_layer_note = ""; if p.get("idml").and_then(Value::as_bool).unwrap_or(true) { let path = package_output_path(&dir, &title, "idml")?; // The images are in Links/ beside it: link them there, without embedded copies. let opts = designcraft_idml::ExportOptions { embed_images: false }; root.write(&path, &designcraft_idml::export_idml_with(&packed, &opts))?; files.push(path); + if packed.has_pdf_layer_overrides() { + idml_layer_note = "\nPlaced PDF layers\nThe IDML copy links the placed PDFs, so it shows their file defaults. The DesignCraft document keeps each placement's Object Layer Options choice.\n"; + } } if p.get("pdf").and_then(Value::as_bool).unwrap_or(false) { let path = package_output_path(&dir, &title, "pdf")?; @@ -456,6 +463,7 @@ fn package(s: &mut Session, p: &Value) -> Result { for l in links.as_array().into_iter().flatten() { r += &format!(" {} ({})\n", l["name"].as_str().unwrap_or(""), l["status"].as_str().unwrap_or("")); } + r += idml_layer_note; if let Some(issues) = pre.get("issues").and_then(Value::as_array) { r += &format!("\nPreflight: {} issue(s)\n", issues.len()); for i in issues { @@ -510,6 +518,32 @@ mod tests { std::fs::remove_dir_all(dir).unwrap(); } + /// A per-placement PDF layer choice cannot travel in an IDML copy that links its files, so the + /// report says the copies differ instead of leaving it to be discovered. + #[test] + fn package_report_names_placed_pdf_layer_choices() { + let nonce = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos(); + let dir = std::env::temp_dir().join(format!("dc-package-pdf-layers-{}-{nonce}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + let pdf = super::super::pdflayers::tests::two_layer_pdf(); + let mut s = Session::new(); + s.execute("file.new", &json!({"title": "LayeredPackage", "width": 100, "height": 100})).unwrap(); + let id = s + .execute("file.place", &json!({"base64": super::super::base64_encode(&pdf), "name": "art.pdf", "x": 0, "y": 0, "width": 100})) + .unwrap()["id"] + .clone(); + s.execute("object.layerOptions", &json!({"ids": [id], "hidden": ["Red"]})).unwrap(); + let r = s.execute("file.package", &json!({"dir": dir.to_string_lossy(), "idml": true, "pdf": false})).unwrap(); + let report = r["report"].as_str().unwrap(); + assert!(report.contains("Placed PDF layers"), "{report}"); + // The DesignCraft copy keeps the choice; only the linked IDML copy shows file defaults. + let packed = super::super::from_bytes(&std::fs::read(dir.join("LayeredPackage.designcraft")).unwrap()).unwrap(); + let item = packed.item(designcraft_doc::ItemId(id.as_u64().unwrap())).unwrap(); + assert_eq!(item.pdf_hidden_layers, vec!["Red".to_string()]); + assert!(item.has_pdf_layer_override()); + std::fs::remove_dir_all(dir).unwrap(); + } + /// The package's IDML links its images to the files in the package's Links folder and embeds /// no copies of them. #[test]