diff --git a/ATTRIBUTION.toml b/ATTRIBUTION.toml index 002b91c90..61729e35b 100644 --- a/ATTRIBUTION.toml +++ b/ATTRIBUTION.toml @@ -7,7 +7,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Greek UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "7ed6a7c482dab19fe1b54a7704902610e79e9bc60f19115ce8af7b1ebca504ff" +sha256 = "1049c27ed90afac974f371c7e7e3bff94a2baebde2d820adc4c4c84e822e4cdd" # ATTRIBUTION.toml: one entry per asset (AGENTS.md §1). The source of truth for provenance and licences. # ATTRIBUTION.md is generated from this file: `cargo xtask assets --write`. Checked by `cargo xtask assets` (part of `xtask ci`). @@ -30,7 +30,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Simplified Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "e5141febe07d89c4c4c7b2d2f87abf71ad1ab8dc4a7880d03805db6f0022118b" +sha256 = "c30f72a1e902ddee4cb902560aaa91dfd3567490fd64db972f74a64fdb9af62c" [[asset]] path = "crates/ui-egui/src/i18n/zh-hant.tsv" @@ -41,7 +41,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Traditional Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "6dff4fded1106659f7c0b1c7e2c671c02e59313ab58875fabf591a9356098767" +sha256 = "ad2c35209b7dbcd366038e6a179d695937ef1c3e333f64eb803de738766fb3e9" [[asset]] path = "crates/ui-egui/src/i18n/cs.tsv" @@ -52,7 +52,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Czech UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "1978cfd55b08fe9905e00ddd16a10d3d0f5371143e2f00a58a77affcc1d874c3" +sha256 = "5d5456a0e51a3287ada0faec559dc137197159e64b4df8e7ee22ac8c563a9de9" [[asset]] path = "crates/ui-egui/src/i18n/pt-br.tsv" @@ -63,7 +63,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Brazilian Portuguese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "928435778bd8c703083087d401851ab01dc038db3ff568e572b6cf646f805b03" +sha256 = "f0b1e7ac0bf248cf09bf2d038e9224c24d2edb4c3b3ba92b3806f9384161e77b" [[asset]] path = "crates/ui-egui/src/i18n/es.tsv" @@ -74,7 +74,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Spanish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "4f95f2dc524177a5a6261f2f6df6def2cd061596bddac20b264cf6051978b3e3" +sha256 = "468443e23821e248c934e222b245e87f926c38786575b6290d33f959d39a20bf" [[asset]] path = "crates/ui-egui/src/i18n/fr.tsv" @@ -85,7 +85,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "French UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "7c0df34f12030b1dc266413839dbb507bc890cc8847f6b2eac324ed54ff3fc19" +sha256 = "575b7487137ef8c4b4ae33fc7bdbd50370fa28078e41727216046fdc5b89b81a" [[asset]] path = "crates/ui-egui/src/i18n/de.tsv" @@ -96,7 +96,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "German UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "77f4ec8eb9cba7e2f2aad0883a91a62ca871140135c586f32c725f66774a0632" +sha256 = "30f526fc7a1c14737bbaea272f45f123ffe61107188fe74375bd51b4808c9575" [[asset]] path = "crates/ui-egui/src/i18n/te.tsv" @@ -118,7 +118,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Italian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "3d1af7e703706bf4c814062871b47308b70f358356c3b17782dde3eaaa66abb7" +sha256 = "8404b7ae758b3cddb28efd8f27405d260cf4193e3fecf68ea9422aebd4d063b4" [[asset]] path = "crates/ui-egui/src/i18n/pl.tsv" @@ -129,7 +129,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Polish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "d03048fe760c7daf08c0d8ed8470633c0eaca8f68c41d1c8ce889813c7f6fab1" +sha256 = "0ae768bcb0d7d3c24f5636d85df1532e626452ff97397312270379501a025c52" [[asset]] path = "crates/ui-egui/src/i18n/ru.tsv" @@ -140,7 +140,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Russian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "69e07b82ae409de7544f538fedc8e56cdb9f4092be7f45cc7366bddbc03ce93f" +sha256 = "7b0798851885f59105046e861f682f529f5666b0ef8edc3fb3ba6255459d80ff" [[asset]] path = "crates/ui-egui/src/i18n/hu.tsv" @@ -151,7 +151,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Hungarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "0a71d58a88ea532358d3b133eba3aae217ad4bcbb763b6235b137615ce226d2b" +sha256 = "b1c44f5f1b6deadcdbe216019750f24beeac5533b6f519be4e361c97b57d5ac1" [[asset]] path = "crates/ui-egui/src/i18n/bg.tsv" @@ -162,7 +162,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Bulgarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "1e5172befbe995c800cd69e9d6abc02c92544b62d8e6dac0f4a0c26cd0b1efbb" +sha256 = "9dc87fafc98f9d30c4bafa8014f19b32b0e83a0608b2202e647988abb4e6303d" [[asset]] path = "crates/ui-egui/src/i18n/kk.tsv" @@ -173,7 +173,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Kazakh UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "dd76d58abda6153e5c0b9fcc761d652cf2ed96522fab18c9ce87e9cec594043a" +sha256 = "af1cf608a1561057c530d5716ea8aac27dacf9fe4ab49741743b980f587a1409" [[asset]] path = "crates/ui-egui/src/i18n/ja.tsv" @@ -184,7 +184,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Japanese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "6c1ca7f6d53454c69b3e362a2a09c5c28d04b615c53936c639e3b43c880552d3" +sha256 = "f0e5375a5ee6b1151e312ba333f1a9a9ce0c55b27928da9bd6cff1ba95b8a5a1" [[asset]] path = "crates/ui-egui/src/i18n/uk.tsv" @@ -195,7 +195,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Ukrainian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "9151c1049289e318409965c5ad061448db9f35a7e8f14a754ba83616e3d87930" +sha256 = "9663c11a1f02c55bafb37ed2b12fc4d8542a2f2ed1c6dfdefd0b1f0629208cb8" [[asset]] path = "crates/ui-egui/src/i18n/ar.tsv" @@ -206,7 +206,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Arabic UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "096be489498c16dbf85fcd6d2d0d96590736a8b0bbe3b3a5b557e2627c89f9c1" +sha256 = "3bfd85b5f6d3ab75573fa7511487561d8ee8b37a69dbf361c5b142cd50fb38e6" [[asset]] path = "crates/ui-egui/src/i18n/tr.tsv" @@ -217,7 +217,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Turkish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "e72215bc4bf21f598bf82a6ac55551bdef47d62cceedea239d89c0febd9cb1b7" +sha256 = "e9f46d2738530d4114b9e0a3e3c51bbd2df9bc51cb84506c10f499baa566650d" [[asset]] path = "assets/icons/accessibility.svg" @@ -3339,4 +3339,4 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Slovene UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "7cc8bf51e042d07a397dbebf0747b496879e721a6644f1af197cfb3cbcbe7904" +sha256 = "855e743c3b21f4d8e158d30c5757ead4f2b6b8bf830c87115ae99e6d16da76a3" diff --git a/crates/automation/src/printing.rs b/crates/automation/src/printing.rs index 533f94835..15b56eae8 100644 --- a/crates/automation/src/printing.rs +++ b/crates/automation/src/printing.rs @@ -12,7 +12,16 @@ fn bad(m: impl Into) -> ToolError { impl Automation { pub(crate) fn printers(&self) -> Result { - let list: Vec = spool::printers().into_iter().map(|p| json!({ "name": p.name, "default": p.default })).collect(); + let list: Vec = spool::printers() + .into_iter() + .map(|p| { + let mut v = json!({ "name": p.name, "default": p.default }); + if let Some(two_sided) = p.two_sided { + v["two_sided"] = json!(two_sided); + } + v + }) + .collect(); Ok(json!({ "count": list.len(), "printers": list })) } diff --git a/crates/print/src/spool.rs b/crates/print/src/spool.rs index 92a54fa81..0d949aae6 100644 --- a/crates/print/src/spool.rs +++ b/crates/print/src/spool.rs @@ -12,6 +12,10 @@ use crate::PrintError; pub struct Printer { pub name: String, pub default: bool, + /// Whether the driver says it can print two-sided, where the spooler tells (Windows: the + /// printer's `Duplex Printing` capability). A driver without it ignores Two-sided and uses + /// its own setting. `None` when unknown (CUPS maps `sides` itself). + pub two_sided: Option, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] @@ -53,7 +57,7 @@ pub fn parse_lpstat(out: &str) -> Vec { out.lines() .filter_map(|l| l.strip_prefix("printer ")) .filter_map(|l| l.split_whitespace().next()) - .map(|n| Printer { name: n.to_string(), default: default == Some(n) }) + .map(|n| Printer { name: n.to_string(), default: default == Some(n), two_sided: None }) .collect() } @@ -340,7 +344,7 @@ pub fn printers_parsed(queues: &str, available: Option<&str>) -> Vec { if let Some(names) = available { for name in parse_lpstat_e(names) { if !printers.iter().any(|p| p.name == name) { - printers.push(Printer { default: default == Some(name.as_str()), name }); + printers.push(Printer { default: default == Some(name.as_str()), name, two_sided: None }); } } } @@ -367,23 +371,32 @@ pub(crate) fn powershell_command(script: &str) -> std::process::Command { c } -/// The Windows printer list: one line per printer, `*` on the system default. UTF-8, through -/// the output encoding the script sets first. Only the two properties the list needs are +/// The Windows printer list: one line per printer, `*` on the system default, then a tab and +/// `2` when the driver reports the Duplex Printing capability (3), `1` when it doesn't. UTF-8, +/// through the output encoding the script sets first. Only the properties the list needs are /// pulled, so a machine with many printers doesn't wait for the rest of each record. #[cfg(windows)] -const POWERSHELL_PRINTERS: &str = "[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false); Get-CimInstance Win32_Printer -Property Name, Default | ForEach-Object { if ($_.Default) { '*' + $_.Name } else { $_.Name } }"; +const POWERSHELL_PRINTERS: &str = "[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false); Get-CimInstance Win32_Printer -Property Name, Default, Capabilities | ForEach-Object { $sides = if (@($_.Capabilities) -contains 3) { '2' } else { '1' }; if ($_.Default) { '*' + $_.Name + [char]9 + $sides } else { $_.Name + [char]9 + $sides } }"; /// The Print dialog's printers from the Windows printer list: one per line, `*` before the -/// system default (a Windows queue name may not contain `*`). The first `*` wins when a broken -/// spooler marks more than one, so the dialog still has a single default to preselect. +/// system default (a Windows queue name may not contain `*`), and after a tab `2` or `1` for +/// whether the driver can print two-sided. The first `*` wins when a broken spooler marks more +/// than one, so the dialog still has a single default to preselect. pub fn parse_windows_printers(out: &str) -> Vec { let mut printers: Vec = out .lines() .map(|l| l.strip_suffix('\r').unwrap_or(l)) .filter(|l| !l.is_empty()) - .map(|l| match l.strip_prefix('*') { - Some(name) => Printer { name: name.to_string(), default: true }, - None => Printer { name: l.to_string(), default: false }, + .map(|l| { + let (l, two_sided) = match l.rsplit_once('\t') { + Some((name, "2")) => (name, Some(true)), + Some((name, "1")) => (name, Some(false)), + _ => (l, None), + }; + match l.strip_prefix('*') { + Some(name) => Printer { name: name.to_string(), default: true, two_sided }, + None => Printer { name: l.to_string(), default: false, two_sided }, + } }) .collect(); let mut seen_default = false; diff --git a/crates/print/src/tests.rs b/crates/print/src/tests.rs index 935592297..bf186fb7d 100644 --- a/crates/print/src/tests.rs +++ b/crates/print/src/tests.rs @@ -234,7 +234,10 @@ fn spooler_arguments_and_printer_list() { let out = "printer Office_Laser is idle. enabled since Thu Oct 1 09:00:00 2026\nprinter Label_Writer disabled since …\nsystem default destination: Office_Laser\n"; assert_eq!( parse_lpstat(out), - [spool::Printer { name: "Office_Laser".into(), default: true }, spool::Printer { name: "Label_Writer".into(), default: false }] + [ + spool::Printer { name: "Office_Laser".into(), default: true, two_sided: None }, + spool::Printer { name: "Label_Writer".into(), default: false, two_sided: None } + ] ); assert!(parse_lpstat("lpstat: No destinations added.\nno system default destination\n").is_empty()); let job = Job { @@ -274,16 +277,19 @@ fn driverless_printers_join_the_list_without_a_queue() { let queues = "printer Office_Laser is idle. enabled since Thu Oct 1 09:00:00 2026\nsystem default destination: Office_Laser\n"; assert_eq!( spool::printers_parsed(queues, Some("Basement_Color\nOffice_Laser\n")), - [spool::Printer { name: "Office_Laser".into(), default: true }, spool::Printer { name: "Basement_Color".into(), default: false },], + [ + spool::Printer { name: "Office_Laser".into(), default: true, two_sided: None }, + spool::Printer { name: "Basement_Color".into(), default: false, two_sided: None }, + ], "queues first, then the driverless destination; the queue listed twice is not doubled" ); // An old spooler without `-e` (CUPS < 1.7) keeps its queues; only the driverless names are lost. - assert_eq!(spool::printers_parsed(queues, None), [spool::Printer { name: "Office_Laser".into(), default: true }]); + assert_eq!(spool::printers_parsed(queues, None), [spool::Printer { name: "Office_Laser".into(), default: true, two_sided: None }]); // The default can itself be driverless (`lpoptions -d` names a destination no queue exists // for): it keeps its marker, so the dialog preselects the user's default, not the first queue. assert_eq!( spool::printers_parsed("system default destination: Basement_Color\n", Some("Basement_Color\n")), - [spool::Printer { name: "Basement_Color".into(), default: true }], + [spool::Printer { name: "Basement_Color".into(), default: true, two_sided: None }], "a default destination without a queue is still the default" ); assert!(spool::parse_lpstat_e("\n \n").is_empty(), "no destinations is empty, not [\"\"]"); @@ -443,12 +449,26 @@ fn windows_printers_carry_one_default_and_keep_their_names() { // Windows marks the system default with a `*` no queue name may contain; names hold spaces, // parentheses and non-ASCII letters, and the lines end with CRLF. let printers = parse_windows_printers("*Microsoft Print to PDF\r\nHP LaserJet 400 (Copy 2)\r\nКопир Canon iR\r\n\r\n*XPS via USB\r\n"); - assert_eq!(printers[0], Printer { name: "Microsoft Print to PDF".into(), default: true }); - assert_eq!(printers[1], Printer { name: "HP LaserJet 400 (Copy 2)".into(), default: false }); - assert_eq!(printers[2], Printer { name: "Копир Canon iR".into(), default: false }); + assert_eq!(printers[0], Printer { name: "Microsoft Print to PDF".into(), default: true, two_sided: None }); + assert_eq!(printers[1], Printer { name: "HP LaserJet 400 (Copy 2)".into(), default: false, two_sided: None }); + assert_eq!(printers[2], Printer { name: "Копир Canon iR".into(), default: false, two_sided: None }); // A spooler marking two defaults: the first still wins, so the dialog has one to preselect. - assert_eq!(printers[3], Printer { name: "XPS via USB".into(), default: false }); + assert_eq!(printers[3], Printer { name: "XPS via USB".into(), default: false, two_sided: None }); assert_eq!(parse_windows_printers("\r\n"), Vec::new()); + // After a tab, whether the driver reports two-sided printing; a tab inside a name stays. + let sided = parse_windows_printers("*Office PCL\t2\r\nOffice PS\t1\r\nTab\tName\t2\r\n"); + assert_eq!(sided[0], Printer { name: "Office PCL".into(), default: true, two_sided: Some(true) }); + assert_eq!(sided[1], Printer { name: "Office PS".into(), default: false, two_sided: Some(false) }); + assert_eq!(sided[2], Printer { name: "Tab\tName".into(), default: false, two_sided: Some(true) }); +} + +#[test] +fn the_windows_print_script_binds_long_edge_on_the_long_edge() { + // System.Drawing's Duplex values are DEVMODE's: Vertical binds a portrait page on its long + // edge, Horizontal on its short edge (checked against a PCL driver's @PJL SET BINDING). + let script = include_str!("windows_print.ps1").replace("\r\n", "\n"); + assert!(script.contains("'long-edge' { [System.Drawing.Printing.Duplex]::Vertical }")); + assert!(script.contains("'short-edge' { [System.Drawing.Printing.Duplex]::Horizontal }")); } #[test] diff --git a/crates/print/src/windows_print.ps1 b/crates/print/src/windows_print.ps1 index 095b929ff..2292c297c 100644 --- a/crates/print/src/windows_print.ps1 +++ b/crates/print/src/windows_print.ps1 @@ -97,9 +97,11 @@ if ($env:PDFCRAFT_PRINT_PRINTER) { } $settings.Copies = [Math]::Min(32767, [Math]::Max(1, [int]$env:PDFCRAFT_PRINT_COPIES)) $settings.Collate = ($env:PDFCRAFT_PRINT_COLLATE -eq '1') +# DEVMODE's names are about the binding edge of a portrait page: Vertical (DMDUP_VERTICAL) +# binds on the long edge, Horizontal (DMDUP_HORIZONTAL) on the short edge. $settings.Duplex = switch ($env:PDFCRAFT_PRINT_DUPLEX) { - 'long-edge' { [System.Drawing.Printing.Duplex]::Horizontal } - 'short-edge' { [System.Drawing.Printing.Duplex]::Vertical } + 'long-edge' { [System.Drawing.Printing.Duplex]::Vertical } + 'short-edge' { [System.Drawing.Printing.Duplex]::Horizontal } default { [System.Drawing.Printing.Duplex]::Simplex } } if ($env:PDFCRAFT_PRINT_GRAYSCALE -eq '1') { $printer.DefaultPageSettings.Color = $false } diff --git a/crates/ui-egui/src/i18n/ar.tsv b/crates/ui-egui/src/i18n/ar.tsv index c2b21d8d3..252a2aa79 100644 --- a/crates/ui-egui/src/i18n/ar.tsv +++ b/crates/ui-egui/src/i18n/ar.tsv @@ -2270,3 +2270,4 @@ scroll speed Fast سريعة Original الأصل Greyscale تدرج رمادي Black and white أبيض وأسود + This printer's driver doesn't take two-sided settings from other programs, so its own setting applies. Change it in Properties… لا يقبل برنامج تشغيل هذه الطابعة إعدادات الطباعة على الوجهين من البرامج الأخرى، لذا يُطبَّق إعداده الخاص. غيّره من خصائص… diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 91c77d442..5290ffee7 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -2212,3 +2212,4 @@ scroll speed Fast Висока Original Оригинал Greyscale Нива на сивото Black and white Черно-бяло + This printer's driver doesn't take two-sided settings from other programs, so its own setting applies. Change it in Properties… Драйверът на този принтер не приема настройки за двустранен печат от други програми и прилага собствената си настройка. Променете я от Свойства… diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 604f4f45d..bebd1cac4 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -2324,5 +2324,6 @@ scroll speed Fast Rychlá Original Originál Greyscale Stupně šedi Black and white Černobílé + This printer's driver doesn't take two-sided settings from other programs, so its own setting applies. Change it in Properties… Ovladač této tiskárny nepřijímá nastavení oboustranného tisku z jiných programů, proto platí jeho vlastní nastavení. Změňte ho ve Vlastnosti… This field's font can't draw “{c}” ({code}), so PdfCraft shows ? in its place. The value is saved, and other PDF viewers redraw the field from it. Písmo tohoto pole neumí zobrazit „{c}“ ({code}), proto PdfCraft místo něj ukazuje ?. Hodnota se uloží a jiné prohlížeče PDF pole podle ní překreslí. The fonts PdfCraft writes with can't draw “{c}” ({code}), so it shows as ? in every PDF viewer. The text is saved. Písma, kterými PdfCraft píše, neumí zobrazit „{c}“ ({code}), proto se ve všech prohlížečích PDF zobrazí jako ?. Text se uloží. diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 4f6e87570..278fb7cce 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -2250,3 +2250,4 @@ scroll speed Fast Schnell Original Original Greyscale Graustufen Black and white Schwarzweiß + This printer's driver doesn't take two-sided settings from other programs, so its own setting applies. Change it in Properties… Der Treiber dieses Druckers übernimmt keine Einstellungen für beidseitigen Druck von anderen Programmen, daher gilt seine eigene Einstellung. Ändern unter Eigenschaften… diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 336e0c877..3513d352c 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -2266,5 +2266,6 @@ scroll speed Fast Γρήγορη Original Πρωτότυπο Greyscale Κλίμακα του γκρι Black and white Ασπρόμαυρο + This printer's driver doesn't take two-sided settings from other programs, so its own setting applies. Change it in Properties… Το πρόγραμμα οδήγησης αυτού του εκτυπωτή δεν δέχεται ρυθμίσεις εκτύπωσης διπλής όψης από άλλα προγράμματα, οπότε ισχύει η δική του ρύθμιση. Αλλάξτε τη στις Ιδιότητες… This field's font can't draw “{c}” ({code}), so PdfCraft shows ? in its place. The value is saved, and other PDF viewers redraw the field from it. Η γραμματοσειρά αυτού του πεδίου δεν μπορεί να σχεδιάσει το «{c}» ({code}), γι' αυτό το PdfCraft δείχνει ? στη θέση του. Η τιμή αποθηκεύεται και άλλα προγράμματα προβολής PDF επανασχεδιάζουν το πεδίο από αυτήν. The fonts PdfCraft writes with can't draw “{c}” ({code}), so it shows as ? in every PDF viewer. The text is saved. Οι γραμματοσειρές με τις οποίες γράφει το PdfCraft δεν μπορούν να σχεδιάσουν το «{c}» ({code}), γι' αυτό εμφανίζεται ως ? σε κάθε πρόγραμμα προβολής PDF. Το κείμενο αποθηκεύεται. diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index d046a2aae..9c9b36c56 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -2300,3 +2300,4 @@ scroll speed Fast Rápida Original Original Greyscale Escala de grises Black and white Blanco y negro + This printer's driver doesn't take two-sided settings from other programs, so its own setting applies. Change it in Properties… El controlador de esta impresora no acepta los ajustes de doble cara de otros programas, así que se aplica su propio ajuste. Cámbielo en Propiedades… diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 66521af6d..38d692675 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -2249,3 +2249,4 @@ scroll speed Fast Rapide Original Original Greyscale Niveaux de gris Black and white Noir et blanc + This printer's driver doesn't take two-sided settings from other programs, so its own setting applies. Change it in Properties… Le pilote de cette imprimante n'accepte pas les réglages recto verso des autres programmes ; son propre réglage s'applique. Modifiez-le dans Propriétés… diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 9b187e626..101428e4a 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -2248,3 +2248,4 @@ scroll speed Fast Gyors Original Eredeti Greyscale Szürkeárnyalatos Black and white Fekete-fehér + This printer's driver doesn't take two-sided settings from other programs, so its own setting applies. Change it in Properties… A nyomtató illesztőprogramja nem fogadja el más programoktól a kétoldalas nyomtatás beállítását, ezért a saját beállítása érvényes. Módosítás: Tulajdonságok… diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 4c2cbb78e..ba10d5ba9 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -2258,3 +2258,4 @@ scroll speed Fast Veloce Original Originale Greyscale Scala di grigi Black and white Bianco e nero + This printer's driver doesn't take two-sided settings from other programs, so its own setting applies. Change it in Properties… Il driver di questa stampante non accetta le impostazioni fronte-retro da altri programmi, quindi vale la sua impostazione. Modificala in Proprietà… diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index b191a7046..717e3687c 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -2249,3 +2249,4 @@ scroll speed Fast 速い Original オリジナル Greyscale グレースケール Black and white 白黒 + This printer's driver doesn't take two-sided settings from other programs, so its own setting applies. Change it in Properties… このプリンターのドライバーは他のプログラムからの両面印刷の設定を受け付けないため、ドライバー自身の設定が使われます。変更先: プロパティ… diff --git a/crates/ui-egui/src/i18n/kk.tsv b/crates/ui-egui/src/i18n/kk.tsv index 669078db9..0c76f370b 100644 --- a/crates/ui-egui/src/i18n/kk.tsv +++ b/crates/ui-egui/src/i18n/kk.tsv @@ -2248,5 +2248,6 @@ scroll speed Fast Жылдам Original Түпнұсқа Greyscale Сұр реңктер Black and white Ақ-қара + This printer's driver doesn't take two-sided settings from other programs, so its own setting applies. Change it in Properties… Бұл принтердің драйвері басқа бағдарламалардан екі жақты басып шығару баптауларын қабылдамайды, сондықтан өз баптауы қолданылады. Оны мына жерден өзгертіңіз: Сипаттар… This field's font can't draw “{c}” ({code}), so PdfCraft shows ? in its place. The value is saved, and other PDF viewers redraw the field from it. Бұл өрістің қаріпі «{c}» ({code}) таңбасын сала алмайды, сондықтан PdfCraft оның орнына ? көрсетеді. Мән сақталады, ал басқа PDF қарау құралдары өрісті соған қарап қайта салады. The fonts PdfCraft writes with can't draw “{c}” ({code}), so it shows as ? in every PDF viewer. The text is saved. PdfCraft жазатын қаріптер «{c}» ({code}) таңбасын сала алмайды, сондықтан ол барлық PDF қарау құралдарында ? болып көрінеді. Мәтін сақталады. diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 56e2f243d..7b22aadc0 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -2232,3 +2232,4 @@ scroll speed Fast Szybka Original Oryginał Greyscale Skala szarości Black and white Czarno-biały + This printer's driver doesn't take two-sided settings from other programs, so its own setting applies. Change it in Properties… Sterownik tej drukarki nie przyjmuje ustawień druku dwustronnego z innych programów, więc obowiązuje jego własne ustawienie. Zmień je w oknie Właściwości… diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 878439980..0b3e174f3 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -2263,3 +2263,4 @@ scroll speed Fast Rápida Original Original Greyscale Escala de cinza Black and white Preto e branco + This printer's driver doesn't take two-sided settings from other programs, so its own setting applies. Change it in Properties… O driver desta impressora não aceita configurações de frente e verso de outros programas, então vale a configuração dele. Altere em Propriedades… diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 1784d8ab9..443c73db0 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -2162,3 +2162,4 @@ scroll speed Fast Высокая Original Оригинал Greyscale Оттенки серого Black and white Чёрно-белый + This printer's driver doesn't take two-sided settings from other programs, so its own setting applies. Change it in Properties… Драйвер этого принтера не принимает настройки двусторонней печати от других программ, поэтому действует его собственная настройка. Измените её в окне Свойства… diff --git a/crates/ui-egui/src/i18n/sl.tsv b/crates/ui-egui/src/i18n/sl.tsv index da8ae8b0c..a5a465a0f 100644 --- a/crates/ui-egui/src/i18n/sl.tsv +++ b/crates/ui-egui/src/i18n/sl.tsv @@ -2255,5 +2255,6 @@ scroll speed Fast Hitro Original Izvirnik Greyscale Sivine Black and white Črno-belo + This printer's driver doesn't take two-sided settings from other programs, so its own setting applies. Change it in Properties… Gonilnik tega tiskalnika ne sprejema nastavitev obojestranskega tiskanja iz drugih programov, zato velja njegova lastna nastavitev. Spremenite jo v Lastnosti… This field's font can't draw “{c}” ({code}), so PdfCraft shows ? in its place. The value is saved, and other PDF viewers redraw the field from it. Pisava tega polja ne more prikazati »{c}« ({code}), zato PdfCraft namesto njega prikaže ?. Vrednost se shrani, drugi pregledovalniki PDF pa polje znova narišejo iz nje. The fonts PdfCraft writes with can't draw “{c}” ({code}), so it shows as ? in every PDF viewer. The text is saved. Pisave, s katerimi piše PdfCraft, ne morejo prikazati »{c}« ({code}), zato se v vseh pregledovalnikih PDF prikaže kot ?. Besedilo se shrani. diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 510f49937..6db2ea5bb 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -2246,5 +2246,6 @@ scroll speed Fast Hızlı Original Orijinal Greyscale Gri tonlamalı Black and white Siyah beyaz + This printer's driver doesn't take two-sided settings from other programs, so its own setting applies. Change it in Properties… Bu yazıcının sürücüsü diğer programlardan gelen çift taraflı yazdırma ayarlarını kabul etmez, bu yüzden kendi ayarı geçerlidir. Değiştirmek için: Özellikler… This field's font can't draw “{c}” ({code}), so PdfCraft shows ? in its place. The value is saved, and other PDF viewers redraw the field from it. Bu alanın yazı tipi “{c}” ({code}) karakterini çizemiyor, bu yüzden PdfCraft yerine ? gösteriyor. Değer kaydedilir ve diğer PDF görüntüleyiciler alanı ondan yeniden çizer. The fonts PdfCraft writes with can't draw “{c}” ({code}), so it shows as ? in every PDF viewer. The text is saved. PdfCraft'ın yazdığı yazı tipleri “{c}” ({code}) karakterini çizemiyor, bu yüzden her PDF görüntüleyicide ? olarak görünür. Metin kaydedilir. diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 69ac8f85c..592d3eba3 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -2271,3 +2271,4 @@ scroll speed Fast Швидка Original Оригінал Greyscale Відтінки сірого Black and white Чорно-білий + This printer's driver doesn't take two-sided settings from other programs, so its own setting applies. Change it in Properties… Драйвер цього принтера не приймає налаштування двобічного друку від інших програм, тому діє його власне налаштування. Змініть його у вікні Властивості… diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 4c6ddf8ca..0feed72ea 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -2213,3 +2213,4 @@ scroll speed Fast 快 Original 原始 Greyscale 灰度 Black and white 黑白 + This printer's driver doesn't take two-sided settings from other programs, so its own setting applies. Change it in Properties… 此打印机的驱动程序不接受其他程序的双面打印设置,因此使用其自身设置。更改位置:属性… diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 5722dc797..4c2b5aa60 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -2126,3 +2126,4 @@ scroll speed Fast 快 Original 原始 Greyscale 灰階 Black and white 黑白 + This printer's driver doesn't take two-sided settings from other programs, so its own setting applies. Change it in Properties… 此印表機的驅動程式不接受其他程式的雙面列印設定,因此使用其自身設定。變更位置:屬性… diff --git a/crates/ui-egui/src/print_ui.rs b/crates/ui-egui/src/print_ui.rs index 2f1924d1a..16f90f72f 100644 --- a/crates/ui-egui/src/print_ui.rs +++ b/crates/ui-egui/src/print_ui.rs @@ -617,6 +617,13 @@ pub(crate) fn body( }); ui.end_row(); }); + // Windows says which drivers can't print two-sided as they're asked; they keep their + // own setting, even when Two-sided is Off. + if let Some(p) = d.printers.iter().find(|p| d.printer.as_ref() == Some(&p.name)) + && p.two_sided == Some(false) + { + ui.label(egui::RichText::new(tl!("This printer's driver doesn't take two-sided settings from other programs, so its own setting applies. Change it in Properties…")).small().color(t.text_muted)); + } if let Some(e) = &d.driver_error { ui.label(egui::RichText::new(crate::i18n::fmt(tl!("Couldn't open the printer's preferences: {e}"), &[("e", e)])).small().color(t.text_muted)); } diff --git a/crates/ui-egui/tests/print.rs b/crates/ui-egui/tests/print.rs index 4db97e8b0..2486c474a 100644 --- a/crates/ui-egui/tests/print.rs +++ b/crates/ui-egui/tests/print.rs @@ -205,7 +205,10 @@ fn printers_listed_in_the_background_preselect_the_default_unless_one_was_picked let wait = gate.clone(); h.state_mut().printer_source = Some(Arc::new(move || { let _open = wait.lock(); - vec![Printer { name: "Office".into(), default: false }, Printer { name: "Front Desk".into(), default: true }] + vec![ + Printer { name: "Office".into(), default: false, two_sided: None }, + Printer { name: "Front Desk".into(), default: true, two_sided: None }, + ] })); assert!(h.state_mut().execute("print.dialog")); h.run_steps(2); @@ -282,3 +285,29 @@ fn custom_page_cannot_silently_match_a_standard_paper() { assert_eq!(h.state().print_draft.paper, before.paper); assert_eq!(h.state().print_draft.size, before.size); } + +/// A driver Windows reports without two-sided printing keeps its own setting whatever Two-sided +/// says (some print both sides with it Off), so the dialog says where to change it. +#[test] +fn a_printer_without_two_sided_printing_points_to_its_properties() { + use pdfcraft_engine::print::spool::Printer; + let mut h = harness(); + h.state_mut().printer_source = Some(std::sync::Arc::new(|| { + vec![ + Printer { name: "Office PCL".into(), default: true, two_sided: Some(true) }, + Printer { name: "Office PS".into(), default: false, two_sided: Some(false) }, + ] + })); + assert!(h.state_mut().execute("print.dialog")); + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10); + while h.state().print_draft.printers.is_empty() && std::time::Instant::now() < deadline { + h.run_steps(1); + std::thread::sleep(std::time::Duration::from_millis(5)); + } + h.run_steps(2); + let note = "doesn't take two-sided settings"; + assert!(h.query_by_label_contains(note).is_none(), "a driver that prints two-sided as asked"); + h.state_mut().print_draft.printer = Some("Office PS".into()); + h.run_steps(2); + h.get_by_label_contains(note); +}