1use std::fmt;
2
3#[cfg(feature = "graphics")]
4use image::{DynamicImage, GenericImageView, Rgba};
5
6#[cfg(feature = "serde")]
7use serde::{de, Deserializer};
8
9#[cfg(feature = "graphics")]
10use crate::error::{PrinterError, Result};
11
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13#[derive(Debug, PartialEq, Clone)]
14pub enum GraphicSize {
15 #[cfg_attr(feature = "serde", serde(rename = "normal"))]
16 Normal,
17 #[cfg_attr(feature = "serde", serde(rename = "double_width"))]
18 DoubleWidth,
19 #[cfg_attr(feature = "serde", serde(rename = "double_height"))]
20 DoubleHeight,
21 #[cfg_attr(feature = "serde", serde(rename = "double_width_and_height"))]
22 DoubleWidthAndHeight,
23}
24
25impl fmt::Display for GraphicSize {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 match self {
28 GraphicSize::Normal => write!(f, "Normal"),
29 GraphicSize::DoubleWidth => write!(f, "DoubleWidth"),
30 GraphicSize::DoubleHeight => write!(f, "DoubleHeight"),
31 GraphicSize::DoubleWidthAndHeight => write!(f, "DoubleWidthAndHeight"),
32 }
33 }
34}
35
36impl From<&GraphicSize> for u8 {
37 fn from(size: &GraphicSize) -> Self {
38 match size {
39 GraphicSize::Normal => 0x00,
40 GraphicSize::DoubleWidth => 0x01,
41 GraphicSize::DoubleHeight => 0x02,
42 GraphicSize::DoubleWidthAndHeight => 0x03,
43 }
44 }
45}
46
47#[cfg(feature = "graphics")]
48#[cfg_attr(feature = "serde", derive(serde::Serialize))]
49#[derive(Debug, PartialEq, Clone)]
50pub struct Graphic {
51 path: String,
52 #[cfg_attr(feature = "serde", serde(skip_serializing, skip_deserializing))]
53 img: DynamicImage,
54 density: u8,
55 max_width: u32,
56 size: GraphicSize,
57}
58
59#[cfg(feature = "graphics")]
60impl Graphic {
61 pub fn new(path: String, density: u8, max_width: u32, size: GraphicSize) -> Result<Self> {
62 let img = image::open(&path)?;
63 let img = if img.width() > max_width {
64 let resized = img.resize(max_width, max_width, image::imageops::Nearest);
65 resized.grayscale()
66 } else {
67 img.grayscale()
68 };
69 Ok(Self {
70 path,
71 img,
72 density,
73 max_width,
74 size,
75 })
76 }
77
78 pub fn width(&self) -> u16 {
79 self.img.width() as u16
80 }
81
82 pub fn height(&self) -> u16 {
83 self.img.height() as u16
84 }
85
86 #[allow(clippy::cast_sign_loss)]
87 pub fn width_bytes(&self) -> u16 {
88 (f32::from(self.width()) / 8.0).ceil() as u16
89 }
90
91 #[allow(clippy::cast_sign_loss)]
92 pub fn height_bytes(&self) -> u16 {
93 (f32::from(self.height()) / 8.0).ceil() as u16
94 }
95
96 pub fn img(&self) -> &DynamicImage {
97 &self.img
98 }
99
100 pub fn dimensions(&self) -> (u16, u16) {
101 (self.width(), self.height())
102 }
103
104 pub fn pixel(&self, x: u32, y: u32) -> Rgba<u8> {
105 self.img.get_pixel(x, y)
106 }
107
108 pub fn density(&self) -> u8 {
109 self.density
110 }
111
112 pub fn path(&self) -> &str {
113 &self.path
114 }
115
116 pub fn size(&self) -> &GraphicSize {
117 &self.size
118 }
119
120 pub fn max_width(&self) -> u32 {
121 self.max_width
122 }
123
124 pub fn builder() -> GraphicBuilder {
125 GraphicBuilder::default()
126 }
127}
128
129#[cfg(feature = "graphics")]
130pub struct GraphicBuilder {
131 path: Option<String>,
132 density: u8,
133 max_width: u32,
134 size: GraphicSize,
135}
136
137#[cfg(feature = "graphics")]
138impl Default for GraphicBuilder {
139 fn default() -> Self {
140 Self {
141 path: None,
142 density: 8,
143 max_width: 512,
144 size: GraphicSize::Normal,
145 }
146 }
147}
148
149#[cfg(feature = "graphics")]
150impl GraphicBuilder {
151 pub fn path<T: AsRef<str>>(&mut self, path: T) -> &mut Self {
152 let path = path.as_ref().to_string();
153 self.path = Some(path);
154 self
155 }
156
157 pub fn density(mut self, density: u8) -> Self {
158 self.density = density;
159 self
160 }
161
162 pub fn size(&mut self, size: GraphicSize) -> &mut Self {
163 self.size = size;
164 self
165 }
166
167 pub fn max_width(&mut self, max_width: u32) -> &mut Self {
168 self.max_width = max_width;
169 self
170 }
171
172 pub fn build(self) -> Result<Graphic> {
173 let path = self.path.ok_or(PrinterError::input("No path provided"))?;
174 let graphic = Graphic::new(path, self.density, self.max_width, self.size)?;
175 Ok(graphic)
176 }
177}
178
179#[cfg(feature = "serde")]
180struct GraphicVisitor;
181
182#[cfg(feature = "serde")]
183impl<'de> serde::de::Visitor<'de> for GraphicVisitor {
184 type Value = Graphic;
185
186 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
187 formatter.write_str("struct Graphic")
188 }
189
190 fn visit_map<M>(self, mut access: M) -> std::result::Result<Graphic, M::Error>
191 where
192 M: serde::de::MapAccess<'de>,
193 {
194 let mut path: Option<String> = None;
195 let mut density = None;
196 let mut max_width = None;
197 let mut size = None;
198
199 while let Some(key) = access.next_key()? {
200 match key {
201 "path" => {
202 if path.is_some() {
203 return Err(de::Error::duplicate_field("path"));
204 }
205 path = Some(access.next_value()?);
206 }
207 "density" => {
208 if density.is_some() {
209 return Err(de::Error::duplicate_field("density"));
210 }
211 density = Some(access.next_value()?);
212 }
213 "max_width" => {
214 if max_width.is_some() {
215 return Err(de::Error::duplicate_field("max_width"));
216 }
217 max_width = Some(access.next_value()?);
218 }
219 "size" => {
220 if size.is_some() {
221 return Err(de::Error::duplicate_field("size"));
222 }
223 size = Some(access.next_value()?);
224 }
225 _ => {
226 return Err(de::Error::unknown_field(
227 key,
228 &["path", "density", "max_width", "size"],
229 ));
230 }
231 }
232 }
233 let path = path.ok_or_else(|| de::Error::missing_field("path"))?;
234 let density = density.ok_or_else(|| de::Error::missing_field("density"))?;
235 let max_width = max_width.ok_or_else(|| de::Error::missing_field("max_width"))?;
236 let size = size.ok_or_else(|| de::Error::missing_field("size"))?;
237
238 if let Ok(graphic) = Graphic::new(path.clone(), density, max_width, size) {
239 Ok(graphic)
240 } else {
241 Err(de::Error::custom(format!(
242 "Could not load graphic at path: {}",
243 path
244 )))
245 }
246 }
247}
248
249#[cfg(feature = "serde")]
250impl<'de> serde::de::Deserialize<'de> for Graphic {
251 fn deserialize<D>(deserializer: D) -> std::result::Result<Graphic, D::Error>
252 where
253 D: Deserializer<'de>,
254 {
255 deserializer.deserialize_map(GraphicVisitor)
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262
263 #[test]
264 fn test_graphic_size() {
265 let normal = GraphicSize::Normal;
266 let double_width = GraphicSize::DoubleWidth;
267 let double_height = GraphicSize::DoubleHeight;
268 let double_width_and_height = GraphicSize::DoubleWidthAndHeight;
269
270 assert_eq!(u8::from(&normal), 0x00);
271 assert_eq!(u8::from(&double_width), 0x01);
272 assert_eq!(u8::from(&double_height), 0x02);
273 assert_eq!(u8::from(&double_width_and_height), 0x03);
274 }
275
276 #[test]
277 #[cfg(feature = "serde")]
278 fn test_serialize_from_json() -> Result<()> {
279 let json = r#"
280 {
281 "path": "resources/rust-logo-small.png",
282 "density": 8,
283 "max_width": 512,
284 "size": "normal"
285 }
286 "#;
287 let graphic: Graphic = serde_json::from_str(json).unwrap();
288
289 assert_eq!(graphic.path(), "resources/rust-logo-small.png");
290 assert_eq!(graphic.density(), 8);
291 assert_eq!(graphic.max_width(), 512);
292 assert_eq!(graphic.width(), 200);
293 assert_eq!(graphic.height(), 200);
294 assert_eq!(graphic.size(), &GraphicSize::Normal);
295 Ok(())
296 }
297}