From 8559306f77418549fff6ffcf0391cfebdb759427 Mon Sep 17 00:00:00 2001 From: eliets2 Date: Tue, 10 Nov 2026 18:48:48 +0200 Subject: [PATCH] Do not send duplex to printers without two-sided support (#6673) On Windows the printer list reports whether a driver supports two-sided printing, but the job still asked for it anyway, so duplex jobs printed single-sided or misaligned on printers such as the reported Brother model. Leave duplex unset for a printer reported as not two-sided, so that printer keeps its own setting, while printers that support two-sided or report no capability keep the chosen setting. Test: duplex_is_not_sent_to_a_printer_without_two_sided failed before the change and passes after. Co-authored-by: eliets2 <287273517+eliets2@users.noreply.github.com> Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com> --- crates/ui-egui/src/workflows/print_ui.rs | 32 +++++++++++++++++++ .../ui-egui/src/workflows/print_ui/draft.rs | 15 +++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/workflows/print_ui.rs b/crates/ui-egui/src/workflows/print_ui.rs index c220a5bd2f..452fa10daa 100644 --- a/crates/ui-egui/src/workflows/print_ui.rs +++ b/crates/ui-egui/src/workflows/print_ui.rs @@ -832,6 +832,38 @@ mod tests { assert!(cut.settings(4, &[]).is_err()); } + /// #3208: long or short edge goes to a printer whose driver doesn't take two-sided settings + /// as no duplex setting at all, so the driver keeps its own instead of refusing the job; Off + /// still reaches it, a printer that can or whose support is unknown still gets the choice, + /// and a stale long-edge choice doesn't block cut and stack. The printers are listed as the + /// dialog lists them (#3554), drivers unasked, with each answer in `two_sided`. + #[test] + fn duplex_is_not_sent_to_a_printer_without_two_sided() { + let draft = |name: &str, duplex| PrintDraft { + printers: ["Office", "Duplexer", "Network"] + .map(|n| spool::Printer { name: n.into(), default: n == "Office", two_sided: None, description: String::new() }) + .to_vec(), + two_sided: [("Office".to_string(), Some(false)), ("Duplexer".to_string(), Some(true))].into_iter().collect(), + printer: Some(name.into()), + duplex, + ..PrintDraft::default() + }; + for duplex in [spool::Duplex::LongEdge, spool::Duplex::ShortEdge] { + assert_eq!(draft("Office", Some(duplex)).job("report.pdf").duplex, None, "{duplex:?} to a printer without two-sided"); + assert_eq!(draft("Duplexer", Some(duplex)).job("report.pdf").duplex, Some(duplex)); + assert_eq!(draft("Network", Some(duplex)).job("report.pdf").duplex, Some(duplex), "unknown support keeps the choice"); + } + assert_eq!(draft("Office", Some(spool::Duplex::Off)).job("report.pdf").duplex, Some(spool::Duplex::Off), "Off still reaches it"); + assert_eq!(draft("Office", None).job("report.pdf").duplex, None); + // A long-edge choice left from another printer doesn't block cut and stack on this one. + let mut cut = draft("Office", Some(spool::Duplex::LongEdge)); + (cut.handling, cut.order) = (Handling::Multiple, PageOrder::CutStack); + assert!(cut.settings(4, &[]).is_ok()); + assert_eq!(cut.job("report.pdf").duplex, Some(spool::Duplex::Off)); + cut.printer = Some("Duplexer".into()); + assert!(cut.settings(4, &[]).is_err(), "still refused where it would print two-sided"); + } + /// #5772: a booklet's both sides ask the printer for the short edge, unless its driver doesn't /// take two-sided settings: it would refuse the job, so its own setting (manual two-sided, say) /// prints it. One side on its own stays one-sided. diff --git a/crates/ui-egui/src/workflows/print_ui/draft.rs b/crates/ui-egui/src/workflows/print_ui/draft.rs index 5b02687b62..e74cd4e069 100644 --- a/crates/ui-egui/src/workflows/print_ui/draft.rs +++ b/crates/ui-egui/src/workflows/print_ui/draft.rs @@ -255,7 +255,7 @@ impl PrintDraft { area: Some(area), }); } - if self.cut_and_stack() && matches!(self.duplex, Some(spool::Duplex::LongEdge | spool::Duplex::ShortEdge)) { + if self.cut_and_stack() && matches!(self.chosen_duplex(), Some(spool::Duplex::LongEdge | spool::Duplex::ShortEdge)) { return Err("Cut and stack needs Two-sided: Off. Print single-sided sheets.".into()); } let range = match self.which { @@ -322,6 +322,17 @@ impl PrintDraft { p.two_sided.or_else(|| self.two_sided.get(&p.name).copied().flatten()) } + /// The Two-sided choice as the chosen printer can take it: long or short edge is left out for + /// a printer whose driver doesn't take two-sided settings from other programs, so its own + /// setting applies instead of the job being refused (#3208). Off and the printer's default + /// stay as chosen: one-sided is accepted by every driver. + fn chosen_duplex(&self) -> Option { + match self.duplex { + Some(spool::Duplex::LongEdge | spool::Duplex::ShortEdge) if self.printer_two_sided() == Some(false) => None, + chosen => chosen, + } + } + /// The spooler job for this print: the dialog's copies, two-sided and colour settings, the /// driver options changed from the printer's defaults, and the chosen paper. pub fn job(&self, title: &str) -> spool::Job { @@ -337,7 +348,7 @@ impl PrintDraft { } else if self.handling == Handling::Booklet { print::booklet_job_duplex(self.booklet_subset, self.printer_two_sided()) } else { - self.duplex + self.chosen_duplex() }, scaling: spool::Scaling::Exact, grayscale: print::driver_needs_grayscale(self.grayscale, self.raster()),