From acae52111531ceb7093502c5212dc92c277bbb5d Mon Sep 17 00:00:00 2001 From: Combiler01 Date: Mon, 9 Nov 2026 19:33:31 +0530 Subject: [PATCH] fix(print): robust custom dimension parsing, spool media option fallback, driver paper bounds and name resolution --- crates/automation/src/printing.rs | 15 +++++++++++---- crates/print/src/settings.rs | 7 +++++-- crates/print/src/spool.rs | 3 ++- crates/ui-egui/src/workflows/print_ui/draft.rs | 8 ++++++-- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/crates/automation/src/printing.rs b/crates/automation/src/printing.rs index ef36b5d3a0..6e3d528113 100644 --- a/crates/automation/src/printing.rs +++ b/crates/automation/src/printing.rs @@ -37,12 +37,16 @@ fn bad(m: impl Into) -> ToolError { ToolError::InvalidArgs(m.into()) } -/// Parse explicit custom dimensions like "300x500", "300 x 500", "300x500 pt", "Custom.300x500". +/// Parse explicit custom dimensions like "300x500", "300 x 500", "300x500 pt", "300pt x 500pt", "Custom.300x500". fn parse_custom_dimensions(s: &str) -> Option<(f64, f64)> { - let s = s.trim().trim_start_matches("Custom.").trim_start_matches("custom.").trim_end_matches("pt").trim_end_matches("points").trim(); + let s = s.trim().trim_start_matches("Custom.").trim_start_matches("custom.").trim(); let (w_str, h_str) = s.split_once(['x', 'X', '*'])?; - let w: f64 = w_str.trim().parse().ok()?; - let h: f64 = h_str.trim().parse().ok()?; + let parse_dim = |d: &str| { + let d = d.trim().trim_end_matches("points").trim_end_matches("pt").trim(); + d.parse::().ok() + }; + let w = parse_dim(w_str)?; + let h = parse_dim(h_str)?; if w.is_finite() && h.is_finite() && w >= 72.0 && h >= 72.0 && w <= 14400.0 && h <= 14400.0 { Some((w, h)) } else { None } } @@ -346,7 +350,10 @@ mod tests { // Non-preset dimensions and driver papers carry their media selection. assert_eq!(job(json!({}), Some("300x500")).options, [media("Custom.300x500")]); assert_eq!(job(json!({}), Some("300 x 500 pt")).options, [media("Custom.300x500")]); + assert_eq!(job(json!({}), Some("300pt x 500pt")).options, [media("Custom.300x500")]); + assert_eq!(job(json!({}), Some("300 pt x 500 pt")).options, [media("Custom.300x500")]); assert_eq!(job(json!({}), Some("Custom.300x500")).options, [media("Custom.300x500")]); + assert_eq!(job(json!({}), Some("Custom.300x500pt")).options, [media("Custom.300x500")]); assert!(matches!(resolve_paper(Some("Quarto"), None), Err(ToolError::InvalidArgs(m)) if m.contains("US Letter"))); assert_eq!(spool_job(&Args(&json!({})), "default", String::new(), None, Some("Letter")).ok().map(|j| j.printer), Some(None)); diff --git a/crates/print/src/settings.rs b/crates/print/src/settings.rs index ccfd6417c1..ea2c629c39 100644 --- a/crates/print/src/settings.rs +++ b/crates/print/src/settings.rs @@ -294,8 +294,11 @@ pub fn paper_media(name: Option<&str>, size: (f64, f64)) -> String { } if let Some(n) = name { let is_dim = n.split_once(['x', 'X', '*']).is_some_and(|(w, h)| { - w.trim().trim_start_matches("Custom.").trim_start_matches("custom.").parse::().is_ok() - && h.trim().trim_end_matches("pt").trim_end_matches("points").trim().parse::().is_ok() + let parse_dim = |d: &str| { + let d = d.trim().trim_start_matches("Custom.").trim_start_matches("custom.").trim_end_matches("points").trim_end_matches("pt").trim(); + d.parse::().is_ok() + }; + parse_dim(w) && parse_dim(h) }); let is_clean = !n.is_empty() && n.len() <= 64 diff --git a/crates/print/src/spool.rs b/crates/print/src/spool.rs index a8b7ffcd32..b013d51866 100644 --- a/crates/print/src/spool.rs +++ b/crates/print/src/spool.rs @@ -223,7 +223,7 @@ pub fn printer_papers(printer: &str) -> Vec { let size = media.size(); let w_pts = (size.width_in_micron() as f64) * 72.0 / 25_400.0; let h_pts = (size.height_in_micron() as f64) * 72.0 / 25_400.0; - if w_pts > 0.0 && h_pts > 0.0 { + if w_pts.is_finite() && h_pts.is_finite() && w_pts >= 72.0 && h_pts >= 72.0 && w_pts <= 14400.0 && h_pts <= 14400.0 { let name = media.display_name().map(str::to_string).unwrap_or_else(|| { if let Some(predefined) = media.as_predefined_name() { format!("{predefined:?}") } else { format!("{w_pts:.1} x {h_pts:.1} pt") } }); @@ -252,6 +252,7 @@ fn resolve_paper_name(papers: &[Paper], paper_pts: (f64, f64)) -> Paper { .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()) + .or_else(|| crate::matching_paper(paper_pts).and_then(|idx| crate::PAPERS.get(idx).map(|(n, _)| n.to_string()))) .unwrap_or_default(); (name, (w_pts, h_pts)) } diff --git a/crates/ui-egui/src/workflows/print_ui/draft.rs b/crates/ui-egui/src/workflows/print_ui/draft.rs index 98452ff02c..5692118134 100644 --- a/crates/ui-egui/src/workflows/print_ui/draft.rs +++ b/crates/ui-egui/src/workflows/print_ui/draft.rs @@ -297,7 +297,7 @@ impl PrintDraft { if !(size.0 >= 72.0 && size.1 >= 72.0 && size.0.is_finite() && size.1.is_finite()) { return None; } - if let Some(pos) = self.matching_paper(size) { + if let Some(pos) = print::matching_paper(size) { Some(pos) } else if !name.is_empty() { let norm = (size.0.min(size.1), size.0.max(size.1)); @@ -308,10 +308,14 @@ impl PrintDraft { } }) { Some(pos) + } else if let Some(pos) = self.matching_paper(size) { + Some(pos) } else { self.papers.push((name, size)); Some(self.papers.len() - 1) } + } else if let Some(pos) = self.matching_paper(size) { + Some(pos) } else { let custom_name = format!("{:.1} x {:.1} pt", size.0, size.1); self.papers.push((custom_name, size)); @@ -435,7 +439,7 @@ impl PrintDraft { } .into_iter() // The sheet the PDF was laid out on, not the queue's default paper. - .chain(self.papers.get(self.paper).map(|(name, size)| print::paper_media_option(Some(name), *size))) + .chain(Some(print::paper_media_option(Some(self.paper_name()), self.paper_size()))) .collect(), } }