winprint_ext/ticket/
builder.rs1use super::{PrintTicket, DEFAULT_PRINT_TICKET_XML};
2use crate::{
3 printer::PrinterDevice,
4 utils::{stream::copy_com_stream_to_vec, wchar},
5};
6use thiserror::Error;
7use windows::{
8 core::{BSTR, PCWSTR},
9 Win32::{
10 Graphics::Printing::PrintTicket::{
11 kPTJobScope, PTCloseProvider, PTMergeAndValidatePrintTicket, PTOpenProvider,
12 HPTPROVIDER,
13 },
14 UI::Shell::SHCreateMemStream,
15 },
16};
17
18pub struct PrintTicketBuilder {
20 xml: Vec<u8>,
21 provider: HPTPROVIDER,
22}
23
24#[derive(Error, Debug)]
25pub enum PrintTicketBuilderError {
27 #[error("Failed to open print ticket provider")]
29 OpenProviderFailed(#[source] windows::core::Error),
30 #[error("Stream not allocated")]
32 StreamNotAllocated,
33 #[error("Failed to merge print tickets")]
35 MergePrintTicketsFailed(String, #[source] windows::core::Error),
36 #[error("Failed to decode print ticket")]
38 DecodePrintTicketFailed(#[source] windows::core::Error),
39}
40
41impl PrintTicketBuilder {
42 pub fn new(device: &PrinterDevice) -> Result<Self, PrintTicketBuilderError> {
44 let provider = unsafe {
45 PTOpenProvider(PCWSTR(wchar::to_wide_chars(device.os_name()).as_ptr()), 1)
46 .map_err(PrintTicketBuilderError::OpenProviderFailed)?
47 };
48 Ok(Self {
49 xml: DEFAULT_PRINT_TICKET_XML.into(),
50 provider,
51 })
52 }
53
54 pub fn merge(&mut self, delta: impl Into<PrintTicket>) -> Result<(), PrintTicketBuilderError> {
56 unsafe {
57 let base = SHCreateMemStream(Some(self.xml.as_ref()))
58 .ok_or(PrintTicketBuilderError::StreamNotAllocated)?;
59 let delta = SHCreateMemStream(Some(delta.into().get_xml()))
60 .ok_or(PrintTicketBuilderError::StreamNotAllocated)?;
61 let result =
62 SHCreateMemStream(None).ok_or(PrintTicketBuilderError::StreamNotAllocated)?;
63 let mut error_message = BSTR::default();
64 PTMergeAndValidatePrintTicket(
65 self.provider,
66 &base,
67 &delta,
68 kPTJobScope,
69 Some(&result),
70 Some(&mut error_message),
71 )
72 .map_err(|win32_error| {
73 PrintTicketBuilderError::MergePrintTicketsFailed(
74 error_message.to_string(),
75 win32_error,
76 )
77 })?;
78 copy_com_stream_to_vec(&mut self.xml, &result)
79 .map_err(PrintTicketBuilderError::DecodePrintTicketFailed)?;
80 }
81 Ok(())
82 }
83
84 pub fn build(mut self) -> Result<PrintTicket, PrintTicketBuilderError> {
86 let xml = std::mem::take(&mut self.xml);
87 Ok(PrintTicket { xml })
88 }
89}
90
91impl Drop for PrintTicketBuilder {
92 fn drop(&mut self) {
93 unsafe {
94 let _ = PTCloseProvider(self.provider);
95 }
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::PrintTicketBuilder;
102 use crate::{
103 test_utils::null_device,
104 ticket::{PrintTicket, PrintTicketBuilderError},
105 };
106
107 #[test]
108 fn merge_simple_ticket() {
109 let device = null_device::thread_local();
110 let mut builder = PrintTicketBuilder::new(&device).unwrap();
111 let delta = r#"<psf:PrintTicket xmlns:psf="http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" version="1" xmlns:psk="http://schemas.microsoft.com/windows/2003/08/printing/printschemakeywords">
112 <psf:Feature name="psk:PageMediaSize">
113 <psf:Option name="psk:NorthAmericaTabloid">
114 <psf:ScoredProperty name="psk:MediaSizeWidth">
115 <psf:Value xsi:type="xsd:integer">279400</psf:Value>
116 </psf:ScoredProperty>
117 <psf:ScoredProperty name="psk:MediaSizeHeight">
118 <psf:Value xsi:type="xsd:integer">431800</psf:Value>
119 </psf:ScoredProperty>
120 <psf:Property name="psk:DisplayName">
121 <psf:Value xsi:type="xsd:string">Tabloid</psf:Value>
122 </psf:Property>
123 </psf:Option>
124 </psf:Feature>
125</psf:PrintTicket>"#;
126 builder.merge(PrintTicket::from_xml(delta)).unwrap();
127 let _ticket = builder.build().unwrap();
128 }
129
130 #[test]
131 fn merge_invalid_ticket() {
132 let device = null_device::thread_local();
133 let mut builder = PrintTicketBuilder::new(&device).unwrap();
134 let delta = r#"This is not a valid print ticket"#;
135 let result = builder.merge(PrintTicket::from_xml(delta));
136 assert!(matches!(
137 result,
138 Err(PrintTicketBuilderError::MergePrintTicketsFailed(..))
139 ));
140 }
141}