From 25b6280ac6dad2af052930ee53a6d99a9d8a9d2e Mon Sep 17 00:00:00 2001 From: Combiler01 Date: Sat, 7 Nov 2026 10:30:49 +0530 Subject: [PATCH] fix(print): read papers in background, restore properties handler, and pass dialog owner --- crates/print/src/lib.rs | 2 +- crates/print/src/spool.rs | 52 ++++---- crates/ui-egui/src/app.rs | 1 + crates/ui-egui/src/dialogs.rs | 2 +- crates/ui-egui/src/lib.rs | 7 +- crates/ui-egui/src/workflows/print_ui.rs | 122 ++++++++++++++++-- crates/ui-egui/src/workflows/print_ui/body.rs | 21 ++- .../ui-egui/src/workflows/print_ui/draft.rs | 97 +++++++++----- crates/ui-egui/tests/it/print.rs | 30 ++++- 9 files changed, 247 insertions(+), 87 deletions(-) diff --git a/crates/print/src/lib.rs b/crates/print/src/lib.rs index c9514d083a..d4cecebd7a 100644 --- a/crates/print/src/lib.rs +++ b/crates/print/src/lib.rs @@ -43,4 +43,4 @@ pub use settings::{ #[cfg(test)] use sheets::text_direction; pub use sheets::{LABEL_MARGIN, MARGIN, Placement, Sheet, layout, matching_paper, text_reads_up}; -pub use spool::{open_printer_preferences, printer_default_paper, printer_papers, show_printer_properties}; +pub use spool::{open_printer_preferences, printer_default_paper, printer_paper_info, printer_papers, show_printer_properties}; diff --git a/crates/print/src/spool.rs b/crates/print/src/spool.rs index 3b7a0d1b2b..04c0e0df96 100644 --- a/crates/print/src/spool.rs +++ b/crates/print/src/spool.rs @@ -240,34 +240,47 @@ pub fn printer_papers(_printer: &str) -> Vec { Vec::new() } -/// The default paper size configured for `printer` in the printer properties / DEVMODE. -/// Returns `Some((name, (width, height)))` if readable. #[cfg(windows)] -pub fn printer_default_paper(printer: &str) -> Option { - let settings = craft_print::rgb::default_settings(printer).ok()?; - let page = craft_print::rgb::page(printer, Some(&settings)).ok()?; - let w_pts = (page.paper.0 * 100.0).round() / 100.0; - let h_pts = (page.paper.1 * 100.0).round() / 100.0; - let papers = printer_papers(printer); +fn resolve_paper_name(papers: &[Paper], paper_pts: (f64, f64)) -> Paper { + let w_pts = (paper_pts.0 * 100.0).round() / 100.0; + let h_pts = (paper_pts.1 * 100.0).round() / 100.0; let name = papers .iter() .find(|(_, (w, h))| ((*w - w_pts).abs() <= 1.0 && (*h - h_pts).abs() <= 1.0) || ((*w - h_pts).abs() <= 1.0 && (*h - w_pts).abs() <= 1.0)) .map(|(n, _)| n.clone()) .unwrap_or_default(); - Some((name, (w_pts, h_pts))) + (name, (w_pts, h_pts)) } -#[cfg(not(windows))] -pub fn printer_default_paper(_printer: &str) -> Option { - None +/// The default paper size configured for `printer` in the printer properties / DEVMODE. +/// Returns `Some((name, (width, height)))` if readable. +pub fn printer_default_paper(printer: &str) -> Option { + printer_paper_info(printer).1 +} + +/// Reads the supported paper sizes and the default paper size configured for `printer`. +pub fn printer_paper_info(printer: &str) -> (Vec, Option) { + let papers = printer_papers(printer); + #[cfg(windows)] + let default_paper = { + let settings = craft_print::rgb::default_settings(printer).ok(); + let page = settings.as_ref().and_then(|s| craft_print::rgb::page(printer, Some(s)).ok()); + page.map(|p| resolve_paper_name(&papers, p.paper)) + }; + #[cfg(not(windows))] + let default_paper = { + let _ = &papers; + None + }; + (papers, default_paper) } /// Shows the printer driver's Print Settings / DocumentProperties dialog (Windows). /// If the user clicks OK, returns the updated paper size. -pub fn show_printer_properties(printer: &str) -> Result, crate::PrintError> { +pub fn show_printer_properties(printer: &str, parent: Option) -> Result, crate::PrintError> { #[cfg(windows)] { - let devmode = match craft_print::rgb::settings_dialog(printer, None, None) { + let devmode = match craft_print::rgb::settings_dialog(printer, None, parent) { Ok(Some(dm)) => dm, Ok(None) => return Ok(None), Err(_) => { @@ -276,19 +289,12 @@ pub fn show_printer_properties(printer: &str) -> Result, crate::Pr } }; let page = craft_print::rgb::page(printer, Some(&devmode)).map_err(|e| crate::PrintError::Spool(e.to_string()))?; - let w_pts = (page.paper.0 * 100.0).round() / 100.0; - let h_pts = (page.paper.1 * 100.0).round() / 100.0; let papers = printer_papers(printer); - let name = papers - .iter() - .find(|(_, (w, h))| ((*w - w_pts).abs() <= 1.0 && (*h - h_pts).abs() <= 1.0) || ((*w - h_pts).abs() <= 1.0 && (*h - w_pts).abs() <= 1.0)) - .map(|(n, _)| n.clone()) - .unwrap_or_default(); - Ok(Some((name, (w_pts, h_pts)))) + Ok(Some(resolve_paper_name(&papers, page.paper))) } #[cfg(not(windows))] { - let _ = printer; + let _ = (printer, parent); Ok(None) } } diff --git a/crates/ui-egui/src/app.rs b/crates/ui-egui/src/app.rs index 8d5eba980c..1a48b591c0 100644 --- a/crates/ui-egui/src/app.rs +++ b/crates/ui-egui/src/app.rs @@ -114,6 +114,7 @@ impl PdfCraftApp { printer_source: None, options_source: None, default_paper_source: None, + printer_papers_source: None, print_tasks: print_ui::PrintTasks::default(), palette_open: false, palette_query: String::new(), diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index 26daead107..84c36f41aa 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, app.default_paper_source.as_ref(), &mut |p| { + let (go, cancel) = crate::print_ui::body(ui, &mut app.print_draft, &t, &sizes, &labels, &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 440628b889..c867d775c2 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -58,7 +58,10 @@ mod editing; mod home; mod hotbar; mod panels; -pub use print_ui::{DefaultPaperSource, Handling as PrintHandling, OptionsSource, PrintDraft, PrinterSource, Which as PrintWhich, system_printers}; +pub use print_ui::{ + DefaultPaperSource, Handling as PrintHandling, OptionsSource, PrintDraft, PrinterPapersSource, 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; @@ -343,6 +346,8 @@ pub struct PdfCraftApp { 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, + /// Where a printer's supported paper sizes come from: driver capabilities unless a test stands in. + pub printer_papers_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 316a5dfad9..8c3ee36772 100644 --- a/crates/ui-egui/src/workflows/print_ui.rs +++ b/crates/ui-egui/src/workflows/print_ui.rs @@ -37,11 +37,20 @@ pub type OptionsSource = std::sync::Arc Vec Option<(String, (f64, f64))> + Send + Sync>; +/// Where a printer's supported paper sizes come from: the spooler driver capabilities +/// unless a test stands in. +pub type PrinterPapersSource = std::sync::Arc Vec + Send + Sync>; + /// The system spooler's printers, as the desktop app's [`PrinterSource`]. pub fn system_printers() -> PrinterSource { std::sync::Arc::new(spool::printers) } +#[cfg(not(target_arch = "wasm32"))] +type PaperInfo = (Vec, Option); +#[cfg(not(target_arch = "wasm32"))] +type PendingPaperInfo = (String, std::sync::mpsc::Receiver); + /// The Print dialog's work in the background: the printer list being read, and the jobs being /// spooled, oldest first. #[derive(Default)] @@ -56,6 +65,12 @@ pub(crate) struct PrintTasks { /// The driver options being read, per printer (`read_options_in_background`). #[cfg(not(target_arch = "wasm32"))] options: Vec<(String, std::sync::mpsc::Receiver>)>, + /// The papers and default paper being read in the background, per printer (`read_papers_in_background`). + #[cfg(not(target_arch = "wasm32"))] + papers: Vec, + /// Cached papers and default paper per printer so switching printers avoids re-querying. + #[cfg(not(target_arch = "wasm32"))] + papers_cache: std::collections::HashMap, /// The browser print being drawn, a sheet a frame (`web_print`). #[cfg(target_arch = "wasm32")] web_job: Option, @@ -168,24 +183,20 @@ impl PdfCraftApp { printers_loading, printer_picked: picked, units: self.units, - printer, + printer: printer.clone(), current_page: current, sheet: 0, selected, which, ..keep }; - self.print_draft.update_printer_papers(); - if !self.print_draft.paper_overridden { - self.auto_select_default_paper(); + #[cfg(not(target_arch = "wasm32"))] + if let Some(p) = printer { + self.read_papers_in_background(p); } 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. @@ -286,12 +297,93 @@ impl PdfCraftApp { } } + /// Read `printer`'s papers and default paper off the GUI thread (#5861); `poll_print` picks them up. + /// A read already under way for the same printer is left to finish. + #[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; + } + if self.print_tasks.papers.iter().any(|(p, _)| *p == printer) { + return; + } + self.print_draft.papers_loading = Some(printer.clone()); + let papers_source = self.printer_papers_source.clone(); + let default_source = self.default_paper_source.clone(); + let (tx, rx) = std::sync::mpsc::channel(); + let wake = WakeOnExit(self.ctx.clone()); + let name = printer.clone(); + let spawned = std::thread::Builder::new().name("printer papers".into()).spawn(move || { + let _wake = wake; + let tx = tx; + let info = match (&papers_source, &default_source) { + (None, None) => spool::printer_paper_info(&name), + (papers_s, def_s) => { + let papers = match papers_s { + Some(s) => s(&name), + None => spool::printer_papers(&name), + }; + let def = match def_s { + Some(s) => s(&name), + None => spool::printer_default_paper(&name), + }; + (papers, def) + } + }; + let _ = tx.send(info); + }); + match spawned { + Ok(_) => self.print_tasks.papers.push((printer, rx)), + Err(_) => { + let d = &mut self.print_draft; + if d.papers_loading.as_deref() == Some(printer.as_str()) { + d.papers_loading = None; + } + } + } + } + + /// 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())); + 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 { + d.apply_default_paper(default_paper); + } + } + } + /// Pick up a printer list, driver options or a spooled job that finished (each frame). pub(crate) fn poll_print(&mut self) { #[cfg(target_arch = "wasm32")] self.poll_web_print(); #[cfg(not(target_arch = "wasm32"))] { + if let Some(printer) = self.print_draft.read_papers_for.take() { + self.read_papers_in_background(printer); + } + let mut arrived_papers = Vec::new(); + self.print_tasks.papers.retain(|(printer, rx)| match rx.try_recv() { + Ok((papers, def)) => { + arrived_papers.push((printer.clone(), papers, def)); + false + } + Err(std::sync::mpsc::TryRecvError::Empty) => true, + Err(std::sync::mpsc::TryRecvError::Disconnected) => { + arrived_papers.push((printer.clone(), Vec::new(), None)); + false + } + }); + for (printer, papers, def) in arrived_papers { + self.receive_papers(printer, papers, def); + } if let Some(printer) = self.print_draft.read_options_for.take() { self.read_options_in_background(printer); } @@ -322,16 +414,18 @@ 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. - if !d.printer_picked || !offered(d.printer.as_deref(), &printers) { + let changed = if !d.printer_picked || !offered(d.printer.as_deref(), &printers) { d.printer_picked = false; d.printer = default_printer(&printers); - d.update_printer_papers(); - if !d.paper_overridden { - d.auto_select_default_paper_with(self.default_paper_source.as_ref()); - } - } + true + } else { + false + }; d.printers = printers; d.printers_loading = false; + if changed && let Some(p) = d.printer.clone() { + self.read_papers_in_background(p); + } } } let mut finished = Vec::new(); diff --git a/crates/ui-egui/src/workflows/print_ui/body.rs b/crates/ui-egui/src/workflows/print_ui/body.rs index acde60f898..1e83a6df4f 100644 --- a/crates/ui-egui/src/workflows/print_ui/body.rs +++ b/crates/ui-egui/src/workflows/print_ui/body.rs @@ -87,7 +87,6 @@ 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); @@ -142,14 +141,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); - } - Ok(None) => {} - Err(e) => d.driver_error = Some(e.to_string()), - } + d.driver_error = spool::open_printer_preferences(&p).err().map(|e| e.to_string()); } else { if d.driver_options.as_ref().is_none_or(|(read_for, _)| *read_for != p) && d.driver_options_loading.as_deref() != Some(p.as_str()) { // Read off the GUI thread (#1818): `poll_print` starts it next frame. @@ -166,9 +158,8 @@ pub(crate) fn body( d.printer_picked = true; d.show_driver_options = false; d.driver_error = None; - d.update_printer_papers(); - if !d.paper_overridden { - d.auto_select_default_paper_with(default_paper_source); + if let Some(p) = d.printer.clone() { + d.read_papers_for = Some(p); } } ui.end_row(); @@ -239,7 +230,11 @@ pub(crate) fn body( && let Some(p) = d.printer.clone() { d.driver_error = None; - match spool::show_printer_properties(&p) { + #[cfg(windows)] + let parent = winsafe::HWND::GetActiveWindow().map(|h| h.ptr() as isize); + #[cfg(not(windows))] + let parent = None; + match spool::show_printer_properties(&p, parent) { 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 6d45041bf4..f83c2b3b03 100644 --- a/crates/ui-egui/src/workflows/print_ui/draft.rs +++ b/crates/ui-egui/src/workflows/print_ui/draft.rs @@ -87,6 +87,10 @@ pub struct PrintDraft { pub driver_options_loading: Option, /// Properties… asked for this printer's options; the app starts the read on the next frame. pub read_options_for: Option, + /// The printer whose papers and default paper are being read in the background. + pub papers_loading: Option, + /// A printer change requested reading papers; the app starts the read on the next frame. + pub read_papers_for: Option, } impl Default for PrintDraft { @@ -137,6 +141,8 @@ impl Default for PrintDraft { crop_pages: None, driver_options_loading: None, read_options_for: None, + papers_loading: None, + read_papers_for: None, } } } @@ -168,21 +174,33 @@ impl PrintDraft { /// Refresh `self.papers` with driver-reported media sizes, preserving the current selection. pub fn update_printer_papers(&mut self) { + self.update_printer_papers_with(None); + } + + /// Refresh `self.papers` with driver-reported media sizes from an optional source or the driver. + pub fn update_printer_papers_with(&mut self, source: Option<&super::PrinterPapersSource>) { + let detected = if let Some(ref printer) = self.printer { + if let Some(source) = source { source(printer) } else { spool::printer_papers(printer) } + } else { + Vec::new() + }; + self.apply_printer_papers(detected); + } + + /// Apply driver-reported media sizes to `self.papers`, preserving the current selection. + pub fn apply_printer_papers(&mut self, detected: Vec<(String, (f64, f64))>) { let before_label = self.paper_before_label.and_then(|idx| self.papers.get(idx).cloned()); let mut papers = default_papers(); - if let Some(ref printer) = self.printer { - let detected = spool::printer_papers(printer); - for (name, size) in detected { - if !(size.0 >= 72.0 && size.1 >= 72.0 && size.0.is_finite() && size.1.is_finite()) { - continue; - } - let norm = (size.0.min(size.1), size.0.max(size.1)); - if !papers.iter().any(|(_, s)| { - let s_norm = (s.0.min(s.1), s.0.max(s.1)); - (s_norm.0 - norm.0).abs() <= 0.5 && (s_norm.1 - norm.1).abs() <= 0.5 - }) { - papers.push((name, size)); - } + for (name, size) in detected { + if !(size.0 >= 72.0 && size.1 >= 72.0 && size.0.is_finite() && size.1.is_finite()) { + continue; + } + let norm = (size.0.min(size.1), size.0.max(size.1)); + if !papers.iter().any(|(_, s)| { + let s_norm = (s.0.min(s.1), s.0.max(s.1)); + (s_norm.0 - norm.0).abs() <= 0.5 && (s_norm.1 - norm.1).abs() <= 0.5 + }) { + papers.push((name, size)); } } let current_name = self.paper_name().to_string(); @@ -217,27 +235,34 @@ impl PrintDraft { /// 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; + self.apply_default_paper(detected); + } + } + + /// Apply the detected default paper. + pub fn apply_default_paper(&mut self, detected: Option<(String, (f64, f64))>) { + if let Some((name, size)) = detected + && let Some(pos) = self.find_or_add_paper(name, size) + { + if self.handling == Handling::Label { + if !self.paper_overridden { + self.paper_before_label = Some(pos); + } + } else { + self.paper = pos; } } } - /// Apply paper size and orientation chosen in printer properties or page setup. - pub fn apply_driver_paper(&mut self, name: String, size: (f64, f64)) { + /// Locate `(name, size)` in `self.papers`, or append it if missing. Returns the paper index. + pub fn find_or_add_paper(&mut self, name: String, size: (f64, f64)) -> Option { if !(size.0 >= 72.0 && size.1 >= 72.0 && size.0.is_finite() && size.1.is_finite()) { - return; + return None; } - self.paper_overridden = true; if let Some(pos) = self.matching_paper(size) { - self.paper = pos; + Some(pos) } else if !name.is_empty() { let norm = (size.0.min(size.1), size.0.max(size.1)); if let Some(pos) = self.papers.iter().position(|(n, s)| { @@ -246,20 +271,28 @@ impl PrintDraft { (s_norm.0 - norm.0).abs() <= 1.0 && (s_norm.1 - norm.1).abs() <= 1.0 } }) { - self.paper = pos; + Some(pos) } else { self.papers.push((name, size)); - self.paper = self.papers.len() - 1; + Some(self.papers.len() - 1) } } else { let custom_name = format!("{:.1} x {:.1} pt", size.0, size.1); self.papers.push((custom_name, size)); - self.paper = self.papers.len() - 1; + Some(self.papers.len() - 1) } - if size.0 > size.1 { - self.orientation = Orientation::Landscape; - } else if size.1 > size.0 { - self.orientation = Orientation::Portrait; + } + + /// Apply paper size and orientation chosen in printer properties or page setup. + pub fn apply_driver_paper(&mut self, name: String, size: (f64, f64)) { + if let Some(pos) = self.find_or_add_paper(name, size) { + self.paper_overridden = true; + self.paper = pos; + if size.0 > size.1 { + self.orientation = Orientation::Landscape; + } else if size.1 > size.0 { + self.orientation = Orientation::Portrait; + } } } diff --git a/crates/ui-egui/tests/it/print.rs b/crates/ui-egui/tests/it/print.rs index 62342c55dc..b23b9b7f9b 100644 --- a/crates/ui-egui/tests/it/print.rs +++ b/crates/ui-egui/tests/it/print.rs @@ -812,10 +812,17 @@ fn paper_choice_survives_cancel_and_settings_restore() { 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().printer_papers_source = Some(Arc::new(|_| vec![("A4".into(), (595.28, 841.89))])); 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); + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10); + while (default_h.state().print_draft.printers_loading || default_h.state().print_draft.papers_loading.is_some()) + && std::time::Instant::now() < deadline + { + default_h.run_steps(1); + std::thread::sleep(std::time::Duration::from_millis(5)); + } 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)); @@ -894,10 +901,15 @@ fn legacy_settings_restore_unsets_override_and_open_selects_printer_default() { 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().printer_papers_source = Some(Arc::new(|_| vec![("A4".into(), (595.28, 841.89))])); 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); + 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!(h.state().print_draft.paper, a4); assert!(!h.state().print_draft.paper_overridden); } @@ -910,9 +922,23 @@ fn auto_select_default_paper_leaves_label_stock_unchanged() { paper: pdfcraft_engine::print::LABEL_PAPER, ..Default::default() }; + let a4 = pdfcraft_engine::print::PAPERS.iter().position(|(name, _)| *name == "A4").unwrap(); 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); + assert_eq!(draft.paper_before_label, Some(a4)); +} + +#[test] +fn auto_select_default_paper_preserves_user_orientation() { + let mut draft = pdfcraft_ui_egui::PrintDraft { + printer: Some("Office Printer".into()), + orientation: pdfcraft_engine::print::Orientation::Landscape, + ..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.orientation, pdfcraft_engine::print::Orientation::Landscape); } #[test]