mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-11 16:36:13 -06:00
Merge PR #2584: M10: Two-sided starts at Printer default, so the printer's own duplex applies (#2422)
ATTRIBUTION.toml: kept main's sha256 lines; hashes recomputed after the track.
This commit is contained in:
@@ -6,6 +6,22 @@ use serde_json::{Value, json};
|
||||
|
||||
use crate::{Args, Automation, Result, ToolError, failed, write_atomic};
|
||||
|
||||
/// The job's two-sided setting for `doc_print`'s `duplex`: `printer` sends none, so the printer's
|
||||
/// own default applies (as the Print dialog's Printer default, #2422), except that cut and stack
|
||||
/// needs single-sided sheets.
|
||||
fn duplex(value: &str, cut_stack: bool) -> Result<Option<spool::Duplex>> {
|
||||
Ok(match value {
|
||||
"printer" if cut_stack => Some(spool::Duplex::Off),
|
||||
"printer" => None,
|
||||
"off" => Some(spool::Duplex::Off),
|
||||
"long-edge" if cut_stack => return Err(bad("cut-and-stack printing needs duplex off (single-sided sheets)")),
|
||||
"short-edge" if cut_stack => return Err(bad("cut-and-stack printing needs duplex off (single-sided sheets)")),
|
||||
"long-edge" => Some(spool::Duplex::LongEdge),
|
||||
"short-edge" => Some(spool::Duplex::ShortEdge),
|
||||
d => return Err(bad(format!("unknown duplex {d:?}"))),
|
||||
})
|
||||
}
|
||||
|
||||
fn bad(m: impl Into<String>) -> ToolError {
|
||||
ToolError::InvalidArgs(m.into())
|
||||
}
|
||||
@@ -92,9 +108,7 @@ impl Automation {
|
||||
},
|
||||
l => return Err(bad(format!("unknown layout {l:?}"))),
|
||||
};
|
||||
if matches!(layout, Layout::Multiple { order: PageOrder::CutStack, .. }) && a.opt_str("duplex")?.unwrap_or("off") != "off" {
|
||||
return Err(bad("cut-and-stack printing needs duplex off (single-sided sheets)"));
|
||||
}
|
||||
let duplex = duplex(a.opt_str("duplex")?.unwrap_or("off"), matches!(layout, Layout::Multiple { order: PageOrder::CutStack, .. }))?;
|
||||
if a.opt_str("order")? == Some("cut-stack") && !matches!(layout, Layout::Multiple { .. }) {
|
||||
return Err(bad("cut-stack order needs layout multiple"));
|
||||
}
|
||||
@@ -153,12 +167,7 @@ impl Automation {
|
||||
printer: if printer == "default" { None } else { Some(printer.to_string()) },
|
||||
copies: a.opt_int("copies")?.unwrap_or(1).clamp(1, 999) as u32,
|
||||
collate: a.opt_bool("collate")?.unwrap_or(true),
|
||||
duplex: Some(match a.opt_str("duplex")?.unwrap_or("off") {
|
||||
"off" => spool::Duplex::Off,
|
||||
"long-edge" => spool::Duplex::LongEdge,
|
||||
"short-edge" => spool::Duplex::ShortEdge,
|
||||
d => return Err(bad(format!("unknown duplex {d:?}"))),
|
||||
}),
|
||||
duplex,
|
||||
scaling: spool::Scaling::Exact,
|
||||
grayscale: a.opt_bool("grayscale")?.unwrap_or(false),
|
||||
title: name,
|
||||
@@ -181,3 +190,21 @@ impl Automation {
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// `printer` leaves two-sided to the printer's own default (#2422); cut and stack still asks
|
||||
/// for single-sided sheets, and refuses an explicit edge.
|
||||
#[test]
|
||||
fn duplex_printer_leaves_two_sided_to_the_printer() {
|
||||
assert_eq!(duplex("printer", false).ok(), Some(None));
|
||||
assert_eq!(duplex("printer", true).ok(), Some(Some(spool::Duplex::Off)));
|
||||
assert_eq!(duplex("off", false).ok(), Some(Some(spool::Duplex::Off)));
|
||||
assert_eq!(duplex("long-edge", false).ok(), Some(Some(spool::Duplex::LongEdge)));
|
||||
assert_eq!(duplex("short-edge", false).ok(), Some(Some(spool::Duplex::ShortEdge)));
|
||||
assert!(duplex("long-edge", true).is_err());
|
||||
assert!(duplex("sideways", false).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -518,7 +518,7 @@ pub fn tools() -> Vec<ToolDef> {
|
||||
"printer": { "type": "string" },
|
||||
"copies": { "type": "integer", "minimum": 1, "maximum": 999 },
|
||||
"collate": { "type": "boolean" },
|
||||
"duplex": { "type": "string", "enum": ["off", "long-edge", "short-edge"] },
|
||||
"duplex": { "type": "string", "enum": ["off", "long-edge", "short-edge", "printer"], "description": "Two-sided printing (default off); printer sends no setting, so the printer's own default applies." },
|
||||
"grayscale": { "type": "boolean" },
|
||||
"as_image": { "type": "boolean", "description": "Print as image (Acrobat's Advanced Print Setup): every page rendered to a picture at dpi before it is placed on the paper, for printers that skip text, shading or transparency; the print is no longer searchable text, and with grayscale the pictures are grey." },
|
||||
"dpi": { "type": "number", "minimum": 72, "maximum": 600, "description": "Resolution for as_image (default 300)." },
|
||||
|
||||
@@ -5683,6 +5683,9 @@ fn cut_stack_printing_through_tools() {
|
||||
}
|
||||
assert!(matches!(a.call("doc_print", &json!({"doc": doc, "order": "cut-stack", "path": "refused.pdf"})), Err(ToolError::InvalidArgs(_))));
|
||||
assert!(!dir.join("refused.pdf").exists());
|
||||
// The printer's own two-sided default isn't refused: cut and stack sends single-sided (#2422).
|
||||
ok(&mut a, "doc_print", json!({"doc": doc, "layout": "multiple", "order": "cut-stack", "duplex": "printer", "path": "printer-default.pdf"}));
|
||||
assert!(dir.join("printer-default.pdf").exists());
|
||||
assert!(a.call("doc_print", &json!({"doc": doc, "layout": "multiple", "order": "cut-stack", "path": "../escaped.pdf"})).is_err());
|
||||
let _ = std::fs::remove_dir_all(dir);
|
||||
}
|
||||
|
||||
@@ -1219,6 +1219,7 @@
|
||||
Off متوقف
|
||||
Flip on long edge قلب على الحافة الطويلة
|
||||
Flip on short edge قلب على الحافة القصيرة
|
||||
Printer default افتراضي الطابعة
|
||||
Paper: الورق:
|
||||
Pages to Print الصفحات المراد طباعتها
|
||||
More options: خيارات أخرى:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -899,6 +899,7 @@
|
||||
Off Изключено
|
||||
Flip on long edge Обръщане по дългия ръб
|
||||
Flip on short edge Обръщане по късия ръб
|
||||
Printer default По подразбиране за принтера
|
||||
Paper: Хартия:
|
||||
Pages to Print Страници за печат
|
||||
More options: Още опции:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 8 and column 16.
|
@@ -1287,6 +1287,7 @@ key Space Mezerník
|
||||
Off Vypnuto
|
||||
Flip on long edge Převrátit podle delší strany
|
||||
Flip on short edge Převrátit podle kratší strany
|
||||
Printer default Podle tiskárny
|
||||
Paper: Papír:
|
||||
Pages to Print Stránky k tisku
|
||||
More options: Další možnosti:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1205,6 +1205,7 @@
|
||||
Off Aus
|
||||
Flip on long edge An langer Kante wenden
|
||||
Flip on short edge An kurzer Kante wenden
|
||||
Printer default Druckerstandard
|
||||
Paper: Papier:
|
||||
Pages to Print Zu druckende Seiten
|
||||
More options: Weitere Optionen:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 51.
|
@@ -764,6 +764,7 @@
|
||||
Two-sided: Εκτύπωση διπλής όψης:
|
||||
Flip on long edge Αναστροφή στη μεγάλη πλευρά
|
||||
Flip on short edge Αναστροφή στη μικρή πλευρά
|
||||
Printer default Προεπιλογή εκτυπωτή
|
||||
Paper: Χαρτί:
|
||||
Pages to Print Σελίδες προς εκτύπωση
|
||||
More options: Περισσότερες επιλογές:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 477 and column 9.
|
@@ -1214,6 +1214,7 @@
|
||||
Off Desactivado
|
||||
Flip on long edge Voltear por el borde largo
|
||||
Flip on short edge Voltear por el borde corto
|
||||
Printer default Predeterminado de la impresora
|
||||
Paper: Papel:
|
||||
Pages to Print Páginas que imprimir
|
||||
More options: Más opciones:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 60.
|
@@ -1200,6 +1200,7 @@
|
||||
Off Désactivé
|
||||
Flip on long edge Retourner sur le bord long
|
||||
Flip on short edge Retourner sur le bord court
|
||||
Printer default Par défaut de l'imprimante
|
||||
Paper: Papier :
|
||||
Pages to Print Pages à imprimer
|
||||
More options: Plus d'options :
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 39.
|
@@ -1190,6 +1190,7 @@
|
||||
Off Ki
|
||||
Flip on long edge Hosszú él mentén
|
||||
Flip on short edge Rövid él mentén
|
||||
Printer default Nyomtató alapértéke
|
||||
Paper: Papír:
|
||||
Pages to Print Nyomtatandó oldalak
|
||||
More options: További beállítások:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 44.
|
@@ -1216,6 +1216,7 @@
|
||||
Off Disattivato
|
||||
Flip on long edge Rilegatura sul lato lungo
|
||||
Flip on short edge Rilegatura sul lato corto
|
||||
Printer default Come da stampante
|
||||
Paper: Carta:
|
||||
Pages to Print Pagine da stampare
|
||||
More options: Altre opzioni:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1220,6 +1220,7 @@
|
||||
Off オフ
|
||||
Flip on long edge 長辺とじ
|
||||
Flip on short edge 短辺とじ
|
||||
Printer default プリンターの既定
|
||||
Paper: 用紙:
|
||||
Pages to Print 印刷するページ
|
||||
More options: 詳細オプション:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1164,6 +1164,7 @@
|
||||
Off Өшірулі
|
||||
Flip on long edge Ұзын жиек бойынша аудару
|
||||
Flip on short edge Қысқа жиек бойынша аудару
|
||||
Printer default Принтер әдепкісі
|
||||
Paper: Қағаз:
|
||||
Pages to Print Басып шығарылатын беттер
|
||||
More options: Қосымша параметрлер:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 856 and column 9.
|
@@ -592,6 +592,7 @@
|
||||
Flip horizontal Apversti horizontaliai
|
||||
Flip on long edge Apversti per ilgąją kraštinę
|
||||
Flip on short edge Apversti per trumpąją kraštinę
|
||||
Printer default Pagal spausdintuvą
|
||||
Flip vertical Apversti vertikaliai
|
||||
Folders Aplankai
|
||||
Font Size: Šrifto dydis:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 404 and column 9.
|
@@ -1203,6 +1203,7 @@
|
||||
Off Wyłączony
|
||||
Flip on long edge Odwracaj wzdłuż długiej krawędzi
|
||||
Flip on short edge Odwracaj wzdłuż krótkiej krawędzi
|
||||
Printer default Domyślne drukarki
|
||||
Paper: Papier:
|
||||
Pages to Print Strony do wydrukowania
|
||||
More options: Więcej opcji:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -845,6 +845,7 @@
|
||||
Off Desativado
|
||||
Flip on long edge Virar na borda longa
|
||||
Flip on short edge Virar na borda curta
|
||||
Printer default Padrão da impressora
|
||||
Paper: Papel:
|
||||
Pages to Print Páginas a imprimir
|
||||
More options: Mais opções:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -1220,6 +1220,7 @@
|
||||
Off Выкл
|
||||
Flip on long edge Переворот по длинной стороне
|
||||
Flip on short edge Переворот по короткой стороне
|
||||
Printer default По умолчанию для принтера
|
||||
Paper: Бумага:
|
||||
Pages to Print Страницы для печати
|
||||
More options: Дополнительные параметры:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -839,6 +839,7 @@
|
||||
Flip image Prezrcali sliko
|
||||
Flip on long edge Obrni po dolgem robu
|
||||
Flip on short edge Obrni po kratkem robu
|
||||
Printer default Privzeto tiskalnika
|
||||
Flip vertical Prezrcali navpično
|
||||
Folders Mape
|
||||
Font Size: Velikost pisave:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -1190,6 +1190,7 @@ comment menu Edit సవరించు
|
||||
Off ఆఫ్
|
||||
Flip on long edge పొడవైన అంచున తిరగేయి
|
||||
Flip on short edge పొట్టి అంచున తిరగేయి
|
||||
Printer default ప్రింటర్ డిఫాల్ట్
|
||||
Paper: కాగితం:
|
||||
Pages to Print ముద్రించాల్సిన పేజీలు
|
||||
More options: మరిన్ని ఎంపికలు:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 13 and column 16.
|
@@ -1177,6 +1177,7 @@
|
||||
Off Kapalı
|
||||
Flip on long edge Uzun kenardan çevir
|
||||
Flip on short edge Kısa kenardan çevir
|
||||
Printer default Yazıcı varsayılanı
|
||||
Paper: Kağıt:
|
||||
Pages to Print Yazdırılacak Sayfalar
|
||||
More options: Diğer seçenekler:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 855 and column 9.
|
@@ -1217,6 +1217,7 @@
|
||||
Off Вимкнено
|
||||
Flip on long edge Перевертати за довгим краєм
|
||||
Flip on short edge Перевертати за коротким краєм
|
||||
Printer default За замовчуванням принтера
|
||||
Paper: Папір:
|
||||
Pages to Print Сторінки для друку
|
||||
More options: Додаткові параметри:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -589,6 +589,7 @@ theme Appearance Giao diện
|
||||
Flip horizontal Lật ngang
|
||||
Flip on long edge Lật theo cạnh dài
|
||||
Flip on short edge Lật theo cạnh ngắn
|
||||
Printer default Mặc định của máy in
|
||||
Flip vertical Lật dọc
|
||||
Folders Thư mục
|
||||
Font Size: Cỡ chữ:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 491 and column 12.
|
@@ -906,6 +906,7 @@
|
||||
Off 关
|
||||
Flip on long edge 沿长边翻转
|
||||
Flip on short edge 沿短边翻转
|
||||
Printer default 打印机默认
|
||||
Paper: 纸张:
|
||||
Pages to Print 要打印的页面
|
||||
More options: 更多选项:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -850,6 +850,7 @@
|
||||
Off 關
|
||||
Flip on long edge 沿長邊翻轉
|
||||
Flip on short edge 沿短邊翻轉
|
||||
Printer default 印表機預設
|
||||
Paper: 紙張:
|
||||
Pages to Print 要列印的頁面
|
||||
More options: 更多選項:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -51,7 +51,8 @@ pub struct PrintDraft {
|
||||
pub copies: u32,
|
||||
pub collate: bool,
|
||||
pub grayscale: bool,
|
||||
pub duplex: spool::Duplex,
|
||||
/// `None` = the printer's own default (its Properties), as Acrobat does.
|
||||
pub duplex: Option<spool::Duplex>,
|
||||
pub which: Which,
|
||||
pub range: String,
|
||||
/// The pages `Which::Selected` prints (0-based, in page order); empty when none were picked.
|
||||
@@ -108,7 +109,7 @@ impl Default for PrintDraft {
|
||||
copies: 1,
|
||||
collate: true,
|
||||
grayscale: false,
|
||||
duplex: spool::Duplex::Off,
|
||||
duplex: None,
|
||||
which: Which::All,
|
||||
range: String::new(),
|
||||
selected: Vec::new(),
|
||||
@@ -168,7 +169,7 @@ impl PrintDraft {
|
||||
|
||||
/// The engine settings for this draft (page count and labels from the document).
|
||||
pub fn settings(&self, count: usize, labels: &[String]) -> Result<print::Settings, String> {
|
||||
if self.handling == Handling::Multiple && self.order == PageOrder::CutStack && self.duplex != spool::Duplex::Off {
|
||||
if self.cut_and_stack() && matches!(self.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 {
|
||||
@@ -211,12 +212,17 @@ impl PrintDraft {
|
||||
})
|
||||
}
|
||||
|
||||
fn cut_and_stack(&self) -> bool {
|
||||
self.handling == Handling::Multiple && self.order == PageOrder::CutStack
|
||||
}
|
||||
|
||||
pub fn job(&self, title: &str) -> spool::Job {
|
||||
spool::Job {
|
||||
printer: self.printer.clone(),
|
||||
copies: self.copies.max(1),
|
||||
collate: self.collate,
|
||||
duplex: Some(self.duplex),
|
||||
// Cut and stack needs single-sided sheets, whatever the printer's default.
|
||||
duplex: if self.cut_and_stack() { Some(spool::Duplex::Off) } else { self.duplex },
|
||||
scaling: spool::Scaling::Exact,
|
||||
grayscale: self.grayscale,
|
||||
title: title.to_string(),
|
||||
@@ -928,9 +934,10 @@ pub(crate) fn body(
|
||||
"duplex",
|
||||
&mut d.duplex,
|
||||
&[
|
||||
(spool::Duplex::Off, tl!("Off")),
|
||||
(spool::Duplex::LongEdge, tl!("Flip on long edge")),
|
||||
(spool::Duplex::ShortEdge, tl!("Flip on short edge")),
|
||||
(None, tl!("Printer default")),
|
||||
(Some(spool::Duplex::Off), tl!("Off")),
|
||||
(Some(spool::Duplex::LongEdge), tl!("Flip on long edge")),
|
||||
(Some(spool::Duplex::ShortEdge), tl!("Flip on short edge")),
|
||||
],
|
||||
160.0,
|
||||
);
|
||||
@@ -1305,11 +1312,25 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn composed_sheets_keep_exact_scaling_and_the_dialogs_duplex_choice() {
|
||||
let draft = PrintDraft { duplex: spool::Duplex::LongEdge, ..PrintDraft::default() };
|
||||
let draft = PrintDraft { duplex: Some(spool::Duplex::LongEdge), ..PrintDraft::default() };
|
||||
let job = draft.job("report.pdf");
|
||||
assert_eq!(job.scaling, spool::Scaling::Exact);
|
||||
assert_eq!(job.duplex, Some(spool::Duplex::LongEdge));
|
||||
assert_eq!(PrintDraft::default().job("report.pdf").duplex, Some(spool::Duplex::Off));
|
||||
let off = PrintDraft { duplex: Some(spool::Duplex::Off), ..PrintDraft::default() };
|
||||
assert_eq!(off.job("report.pdf").duplex, Some(spool::Duplex::Off));
|
||||
}
|
||||
|
||||
/// Untouched, Two-sided sends nothing, so the duplex set in the printer's Properties applies
|
||||
/// (#2422); cut and stack still asks for single-sided sheets.
|
||||
#[test]
|
||||
fn two_sided_follows_the_printer_until_chosen() {
|
||||
assert_eq!(PrintDraft::default().duplex, None);
|
||||
assert_eq!(PrintDraft::default().job("report.pdf").duplex, None);
|
||||
let mut cut = PrintDraft { handling: Handling::Multiple, order: PageOrder::CutStack, ..PrintDraft::default() };
|
||||
assert_eq!(cut.job("report.pdf").duplex, Some(spool::Duplex::Off));
|
||||
assert!(cut.settings(4, &[]).is_ok(), "the printer's default doesn't block cut and stack");
|
||||
cut.duplex = Some(spool::Duplex::ShortEdge);
|
||||
assert!(cut.settings(4, &[]).is_err());
|
||||
}
|
||||
|
||||
/// A job being spooled holds the progress card, counting its sheets as the spooler reports
|
||||
|
||||
@@ -182,11 +182,11 @@ fn cut_stack_dialog_previews_saves_and_refuses_duplex() {
|
||||
h.get_by_label("›").click();
|
||||
h.run_steps(2);
|
||||
h.get_by_label("Sheet 2 of 3");
|
||||
h.state_mut().print_draft.duplex = Duplex::LongEdge;
|
||||
h.state_mut().print_draft.duplex = Some(Duplex::LongEdge);
|
||||
h.run_steps(2);
|
||||
h.get_by_label_contains("Cut and stack needs Two-sided: Off");
|
||||
assert!(!h.state_mut().print_now());
|
||||
h.state_mut().print_draft.duplex = Duplex::Off;
|
||||
h.state_mut().print_draft.duplex = Some(Duplex::Off);
|
||||
h.state_mut().print_draft.printer = None;
|
||||
let out = std::env::temp_dir().join(format!("pdfcraft-cut-stack-ui-{}.pdf", std::process::id()));
|
||||
h.state_mut().save_override = Some(out.to_string_lossy().into_owned());
|
||||
|
||||
@@ -8020,8 +8020,8 @@ milestone = "M10"
|
||||
status = "shipped"
|
||||
commands = ["print.dialog"]
|
||||
tools = ["doc_print"]
|
||||
evidence = ["crates/print/src/tests.rs::spooler_arguments_and_printer_list"]
|
||||
notes = "long/short edge via the spooler: lp -o sides=…, and the Windows driver's Duplex setting"
|
||||
evidence = ["crates/print/src/tests.rs::spooler_arguments_and_printer_list", "crates/ui-egui/src/print_ui.rs::two_sided_follows_the_printer_until_chosen", "crates/automation/src/printing.rs::duplex_printer_leaves_two_sided_to_the_printer"]
|
||||
notes = "long/short edge via the spooler: lp -o sides=…, and the Windows driver's Duplex setting. Two-sided starts at Printer default, as in Acrobat: nothing is sent, so the printer's own default (its Properties) applies until Off or an edge is chosen (doc_print: duplex \"printer\"). Missing: the document's /ViewerPreferences /Duplex preset isn't read yet"
|
||||
|
||||
[[feature]]
|
||||
id = "print.n-up"
|
||||
|
||||
Reference in New Issue
Block a user