winprint_ext/printer/
xps.rs1use 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)]
20pub enum XpsPrinterError {
22 #[error("Failed to create event")]
24 FailedToCreateEvent(#[source] windows::core::Error),
25 #[error("Failed to create object factory")]
27 FailedToCreateObjectFactory(#[source] windows::core::Error),
28 #[error("Failed to start job")]
30 FailedToStartJob(#[source] windows::core::Error),
31 #[error("Failed to apply print ticket")]
33 FailedToApplyPrintTicket(#[source] windows::core::Error),
34 #[error("Failed to write document")]
36 FailedToWriteDocument(#[source] windows::core::Error),
37 #[error("Stream is not available")]
39 StreamNotAvailable,
40}
41
42pub struct XpsPrinter {
44 printer: PrinterDevice,
45}
46
47impl XpsPrinter {
48 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(all(test, feature = "test-utils"))]
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}