Skip to main content

cpdb_rs/
media.rs

1//! Paper sizes and margins returned by `GetAllOptions`.
2
3#[cfg(feature = "zbus-backend")]
4use crate::proxy::RawMedia;
5
6/// Margin values for a paper size, in hundredths of a millimetre.
7#[derive(Debug, Clone)]
8pub struct MarginInfo {
9    /// Left margin.
10    pub left: i32,
11    /// Right margin.
12    pub right: i32,
13    /// Top margin.
14    pub top: i32,
15    /// Bottom margin.
16    pub bottom: i32,
17}
18
19/// A single supported paper size with its dimensions and available margins.
20#[derive(Debug, Clone)]
21pub struct MediaInfo {
22    /// The media name (e.g. `"iso_a4_210x297mm"`).
23    pub name: String,
24    /// Width in hundredths of a millimetre.
25    pub width: i32,
26    /// Length in hundredths of a millimetre.
27    pub length: i32,
28    /// Available margin configurations for this media.
29    pub margins: Vec<MarginInfo>,
30}
31
32/// An owned collection of all paper sizes supported by a printer.
33#[derive(Debug, Clone, Default)]
34pub struct MediaCollection {
35    /// All supported media entries.
36    pub media: Vec<MediaInfo>,
37}
38
39impl MediaCollection {
40    /// Build from D-Bus response (the `Vec<RawMedia>` from GetAllOptions)
41    #[cfg(feature = "zbus-backend")]
42    pub fn from_dbus(raw: Vec<RawMedia>) -> Self {
43        let media = raw
44            .into_iter()
45            .map(|r| MediaInfo {
46                name: r.name,
47                width: r.width,
48                length: r.length,
49                margins: r
50                    .margins
51                    .into_iter()
52                    .map(|m| MarginInfo {
53                        left: m.left,
54                        right: m.right,
55                        top: m.top,
56                        bottom: m.bottom,
57                    })
58                    .collect(),
59            })
60            .collect();
61        Self { media }
62    }
63
64    /// Returns the number of media entries.
65    pub fn len(&self) -> usize {
66        self.media.len()
67    }
68
69    /// Returns `true` if the collection contains no media entries.
70    pub fn is_empty(&self) -> bool {
71        self.media.is_empty()
72    }
73
74    /// Finds a media entry by name (e.g. `"iso_a4_210x297mm"`).
75    pub fn get(&self, name: &str) -> Option<&MediaInfo> {
76        self.media.iter().find(|m| m.name == name)
77    }
78
79    /// Returns an iterator over all media entries.
80    pub fn iter(&self) -> impl Iterator<Item = &MediaInfo> {
81        self.media.iter()
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    fn sample_media() -> MediaCollection {
90        MediaCollection {
91            media: vec![
92                MediaInfo {
93                    name: "iso_a4_210x297mm".to_string(),
94                    width: 21000,
95                    length: 29700,
96                    margins: vec![MarginInfo {
97                        left: 500,
98                        right: 500,
99                        top: 500,
100                        bottom: 500,
101                    }],
102                },
103                MediaInfo {
104                    name: "na_letter_8.5x11in".to_string(),
105                    width: 21590,
106                    length: 27940,
107                    margins: vec![],
108                },
109            ],
110        }
111    }
112
113    #[test]
114    fn empty_collection() {
115        let col = MediaCollection::default();
116        assert!(col.is_empty());
117        assert_eq!(col.len(), 0);
118        assert!(col.get("iso_a4_210x297mm").is_none());
119    }
120
121    #[test]
122    fn len_and_is_empty() {
123        let col = sample_media();
124        assert!(!col.is_empty());
125        assert_eq!(col.len(), 2);
126    }
127
128    #[test]
129    fn get_finds_by_name() {
130        let col = sample_media();
131        let a4 = col.get("iso_a4_210x297mm");
132        assert!(a4.is_some());
133        let a4 = a4.unwrap();
134        assert_eq!(a4.width, 21000);
135        assert_eq!(a4.length, 29700);
136        assert_eq!(a4.margins.len(), 1);
137        assert_eq!(a4.margins[0].left, 500);
138    }
139
140    #[test]
141    fn get_returns_none_for_missing() {
142        let col = sample_media();
143        assert!(col.get("nonexistent_paper").is_none());
144    }
145
146    #[test]
147    fn iter_yields_all_entries() {
148        let col = sample_media();
149        let names: Vec<&str> = col.iter().map(|m| m.name.as_str()).collect();
150        assert!(names.contains(&"iso_a4_210x297mm"));
151        assert!(names.contains(&"na_letter_8.5x11in"));
152    }
153
154    #[test]
155    fn media_without_margins() {
156        let col = sample_media();
157        let letter = col.get("na_letter_8.5x11in").unwrap();
158        assert!(letter.margins.is_empty());
159    }
160
161    #[cfg(feature = "zbus-backend")]
162    #[test]
163    fn from_dbus_empty_vec() {
164        let col = MediaCollection::from_dbus(vec![]);
165        assert!(col.is_empty());
166    }
167
168    #[cfg(feature = "zbus-backend")]
169    #[test]
170    fn from_dbus_converts_correctly() {
171        use crate::proxy::{RawMargin, RawMedia};
172
173        let raw = vec![RawMedia {
174            name: "iso_a4_210x297mm".to_string(),
175            width: 21000,
176            length: 29700,
177            num_margins: 1,
178            margins: vec![RawMargin {
179                left: 500,
180                right: 500,
181                top: 300,
182                bottom: 300,
183            }],
184        }];
185
186        let col = MediaCollection::from_dbus(raw);
187        assert_eq!(col.len(), 1);
188        let a4 = &col.media[0];
189        assert_eq!(a4.name, "iso_a4_210x297mm");
190        assert_eq!(a4.width, 21000);
191        assert_eq!(a4.margins.len(), 1);
192        assert_eq!(a4.margins[0].top, 300);
193    }
194}