recibo/domain/
graphic.rs

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}