mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-14 19:27:04 -04:00
M10: Windows two-sided binds on the chosen edge; note drivers that ignore Two-sided (#1154)
The print script mapped "Flip on long edge" to System.Drawing's Duplex.Horizontal and "short edge" to Vertical. Those are DEVMODE's DMDUP_HORIZONTAL (short-edge binding) and DMDUP_VERTICAL (long-edge binding), so every Windows two-sided job bound on the other edge: a PCL driver received @PJL SET BINDING = TOP for long edge. They are swapped back. Win32_Printer's Capabilities say whether a driver prints two-sided (3, Duplex Printing). The printer list already comes from Win32_Printer, so it now carries that as Printer::two_sided (None on CUPS), the printers tool reports it, and the Print dialog notes when the selected printer's driver doesn't take two-sided settings and points to Properties…. Such drivers keep their own setting even with Two-sided Off. No vendor-specific names are used. Co-authored-by: Claude Opus 5.5 <[email protected]> Co-authored-by: Brandon Thomas <[email protected]>
This commit is contained in:
26 files changed
+140
-41
No files matched your search
+19
-19
@@ -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"
|
||||
@@ -12,7 +12,16 @@ fn bad(m: impl Into<String>) -> ToolError {
|
||||
|
||||
impl Automation {
|
||||
pub(crate) fn printers(&self) -> Result<Value> {
|
||||
let list: Vec<Value> = spool::printers().into_iter().map(|p| json!({ "name": p.name, "default": p.default })).collect();
|
||||
let list: Vec<Value> = 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 }))
|
||||
}
|
||||
|
||||
|
||||
+23
-10
@@ -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<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
|
||||
@@ -53,7 +57,7 @@ pub fn parse_lpstat(out: &str) -> Vec<Printer> {
|
||||
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<Printer> {
|
||||
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<Printer> {
|
||||
let mut printers: Vec<Printer> = 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;
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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… لا يقبل برنامج تشغيل هذه الطابعة إعدادات الطباعة على الوجهين من البرامج الأخرى، لذا يُطبَّق إعداده الخاص. غيّره من خصائص…
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -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… Драйверът на този принтер не приема настройки за двустранен печат от други програми и прилага собствената си настройка. Променете я от Свойства…
|
||||
|
Can't render this file because it contains an unexpected character in line 8 and column 16.
|
@@ -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ží.
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -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…
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 51.
|
@@ -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. Το κείμενο αποθηκεύεται.
|
||||
|
Can't render this file because it contains an unexpected character in line 477 and column 12.
|
@@ -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…
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 60.
|
@@ -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…
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 39.
|
@@ -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…
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 44.
|
@@ -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à…
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -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… このプリンターのドライバーは他のプログラムからの両面印刷の設定を受け付けないため、ドライバー自身の設定が使われます。変更先: プロパティ…
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -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 қарау құралдарында ? болып көрінеді. Мәтін сақталады.
|
||||
|
Can't render this file because it contains an unexpected character in line 813 and column 12.
|
@@ -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…
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -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…
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -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… Драйвер этого принтера не принимает настройки двусторонней печати от других программ, поэтому действует его собственная настройка. Измените её в окне Свойства…
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -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.
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -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.
|
||||
|
Can't render this file because it contains an unexpected character in line 811 and column 12.
|
@@ -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… Драйвер цього принтера не приймає налаштування двобічного друку від інших програм, тому діє його власне налаштування. Змініть його у вікні Властивості…
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -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… 此打印机的驱动程序不接受其他程序的双面打印设置,因此使用其自身设置。更改位置:属性…
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -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… 此印表機的驅動程式不接受其他程式的雙面列印設定,因此使用其自身設定。變更位置:屬性…
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in new issue
Block a user