From e75082c9d6f0655d56e638369ed6ec2cbdb8b2a3 Mon Sep 17 00:00:00 2001 From: Alexis Date: Thu, 15 Oct 2026 08:16:23 +0200 Subject: [PATCH] Printing: a layer that is off in the document stays off on the sheets (#1135) The print-ready PDF that `pdfcraft_print::impose` builds starts from a new catalog with only /Type and /Pages, so the document's /OCProperties was not carried over. The pages' marked content still refers to its optional-content groups, but nothing said a group was off, so a layer that is switched off in the document (and not drawn on screen) was printed anyway. Keep /OCProperties in the new catalog, so the sheets hide the same layers the document hides. Test: a one-page document whose red square is in a layer; with the layer off by default it is not drawn on screen and, now, not on the printed sheet; with the layer on it is drawn in both. Printing still follows the layers' default state only; applying the Print event of /AS (a layer shown only on screen or only in print) is not done here. --- crates/engine/src/tests.rs | 36 ++++++++++++++++++++++++++++++++++++ crates/print/src/lib.rs | 6 ++++++ 2 files changed, 42 insertions(+) diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 37cafd440a..2d088d4e86 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -1827,6 +1827,42 @@ fn printing_lays_out_sheets_that_render() { assert_eq!(s4.print_pdf(id4, &print::Settings { pages: vec![0], ..Default::default() }), Err(EditError::NotPermitted("printing"))); } +/// A one-page document whose red square is in a layer; `off` switches the layer off by default. +fn red_layer_pdf(off: bool) -> Vec { + let content = "/OC /L1 BDC 1 0 0 rg 0 0 100 100 re f EMC"; + let off = if off { "/OFF [5 0 R] " } else { "" }; + format!( + "%PDF-1.7\n\ + 1 0 obj << /Type /Catalog /Pages 2 0 R /OCProperties << /OCGs [5 0 R] /D << {off}/Order [5 0 R] >> >> >> endobj\n\ + 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj\n\ + 3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] /Contents 4 0 R /Resources << /Properties << /L1 5 0 R >> >> >> endobj\n\ + 4 0 obj << /Length {} >> stream\n{content}\nendstream endobj\n\ + 5 0 obj << /Type /OCG /Name (Red) >> endobj\n\ + trailer << /Root 1 0 R >>\n%%EOF", + content.len() + ) + .into_bytes() +} + +/// Does the first page of `pdf` show any red when drawn? +fn first_page_has_red(pdf: Vec) -> bool { + let mut renderer = pdfcraft_render::PageRenderer::new(Arc::new(pdf), pdfcraft_render::RenderConfig::default()); + let page = renderer.render(pdfcraft_render::RenderRequest { page: 0, scale: 0.5, ..Default::default() }); + page.rgba.as_chunks::<4>().0.iter().any(|p| p[0] > 200 && p[1] < 60 && p[2] < 60) +} + +#[test] +fn a_layer_that_is_off_does_not_print_and_one_that_is_on_does() { + for (off, prints) in [(true, false), (false, true)] { + let source = red_layer_pdf(off); + assert_eq!(first_page_has_red(source.clone()), prints, "on screen (off: {off})"); + let mut s = Session::new(); + let id = s.open("layers.pdf", None, Arc::new(source), None).unwrap(); + let sheet = s.print_pdf(id, &print::Settings { pages: vec![0], ..Default::default() }).unwrap(); + assert_eq!(first_page_has_red(sheet), prints, "printed (layer off by default: {off})"); + } +} + #[test] fn crop_and_duplicate_pages_show_in_the_viewer() { let (mut s, id) = session_with(2); diff --git a/crates/print/src/lib.rs b/crates/print/src/lib.rs index cbffdc4e2b..7e6413d3cf 100644 --- a/crates/print/src/lib.rs +++ b/crates/print/src/lib.rs @@ -723,6 +723,12 @@ pub fn impose(src: &Document, settings: &Settings) -> Result, PrintError let mut cat = Dict::new(); cat.set(b"Type".to_vec(), Object::name("Catalog")); cat.set(b"Pages".to_vec(), Object::Ref(pages_root)); + // Keep the layers' configuration, or a layer that is off in the document would print: the + // pages' marked content still refers to its groups, and without this nothing says they are off. + let layers = src.trailer().get(b"Root").and_then(|r| src.resolve(r).as_dict().and_then(|root| root.get(b"OCProperties").cloned())); + if let Some(layers) = layers { + cat.set(b"OCProperties".to_vec(), layers); + } let root = doc.add(Object::Dict(cat)); doc.trailer_mut().set(b"Root".to_vec(), Object::Ref(root)); doc.trailer_mut().remove(b"Info");