Skip to main content

winprint_ext/ticket/
print_capabilities.rs

1use super::{
2    document::{
3        reader::{ParsableXmlDocument, ParsePrintSchemaError},
4        ParameterInit, PrintCapabilitiesDocument, PrintFeatureOption, WithProperties, NS_PSF,
5        NS_PSK,
6    },
7    Copies, FeatureOptionPack, JobDuplex, PageMediaSize, PageOrientation, PageOutputColor,
8    PageResolution, PrintTicket,
9};
10use crate::{
11    printer::PrinterDevice,
12    utils::{stream::read_com_stream, wchar},
13};
14use scopeguard::defer;
15use std::fmt::Debug;
16use thiserror::Error;
17use windows::{
18    core::{BSTR, PCWSTR},
19    Win32::{
20        Graphics::Printing::PrintTicket::{
21            PTCloseProvider, PTGetPrintCapabilities, PTOpenProvider,
22        },
23        UI::Shell::SHCreateMemStream,
24    },
25};
26use xml::name::OwnedName;
27
28#[derive(Error, Debug)]
29/// Represents an error occurred while fetching print capabilities.
30pub enum FetchPrintCapabilitiesError {
31    /// Failed to open print ticket provider.
32    #[error("Failed to open print ticket provider")]
33    OpenProviderFailed(#[source] windows::core::Error),
34    /// Stream not allocated.
35    #[error("Stream not allocated")]
36    StreamNotAllocated,
37    /// Cannot get print capabilities.
38    #[error("Cannot get print capabilities")]
39    CannotGetPrintCapabilities(String, #[source] windows::core::Error),
40    /// Failed to read stream.
41    #[error("Failed to read stream")]
42    ReadStreamFailed(#[source] windows::core::Error),
43    /// Failed to parse print capabilities.
44    #[error("Failed to parse print capabilities")]
45    ParseError(#[source] ParsePrintSchemaError),
46}
47
48#[derive(Clone, Debug)]
49/// Represents print capabilities.
50pub struct PrintCapabilities {
51    /// DOM of print capabilities document.
52    pub document: PrintCapabilitiesDocument,
53}
54
55impl PrintCapabilities {
56    /// Fetch print capabilities XML (without parsing it) for the given printer device.
57    pub fn fetch_xml(device: &PrinterDevice) -> Result<Vec<u8>, FetchPrintCapabilitiesError> {
58        Self::fetch_xml_for_ticket(device, None)
59    }
60
61    /// Fetch print capabilities XML (without parsing it) for the given printer device and print ticket.
62    pub fn fetch_xml_for_ticket(
63        device: &PrinterDevice,
64        ticket: Option<&PrintTicket>,
65    ) -> Result<Vec<u8>, FetchPrintCapabilitiesError> {
66        unsafe {
67            let provider =
68                PTOpenProvider(PCWSTR(wchar::to_wide_chars(device.os_name()).as_ptr()), 1)
69                    .map_err(FetchPrintCapabilitiesError::OpenProviderFailed)?;
70            defer! {
71                let _ = PTCloseProvider(provider);
72            }
73            let stream =
74                SHCreateMemStream(None).ok_or(FetchPrintCapabilitiesError::StreamNotAllocated)?;
75            let ticket_stream = ticket
76                .map(|x| {
77                    SHCreateMemStream(Some(x.get_xml()))
78                        .ok_or(FetchPrintCapabilitiesError::StreamNotAllocated)
79                })
80                .transpose()?;
81
82            let mut error_message = BSTR::default();
83            PTGetPrintCapabilities(
84                provider,
85                ticket_stream.as_ref(),
86                &stream,
87                Some(&mut error_message),
88            )
89            .map_err(|win32_error| {
90                FetchPrintCapabilitiesError::CannotGetPrintCapabilities(
91                    error_message.to_string(),
92                    win32_error,
93                )
94            })?;
95
96            let data =
97                read_com_stream(&stream).map_err(FetchPrintCapabilitiesError::ReadStreamFailed)?;
98
99            Ok(data)
100        }
101    }
102
103    /// Fetch and parse print capabilities for the given printer device.
104    pub fn fetch(device: &PrinterDevice) -> Result<PrintCapabilities, FetchPrintCapabilitiesError> {
105        let xml = Self::fetch_xml(device)?;
106        let document = PrintCapabilitiesDocument::parse_from_bytes(xml)
107            .map_err(FetchPrintCapabilitiesError::ParseError)?;
108        Ok(PrintCapabilities { document })
109    }
110
111    /// Defines all parameters with default values.
112    pub fn default_parameters(&self) -> impl Iterator<Item = ParameterInit> + '_ {
113        self.document.parameter_defs.iter().filter_map(|param_def| {
114            param_def
115                .default_value()
116                .map(|default_value| ParameterInit {
117                    name: param_def.name.clone(),
118                    value: default_value.clone(),
119                })
120        })
121    }
122
123    /// Defines the given parameters with default values.
124    pub fn default_parameters_for<'a>(
125        &'a self,
126        filters: &'a [OwnedName],
127    ) -> impl Iterator<Item = ParameterInit> + 'a {
128        let mut filters = filters
129            .iter()
130            .map(|x| (&x.namespace, &x.local_name))
131            .collect::<Vec<_>>();
132        filters.sort_unstable();
133        self.document
134            .parameter_defs
135            .iter()
136            .filter(move |param_def| {
137                filters
138                    .binary_search(&(&param_def.name.namespace, &param_def.name.local_name))
139                    .is_ok()
140            })
141            .filter_map(move |param_def| {
142                param_def
143                    .default_value()
144                    .map(|default_value| ParameterInit {
145                        name: param_def.name.clone(),
146                        value: default_value.clone(),
147                    })
148            })
149    }
150
151    /// Get all options for the given feature.
152    pub fn options_for_feature(
153        &self,
154        feature_name: OwnedName,
155    ) -> impl Iterator<Item = &PrintFeatureOption> + '_ {
156        self.document
157            .features
158            .iter()
159            .filter(move |x| {
160                x.name.local_name == feature_name.local_name
161                    && x.name.namespace == feature_name.namespace
162            })
163            .flat_map(|x| x.options.iter())
164    }
165
166    /// Get all page media sizes.
167    pub fn page_media_sizes(&self) -> impl Iterator<Item = PageMediaSize> + '_ {
168        PageMediaSize::list(self)
169    }
170
171    /// Get all supported page orientations.
172    pub fn page_orientations(&self) -> impl Iterator<Item = PageOrientation> + '_ {
173        PageOrientation::list(self)
174    }
175
176    /// Get all supported job duplex types.
177    ///
178    /// # Note
179    /// This corresponds to the Print Schema's `JobDuplexAllDocumentsContiguously` keyword, not the `DocumentDuplex` keyword.
180    pub fn duplexes(&self) -> impl Iterator<Item = JobDuplex> + '_ {
181        JobDuplex::list(self)
182    }
183
184    /// Get all supported page output colors.
185    pub fn page_output_colors(&self) -> impl Iterator<Item = PageOutputColor> + '_ {
186        PageOutputColor::list(self)
187    }
188
189    /// Get all supported page resolutions.
190    pub fn page_resolutions(&self) -> impl Iterator<Item = PageResolution> + '_ {
191        PageResolution::list(self)
192    }
193
194    /// Get the maximum number of copies that a printer can print. Return `None` if the device does not report a maximum.
195    ///
196    /// # Note
197    /// This corresponds to the Print Schema's `JobCopiesAllDocuments` keyword, not the `DocumentCopiesAllPages` keyword, or the `PageCopies` keyword. If the printer can print unlimited copies, the property value is 9999.
198    pub fn max_copies(&self) -> Option<Copies> {
199        self.document
200            .parameter_defs
201            .iter()
202            .find(|x| {
203                x.name.local_name == "JobCopiesAllDocuments"
204                    && x.name.namespace_ref() == Some(NS_PSK)
205            })
206            .and_then(|x| x.get_property("MaxValue", Some(NS_PSF)))
207            .and_then(|x| x.value.as_ref())
208            .and_then(|x| x.integer())
209            .and_then(|x| u16::try_from(x).ok())
210            .map(Copies)
211    }
212}
213
214#[cfg(all(test, feature = "test-utils"))]
215mod tests {
216    use super::PrintCapabilities;
217    use crate::test_utils::null_device;
218    #[test]
219    fn test_fetch_xml() {
220        let device = null_device::thread_local();
221        PrintCapabilities::fetch_xml(&device).unwrap();
222    }
223
224    #[test]
225    fn test_fetch_xml_and_parse() {
226        let device = null_device::thread_local();
227        PrintCapabilities::fetch(&device).unwrap();
228    }
229}