From f1e2c49d55bfef05ea581627eb96369b8479eb4a Mon Sep 17 00:00:00 2001 From: Combiler01 Date: Sat, 7 Nov 2026 11:20:24 +0530 Subject: [PATCH] fix(print): apply cached papers immediately, refresh in background, and document functions --- crates/print/src/spool.rs | 4 ++ crates/ui-egui/src/app/prefs.rs | 1 + crates/ui-egui/src/workflows/print_ui.rs | 31 ++++++++++---- .../ui-egui/src/workflows/print_ui/draft.rs | 1 + crates/ui-egui/tests/it/print.rs | 41 +++++++++++++++++++ 5 files changed, 69 insertions(+), 9 deletions(-) diff --git a/crates/print/src/spool.rs b/crates/print/src/spool.rs index 04c0e0df96..3689a317a9 100644 --- a/crates/print/src/spool.rs +++ b/crates/print/src/spool.rs @@ -191,6 +191,7 @@ fn spooled(job: &Job) -> Job { } } +/// Submit a PDF to the system print spooler for the configured `job`. pub fn submit(pdf: &[u8], job: &Job) -> Result { craft_print::submit(pdf, &spooled(job)).map_err(|e| crate::PrintError::Spool(e.to_string())) } @@ -201,6 +202,7 @@ pub fn submit_with_progress(pdf: &[u8], job: &Job, progress: &mut dyn FnMut(usiz craft_print::submit_with_progress(pdf, &spooled(job), progress).map_err(|e| crate::PrintError::Spool(e.to_string())) } +/// Opens the printer driver's native Printing Preferences window (Windows `printui.dll`). pub fn open_printer_preferences(printer: &str) -> Result<(), crate::PrintError> { craft_print::open_printer_preferences(printer).map_err(|e| crate::PrintError::Spool(e.to_string())) } @@ -235,11 +237,13 @@ pub fn printer_papers(printer: &str) -> Vec { papers } +/// The paper sizes supported by `printer` (fallback for non-Windows platforms). #[cfg(not(windows))] pub fn printer_papers(_printer: &str) -> Vec { Vec::new() } +/// Match `paper_pts` against detected driver `papers` within a 1.0 pt tolerance. #[cfg(windows)] fn resolve_paper_name(papers: &[Paper], paper_pts: (f64, f64)) -> Paper { let w_pts = (paper_pts.0 * 100.0).round() / 100.0; diff --git a/crates/ui-egui/src/app/prefs.rs b/crates/ui-egui/src/app/prefs.rs index fc31f2c6e5..a237cddf8e 100644 --- a/crates/ui-egui/src/app/prefs.rs +++ b/crates/ui-egui/src/app/prefs.rs @@ -36,6 +36,7 @@ fn rgb_triple(v: &serde_json::Value) -> Option<[f64; 3]> { } impl PdfCraftApp { + /// Sets the user's interface theme preference (Light, Dark, or System). pub fn set_theme_preference(&mut self, preference: ThemePreference) { self.theme_preference = preference; if let Some(ctx) = self.ctx.clone() { diff --git a/crates/ui-egui/src/workflows/print_ui.rs b/crates/ui-egui/src/workflows/print_ui.rs index 8c3ee36772..c2e8f79d4f 100644 --- a/crates/ui-egui/src/workflows/print_ui.rs +++ b/crates/ui-egui/src/workflows/print_ui.rs @@ -151,6 +151,7 @@ impl Drop for WakeOnExit { } impl PdfCraftApp { + /// Open the Print dialog, preselecting the default printer and reading papers in the background. pub fn open_print(&mut self) { let Some((i, _)) = self.active_ids() else { return }; let (printers, printers_loading) = match self.printer_source.clone() { @@ -241,6 +242,7 @@ impl PdfCraftApp { self.print_draft.crop_pages = Some(pages); } + /// Read the printer list in the background off the GUI thread; `poll_print` picks it up. #[cfg(not(target_arch = "wasm32"))] fn list_printers_in_background(&mut self, source: PrinterSource) { // A list already being read is as fresh as a new one would be. @@ -302,8 +304,13 @@ impl PdfCraftApp { #[cfg(not(target_arch = "wasm32"))] fn read_papers_in_background(&mut self, printer: String) { if let Some((papers, default_paper)) = self.print_tasks.papers_cache.get(&printer).cloned() { - self.receive_papers(printer, papers, default_paper); - return; + let d = &mut self.print_draft; + if d.printer.as_deref() == Some(printer.as_str()) { + d.apply_printer_papers(papers); + if !d.paper_overridden { + d.apply_default_paper(default_paper); + } + } } if self.print_tasks.papers.iter().any(|(p, _)| *p == printer) { return; @@ -347,14 +354,18 @@ impl PdfCraftApp { /// The papers and default paper read for `printer` arrived, or the read died. #[cfg(not(target_arch = "wasm32"))] fn receive_papers(&mut self, printer: String, papers: Vec, default_paper: Option) { - self.print_tasks.papers_cache.insert(printer.clone(), (papers.clone(), default_paper.clone())); + if !papers.is_empty() || default_paper.is_some() { + self.print_tasks.papers_cache.insert(printer.clone(), (papers.clone(), default_paper.clone())); + } let d = &mut self.print_draft; if d.papers_loading.as_deref() == Some(printer.as_str()) { d.papers_loading = None; } if d.printer.as_deref() == Some(printer.as_str()) { - d.apply_printer_papers(papers); - if !d.paper_overridden { + if !papers.is_empty() { + d.apply_printer_papers(papers); + } + if !d.paper_overridden && default_paper.is_some() { d.apply_default_paper(default_paper); } } @@ -414,13 +425,14 @@ impl PdfCraftApp { self.print_tasks.printers = None; let d = &mut self.print_draft; // A picked printer that is no longer listed falls back to the default too. - let changed = if !d.printer_picked || !offered(d.printer.as_deref(), &printers) { + let new_printer = if !d.printer_picked || !offered(d.printer.as_deref(), &printers) { d.printer_picked = false; - d.printer = default_printer(&printers); - true + default_printer(&printers) } else { - false + d.printer.clone() }; + let changed = d.printer != new_printer; + d.printer = new_printer; d.printers = printers; d.printers_loading = false; if changed && let Some(p) = d.printer.clone() { @@ -486,6 +498,7 @@ impl PdfCraftApp { } } + /// Report the outcome of a finished print job to the user. fn report_print_job(&mut self, printer: &str, result: Result) { match result { Ok(msg) if msg.is_empty() => self.notify(crate::i18n::fmt(tl!("Sent to {printer}"), &[("printer", printer)])), diff --git a/crates/ui-egui/src/workflows/print_ui/draft.rs b/crates/ui-egui/src/workflows/print_ui/draft.rs index f83c2b3b03..923be2649d 100644 --- a/crates/ui-egui/src/workflows/print_ui/draft.rs +++ b/crates/ui-egui/src/workflows/print_ui/draft.rs @@ -361,6 +361,7 @@ impl PrintDraft { }) } + /// Whether multiple pages per sheet should be cut and stacked into sorted piles. fn cut_and_stack(&self) -> bool { self.handling == Handling::Multiple && self.order == PageOrder::CutStack } diff --git a/crates/ui-egui/tests/it/print.rs b/crates/ui-egui/tests/it/print.rs index b23b9b7f9b..5298e478d1 100644 --- a/crates/ui-egui/tests/it/print.rs +++ b/crates/ui-egui/tests/it/print.rs @@ -941,6 +941,47 @@ fn auto_select_default_paper_preserves_user_orientation() { assert_eq!(draft.orientation, pdfcraft_engine::print::Orientation::Landscape); } +#[test] +fn cached_papers_apply_immediately_and_refresh_in_background() { + use pdfcraft_engine::print::spool; + use std::sync::Arc; + use std::sync::atomic::{AtomicUsize, Ordering}; + + let calls = Arc::new(AtomicUsize::new(0)); + let calls_clone = calls.clone(); + + let mut h = harness(); + h.state_mut().printer_source = + Some(Arc::new(|| vec![spool::Printer { name: "Cached Printer".into(), default: true, two_sided: None, description: String::new() }])); + h.state_mut().printer_papers_source = Some(Arc::new(move |_| { + calls_clone.fetch_add(1, Ordering::SeqCst); + vec![("A4".into(), (595.28, 841.89))] + })); + h.state_mut().default_paper_source = + Some(Arc::new(|printer: &str| if printer == "Cached Printer" { Some(("A4".into(), (595.28, 841.89))) } else { None })); + + assert!(h.state_mut().execute("print.dialog")); + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10); + while (h.state().print_draft.printers_loading || h.state().print_draft.papers_loading.is_some()) && std::time::Instant::now() < deadline { + h.run_steps(1); + std::thread::sleep(std::time::Duration::from_millis(5)); + } + assert_eq!(calls.load(Ordering::SeqCst), 1); + let a4 = h.state().print_draft.paper; + + h.state_mut().dialog = None; + + assert!(h.state_mut().execute("print.dialog")); + assert_eq!(h.state().print_draft.paper, a4); + + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10); + while h.state().print_draft.papers_loading.is_some() && std::time::Instant::now() < deadline { + h.run_steps(1); + std::thread::sleep(std::time::Duration::from_millis(5)); + } + assert_eq!(calls.load(Ordering::SeqCst), 2); +} + #[test] fn restored_paper_keeps_preview_and_saved_sheet_dimensions() { use pdfcraft_engine::print::{self, PAPERS};