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 _ = CoInitializeEx(None, COINIT_MULTITHREADED);
60 defer! {
61 CoUninitialize();
62 }
63 let event = CreateEventW(None, true, false, None)
64 .map_err(XpsPrinterError::FailedToCreateEvent)?;
65 defer! {
66 let _ = CloseHandle(event);
67 }
68 let xps_factory: IXpsOMObjectFactory =
69 CoCreateInstance(&XpsOMObjectFactory, None, CLSCTX_ALL)
70 .map_err(XpsPrinterError::FailedToCreateObjectFactory)?;
71 let mut document_stream = Option::<IXpsPrintJobStream>::None;
72 let mut print_ticket_stream = Option::<IXpsPrintJobStream>::None;
73 StartXpsPrintJob(
74 PCWSTR(wchar::to_wide_chars(self.printer.os_name()).as_ptr()),
75 PCWSTR(wchar::to_wide_chars(path.file_name().unwrap_or(path.as_ref())).as_ptr()),
76 None,
77 HANDLE::default(),
78 event,
79 &[],
80 ptr::null_mut(),
81 ptr::addr_of_mut!(document_stream),
82 ptr::addr_of_mut!(print_ticket_stream),
83 )
84 .map_err(XpsPrinterError::FailedToStartJob)?;
85 let print_ticket_stream =
86 print_ticket_stream.ok_or(XpsPrinterError::StreamNotAvailable)?;
87 let print_ticket = options.get_xml();
88 let mut print_ticket_written = MaybeUninit::<u32>::uninit();
89 print_ticket_stream
90 .Write(
91 print_ticket.as_ptr() as *const _,
92 print_ticket.len() as u32,
93 Some(print_ticket_written.as_mut_ptr()),
94 )
95 .ok()
96 .map_err(XpsPrinterError::FailedToApplyPrintTicket)?;
97 print_ticket_stream
98 .Close()
99 .map_err(XpsPrinterError::FailedToApplyPrintTicket)?;
100 let document_stream = document_stream.ok_or(XpsPrinterError::StreamNotAvailable)?;
101 let xps_package = xps_factory
102 .CreatePackageFromFile(PCWSTR(wchar::to_wide_chars(path).as_ptr()), false)
103 .map_err(XpsPrinterError::FailedToWriteDocument)?;
104 xps_package
105 .WriteToStream(&document_stream, false)
106 .map_err(XpsPrinterError::FailedToWriteDocument)?;
107 document_stream
108 .Close()
109 .map_err(XpsPrinterError::FailedToWriteDocument)?;
110 WaitForSingleObject(event, INFINITE);
111 }
112 Ok(())
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::XpsPrinter;
119 use crate::{printer::FilePrinter, test_utils::null_device};
120 use std::path::Path;
121
122 #[test]
123 fn print_simple_xps_document() {
124 let device = null_device::thread_local();
125 let xps = XpsPrinter::new(device);
126 let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("test_data/test_document.xps");
127 xps.print(path.as_path(), Default::default()).unwrap();
128 }
129}