print: xps_driver says whether a Windows printer's driver renders XPS itself (#804)

* print: xps_driver says whether a Windows printer's driver renders XPS itself

craft_print::xps_driver(printer) reads PRINTER_DRIVER_XPS through winprint's new
PrinterDevice::is_xps_driver (storytold/winprint.rs#21): Some(true) for v4 drivers and v3
drivers with an XPS rendering filter, Some(false) for GDI drivers, None when it can't be read and
on other systems. On a GDI driver, reached through Windows' XPS-to-GDI conversion, a page with
transparency prints with halos, as black boxes or not at all (storytold/pdfcraft#6155, #5838,
#5940), so PdfCraft will flatten it for those drivers only.

The Windows end-to-end test checks the CI queue's "Generic / Text Only" driver reads as GDI.

* Test: the GDI-driver check in a helper (keeps the end-to-end test under the complexity limit)

* winprint bc06a7c48b768eb27a828c7986f6d2868b245ee0: storytold/winprint.rs main with #21 (PrinterDevice::is_xps_driver)
This commit is contained in:
FredApps
2026-11-11 07:48:44 +01:00
committed by GitHub
parent 797d21a3f3
commit 60ede99985
5 changed files with 34 additions and 2 deletions
Generated
+1 -1
View File
@@ -7142,7 +7142,7 @@ dependencies = [
[[package]]
name = "winprint"
version = "0.2.1"
source = "git+https://github.com/storytold/winprint.rs?rev=3c978dfcf3d460577fabd87aeea7a84ffdcfb396#3c978dfcf3d460577fabd87aeea7a84ffdcfb396"
source = "git+https://github.com/storytold/winprint.rs?rev=bc06a7c48b768eb27a828c7986f6d2868b245ee0#bc06a7c48b768eb27a828c7986f6d2868b245ee0"
dependencies = [
"flate2",
"fmt-derive",
+1 -1
View File
@@ -35,7 +35,7 @@ log = "0.4"
# ticket (winprint.rs#12, storytold/pdfcraft#3823), and reads whitespace-padded integers in
# PrintCapabilities (winprint.rs#3, storytold/pdfcraft#2430). Pinned to an immutable revision;
# review before bumping.
winprint = { git = "https://github.com/storytold/winprint.rs", rev = "3c978dfcf3d460577fabd87aeea7a84ffdcfb396", default-features = false }
winprint = { git = "https://github.com/storytold/winprint.rs", rev = "bc06a7c48b768eb27a828c7986f6d2868b245ee0", default-features = false }
# GetDefaultPrinter (MIT).
winsafe = { version = "0.0.29", default-features = false, features = ["winspool"] }
# The job file's private folder.
+17
View File
@@ -653,6 +653,23 @@ pub fn two_sided(printer: &str) -> Option<bool> {
}
}
/// Whether `printer`'s driver renders XPS itself: on Windows, `Some(true)` for v4 drivers and v3
/// drivers with an XPS rendering filter, `Some(false)` for GDI drivers, which the XPS print API
/// reaches through Windows' XPS-to-GDI conversion. A page with transparency can come out wrong or
/// fail to print there (storytold/pdfcraft#6155, #5838, #5940), so an app may flatten it first.
/// `None` when it can't be read or no queue has that name, and on other systems.
pub fn xps_driver(printer: &str) -> Option<bool> {
#[cfg(windows)]
{
windows::xps_driver_named(printer)
}
#[cfg(not(windows))]
{
let _ = printer;
None
}
}
/// The Print dialog's printers from lpstat's two answers: the spooler's queues (`-p -d`), then
/// the driverless destinations of `available` (`-e`) that have no queue — CUPS builds a temporary
/// queue when `lp` sends them a job. Each destination once, queues first; the default still comes
+9
View File
@@ -454,6 +454,14 @@ fn windows_powershell(script: &str, printer: &str) -> Result<String, String> {
/// them; the test uses PowerShell only to add, inspect and remove the printer. Its driver is the
/// inbox "Generic / Text Only"; a machine that cannot add one (no rights, no driver) skips the
/// test, unless `CRAFT_PRINT_REQUIRE_QUEUE=1` (CI) makes it mandatory.
/// "Generic / Text Only" is a GDI (v3) driver: the XPS print API reaches it through Windows'
/// XPS-to-GDI conversion, where an app may flatten transparency first (storytold/pdfcraft#6155).
#[cfg(windows)]
fn assert_gdi_driver(name: &str) {
assert_eq!(spool::xps_driver(name), Some(false), "the test queue's driver is a GDI driver");
assert_eq!(spool::xps_driver("Craft Test Print: no such queue"), None);
}
#[cfg(windows)]
#[test]
fn windows_jobs_reach_the_spooler() {
@@ -474,6 +482,7 @@ fn windows_jobs_reach_the_spooler() {
return;
}
let (listed, probed) = driver_queries(|| spool::printers().into_iter().find(|p| p.name == name));
assert_gdi_driver(name);
// The list a Print dialog shows first: the same queue, named without asking its driver (a
// networked printer in power save can hold that query, storytold/wordcraft#5710).
let (queue, unprobed) = driver_queries(|| spool::printer_queues().into_iter().find(|p| p.name == name));
+6
View File
@@ -68,6 +68,12 @@ pub(crate) fn two_sided_named(name: &str) -> Option<bool> {
PrinterDevice::all().ok()?.iter().find(|d| d.name() == name).and_then(two_sided)
}
/// Whether the driver of the queue `name` renders XPS itself (`PRINTER_DRIVER_XPS`); `None`
/// when no queue has that name or its driver can't be read.
pub(crate) fn xps_driver_named(name: &str) -> Option<bool> {
PrinterDevice::all().ok()?.iter().find(|d| d.name() == name)?.is_xps_driver().ok()
}
#[cfg(test)]
thread_local! {
/// The driver capability queries printer discovery made on this thread, so a test can show