forked from 1b-agent/opencadstudio-upstream
fix(print): ask the driver for the DEVMODE size with no mode flags
`with_printer_devmode` sized the driver's DEVMODE with a
`DocumentPropertiesW` call that passed `DM_OUT_BUFFER` and no output
buffer to write into. That combination is not a size query: drivers
answer it with -1, the `needed <= 0` guard returned early, and the
function handed back `None` for every printer on every machine.
Measured here on Windows 11 x86-64, both an installed virtual printer
and a physical one:
fMode = 0 -> 5428 / 2684 bytes (the real size)
fMode = DM_OUT_BUFFER, NULL out -> -1
Two things were broken by it, both silently:
- `oriented_printer_devmode` builds the DEVMODE a print job is spooled
with. Getting `None` back, the direct GDI path fell through to
"CreateDC then falls back to the driver default", so the plot's
orientation and sheet never reached the job. A landscape plot came
out however the driver happened to be set. This is the route users
without a PDF application take.
- `PRINTERS <name>` could never name the driver's default sheet,
because the id it matches on comes from that same DEVMODE.
With the size query asking for a size, both work: the report now says
"Default sheet: ISO A4 (210 x 297 mm)" for both printers here, and a
landscape plot leaves `dmOrientation = DMORIENT_LANDSCAPE` set in the
job's DEVMODE.
The two new tests both fail with the old flag and pass with it removed.
The existing GDI ink tests do not catch this: they accept the raster
rotation fallback as an equally good outcome, so the page looked right
while the DEVMODE was empty.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
1 parent
d40e6c8d1d
commit
c73ee2c5aa
1 file changed
+76
-1
@@ -625,13 +625,17 @@ fn with_printer_devmode(
|
||||
return None;
|
||||
}
|
||||
let result = (|| {
|
||||
// The size query is the one call that takes no mode flags at all:
|
||||
// with DM_OUT_BUFFER and no output buffer to write into, the
|
||||
// driver reports failure (-1) instead of the size, and every
|
||||
// caller below silently lost the driver's DEVMODE.
|
||||
let needed = DocumentPropertiesW(
|
||||
std::ptr::null_mut(),
|
||||
handle,
|
||||
device_wide.as_ptr(),
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null(),
|
||||
DM_OUT_BUFFER,
|
||||
0,
|
||||
);
|
||||
if needed <= 0 {
|
||||
return None;
|
||||
@@ -1518,6 +1522,77 @@ mod printer_properties_tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// The driver's DEVMODE has to come back for a real printer. It stopped
|
||||
/// coming back once the size query asked with `DM_OUT_BUFFER` and no
|
||||
/// output buffer, which drivers answer with -1: `with_printer_devmode`
|
||||
/// then returned `None` for every printer, so the plot's sheet and
|
||||
/// orientation never reached the job's DEVMODE and the printer report
|
||||
/// could never name a default sheet.
|
||||
#[cfg(target_os = "windows")]
|
||||
#[test]
|
||||
fn printer_devmode_comes_back_for_a_real_printer() {
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use windows_sys::Win32::Graphics::Gdi::DEVMODEW;
|
||||
|
||||
let device: Vec<u16> = std::ffi::OsStr::new("Microsoft Print to PDF")
|
||||
.encode_wide()
|
||||
.chain(Some(0))
|
||||
.collect();
|
||||
let devmode = super::with_printer_devmode(&device, |_, _| true)
|
||||
.expect("the driver must report a DEVMODE for an installed printer");
|
||||
assert!(
|
||||
devmode.len() >= std::mem::size_of::<DEVMODEW>(),
|
||||
"DEVMODE buffer is {} bytes, shorter than the public struct",
|
||||
devmode.len()
|
||||
);
|
||||
// SAFETY: the buffer holds at least a public DEVMODEW, just checked.
|
||||
let named = unsafe { (*(devmode.as_ptr() as *const DEVMODEW)).dmSize } as usize;
|
||||
assert!(named > 0 && named <= devmode.len(), "dmSize {named} outside the buffer");
|
||||
|
||||
// And the symptom the lost DEVMODE produced: the driver's default
|
||||
// sheet is one of the sheets it lists, so the report can name it.
|
||||
let caps = crate::io::plot_device::printer_capabilities("Microsoft Print to PDF")
|
||||
.expect("an installed printer reports sheets");
|
||||
assert!(
|
||||
caps.default_paper.is_some(),
|
||||
"the driver's default sheet should resolve to one of its {} listed sheets",
|
||||
caps.media.len()
|
||||
);
|
||||
}
|
||||
|
||||
/// The plot's orientation has to survive into the DEVMODE the print job
|
||||
/// is spooled with. While the DEVMODE query was broken this silently
|
||||
/// returned `None` for every printer and `CreateDC` fell back to the
|
||||
/// driver's own default, so a landscape plot came out however the driver
|
||||
/// happened to be set. The GDI ink tests cannot catch that: they accept
|
||||
/// the raster-rotation fallback as an equally good outcome.
|
||||
#[cfg(target_os = "windows")]
|
||||
#[test]
|
||||
fn the_plot_orientation_reaches_the_job_devmode() {
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use windows_sys::Win32::Graphics::Gdi::{
|
||||
DEVMODEW, DMORIENT_LANDSCAPE, DMORIENT_PORTRAIT, DM_ORIENTATION,
|
||||
};
|
||||
|
||||
let device: Vec<u16> = std::ffi::OsStr::new("Microsoft Print to PDF")
|
||||
.encode_wide()
|
||||
.chain(Some(0))
|
||||
.collect();
|
||||
for (landscape, want) in [(false, DMORIENT_PORTRAIT), (true, DMORIENT_LANDSCAPE)] {
|
||||
let buf = super::oriented_printer_devmode(&device, landscape, None)
|
||||
.unwrap_or_else(|| panic!("landscape={landscape}: the driver reported no DEVMODE"));
|
||||
// SAFETY: `with_printer_devmode` sizes the buffer from the driver.
|
||||
let dm = unsafe { &*(buf.as_ptr() as *const DEVMODEW) };
|
||||
assert!(
|
||||
dm.dmFields & DM_ORIENTATION != 0,
|
||||
"landscape={landscape}: DM_ORIENTATION not marked as set"
|
||||
);
|
||||
// SAFETY: the printer branch of the DEVMODE union.
|
||||
let got = unsafe { dm.Anonymous1.Anonymous1.dmOrientation };
|
||||
assert_eq!(got, want as i16, "landscape={landscape}: wrong dmOrientation");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[test]
|
||||
fn macos_opens_print_settings() {
|
||||
|
||||
Reference in new issue
Block a user