From 6a9dd32ef9286ee96fd32bb0ae90eac19fb07e67 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Fri, 2 Oct 2026 04:34:26 -0400 Subject: [PATCH] M3: Tabs and the window title show the document title when Initial View asks for it (DisplayDocTitle); parity for setting the document language Co-Authored-By: Claude Opus 5.5 --- crates/engine/src/lib.rs | 12 ++++++++++++ crates/organize/src/lib.rs | 2 +- crates/organize/src/view.rs | 10 ++++++++++ crates/ui-egui/src/chrome.rs | 2 +- crates/ui-egui/src/lib.rs | 12 ++++++++++++ crates/ui-egui/tests/viewer.rs | 19 +++++++++++++++++++ parity/acrobat-features.toml | 20 ++++++++++---------- 7 files changed, 65 insertions(+), 12 deletions(-) diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 97e06b06..c8c1832f 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -267,6 +267,18 @@ impl Document { self.editor.as_ref().and_then(|e| printcraft_organize::info(&e.cos, key)) } + /// The name shown on the tab and window: the document title when the document asks for it + /// (Initial View ▸ Show: Document Title) and has one, else the file name. + pub fn display_name(&self) -> String { + self.editor + .as_ref() + .filter(|e| printcraft_organize::displays_doc_title(&e.cos)) + .and_then(|e| printcraft_organize::info(&e.cos, "Title")) + .map(|t| t.trim().to_owned()) + .filter(|t| !t.is_empty()) + .unwrap_or_else(|| self.name.clone()) + } + /// Notes about damage repaired while opening (Document Properties ▸ Advanced, notices). pub fn repair_log(&self) -> Vec { self.editor.as_ref().map(|e| e.cos.repair_log().to_vec()).unwrap_or_default() diff --git a/crates/organize/src/lib.rs b/crates/organize/src/lib.rs index f46c8f36..41aeb4ec 100644 --- a/crates/organize/src/lib.rs +++ b/crates/organize/src/lib.rs @@ -25,7 +25,7 @@ pub use labels::{LabelRange, LabelStyle, number_pages, page_label_ranges, page_l pub use outline::{ Bookmark, OutlineError, add_bookmark, bookmarks, delete_bookmark, move_bookmark, rename_bookmark, set_bookmark_open, set_bookmark_page, }; -pub use view::{InitialView, initial_view, set_initial_view}; +pub use view::{InitialView, displays_doc_title, initial_view, set_initial_view}; #[derive(Debug, thiserror::Error, PartialEq)] pub enum OrganizeError { diff --git a/crates/organize/src/view.rs b/crates/organize/src/view.rs index 41498c72..0f951698 100644 --- a/crates/organize/src/view.rs +++ b/crates/organize/src/view.rs @@ -138,6 +138,16 @@ pub fn initial_view(doc: &Document) -> InitialView { v } +/// Whether viewers should show the document title rather than the file name +/// (`/ViewerPreferences /DisplayDocTitle`), without reading the rest of the initial view. +pub fn displays_doc_title(doc: &Document) -> bool { + let Ok((_, c)) = catalog(doc) else { return false }; + c.get(b"ViewerPreferences") + .map(|o| doc.resolve(o)) + .and_then(|o| o.as_dict().map(|vp| matches!(vp.get(b"DisplayDocTitle").map(|x| doc.resolve(x)).as_deref(), Some(Object::Bool(true))))) + .unwrap_or(false) +} + /// Write the initial view. An opening action that isn't a plain destination (a script, say) is /// kept unless the page or magnification asks for a destination. pub fn set_initial_view(doc: &mut Document, v: &InitialView) -> Result<(), OrganizeError> { diff --git a/crates/ui-egui/src/chrome.rs b/crates/ui-egui/src/chrome.rs index de59e8d9..c95a66b9 100644 --- a/crates/ui-egui/src/chrome.rs +++ b/crates/ui-egui/src/chrome.rs @@ -30,7 +30,7 @@ pub fn tab_strip(app: &mut PrintCraftApp, ui: &mut egui::Ui) { let mut close = None; for i in 0..app.views.len() { let Some(doc) = app.session.get(app.views[i].id) else { continue }; - let (name, dirty) = (doc.name.clone(), doc.dirty); + let (name, dirty) = (doc.display_name(), doc.dirty); if tab(ui, &t, &name, dirty, app.active == Some(i), &mut close, i).clicked() { app.active = Some(i); } diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index b3aebf3e..2dc22086 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -294,6 +294,8 @@ pub struct PrintCraftApp { pending_theme: Option, styled: bool, fonts_ready: bool, + /// The window title last sent to the platform. + pub window_title: String, /// The UI control channel, when enabled (`--control`; off by default). control: Option, /// A bookmark being renamed in the Bookmarks panel: (path, text so far). @@ -391,6 +393,7 @@ impl PrintCraftApp { attach_override: None, duplicate_draft: None, form_preview: false, + window_title: String::new(), recovery: None, recoverable: Vec::new(), recovery_keys: Default::default(), @@ -976,6 +979,15 @@ impl eframe::App for PrintCraftApp { ctx.request_repaint(); return; } + // The window shows the active document's name (or title, if it asks for that). + let title = self + .active + .and_then(|i| self.session.get(self.views[i].id)) + .map_or_else(|| "PrintCraft".to_owned(), |d| format!("{} — PrintCraft", d.display_name())); + if title != self.window_title { + ctx.send_viewport_cmd(egui::ViewportCommand::Title(title.clone())); + self.window_title = title; + } if self.full_screen && self.active.is_some() { // Full screen: the page, nothing else (Esc or ⌘L to leave). let t = theme::Tokens::get(&ctx); diff --git a/crates/ui-egui/tests/viewer.rs b/crates/ui-egui/tests/viewer.rs index 45e834a5..4187fa42 100644 --- a/crates/ui-egui/tests/viewer.rs +++ b/crates/ui-egui/tests/viewer.rs @@ -279,3 +279,22 @@ fn fit_visible_zooms_to_the_content_width() { let vp = v.viewport_rect(); assert!((ink_left - vp.left() - 16.0).abs() < 12.0, "ink at {ink_left}, viewport {vp:?}"); } + +#[test] +fn tab_and_window_show_the_document_title_when_asked() { + use printcraft_engine::Edit; + let mut h = harness(); + h.run_steps(2); + assert_eq!(h.state().window_title, "b.pdf — PrintCraft"); + { + let s = h.state_mut(); + let id = s.views[s.active.unwrap()].id; + s.session.apply(id, Edit::SetInfo { key: "Title".into(), value: "Quarterly report".into() }).unwrap(); + let mut v = s.session.get(id).unwrap().initial_view(); + v.display_title = true; + s.session.apply(id, Edit::SetInitialView(Box::new(v))).unwrap(); + } + h.run_steps(2); + assert_eq!(h.state().window_title, "Quarterly report — PrintCraft"); + h.get_by_label_contains("Quarterly report"); +} diff --git a/parity/acrobat-features.toml b/parity/acrobat-features.toml index 81a53c66..f7b69448 100644 --- a/parity/acrobat-features.toml +++ b/parity/acrobat-features.toml @@ -8863,11 +8863,11 @@ title = "Set document language" area = "M" tier = "P1" milestone = "M12" -status = "planned" -commands = [] -tools = [] -evidence = [] -notes = "" +status = "shipped" +commands = ["file.properties"] +tools = ["doc_initial_view"] +evidence = ["crates/organize/src/tests.rs::initial_view_round_trips_and_keeps_scripts", "crates/automation/tests/automation.rs::initial_view_through_tools", "crates/ui-egui/tests/viewer.rs::initial_view_is_edited_and_honoured_on_open"] +notes = "Document Properties ▸ Initial View ▸ Language (catalog /Lang)" [[feature]] id = "a11y.set-title-display" @@ -8875,11 +8875,11 @@ title = "Show the document title in the window" area = "M" tier = "P1" milestone = "M12" -status = "planned" -commands = [] -tools = [] -evidence = [] -notes = "" +status = "shipped" +commands = ["file.properties"] +tools = ["doc_initial_view"] +evidence = ["crates/automation/tests/automation.rs::initial_view_through_tools", "crates/ui-egui/tests/viewer.rs::tab_and_window_show_the_document_title_when_asked"] +notes = "Initial View ▸ Show: Document Title (/DisplayDocTitle); the tab and window then show the title" [[feature]] id = "a11y.make-accessible-action"