1#[cfg(feature = "pyo3")]
2use pyo3::prelude::*;
3
4use serde::{
5 Deserialize, Deserializer, Serialize,
6 de::{self, MapAccess, Visitor},
7};
8
9use super::{
10 SolidColor,
11 layers::{Background, Ellipse, Icon, LayerOffset, Polygon, Rectangle, Size, Typography},
12};
13
14#[cfg_attr(
16 feature = "pyo3",
17 pyclass(module = "img_gen", get_all, set_all, from_py_object)
18)]
19#[derive(Debug, Clone, Default, Serialize, Deserialize)]
20pub struct Mask {
21 pub size: Option<Size>,
23
24 #[serde(default)]
26 pub offset: LayerOffset,
27
28 #[serde(default)]
33 pub invert: bool,
34
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pub background: Option<Background>,
38 #[serde(skip_serializing_if = "Option::is_none")]
40 pub rectangle: Option<Rectangle>,
41 #[serde(skip_serializing_if = "Option::is_none")]
43 pub ellipse: Option<Ellipse>,
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub polygon: Option<Polygon>,
47 #[serde(skip_serializing_if = "Option::is_none")]
49 pub icon: Option<Icon>,
50 #[serde(skip_serializing_if = "Option::is_none")]
52 pub typography: Option<Typography>,
53}
54
55#[cfg_attr(
57 feature = "pyo3",
58 pyclass(module = "img_gen", get_all, set_all, from_py_object)
59)]
60#[derive(Debug, Clone, Default, Serialize, Deserialize)]
61pub struct Layer {
62 pub size: Option<Size>,
64
65 #[serde(default)]
67 pub offset: LayerOffset,
68
69 pub background: Option<Background>,
71 pub rectangle: Option<Rectangle>,
73 pub ellipse: Option<Ellipse>,
75 pub polygon: Option<Polygon>,
77 pub icon: Option<Icon>,
79 pub typography: Option<Typography>,
81 pub mask: Option<Mask>,
83}
84
85#[cfg_attr(
87 feature = "pyo3",
88 pyclass(module = "img_gen", set_all, get_all, from_py_object)
89)]
90#[derive(Debug, Clone, Serialize)]
91pub struct Debug {
92 pub enable: bool,
94 pub grid: bool,
96 pub grid_step: u32,
98 pub color: SolidColor,
100}
101
102impl Debug {
103 pub(crate) const fn default_grid_step() -> u32 {
104 30
105 }
106
107 pub(crate) fn default_color() -> SolidColor {
108 SolidColor::new(128, 128, 128, 255)
109 }
110
111 const fn default_grid() -> bool {
112 true
113 }
114}
115
116impl<'de> Deserialize<'de> for Debug {
117 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
118 struct DebugVisitor;
119
120 impl<'de> Visitor<'de> for DebugVisitor {
121 type Value = Debug;
122
123 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
124 formatter.write_str("a boolean or a debug config object")
125 }
126
127 fn visit_bool<E: de::Error>(self, v: bool) -> Result<Debug, E> {
128 Ok(if v {
129 Debug {
130 enable: true,
131 ..Debug::default()
132 }
133 } else {
134 Debug::default()
135 })
136 }
137
138 fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Debug, A::Error> {
139 let mut enable = None;
140 let mut grid = None;
141 let mut grid_step = None;
142 let mut color = None;
143
144 while let Some(key) = map.next_key::<std::borrow::Cow<str>>()? {
145 match key.as_ref() {
146 "enable" => enable = Some(map.next_value()?),
147 "grid" => grid = Some(map.next_value()?),
148 "grid_step" => grid_step = Some(map.next_value()?),
149 "color" => color = Some(map.next_value()?),
150 unknown => {
151 return Err(de::Error::unknown_field(
152 unknown,
153 &["enable", "grid", "grid_step", "color"],
154 ));
155 }
156 }
157 }
158
159 Ok(Debug {
160 enable: enable.unwrap_or(false),
161 grid: grid.unwrap_or_else(Debug::default_grid),
162 grid_step: grid_step.unwrap_or_else(Debug::default_grid_step),
163 color: color.unwrap_or_else(Debug::default_color),
164 })
165 }
166 }
167
168 deserializer.deserialize_any(DebugVisitor)
169 }
170}
171
172impl Default for Debug {
173 fn default() -> Self {
174 Self {
175 enable: false,
176 grid: Self::default_grid(),
177 grid_step: Self::default_grid_step(),
178 color: Self::default_color(),
179 }
180 }
181}
182
183#[cfg_attr(
185 feature = "pyo3",
186 pyclass(module = "img_gen", set_all, get_all, from_py_object)
187)]
188#[derive(Debug, Clone, Default, Serialize, Deserialize)]
189pub struct Layout {
190 #[serde(default)]
192 pub size: Size,
193
194 #[serde(default)]
196 pub layers: Vec<Layer>,
197
198 pub debug: Option<Debug>,
200}
201
202#[cfg(test)]
203mod tests {
204 #![allow(clippy::unwrap_used)]
205
206 use super::Debug;
207
208 fn assert_all_defaults(d: &Debug) {
209 assert_eq!(d.grid, Debug::default_grid());
210 assert_eq!(d.grid_step, Debug::default_grid_step());
211 assert_eq!(d.color.to_tuple(), Debug::default_color().to_tuple());
212 }
213
214 #[test]
215 fn debug_bool_true() {
216 let d: Debug = serde_saphyr::from_str("true").unwrap();
217 assert!(d.enable);
218 assert_all_defaults(&d);
219 }
220
221 #[test]
222 fn debug_bool_false() {
223 let d: Debug = serde_saphyr::from_str("false").unwrap();
224 assert!(!d.enable);
225 assert_all_defaults(&d);
226 }
227
228 #[test]
229 fn debug_map_full() {
230 let yaml = "enable: true\ngrid: false\ngrid_step: 50\ncolor: \"blue\"\n";
231 let d: Debug = serde_saphyr::from_str(yaml).unwrap();
232 assert!(d.enable);
233 assert!(!d.grid);
234 assert_eq!(d.grid_step, 50);
235 assert_eq!(d.color.to_tuple(), (0, 0, 255, 255));
236 }
237
238 #[test]
239 fn debug_map_defaults() {
240 let d: Debug = serde_saphyr::from_str("{}").unwrap();
241 assert!(!d.enable);
242 assert_all_defaults(&d);
243 }
244
245 #[test]
246 fn debug_map_unknown_field() {
247 let result: Result<Debug, _> = serde_saphyr::from_str("unknown_key: true\n");
248 assert!(result.is_err());
249 }
250
251 #[test]
252 fn debug_invalid_type() {
253 let result: Result<Debug, _> = serde_saphyr::from_str("42");
255 assert!(result.is_err());
256 }
257
258 #[test]
259 fn debug_foreground_dark_color() {
260 let d: Debug = serde_saphyr::from_str("color: \"black\"\n").unwrap();
263 assert_eq!(
264 d.color.get_foreground_color().to_tuple(),
265 (255, 255, 255, 255)
266 );
267 }
268
269 #[test]
270 fn debug_foreground_bright_color() {
271 let d: Debug = serde_saphyr::from_str("color: \"white\"\n").unwrap();
273 assert_eq!(d.color.get_foreground_color().to_tuple(), (0, 0, 0, 255));
274 }
275}