From fcdf2a21467b9acc400034b1be882c3cc3a7fba7 Mon Sep 17 00:00:00 2001 From: Marco Arnulfo Date: Fri, 16 Oct 2026 03:08:26 +0200 Subject: [PATCH] Layers panel: Reset to Initial Visibility Layer options in the Layers panel header resets every layer to the state the file's default configuration (/OCProperties /D) gives it, as Acrobat does. Visibility is view state, so the reset doesn't mark the document changed and doesn't enter Undo. doc_info now reports each layer's initial state. --- ATTRIBUTION.toml | 44 +++++++-------- Cargo.lock | 1 + crates/automation/src/lib.rs | 2 +- crates/automation/tests/automation.rs | 6 +- crates/engine/Cargo.toml | 3 + crates/engine/src/lib.rs | 16 ++++++ crates/engine/src/tests.rs | 55 ++++++++++++++++++- crates/render/src/inspect.rs | 22 +++++++- crates/ui-egui/src/i18n/ar.tsv | 2 + crates/ui-egui/src/i18n/bg.tsv | 2 + crates/ui-egui/src/i18n/cs.tsv | 2 + crates/ui-egui/src/i18n/de.tsv | 2 + crates/ui-egui/src/i18n/el.tsv | 2 + crates/ui-egui/src/i18n/es.tsv | 2 + crates/ui-egui/src/i18n/fr.tsv | 2 + crates/ui-egui/src/i18n/hu.tsv | 2 + crates/ui-egui/src/i18n/it.tsv | 2 + crates/ui-egui/src/i18n/ja.tsv | 2 + crates/ui-egui/src/i18n/kk.tsv | 2 + crates/ui-egui/src/i18n/lt.tsv | 2 + crates/ui-egui/src/i18n/pl.tsv | 2 + crates/ui-egui/src/i18n/pt-br.tsv | 2 + crates/ui-egui/src/i18n/ru.tsv | 2 + crates/ui-egui/src/i18n/sl.tsv | 2 + crates/ui-egui/src/i18n/te.tsv | 2 + crates/ui-egui/src/i18n/tr.tsv | 2 + crates/ui-egui/src/i18n/uk.tsv | 2 + crates/ui-egui/src/i18n/vi.tsv | 2 + crates/ui-egui/src/i18n/zh-hans.tsv | 2 + crates/ui-egui/src/i18n/zh-hant.tsv | 2 + crates/ui-egui/src/panels.rs | 14 +++++ .../ui-egui/tests/ui_group/layer_actions.rs | 20 +++++++ docs/pdf-spec-parity.md | 2 +- parity/acrobat-features.toml | 8 +-- 34 files changed, 205 insertions(+), 32 deletions(-) diff --git a/ATTRIBUTION.toml b/ATTRIBUTION.toml index db3dd6124..7ed20787f 100644 --- a/ATTRIBUTION.toml +++ b/ATTRIBUTION.toml @@ -7,7 +7,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Greek UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "6eee0b34db6529f26235bfc07fa1fdb9030b8e7b0b10ede271ca9242cb491919" +sha256 = "85100503b2b0b30b72927783a8c2a4c601f88b69c9d83a51426aaa548ecc919e" # ATTRIBUTION.toml: one entry per asset (AGENTS.md §1). The source of truth for provenance and licences. # ATTRIBUTION.md is generated from this file: `cargo xtask assets --write`. Checked by `cargo xtask assets` (part of `xtask ci`). @@ -30,7 +30,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Simplified Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "9870c0fb9f7198155be16adee7f45d36cb9a254af90b545a3ce5b04cbbbf24d4" +sha256 = "d24435fe507f4a466a04a03e0ded2fa174cc66382d669f67cdfbdf1637b45e39" [[asset]] path = "crates/ui-egui/src/i18n/zh-hant.tsv" @@ -41,7 +41,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Traditional Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "fc31e40877ce115cfb7033c57dd6acabcae3725b249364387e3fe23e04d94e5e" +sha256 = "dba5335f7185bc07036abec5630038bd050a89402145a1a9a1636f8bcb622192" [[asset]] path = "crates/ui-egui/src/i18n/cs.tsv" @@ -52,7 +52,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Czech UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "5de19834f293efaea68cb6b7f51a71d320391f53345f56cfebcd73dab828b665" +sha256 = "db22bb0649ab8922d7a6d882548037f9cd134329aee9047172ef405063e0a42a" [[asset]] path = "crates/ui-egui/src/i18n/pt-br.tsv" @@ -63,7 +63,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Brazilian Portuguese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "cef0d22341d0722f029c6543fc846fce164331b7ad8f693a6754b7567b9d9e95" +sha256 = "86f72cce105dfad308eb67dee2a46f15319303a6ef45ddbe9f09a04c48b8577d" [[asset]] path = "crates/ui-egui/src/i18n/es.tsv" @@ -74,7 +74,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Spanish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "c0ca7761cbabd8096bc9af54cdf6b88b3ae0cd8210003db8b3356e2706e44fc8" +sha256 = "167cfb16e67622356a0563157904193d9af891dbea69a99094ff18f5ebfec7d4" [[asset]] path = "crates/ui-egui/src/i18n/fr.tsv" @@ -85,7 +85,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "French UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "a8afd51c3296f44b0446d6b06492b1348444b3e3c448a326ad213d18ce7af714" +sha256 = "09acccff04e4c4fe8288787eeacffa58fd63b57f3c93f512a6d7f28a62228bb5" [[asset]] path = "crates/ui-egui/src/i18n/de.tsv" @@ -96,7 +96,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "German UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "7676a2dfaa7d66e2e9ef608d2d9de7f863557dcecbef6d94bafa981097405e6d" +sha256 = "61c333edf6b96cbd428616c427f5921d7bde9f780e06b759adcf609ebe9e3584" [[asset]] path = "crates/ui-egui/src/i18n/te.tsv" @@ -107,7 +107,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Telugu UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "9a22996c0308ea5b20ec5e328504cd83cbb560147763faaf0e6987f974844c29" +sha256 = "81147bf7c3a0e1651c862a74da86d91347ee27166a4e9817c1bf4484b2f6da33" [[asset]] path = "crates/ui-egui/src/i18n/it.tsv" @@ -118,7 +118,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Italian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "eacc972d65a2d951e94e1a8cf944638e8328f73f6c05759de6059a425f159cd7" +sha256 = "3a6400e7009e4d69ac014954a169055c26b95244d5b6acef68c7023bee55ed21" [[asset]] path = "crates/ui-egui/src/i18n/pl.tsv" @@ -129,7 +129,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Polish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "dfa4aa540cc7d61fa22a27a949e6eec0f78dcc5b883d0e4e004d695e460b95aa" +sha256 = "5c67bd8796d3de37097ee0e70baeaceaa9dbf5fba830c01237561893587c9cee" [[asset]] path = "crates/ui-egui/src/i18n/ru.tsv" @@ -140,7 +140,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Russian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "27e02e7bb3d378bceca76988a9dceaa68d974f57cbee962954deca6184cf62aa" +sha256 = "f7297ea2bc5bd8fc74900bda036141f71cfdebfd7d2ad60528bd3ba989a9e27e" [[asset]] path = "crates/ui-egui/src/i18n/hu.tsv" @@ -151,7 +151,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Hungarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "9675edd36c5ae54d9893fd10c4325aab664941bf714c08fbfeef9134fa42056c" +sha256 = "21cbdc6ba7df33318e49c6223baa3103c8b9c64c76c58ba6221b904a4a6090bc" [[asset]] path = "crates/ui-egui/src/i18n/bg.tsv" @@ -162,7 +162,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Bulgarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "ce18fc96f8516aa917e4a8bcfd290edec13a9800b3d1ee102e4b2a71b8467d2b" +sha256 = "85b7aa8f198de76c7045ffc4864e094717131f9256bc9fccbc4e3e44786eb254" [[asset]] path = "crates/ui-egui/src/i18n/kk.tsv" @@ -173,7 +173,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Kazakh UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "286a1e2e051748eb4bcee3906918dc78d0704f8a37bde22dc50d3376d5e75017" +sha256 = "b403a9907434e35705c8062b1a0feb1eefc20c28cae3ca101f6d3877717e80a7" [[asset]] path = "crates/ui-egui/src/i18n/ja.tsv" @@ -184,7 +184,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Japanese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "77593b7497c67f4a79cc31fa43fdd6eae11601dd87c079a8e666b25cdd9117ba" +sha256 = "8cabeb2cf2ea5cd1d937739f33a785ab4221a3b3249e1a2b4dafe48c4ef425dd" [[asset]] path = "crates/ui-egui/src/i18n/uk.tsv" @@ -195,7 +195,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Ukrainian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "d7d93a03f22ab380696f69b20b12104bf30e78c4440b683df63594af716894e2" +sha256 = "8592c561daeb872215ef5f1615a864bfcd93a80952d84cc2a002fc6112aa8085" [[asset]] path = "crates/ui-egui/src/i18n/ar.tsv" @@ -206,7 +206,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Arabic UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "889a0d0a9ea53ca0a818c2ba672c053c232d2cca140f8d63127353e6f36505fe" +sha256 = "d1ba0e543c058d5500c47fd90df6c503b43572937d1f8245e88b6334a688fbdf" [[asset]] path = "crates/ui-egui/src/i18n/tr.tsv" @@ -217,7 +217,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Turkish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "bae8b21651ae2a69c71c91b69ba79727254dbfb078c1be312a13383332934927" +sha256 = "cbfcbb27e0de3a3952698d7c49c5a449602e86ad7a2c5ceb25f4f26af41a0975" [[asset]] path = "crates/ui-egui/src/i18n/lt.tsv" @@ -228,7 +228,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Lithuanian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "7fa70f68a9fa24d0b1cac05fe3aaabaeaca9750c57416124bd317a4eda806d5a" +sha256 = "f4411c01d9d8df98944da540ef8bcf0d700201bd78eb3595d23862fdab210807" [[asset]] @@ -3352,7 +3352,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Vietnamese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "95b5fc6d10f7bc8371688772741870448de716fa2dd9af62fdfe4e009d269b45" +sha256 = "8f887d8c969cdd3e9e2dd95fa7de7b8bf683297f0edee3141fb1cdcd73243df0" [[asset]] path = "crates/ui-egui/src/i18n/sl.tsv" @@ -3363,7 +3363,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Slovene UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "3ac2b113045b7e399dd6d9ddb7773e5b0874c8b43c2ba4cca5854598a2a7ba92" +sha256 = "cd7b0bd7075e4497d1e74283877d99f9ca4b9d608290ee2301aed9757ac19b83" [[asset]] diff --git a/Cargo.lock b/Cargo.lock index c6f4f49d6..f6636cabd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4131,6 +4131,7 @@ dependencies = [ "pdfcraft-redact", "pdfcraft-render", "pdfcraft-sign", + "pdfcraft-testkit", "pdfcraft-xfa", "pdfcraft-xfdf", "png 0.18.1", diff --git a/crates/automation/src/lib.rs b/crates/automation/src/lib.rs index c01187306..16504bc4d 100644 --- a/crates/automation/src/lib.rs +++ b/crates/automation/src/lib.rs @@ -2466,7 +2466,7 @@ fn info(d: &Document) -> Value { pdfcraft_render::LinkTarget::Other(o) => json!({ "other": o }), }, })).collect::>(), - "layers": i.layers.iter().map(|l| json!({ "name": l.name, "visible": l.visible })).collect::>(), + "layers": i.layers.iter().map(|l| json!({ "name": l.name, "visible": l.visible, "initial": l.initial })).collect::>(), // Full-screen presentation settings a page carries (ISO 32000-2 §12.4.4): only pages with // a `/Trans` or `/Dur` appear; `style` is null for a display time alone. "transitions": i.transitions.iter().map(|t| json!({ diff --git a/crates/automation/tests/automation.rs b/crates/automation/tests/automation.rs index 6a348e0d8..dec3fd7ea 100644 --- a/crates/automation/tests/automation.rs +++ b/crates/automation/tests/automation.rs @@ -5638,12 +5638,13 @@ fn doc_info_says_when_a_runaway_script_turned_the_forms_scripts_off() { let _ = std::fs::remove_dir_all(dir); } -/// doc_info describes a link's set-layer-visibility action by layer name. +/// doc_info describes a link's set-layer-visibility action by layer name, and each layer's +/// visibility now and as the file opens. #[test] fn doc_info_describes_set_layer_links() { let dir = workdir("layer-links"); let objs = [ - "<< /Type /Catalog /Pages 2 0 R /OCProperties << /OCGs [4 0 R 5 0 R] /D << >> >> >>", + "<< /Type /Catalog /Pages 2 0 R /OCProperties << /OCGs [4 0 R 5 0 R] /D << /OFF [5 0 R] >> >> >>", "<< /Type /Pages /Kids [3 0 R] /Count 1 >>", "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] /Annots [6 0 R] >>", "<< /Type /OCG /Name (Red) >>", @@ -5674,6 +5675,7 @@ fn doc_info_describes_set_layer_links() { "preserve_rb": false, }) ); + assert_eq!(info["layers"], json!([{ "name": "Red", "visible": true, "initial": true }, { "name": "Green", "visible": false, "initial": false }])); } mod close_argument_tests { diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml index 74567eb48..2de1b4245 100644 --- a/crates/engine/Cargo.toml +++ b/crates/engine/Cargo.toml @@ -47,3 +47,6 @@ unicode-segmentation = "1.13" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] # Already in the graph through lopdf; used only for the local UTC offset ("today"). chrono = { version = "0.4", default-features = false, features = ["clock"] } + +[dev-dependencies] +pdfcraft-testkit = { workspace = true } diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index bf689af54..7dd06f60d 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -4785,6 +4785,22 @@ impl Session { true } + /// Show each layer as the file opens (Acrobat's Layers ▸ Reset to Initial Visibility). Returns + /// `true` if any layer changed; callers must drop cached rasters and text for the document, + /// as for [`Self::set_layer_visible`]. + pub fn reset_layers(&mut self, id: DocId) -> bool { + let Some(doc) = self.docs.iter_mut().find(|d| d.id == id) else { return false }; + let mut changed = false; + for l in &mut doc.info.layers { + changed |= l.visible != l.initial; + l.visible = l.initial; + } + if changed { + use_layer_choices(doc); + } + changed + } + /// Hide or show every comment on the page (Acrobat's Comments ▸ Hide all comments). Form /// fields and links still draw. Returns whether anything changed. pub fn set_hide_comments(&mut self, id: DocId, hide: bool) -> bool { diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index d5ffac500..2f2b80977 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -685,7 +685,7 @@ fn layer_choices_survive_edits() { #[test] fn set_layer_actions_toggle_in_order_and_keep_radio_groups() { use pdfcraft_render::LayerOp::{Off, On, Toggle}; - let layer = |n: u32, visible: bool| Layer { id: (n, 0), name: format!("L{n}"), visible }; + let layer = |n: u32, visible: bool| Layer { id: (n, 0), name: format!("L{n}"), visible, initial: visible }; let run = |changes: &[(LayerOp, u32)], preserve_rb: bool| { // L1 and L2 are a radio-button group; L3 is on its own. let mut layers = vec![layer(1, true), layer(2, false), layer(3, true)]; @@ -761,6 +761,59 @@ fn set_layer_actions_change_what_renders() { assert!(!s.set_layer_state(DocId(999), &[(LayerOp::On, (5, 0))], true)); } +#[test] +fn reset_layers_returns_to_the_files_visibility() { + // Red (left half, on) and Green (right half, off) are a radio-button group. + let content = "/OC /R BDC 1 0 0 rg 0 0 50 100 re f EMC /OC /G BDC 0 1 0 rg 50 0 50 100 re f EMC"; + let pdf = pdfcraft_testkit::Pdf::new() + .obj("<< /Type /Catalog /Pages 2 0 R /OCProperties << /OCGs [5 0 R 6 0 R] /D << /OFF [6 0 R] /RBGroups [[5 0 R 6 0 R]] >> >> >>") + .obj("<< /Type /Pages /Kids [3 0 R] /Count 1 >>") + .obj("<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] /Contents 4 0 R /Resources << /Properties << /R 5 0 R /G 6 0 R >> >> >>") + .stream("", content) + .obj("<< /Type /OCG /Name (Red) >>") + .obj("<< /Type /OCG /Name (Green) >>") + .build(); + // A page drawing on layer Blue, which that file has off. + let other = pdfcraft_testkit::Pdf::new() + .obj("<< /Type /Catalog /Pages 2 0 R /OCProperties << /OCGs [5 0 R] /D << /OFF [5 0 R] >> >> >>") + .obj("<< /Type /Pages /Kids [3 0 R] /Count 1 >>") + .obj("<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] /Contents 4 0 R /Resources << /Properties << /B 5 0 R >> >> >>") + .stream("", "/OC /B BDC 0 0 1 rg 0 0 10 10 re f EMC") + .obj("<< /Type /OCG /Name (Blue) >>") + .build(); + let mut s = Session::new(); + let id = s.open("layers.pdf", None, Arc::new(pdf), None).unwrap(); + let shown = |s: &Session| s.get(id).unwrap().info.layers.iter().map(|l| (l.name.clone(), l.visible)).collect::>(); + let left = |s: &Session| { + let doc = s.get(id).unwrap(); + let mut r = pdfcraft_render::PageRenderer::new(doc.bytes.clone(), doc.config.clone()); + let p = r.render(pdfcraft_render::RenderRequest { page: 0, kind: pdfcraft_render::RequestKind::Pixels, scale: 1.0, ..Default::default() }); + let i = (50 * p.width as usize + 25) * 4; + p.rgba[i..i + 3].to_vec() + }; + assert!(s.set_layer_state(id, &[(LayerOp::On, (6, 0))], true)); + let edit = Edit::InsertPagesFrom { + name: "b.pdf".into(), + bytes: Arc::new(other), + pages: None, + at: 1, + field_conflicts: pdfcraft_organize::FieldConflicts::Rename, + }; + s.apply(id, edit).unwrap(); + let blue = s.get(id).unwrap().info.layers.iter().position(|l| l.name == "Blue").unwrap(); + assert!(s.set_layer_visible(id, blue, true)); + assert_eq!(shown(&s), [("Red".into(), false), ("Green".into(), true), ("Blue".into(), true)]); + assert_eq!(left(&s), [255, 255, 255]); + // Every layer, the inserted one too, goes back to the file's default configuration. + assert!(s.reset_layers(id)); + assert_eq!(shown(&s), [("Red".into(), true), ("Green".into(), false), ("Blue".into(), false)]); + assert_eq!(left(&s), [255, 0, 0]); + assert!(!s.reset_layers(id), "already as the file opens"); + // What the eye sees is not an edit: Undo still undoes the insert. + assert_eq!(s.get(id).unwrap().can_undo(), Some("Insert pages from b.pdf")); + assert!(!s.reset_layers(DocId(999))); +} + #[test] fn edits_to_unknown_documents_are_rejected() { let (mut s, _) = session_with(1); diff --git a/crates/render/src/inspect.rs b/crates/render/src/inspect.rs index 0979f3d38..a55c89fec 100644 --- a/crates/render/src/inspect.rs +++ b/crates/render/src/inspect.rs @@ -425,6 +425,8 @@ pub struct Layer { pub id: (u32, u16), pub name: String, pub visible: bool, + /// Shown as the file opens: the default configuration (`/OCProperties /D`). + pub initial: bool, } #[derive(Clone, Debug)] @@ -1428,7 +1430,7 @@ impl<'a> Inspector<'a> { _ => !base_off, }; let Some(id) = id else { continue }; - out.push(Layer { id, name: self.text(d, b"Name").unwrap_or_else(|| "Layer".into()), visible }); + out.push(Layer { id, name: self.text(d, b"Name").unwrap_or_else(|| "Layer".into()), visible, initial: visible }); } } @@ -2358,6 +2360,24 @@ trailer << /Root 1 0 R /Info 4 0 R >> assert_eq!(info.layer_groups, [vec![(5, 0), (6, 0)], vec![(6, 0), (7, 0)]]); } + #[test] + fn initial_layer_visibility_is_the_default_configuration() { + // BaseState OFF: Red is listed ON; Green is listed both ON and OFF, and OFF wins; Blue + // isn't listed, so it takes the base state. + let pdf = b"%PDF-1.7 +1 0 obj << /Type /Catalog /Pages 2 0 R /OCProperties << /OCGs [5 0 R 6 0 R 7 0 R] /D << /BaseState /OFF /ON [5 0 R 6 0 R] /OFF [6 0 R] >> >> >> endobj +2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj +3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] >> endobj +5 0 obj << /Type /OCG /Name (Red) >> endobj +6 0 obj << /Type /OCG /Name (Green) >> endobj +7 0 obj << /Type /OCG /Name (Blue) >> endobj +trailer << /Root 1 0 R >> +%%EOF"; + let info = inspect(Arc::new(pdf.to_vec()), None).expect("opens"); + let layers: Vec<_> = info.layers.iter().map(|l| (l.name.as_str(), l.initial)).collect(); + assert_eq!(layers, [("Red", true), ("Green", false), ("Blue", false)]); + } + const ORPHAN_FIELDS: &[u8] = b"%PDF-1.7 1 0 obj << /Type /Catalog /Pages 2 0 R /AcroForm 4 0 R >> endobj 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj diff --git a/crates/ui-egui/src/i18n/ar.tsv b/crates/ui-egui/src/i18n/ar.tsv index 8697887e1..de01ec7d1 100644 --- a/crates/ui-egui/src/i18n/ar.tsv +++ b/crates/ui-egui/src/i18n/ar.tsv @@ -1332,6 +1332,8 @@ This document has no layers. هذا المستند لا يحتوي على طبقات. This document has no attachments. هذا المستند لا يحتوي على مرفقات. Hide layer إخفاء الطبقة + Layer options خيارات الطبقات + Reset to Initial Visibility إعادة تعيين إلى الرؤية الأولية Show layer إظهار الطبقة Set layer visibility ضبط ظهور الطبقة on page {label} في الصفحة {label} diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 813a2c7ae..6f910e29e 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -1274,6 +1274,8 @@ This document has no layers. Този документ няма слоеве. This document has no attachments. Този документ няма прикачени файлове. Hide layer Скрий слоя + Layer options Опции за слоеве + Reset to Initial Visibility Възстанови първоначалната видимост Show layer Покажи слоя Set layer visibility Задай видимост на слоя on page {label} на страница {label} diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 993632552..e1af800e1 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -1400,6 +1400,8 @@ key Space Mezerník This document has no layers. Tento dokument nemá žádné vrstvy. This document has no attachments. Tento dokument nemá žádné přílohy. Hide layer Skrýt vrstvu + Layer options Možnosti vrstev + Reset to Initial Visibility Obnovit počáteční viditelnost Show layer Zobrazit vrstvu Set layer visibility Nastavit viditelnost vrstvy on page {label} na stránce {label} diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 4d9207eb9..70bca4903 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -1318,6 +1318,8 @@ This document has no layers. Dieses Dokument hat keine Ebenen. This document has no attachments. Dieses Dokument hat keine Anhänge. Hide layer Ebene ausblenden + Layer options Ebenenoptionen + Reset to Initial Visibility Ursprüngliche Sichtbarkeit wiederherstellen Show layer Ebene einblenden on page {label} auf Seite {label} Save attachment… Anhang speichern… diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 06fe2b540..888732aeb 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -363,6 +363,8 @@ Distribute Κατανομή Show layer Εμφάνιση επιπέδου Hide layer Απόκρυψη επιπέδου + Layer options Επιλογές επιπέδων + Reset to Initial Visibility Επαναφορά αρχικής ορατότητας Collate Συρραφή αντιγράφων Fit to page Προσαρμογή στη σελίδα Reverse pages Αντιστροφή σειράς σελίδων diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 79c668992..29105aff1 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -1327,6 +1327,8 @@ This document has no layers. Este documento no tiene capas. This document has no attachments. Este documento no tiene archivos adjuntos. Hide layer Ocultar capa + Layer options Opciones de capas + Reset to Initial Visibility Restablecer visibilidad inicial Show layer Mostrar capa Set layer visibility Definir visibilidad de capas on page {label} en la página {label} diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 9bdcf0bc8..224746bad 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -1313,6 +1313,8 @@ This document has no layers. Ce document ne contient aucun calque. This document has no attachments. Ce document ne contient aucune pièce jointe. Hide layer Masquer le calque + Layer options Options des calques + Reset to Initial Visibility Rétablir la visibilité initiale Show layer Afficher le calque on page {label} à la page {label} Save attachment… Enregistrer la pièce jointe… diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index d537677c7..821e991f1 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -1303,6 +1303,8 @@ This document has no layers. Ebben a dokumentumban nincs réteg. This document has no attachments. Ebben a dokumentumban nincs melléklet. Hide layer Réteg elrejtése + Layer options Réteg beállításai + Reset to Initial Visibility Kezdeti láthatóság visszaállítása Show layer Réteg megjelenítése on page {label} {label}. oldal Save attachment… Melléklet mentése… diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 35cba7a41..6af7e04e1 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -1328,6 +1328,8 @@ This document has no layers. Questo documento non ha livelli. This document has no attachments. Questo documento non ha allegati. Hide layer Nascondi livello + Layer options Opzioni livelli + Reset to Initial Visibility Ripristina visibilità iniziale Show layer Mostra livello Set layer visibility Imposta visibilità livello on page {label} a pagina {label} diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index e845a736d..973714c15 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -1333,6 +1333,8 @@ This document has no layers. この文書にレイヤーはありません。 This document has no attachments. この文書に添付ファイルはありません。 Hide layer レイヤーを非表示 + Layer options レイヤーのオプション + Reset to Initial Visibility 初期表示状態にリセット Show layer レイヤーを表示 Set layer visibility レイヤーの表示を設定 on page {label} ページ {label} diff --git a/crates/ui-egui/src/i18n/kk.tsv b/crates/ui-egui/src/i18n/kk.tsv index 9acb4ec1b..541968d64 100644 --- a/crates/ui-egui/src/i18n/kk.tsv +++ b/crates/ui-egui/src/i18n/kk.tsv @@ -1275,6 +1275,8 @@ This document has no layers. Бұл құжатта қабаттар жоқ. This document has no attachments. Бұл құжатта тіркемелер жоқ. Hide layer Қабатты жасыру + Layer options Қабат параметрлері + Reset to Initial Visibility Бастапқы көрінуді қалпына келтіру Show layer Қабатты көрсету Set layer visibility Қабаттың көрінісін орнату on page {label} {label} бетінде diff --git a/crates/ui-egui/src/i18n/lt.tsv b/crates/ui-egui/src/i18n/lt.tsv index d1dfc3304..dd95e237b 100644 --- a/crates/ui-egui/src/i18n/lt.tsv +++ b/crates/ui-egui/src/i18n/lt.tsv @@ -629,6 +629,8 @@ Height: Aukštis: Hide field highlights Slėpti laukų paryškinimą Hide layer Slėpti sluoksnį + Layer options Sluoksnių parinktys + Reset to Initial Visibility Atkurti pradinį matomumą Hide menu bar Slėpti meniu juostą Hide password Slėpti slaptažodį Hide toolbars Slėpti įrankių juostas diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index c432a6a92..dfc1e2feb 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -1318,6 +1318,8 @@ This document has no layers. Ten dokument nie zawiera warstw. This document has no attachments. Ten dokument nie zawiera załączników. Hide layer Ukryj warstwę + Layer options Opcje warstw + Reset to Initial Visibility Przywróć początkową widoczność Show layer Pokaż warstwę Set layer visibility Ustaw widoczność warstwy on page {label} na stronie {label} diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 89ff80f9d..38c0df51c 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -1218,6 +1218,8 @@ This document has no layers. Este documento não tem camadas. This document has no attachments. Este documento não tem anexos. Hide layer Ocultar camada + Layer options Opções de camadas + Reset to Initial Visibility Redefinir visibilidade inicial Show layer Mostrar camada on page {label} na página {label} Save attachment… Salvar anexo… diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 73f21aa0d..cb34b11e9 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -1333,6 +1333,8 @@ This document has no layers. В этом документе нет слоёв. This document has no attachments. В этом документе нет вложений. Hide layer Скрыть слой + Layer options Параметры слоёв + Reset to Initial Visibility Восстановить исходную видимость Show layer Показать слой Set layer visibility Задать видимость слоя on page {label} на стр. {label} diff --git a/crates/ui-egui/src/i18n/sl.tsv b/crates/ui-egui/src/i18n/sl.tsv index dd169bc96..8c4b3a20a 100644 --- a/crates/ui-egui/src/i18n/sl.tsv +++ b/crates/ui-egui/src/i18n/sl.tsv @@ -902,6 +902,8 @@ Hide all comments Skrij vse komentarje Hide field highlights Skrij osvetlitev polj Hide layer Skrij plast + Layer options Možnosti plasti + Reset to Initial Visibility Ponastavi na začetno vidnost Hide menu bar Skrij menijsko vrstico Hide password Skrij geslo Hide toolbars Skrij orodne vrstice diff --git a/crates/ui-egui/src/i18n/te.tsv b/crates/ui-egui/src/i18n/te.tsv index 47aee36f8..fe586e988 100644 --- a/crates/ui-egui/src/i18n/te.tsv +++ b/crates/ui-egui/src/i18n/te.tsv @@ -1303,6 +1303,8 @@ signature pad Type టైప్ చేయి This document has no layers. ఈ పత్రంలో లేయర్‌లు లేవు. This document has no attachments. ఈ పత్రంలో జోడింపులు లేవు. Hide layer లేయర్‌ను దాచు + Layer options లేయర్ ఎంపికలు + Reset to Initial Visibility ప్రారంభ దృశ్యతకు రీసెట్ చేయి Show layer లేయర్‌ను చూపించు on page {label} పేజీ {label}లో Save attachment… జోడింపును సేవ్ చేయి… diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 95ca0b608..ebed0ff7f 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -1290,6 +1290,8 @@ This document has no layers. Bu belgede katman yok. This document has no attachments. Bu belgede ek yok. Hide layer Katmanı gizle + Layer options Katman seçenekleri + Reset to Initial Visibility Başlangıç görünürlüğüne sıfırla Show layer Katmanı göster on page {label} sayfa {label} üzerinde Save attachment… Eki kaydet… diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 7993a0618..444387a8d 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -1330,6 +1330,8 @@ This document has no layers. Цей документ не має шарів. This document has no attachments. Цей документ не має вкладень. Hide layer Приховати шар + Layer options Параметри шарів + Reset to Initial Visibility Відновити початкову видимість Show layer Показати шар Set layer visibility Задати видимість шару on page {label} на сторінці {label} diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index bae599bf6..a00fc8648 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -625,6 +625,8 @@ theme Appearance Giao diện Height: Chiều cao: Hide field highlights Ẩn tô sáng trường Hide layer Ẩn lớp + Layer options Tùy chọn lớp + Reset to Initial Visibility Đặt lại khả năng hiển thị ban đầu Hide menu bar Ẩn thanh trình đơn Hide password Ẩn mật khẩu Hide toolbars Ẩn thanh công cụ diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 1ddecc655..29ba69b37 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -1281,6 +1281,8 @@ This document has no layers. 本文档没有图层。 This document has no attachments. 本文档没有附件。 Hide layer 隐藏图层 + Layer options 图层选项 + Reset to Initial Visibility 重置为初始可见性 Show layer 显示图层 Set layer visibility 设置图层可见性 on page {label} 第 {label} 页 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index bb5ec3649..5273bf121 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -1225,6 +1225,8 @@ This document has no layers. 本文件沒有圖層。 This document has no attachments. 本文件沒有附件。 Hide layer 隱藏圖層 + Layer options 圖層選項 + Reset to Initial Visibility 重設為初始可見性 Show layer 顯示圖層 Set layer visibility 設定圖層可見性 on page {label} 第 {label} 頁 diff --git a/crates/ui-egui/src/panels.rs b/crates/ui-egui/src/panels.rs index 0f2c60149..64589195c 100644 --- a/crates/ui-egui/src/panels.rs +++ b/crates/ui-egui/src/panels.rs @@ -507,6 +507,7 @@ pub fn right_panel(app: &mut PdfCraftApp, ui: &mut egui::Ui) { let mut nav: Option