escl/
capabilities.rs

1use serde::{de::Visitor, Deserialize, Serialize};
2
3#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
4#[serde(rename_all = "PascalCase")]
5pub struct ScannerCapabilities {
6    pub version: String,
7    pub make_and_model: String,
8    #[serde(default, skip_serializing_if = "Option::is_none")]
9    pub manufacturer: Option<String>,
10    pub serial_number: String,
11    #[serde(rename = "UUID")]
12    pub uuid: String,
13    #[serde(rename = "AdminURI")]
14    pub admin_uri: String,
15    #[serde(rename = "IconURI")]
16    pub icon_uri: String,
17    #[serde(default, skip_serializing_if = "Certifications::is_empty")]
18    pub certifications: Certifications,
19    pub platen: Platen,
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    pub adf: Option<Adf>,
22    #[serde(default, skip_serializing_if = "Option::is_none")]
23    pub compression_factor_support: Option<CompressionFactorSupport>,
24    #[serde(default, skip_serializing_if = "SupportedMediaTypes::is_empty")]
25    pub supported_media_types: SupportedMediaTypes,
26    #[serde(default, skip_serializing_if = "Option::is_none")]
27    pub sharpen_support: Option<SharpenSupport>,
28}
29
30#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
31#[serde(rename_all = "PascalCase")]
32pub struct Certifications {
33    pub certification: Vec<Certification>,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
37#[serde(rename_all = "PascalCase")]
38pub struct Certification {
39    pub name: String,
40    pub version: String,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44#[serde(rename_all = "PascalCase")]
45pub struct Platen {
46    pub platen_input_caps: InputCaps,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
50#[serde(rename_all = "PascalCase")]
51pub struct Adf {
52    pub adf_simplex_input_caps: InputCaps,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
56#[serde(rename_all = "PascalCase")]
57pub struct InputCaps {
58    pub min_width: u32,
59    pub max_width: u32,
60    pub min_height: u32,
61    pub max_height: u32,
62    pub max_scan_regions: u32,
63    pub setting_profiles: SettingProfiles,
64    pub supported_intents: SupportedIntents,
65    pub max_optical_x_resolution: u32,
66    pub max_optical_y_resolution: u32,
67    pub risky_left_margin: u32,
68    pub risky_right_margin: u32,
69    pub risky_top_margin: u32,
70    pub risky_bottom_margin: u32,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
74#[serde(rename_all = "PascalCase")]
75pub struct SettingProfiles {
76    pub setting_profile: SettingProfile,
77}
78
79#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
80#[serde(rename_all = "PascalCase")]
81pub struct SettingProfile {
82    pub color_modes: ColorModes,
83    #[serde(default, skip_serializing_if = "ContentTypes::is_empty")]
84    pub content_types: ContentTypes,
85    pub document_formats: DocumentFormats,
86    pub supported_resolutions: SupportedResolutions,
87    pub color_spaces: ColorSpaces,
88    #[serde(default, skip_serializing_if = "Option::is_none")]
89    pub ccd_channels: Option<CcdChannels>,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
93#[serde(rename_all = "PascalCase")]
94pub struct ColorModes {
95    pub color_mode: Vec<ColorMode>,
96}
97
98#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
99#[serde(rename_all = "PascalCase")]
100pub struct ContentTypes {
101    pub content_type: Vec<ContentType>,
102}
103
104#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
105#[serde(rename_all = "PascalCase")]
106pub struct DocumentFormats {
107    pub document_format: Vec<String>,
108    pub document_format_ext: Vec<String>,
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
112#[serde(rename_all = "PascalCase")]
113pub struct SupportedResolutions {
114    pub discrete_resolutions: DiscreteResolutions,
115}
116
117#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
118#[serde(rename_all = "PascalCase")]
119pub struct DiscreteResolutions {
120    pub discrete_resolution: Vec<DiscreteResolution>,
121}
122
123#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
124#[serde(rename_all = "PascalCase")]
125pub struct DiscreteResolution {
126    pub x_resolution: u32,
127    pub y_resolution: u32,
128}
129
130#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
131#[serde(rename_all = "PascalCase")]
132pub struct ColorSpaces {
133    pub color_space: Vec<String>,
134}
135
136#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
137#[serde(rename_all = "PascalCase")]
138pub struct CcdChannels {
139    pub ccd_channel: Vec<CcdChannel>,
140}
141
142#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
143#[serde(rename_all = "PascalCase")]
144pub struct SupportedIntents {
145    pub intent: Vec<ScanIntent>,
146}
147
148#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
149#[serde(rename_all = "PascalCase")]
150pub struct CompressionFactorSupport {
151    pub min: u32,
152    pub max: u32,
153    pub normal: u32,
154    pub step: u32,
155}
156
157#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
158#[serde(rename_all = "PascalCase")]
159pub struct SupportedMediaTypes {
160    pub media_type: Vec<String>,
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
164#[serde(rename_all = "PascalCase")]
165pub struct SharpenSupport {
166    pub min: u32,
167    pub max: u32,
168    pub normal: u32,
169    pub step: u32,
170}
171
172#[derive(Debug, Clone, PartialEq, Eq)]
173pub enum ColorMode {
174    /// Binary monochrome scanning. Valid only for certain DocumentFormat/DocumentFormatExt values -
175    /// like 'application/octet-stream', 'image/tiff' that can support single-bit scans. For
176    /// document format not supporting BlackAndWhite1 color mode, scanner SHOULD report a 409
177    /// Conflict error.
178    BlackAndWhite1,
179    /// 8-bit grayscale
180    Grayscale8,
181    /// 16-bit grayscale
182    Grayscale16,
183    /// 8-bit per channel RGB
184    RGB24,
185    /// 16-bit per channel RGB
186    RGB48,
187}
188
189#[derive(Debug, Clone, PartialEq, Eq)]
190pub enum ContentType {
191    Photo,
192    Text,
193    TextAndPhoto,
194    LineArt,
195    Magazine,
196    Halftone,
197    Auto,
198    Custom(String),
199}
200
201#[derive(Debug, Clone, PartialEq, Eq)]
202pub enum CcdChannel {
203    /// Use the Red CCD
204    Red,
205    /// Use the Green CCD
206    Green,
207    /// Use the Blue CCD
208    Blue,
209    /// Weighted combination of the three color channels optimized for photos
210    NTSC,
211    /// A dedicated Gray CCD array in the hardware (optimized for documents)
212    GrayCcd,
213    /// An emulated Gray CCD mode where each CCD line are given even weight (1/3 R, 1/3 G, 1/3 B)
214    /// (optimized for documents).
215    GrayCcdEmulated,
216}
217
218#[derive(Debug, Clone, PartialEq, Eq)]
219pub enum ScanIntent {
220    /// Scanning optimized for text.
221    Document,
222    /// A composite document with mixed text/graphic/photo content.
223    TextAndGraphic,
224    /// Scanning optimized for photo
225    Photo,
226    /// Scanning optimized for performance (fast output)
227    Preview,
228    /// Scanning optimized for 3 dimensional objects - objects with depth
229    Object,
230    /// Scanning optimized for a business card
231    BusinessCard,
232    Custom(String),
233}
234
235struct ColorModeVisitor;
236struct ContentTypeVisitor;
237struct CcdChannelVisitor;
238struct ScanIntentVisitor;
239
240impl Certifications {
241    fn is_empty(&self) -> bool {
242        self.certification.is_empty()
243    }
244}
245
246impl ContentTypes {
247    fn is_empty(&self) -> bool {
248        self.content_type.is_empty()
249    }
250}
251
252impl SupportedMediaTypes {
253    fn is_empty(&self) -> bool {
254        self.media_type.is_empty()
255    }
256}
257
258impl ColorModes {
259    /// Gets the highest support quality RGB color mode. If no RGB color mode is supported, `None`
260    // is returned.
261    pub fn color(&self) -> Option<ColorMode> {
262        if self.color_mode.contains(&ColorMode::RGB48) {
263            Some(ColorMode::RGB48)
264        } else if self.color_mode.contains(&ColorMode::RGB24) {
265            Some(ColorMode::RGB24)
266        } else {
267            None
268        }
269    }
270}
271
272impl Serialize for ColorMode {
273    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
274    where
275        S: serde::Serializer,
276    {
277        serializer.serialize_str(match self {
278            Self::BlackAndWhite1 => "BlackAndWhite1",
279            Self::Grayscale8 => "Grayscale8",
280            Self::Grayscale16 => "Grayscale16",
281            Self::RGB24 => "RGB24",
282            Self::RGB48 => "RGB48",
283        })
284    }
285}
286
287impl<'de> Deserialize<'de> for ColorMode {
288    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
289    where
290        D: serde::Deserializer<'de>,
291    {
292        deserializer.deserialize_str(ColorModeVisitor)
293    }
294}
295
296impl<'de> Visitor<'de> for ColorModeVisitor {
297    type Value = ColorMode;
298
299    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
300        write!(formatter, "string")
301    }
302
303    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
304    where
305        E: serde::de::Error,
306    {
307        Ok(match v {
308            "BlackAndWhite1" => ColorMode::BlackAndWhite1,
309            "Grayscale8" => ColorMode::Grayscale8,
310            "Grayscale16" => ColorMode::Grayscale16,
311            "RGB24" => ColorMode::RGB24,
312            "RGB48" => ColorMode::RGB48,
313            _ => {
314                return Err(serde::de::Error::invalid_value(
315                    serde::de::Unexpected::Str(v),
316                    &"valid ColorMode value",
317                ))
318            }
319        })
320    }
321}
322
323impl Serialize for ContentType {
324    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
325    where
326        S: serde::Serializer,
327    {
328        serializer.serialize_str(match self {
329            Self::Photo => "Photo",
330            Self::Text => "Text",
331            Self::TextAndPhoto => "TextAndPhoto",
332            Self::LineArt => "LineArt",
333            Self::Magazine => "Magazine",
334            Self::Halftone => "Halftone",
335            Self::Auto => "Auto",
336            Self::Custom(custom) => custom,
337        })
338    }
339}
340
341impl<'de> Deserialize<'de> for ContentType {
342    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
343    where
344        D: serde::Deserializer<'de>,
345    {
346        deserializer.deserialize_str(ContentTypeVisitor)
347    }
348}
349
350impl<'de> Visitor<'de> for ContentTypeVisitor {
351    type Value = ContentType;
352
353    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
354        write!(formatter, "string")
355    }
356
357    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
358    where
359        E: serde::de::Error,
360    {
361        Ok(match v {
362            "Photo" => ContentType::Photo,
363            "Text" => ContentType::Text,
364            "TextAndPhoto" => ContentType::TextAndPhoto,
365            "LineArt" => ContentType::LineArt,
366            "Magazine" => ContentType::Magazine,
367            "Halftone" => ContentType::Halftone,
368            "Auto" => ContentType::Auto,
369            custom => ContentType::Custom(custom.to_owned()),
370        })
371    }
372}
373
374impl Serialize for CcdChannel {
375    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
376    where
377        S: serde::Serializer,
378    {
379        serializer.serialize_str(match self {
380            CcdChannel::Red => "Red",
381            CcdChannel::Green => "Green",
382            CcdChannel::Blue => "Blue",
383            CcdChannel::NTSC => "NTSC",
384            CcdChannel::GrayCcd => "GrayCcd",
385            CcdChannel::GrayCcdEmulated => "GrayCcdEmulated",
386        })
387    }
388}
389
390impl<'de> Deserialize<'de> for CcdChannel {
391    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
392    where
393        D: serde::Deserializer<'de>,
394    {
395        deserializer.deserialize_str(CcdChannelVisitor)
396    }
397}
398
399impl<'de> Visitor<'de> for CcdChannelVisitor {
400    type Value = CcdChannel;
401
402    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
403        write!(formatter, "string")
404    }
405
406    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
407    where
408        E: serde::de::Error,
409    {
410        Ok(match v {
411            "Red" => CcdChannel::Red,
412            "Green" => CcdChannel::Green,
413            "Blue" => CcdChannel::Blue,
414            "NTSC" => CcdChannel::NTSC,
415            "GrayCcd" => CcdChannel::GrayCcd,
416            "GrayCcdEmulated" => CcdChannel::GrayCcdEmulated,
417            _ => {
418                return Err(serde::de::Error::invalid_value(
419                    serde::de::Unexpected::Str(v),
420                    &"valid ColorMode value",
421                ))
422            }
423        })
424    }
425}
426
427impl Serialize for ScanIntent {
428    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
429    where
430        S: serde::Serializer,
431    {
432        serializer.serialize_str(match self {
433            Self::Document => "Document",
434            Self::TextAndGraphic => "TextAndGraphic",
435            Self::Photo => "Photo",
436            Self::Preview => "Preview",
437            Self::Object => "Object",
438            Self::BusinessCard => "BusinessCard",
439            Self::Custom(custom) => custom,
440        })
441    }
442}
443
444impl<'de> Deserialize<'de> for ScanIntent {
445    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
446    where
447        D: serde::Deserializer<'de>,
448    {
449        deserializer.deserialize_str(ScanIntentVisitor)
450    }
451}
452
453impl<'de> Visitor<'de> for ScanIntentVisitor {
454    type Value = ScanIntent;
455
456    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
457        write!(formatter, "string")
458    }
459
460    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
461    where
462        E: serde::de::Error,
463    {
464        Ok(match v {
465            "Document" => ScanIntent::Document,
466            "TextAndGraphic" => ScanIntent::TextAndGraphic,
467            "Photo" => ScanIntent::Photo,
468            "Preview" => ScanIntent::Preview,
469            "Object" => ScanIntent::Object,
470            "BusinessCard" => ScanIntent::BusinessCard,
471            custom => ScanIntent::Custom(custom.to_owned()),
472        })
473    }
474}
475
476#[cfg(test)]
477mod tests {
478    use super::*;
479
480    #[test]
481    pub fn test_capabilities_deser() {
482        for raw_xml in [
483            include_str!("../test-data/capabilities/brother_mfc_j497dw.xml"),
484            include_str!("../test-data/capabilities/canon_ts5300_series.xml"),
485            include_str!("../test-data/capabilities/canon_ts7450.xml"),
486        ]
487        .into_iter()
488        {
489            serde_xml_rs::from_str::<ScannerCapabilities>(raw_xml)
490                .expect("capabilities deserializing failure");
491        }
492    }
493}