Skip to main content

winprint_ext/ticket/
print_ticket.rs

1use super::{
2    document::{writer::XmlDocumentRoot, PrintTicketDocument},
3    DEFAULT_PRINT_TICKET_XML,
4};
5use crate::{printer::PrinterDevice, utils::wchar};
6use scopeguard::defer;
7use std::ptr;
8use thiserror::Error;
9use windows::{
10    core::{BSTR, PCWSTR},
11    Win32::{
12        Graphics::{
13            Gdi::{DM_IN_BUFFER, DM_OUT_BUFFER},
14            Printing::{
15                ClosePrinter, DocumentPropertiesW, OpenPrinterW,
16                PrintTicket::{
17                    kPTJobScope, kUserDefaultDevmode, PTCloseProvider,
18                    PTConvertPrintTicketToDevMode, PTOpenProvider, PTReleaseMemory,
19                },
20                PRINTER_HANDLE,
21            },
22        },
23        UI::{Shell::SHCreateMemStream, WindowsAndMessaging::IDOK},
24    },
25};
26
27#[derive(Clone, Debug)]
28/// Represents a print ticket.
29pub struct PrintTicket {
30    pub(crate) xml: Vec<u8>,
31}
32
33impl Default for PrintTicket {
34    fn default() -> Self {
35        Self {
36            xml: DEFAULT_PRINT_TICKET_XML.into(),
37        }
38    }
39}
40
41#[derive(Error, Debug)]
42/// Represents an error occurred while converting print ticket to dev mode.
43pub enum ToDevModeError {
44    /// Failed to open print ticket provider.
45    #[error("Failed to open print ticket provider")]
46    OpenProviderFailed(#[source] windows::core::Error),
47    /// Stream not allocated.
48    #[error("Stream not allocated")]
49    StreamNotAllocated,
50    /// Failed to convert print ticket to dev mode.
51    #[error("Failed to convert print ticket to dev mode")]
52    ConvertPrintTicketToDevModeFailed(String, #[source] windows::core::Error),
53    /// Failed to open printer.
54    #[error("Failed to open printer")]
55    FailedToOpenPrinter(#[source] windows::core::Error),
56    /// Failed to correct dev mode via [`DocumentProperties`].
57    ///
58    /// [`DocumentProperties`]: https://learn.microsoft.com/en-us/windows/win32/printdocs/documentproperties
59    #[error("Failed to correct dev mode via DocumentProperties")]
60    FailedToCorrectDevMode,
61}
62
63impl PrintTicket {
64    /// Create a new print ticket from the given XML.
65    pub fn from_xml(xml: impl Into<Vec<u8>>) -> Self {
66        Self { xml: xml.into() }
67    }
68
69    /// Into the XML of the print ticket.
70    pub fn into_xml(self) -> Vec<u8> {
71        self.xml
72    }
73
74    /// Get the XML of the print ticket.
75    pub fn get_xml(&self) -> &[u8] {
76        &self.xml
77    }
78
79    /// Convert the print ticket to [`DEVMODE`] data.
80    ///
81    /// [`DEVMODE`]: https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-devmodew
82    pub fn to_dev_mode(&self, device: &PrinterDevice) -> Result<Vec<u8>, ToDevModeError> {
83        unsafe {
84            let provider =
85                PTOpenProvider(PCWSTR(wchar::to_wide_chars(device.os_name()).as_ptr()), 1)
86                    .map_err(ToDevModeError::OpenProviderFailed)?;
87            defer! {
88                let _ = PTCloseProvider(provider);
89            }
90
91            let stream = SHCreateMemStream(Some(self.get_xml()))
92                .ok_or(ToDevModeError::StreamNotAllocated)?;
93
94            let mut dev_mode_size = 0;
95            let mut dev_mode_data = std::ptr::null_mut();
96            let mut error_message = BSTR::default();
97            PTConvertPrintTicketToDevMode(
98                provider,
99                &stream,
100                kUserDefaultDevmode,
101                kPTJobScope,
102                ptr::addr_of_mut!(dev_mode_size),
103                ptr::addr_of_mut!(dev_mode_data),
104                Some(&mut error_message),
105            )
106            .map_err(|win32_error| {
107                ToDevModeError::ConvertPrintTicketToDevModeFailed(
108                    error_message.to_string(),
109                    win32_error,
110                )
111            })?;
112            defer! {
113                let _ = PTReleaseMemory(dev_mode_data as *mut _);
114            }
115
116            let printer_name = wchar::to_wide_chars(device.os_name());
117            let printer_handle = {
118                let mut printer_handle = PRINTER_HANDLE::default();
119                OpenPrinterW(
120                    PCWSTR(printer_name.as_ptr()),
121                    ptr::addr_of_mut!(printer_handle),
122                    None,
123                )
124                .map_err(ToDevModeError::FailedToOpenPrinter)?;
125                printer_handle
126            };
127            defer! {
128                let _ = ClosePrinter(printer_handle);
129            }
130
131            let mut buffer_size = DocumentPropertiesW(
132                None,
133                printer_handle,
134                PCWSTR(printer_name.as_ptr()),
135                None,
136                None,
137                0,
138            );
139
140            // Workaround for buggy printer drivers
141            // See also: https://chromium.googlesource.com/chromium/src/+/refs/tags/124.0.6347.1/printing/backend/win_helper.cc#586
142            buffer_size = buffer_size * 2 + 8192;
143
144            let mut buffer = vec![0u8; buffer_size as usize];
145            if DocumentPropertiesW(
146                None,
147                printer_handle,
148                PCWSTR(printer_name.as_ptr()),
149                Some(buffer.as_mut_ptr() as *mut _),
150                Some(dev_mode_data as *mut _),
151                (DM_IN_BUFFER | DM_OUT_BUFFER).0,
152            ) != IDOK.0
153            {
154                return Err(ToDevModeError::FailedToCorrectDevMode);
155            }
156
157            Ok(buffer)
158        }
159    }
160}
161
162impl From<PrintTicketDocument> for PrintTicket {
163    fn from(value: PrintTicketDocument) -> Self {
164        Self {
165            xml: value.to_xml(),
166        }
167    }
168}