From 04fa9910ca631575a23e54750e718fed29a8c6bf Mon Sep 17 00:00:00 2001 From: Combiler01 Date: Wed, 4 Nov 2026 14:18:52 +0530 Subject: [PATCH] fix(print): support default-paper injection, clear driver error on retry, and fix legacy settings restore --- crates/ui-egui/src/app.rs | 1 + crates/ui-egui/src/app/prefs.rs | 4 +- crates/ui-egui/src/dialogs.rs | 2 +- crates/ui-egui/src/lib.rs | 4 +- crates/ui-egui/src/workflows/print_ui.rs | 12 ++++- crates/ui-egui/src/workflows/print_ui/body.rs | 5 +- .../ui-egui/src/workflows/print_ui/draft.rs | 21 +++++--- crates/ui-egui/tests/it/print.rs | 54 ++++++++++++++++++- 8 files changed, 89 insertions(+), 14 deletions(-) diff --git a/crates/ui-egui/src/app.rs b/crates/ui-egui/src/app.rs index 6ac8ea8783..8d5eba980c 100644 --- a/crates/ui-egui/src/app.rs +++ b/crates/ui-egui/src/app.rs @@ -113,6 +113,7 @@ impl PdfCraftApp { updates: updates::Updates::default(), printer_source: None, options_source: None, + default_paper_source: None, print_tasks: print_ui::PrintTasks::default(), palette_open: false, palette_query: String::new(), diff --git a/crates/ui-egui/src/app/prefs.rs b/crates/ui-egui/src/app/prefs.rs index 6d26b665b5..fc31f2c6e5 100644 --- a/crates/ui-egui/src/app/prefs.rs +++ b/crates/ui-egui/src/app/prefs.rs @@ -293,11 +293,11 @@ impl PdfCraftApp { }); if let Some(paper) = matched_pos { self.print_draft.paper = paper; - self.print_draft.paper_overridden = v.get("print_paper_overridden").and_then(serde_json::Value::as_bool).unwrap_or(true); + self.print_draft.paper_overridden = v.get("print_paper_overridden").and_then(serde_json::Value::as_bool).unwrap_or(false); } else if let Some(size) = saved_size { self.print_draft.papers.push((name.to_string(), size)); self.print_draft.paper = self.print_draft.papers.len() - 1; - self.print_draft.paper_overridden = v.get("print_paper_overridden").and_then(serde_json::Value::as_bool).unwrap_or(true); + self.print_draft.paper_overridden = v.get("print_paper_overridden").and_then(serde_json::Value::as_bool).unwrap_or(false); } } if let Some(on) = v["highlight_fields"].as_bool() { diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index 84c36f41aa..26daead107 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -1325,7 +1325,7 @@ fn dialog_print(ui: &mut egui::Ui, app: &mut PdfCraftApp, req: &mut Pending) -> let view = &mut app.views[i]; view.set_print_grayscale(app.print_draft.grayscale); view.queue_print_previews(&rasters); - let (go, cancel) = crate::print_ui::body(ui, &mut app.print_draft, &t, &sizes, &labels, &mut |p| { + let (go, cancel) = crate::print_ui::body(ui, &mut app.print_draft, &t, &sizes, &labels, app.default_paper_source.as_ref(), &mut |p| { view.need_thumbnail(p, true); view.page_preview(p) }); diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 643d5e90ff..440628b889 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -58,7 +58,7 @@ mod editing; mod home; mod hotbar; mod panels; -pub use print_ui::{Handling as PrintHandling, OptionsSource, PrintDraft, PrinterSource, Which as PrintWhich, system_printers}; +pub use print_ui::{DefaultPaperSource, Handling as PrintHandling, OptionsSource, PrintDraft, PrinterSource, Which as PrintWhich, system_printers}; pub use redact_ui::{HiddenDraft, PagesDraft as RedactPagesDraft, RedactPrefs, SearchDraft as RedactSearchDraft, SearchMode as RedactSearchMode}; pub mod i18n; pub mod theme; @@ -341,6 +341,8 @@ pub struct PdfCraftApp { pub printer_source: Option, /// Where Properties… reads a printer's driver options: the spooler's unless a test stands in. pub options_source: Option, + /// Where a printer's configured default paper comes from: DEVMODE / driver settings unless a test stands in. + pub default_paper_source: Option, // A browser has no background print work: the type is empty there. #[cfg_attr(target_arch = "wasm32", allow(dead_code))] pub(crate) print_tasks: print_ui::PrintTasks, diff --git a/crates/ui-egui/src/workflows/print_ui.rs b/crates/ui-egui/src/workflows/print_ui.rs index c9952428be..316a5dfad9 100644 --- a/crates/ui-egui/src/workflows/print_ui.rs +++ b/crates/ui-egui/src/workflows/print_ui.rs @@ -33,6 +33,10 @@ pub type PrinterSource = std::sync::Arc Vec + Send + /// UI language) unless a test stands in. Called off the GUI thread. pub type OptionsSource = std::sync::Arc Vec + Send + Sync>; +/// Where a printer's configured default paper comes from: the spooler's DEVMODE / driver settings +/// unless a test stands in. +pub type DefaultPaperSource = std::sync::Arc Option<(String, (f64, f64))> + Send + Sync>; + /// The system spooler's printers, as the desktop app's [`PrinterSource`]. pub fn system_printers() -> PrinterSource { std::sync::Arc::new(spool::printers) @@ -173,11 +177,15 @@ impl PdfCraftApp { }; self.print_draft.update_printer_papers(); if !self.print_draft.paper_overridden { - self.print_draft.auto_select_default_paper(); + self.auto_select_default_paper(); } self.dialog = Some(crate::Dialog::Print); } + pub(crate) fn auto_select_default_paper(&mut self) { + self.print_draft.auto_select_default_paper_with(self.default_paper_source.as_ref()); + } + /// Label printing: find each printed page's printed area (a quick low-resolution render, as /// Fit Visible does) and which way its text runs. Only the pages being printed are read, at /// most [`LABEL_CROP_PAGES`] of them (the rest print whole); read again when they change. @@ -319,7 +327,7 @@ impl PdfCraftApp { d.printer = default_printer(&printers); d.update_printer_papers(); if !d.paper_overridden { - d.auto_select_default_paper(); + d.auto_select_default_paper_with(self.default_paper_source.as_ref()); } } d.printers = printers; diff --git a/crates/ui-egui/src/workflows/print_ui/body.rs b/crates/ui-egui/src/workflows/print_ui/body.rs index dcf90a1fb5..acde60f898 100644 --- a/crates/ui-egui/src/workflows/print_ui/body.rs +++ b/crates/ui-egui/src/workflows/print_ui/body.rs @@ -87,6 +87,7 @@ pub(crate) fn body( t: &Tokens, sizes: &[(f64, f64)], labels: &[String], + default_paper_source: Option<&super::DefaultPaperSource>, thumb: &mut dyn FnMut(usize) -> Option, ) -> (bool, bool) { ui.set_width(820.0); @@ -141,6 +142,7 @@ pub(crate) fn body( && let Some(p) = d.printer.clone() { if spool::HAS_PRINTER_PREFERENCES { + d.driver_error = None; match spool::show_printer_properties(&p) { Ok(Some((name, size))) => { d.apply_driver_paper(name, size); @@ -166,7 +168,7 @@ pub(crate) fn body( d.driver_error = None; d.update_printer_papers(); if !d.paper_overridden { - d.auto_select_default_paper(); + d.auto_select_default_paper_with(default_paper_source); } } ui.end_row(); @@ -236,6 +238,7 @@ pub(crate) fn body( if page_setup.clicked() && let Some(p) = d.printer.clone() { + d.driver_error = None; match spool::show_printer_properties(&p) { Ok(Some((name, size))) => { d.apply_driver_paper(name, size); diff --git a/crates/ui-egui/src/workflows/print_ui/draft.rs b/crates/ui-egui/src/workflows/print_ui/draft.rs index bafe5dc3a6..6d45041bf4 100644 --- a/crates/ui-egui/src/workflows/print_ui/draft.rs +++ b/crates/ui-egui/src/workflows/print_ui/draft.rs @@ -212,12 +212,21 @@ impl PrintDraft { /// Auto-select the printer's configured default paper (from DEVMODE / driver settings). pub fn auto_select_default_paper(&mut self) { - if let Some(ref printer) = self.printer - && let Some((name, size)) = spool::printer_default_paper(printer) - { - let overridden = self.paper_overridden; - self.apply_driver_paper(name, size); - self.paper_overridden = overridden; + self.auto_select_default_paper_with(None); + } + + /// Auto-select the printer's default paper using an optional source (or DEVMODE / driver settings). + pub fn auto_select_default_paper_with(&mut self, source: Option<&super::DefaultPaperSource>) { + if self.handling == Handling::Label { + return; + } + if let Some(ref printer) = self.printer { + let detected = if let Some(source) = source { source(printer) } else { spool::printer_default_paper(printer) }; + if let Some((name, size)) = detected { + let overridden = self.paper_overridden; + self.apply_driver_paper(name, size); + self.paper_overridden = overridden; + } } } diff --git a/crates/ui-egui/tests/it/print.rs b/crates/ui-egui/tests/it/print.rs index 04a7d67b52..62342c55dc 100644 --- a/crates/ui-egui/tests/it/print.rs +++ b/crates/ui-egui/tests/it/print.rs @@ -805,6 +805,19 @@ fn paper_choice_survives_cancel_and_settings_restore() { let mut restored_default = PdfCraftApp::new(); restored_default.restore(&default_saved); assert!(!restored_default.print_draft.paper_overridden); + + // Opening print with a printer default paper selects that paper when paper_overridden is false. + let mut default_h = harness(); + use pdfcraft_engine::print::spool; + use std::sync::Arc; + default_h.state_mut().printer_source = + Some(Arc::new(|| vec![spool::Printer { name: "Office Printer".into(), default: true, two_sided: None, description: String::new() }])); + default_h.state_mut().default_paper_source = + Some(Arc::new(|printer: &str| if printer == "Office Printer" { Some(("A4".into(), (595.28, 841.89))) } else { None })); + assert!(default_h.state_mut().execute("print.dialog")); + default_h.run_steps(3); + assert_eq!(default_h.state().print_draft.paper, a4); + assert!(!default_h.state().print_draft.paper_overridden); let mut restarted = harness_with_settings(Some(saved)); assert!(restarted.state_mut().execute("print.dialog")); restarted.run_steps(3); @@ -832,6 +845,7 @@ fn paper_restore_accepts_canonical_names_and_ignores_invalid_settings() { let mut app = PdfCraftApp::new(); app.restore(&serde_json::json!({"print_paper": name}).to_string()); assert_eq!(app.print_draft.paper, index); + assert!(!app.print_draft.paper_overridden); assert_eq!(serde_json::from_str::(&app.persist()).unwrap()["print_paper"], *name); } for invalid in [ @@ -863,11 +877,49 @@ fn paper_restore_accepts_canonical_names_and_ignores_invalid_settings() { assert_eq!(serde_json::from_str::(&app.persist()).unwrap()["print_paper"], serde_json::Value::Null); } +#[test] +fn legacy_settings_restore_unsets_override_and_open_selects_printer_default() { + use pdfcraft_engine::print::{PAPERS, spool}; + use std::sync::Arc; + let letter = PAPERS.iter().position(|(name, _)| *name == "US Letter").unwrap(); + let a4 = PAPERS.iter().position(|(name, _)| *name == "A4").unwrap(); + let mut h = harness(); + let legacy = serde_json::json!({ + "print_paper": "US Letter", + }) + .to_string(); + h.state_mut().restore(&legacy); + assert_eq!(h.state().print_draft.paper, letter); + assert!(!h.state().print_draft.paper_overridden, "legacy settings restore leaves paper_overridden as false"); + + h.state_mut().printer_source = + Some(Arc::new(|| vec![spool::Printer { name: "Office Printer".into(), default: true, two_sided: None, description: String::new() }])); + h.state_mut().default_paper_source = + Some(Arc::new(|printer: &str| if printer == "Office Printer" { Some(("A4".into(), (595.28, 841.89))) } else { None })); + assert!(h.state_mut().execute("print.dialog")); + h.run_steps(3); + assert_eq!(h.state().print_draft.paper, a4); + assert!(!h.state().print_draft.paper_overridden); +} + +#[test] +fn auto_select_default_paper_leaves_label_stock_unchanged() { + let mut draft = pdfcraft_ui_egui::PrintDraft { + printer: Some("Office Printer".into()), + handling: pdfcraft_ui_egui::PrintHandling::Label, + paper: pdfcraft_engine::print::LABEL_PAPER, + ..Default::default() + }; + let source: pdfcraft_ui_egui::DefaultPaperSource = std::sync::Arc::new(move |_| Some(("A4".into(), (595.28, 841.89)))); + draft.auto_select_default_paper_with(Some(&source)); + assert_eq!(draft.paper, pdfcraft_engine::print::LABEL_PAPER); +} + #[test] fn restored_paper_keeps_preview_and_saved_sheet_dimensions() { use pdfcraft_engine::print::{self, PAPERS}; let a4 = PAPERS.iter().find(|(name, _)| *name == "A4").unwrap().1; - let mut h = harness_with_settings(Some(r#"{"print_paper":"A4"}"#.into())); + let mut h = harness_with_settings(Some(r#"{"print_paper":"A4","print_paper_overridden":true}"#.into())); assert!(h.state_mut().execute("print.dialog")); h.run_steps(3); h.get_by_value("A4");