diff --git a/crates/pdf/src/export/entry.rs b/crates/pdf/src/export/entry.rs index 6cf8677269..00ac16a3fe 100644 --- a/crates/pdf/src/export/entry.rs +++ b/crates/pdf/src/export/entry.rs @@ -45,6 +45,11 @@ fn export_with_report_inner(doc: &Document, opts: &PdfOptions, left_out: &mut Ha let set = &opts.settings; set.check()?; vectorcraft_doc::cancel::check().map_err(|_| PdfError::Cancelled)?; + // The pages come from the artboards, which baking leaves as they are. The version a long page + // raises the file to decides whether the layers are written, so it is settled before baking. + let pages = pages(doc, opts)?; + let raised = for_long_pages(set, &pages)?; + let set = raised.as_ref().unwrap_or(set); // Live geometry effects export as their result, in symbols' art too; raster effects are // reported below. // Only the layers this export writes place their instances' art (see `ex.non_printing` and @@ -57,9 +62,6 @@ fn export_with_report_inner(doc: &Document, opts: &PdfOptions, left_out: &mut Ha // Baking stops early when cancelled, leaving a half-baked copy that must not be written. vectorcraft_doc::cancel::check().map_err(|_| PdfError::Cancelled)?; let doc = baked.as_ref().unwrap_or(doc); - let pages = pages(doc, opts)?; - let raised = for_long_pages(set, &pages)?; - let set = raised.as_ref().unwrap_or(set); // The file's Title is File Info's; the page information names the document, as its tab does. let title = crate::pdfx::title(set.standard, opts.title.clone().unwrap_or_else(|| doc.info_title().to_owned())); diff --git a/crates/pdf/src/tests/layers.rs b/crates/pdf/src/tests/layers.rs index 0650e3f0ef..b11cbc47d8 100644 --- a/crates/pdf/src/tests/layers.rs +++ b/crates/pdf/src/tests/layers.rs @@ -131,6 +131,37 @@ fn pdf_layers_import_back_as_layers() { } } +/// A hidden layer written as a PDF layer places the live effects of its instances' symbol art, +/// also in a PDF/A-2b file at PDF 1.4 whose 17,000 pt page has it written as PDF 1.6: the symbol's +/// square from x = 10 to 40, which a Transform effect moves 100 pt right, is written from x = 110 +/// to 140. +#[test] +fn a_hidden_layer_places_symbol_art_effects_when_a_long_page_raises_the_version() { + let mut d = Document::new(17_000.0, 300.0); + d.layers.clear(); + let mut square = rect(&mut d, 10.0, RED); + square.appearance.effects.push(vectorcraft_doc::Effect { id: "distort.transform".into(), params: json!({"moveH": 100.0}), visible: true }); + d.symbols.push(vectorcraft_doc::Symbol { dynamic: false, name: "Moved".into(), art: square.into() }); + let instance = Node::new(d.alloc_id(), NodeKind::SymbolInstance { symbol: "Moved".into(), xf: vectorcraft_geom::Affine::IDENTITY }); + let mut l = Node::layer(d.alloc_id(), "Hidden", LayerColor::Preset(0)); + l.visible = false; + if let NodeKind::Layer { children, .. } = &mut l.kind { + children.push(instance.into()); + } + d.layers.push(l.into()); + for compatibility in [Compatibility::Pdf14, Compatibility::Pdf16] { + let settings = PdfSettings { standard: Standard::PdfA2b, compatibility, create_layers: true, ..PdfSettings::default() }; + let r = export_with_report(&d, &PdfOptions { settings, created: Some(1_791_200_000), ..Default::default() }).unwrap(); + assert!(r.bytes.starts_with(b"%PDF-1.6"), "{compatibility:?}"); + let back = import_with_report(&r.bytes, &ImportOptions::default()).unwrap().document; + let hidden = back.layers.iter().find(|l| l.name.as_deref() == Some("Hidden")).expect("the hidden layer"); + let art = hidden.children().unwrap(); + assert_eq!(art.len(), 1, "{compatibility:?}"); + let b = art[0].geometric_bounds().unwrap(); + assert!((b.x0 - 110.0).abs() < 0.1 && (b.x1 - 140.0).abs() < 0.1, "{compatibility:?}: {b:?}"); + } +} + /// The rows of `nodes` in paint order, depth first: `depth name visible` for layers, `depth fill` /// for art. fn tree(nodes: &[std::sync::Arc], depth: usize, out: &mut Vec) {