Skip to main content

winprint_ext/printer/
xps.rs

1use crate::printer::FilePrinter;
2use crate::printer::PrinterDevice;
3use crate::ticket::PrintTicket;
4use crate::utils::wchar;
5use scopeguard::defer;
6use std::mem::MaybeUninit;
7use std::path::Path;
8use std::ptr;
9use thiserror::Error;
10use windows::{
11    core::PCWSTR,
12    Win32::{
13        Foundation::{CloseHandle, HANDLE},
14        Storage::Xps::{Printing::*, *},
15        System::{Com::*, Threading::*},
16    },
17};
18
19#[derive(Error, Debug)]
20/// Represents an error from [`XpsPrinter`].
21pub enum XpsPrinterError {
22    /// Failed to create event.
23    #[error("Failed to create event")]
24    FailedToCreateEvent(#[source] windows::core::Error),
25    /// Failed to create object factory.
26    #[error("Failed to create object factory")]
27    FailedToCreateObjectFactory(#[source] windows::core::Error),
28    /// Failed to start job.
29    #[error("Failed to start job")]
30    FailedToStartJob(#[source] windows::core::Error),
31    /// Failed to apply print ticket.
32    #[error("Failed to apply print ticket")]
33    FailedToApplyPrintTicket(#[source] windows::core::Error),
34    /// Failed to write document.
35    #[error("Failed to write document")]
36    FailedToWriteDocument(#[source] windows::core::Error),
37    /// Stream is not available.
38    #[error("Stream is not available")]
39    StreamNotAvailable,
40}
41
42/// A printer to print XPS documents.
43pub struct XpsPrinter {
44    printer: PrinterDevice,
45}
46
47impl XpsPrinter {
48    /// Create a new [`XpsPrinter`] for the given printer device.
49    pub fn new(printer: PrinterDevice) -> Self {
50        Self { printer }
51    }
52}
53
54impl FilePrinter for XpsPrinter {
55    type Options = PrintTicket;
56    type Error = XpsPrinterError;
57    fn print(&self, path: &Path, options: PrintTicket) -> std::result::Result<(), XpsPrinterError> {
58        unsafe {
59            let _com = crate::utils::com::ComInitializer::new();
60            let event = CreateEventW(None, true, false, None)
61                .map_err(XpsPrinterError::FailedToCreateEvent)?;
62            defer! {
63                let _ = CloseHandle(event);
64            }
65            let xps_factory: IXpsOMObjectFactory =
66                CoCreateInstance(&XpsOMObjectFactory, None, CLSCTX_ALL)
67                    .map_err(XpsPrinterError::FailedToCreateObjectFactory)?;
68            let mut document_stream = Option::<IXpsPrintJobStream>::None;
69            let mut print_ticket_stream = Option::<IXpsPrintJobStream>::None;
70            StartXpsPrintJob(
71                PCWSTR(wchar::to_wide_chars(self.printer.os_name()).as_ptr()),
72                PCWSTR(wchar::to_wide_chars(path.file_name().unwrap_or(path.as_ref())).as_ptr()),
73                None,
74                HANDLE::default(),
75                event,
76                &[],
77                ptr::null_mut(),
78                ptr::addr_of_mut!(document_stream),
79                ptr::addr_of_mut!(print_ticket_stream),
80            )
81            .map_err(XpsPrinterError::FailedToStartJob)?;
82            let print_ticket_stream =
83                print_ticket_stream.ok_or(XpsPrinterError::StreamNotAvailable)?;
84            let print_ticket = options.get_xml();
85            let mut print_ticket_written = MaybeUninit::<u32>::uninit();
86            print_ticket_stream
87                .Write(
88                    print_ticket.as_ptr() as *const _,
89                    print_ticket.len() as u32,
90                    Some(print_ticket_written.as_mut_ptr()),
91                )
92                .ok()
93                .map_err(XpsPrinterError::FailedToApplyPrintTicket)?;
94            print_ticket_stream
95                .Close()
96                .map_err(XpsPrinterError::FailedToApplyPrintTicket)?;
97            let document_stream = document_stream.ok_or(XpsPrinterError::StreamNotAvailable)?;
98            let xps_package = xps_factory
99                .CreatePackageFromFile(PCWSTR(wchar::to_wide_chars(path).as_ptr()), false)
100                .map_err(XpsPrinterError::FailedToWriteDocument)?;
101            xps_package
102                .WriteToStream(&document_stream, false)
103                .map_err(XpsPrinterError::FailedToWriteDocument)?;
104            document_stream
105                .Close()
106                .map_err(XpsPrinterError::FailedToWriteDocument)?;
107            WaitForSingleObject(event, INFINITE);
108        }
109        Ok(())
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::XpsPrinter;
116    use crate::{printer::FilePrinter, test_utils::null_device};
117    use std::path::Path;
118
119    #[test]
120    fn print_simple_xps_document() {
121        let device = null_device::thread_local();
122        let xps = XpsPrinter::new(device);
123        let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("test_data/test_document.xps");
124        xps.print(path.as_path(), Default::default()).unwrap();
125    }
126}