1mod error;
2
3use std::collections::HashMap;
4
5use geom::{Point, Rect, Size};
6use serde::de::{Error as _, Unexpected};
7use serde::{Deserialize, Deserializer};
8
9use crate::{BottomSurface, HomingProps, TextHeight, TextMargin, Type};
10
11use super::{BarProps, BumpProps, Profile, TopSurface};
12
13pub use error::{Error, Result};
14
15impl<'de> Deserialize<'de> for BarProps {
16 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
17 where
18 D: Deserializer<'de>,
19 {
20 #[derive(Deserialize)]
21 #[serde(rename_all = "kebab-case")]
22 struct RawBarProps {
23 width: f64,
24 height: f64,
25 y_offset: f64,
26 }
27
28 RawBarProps::deserialize(deserializer).map(|props| {
29 Self {
31 size: Size::new(props.width, props.height) * (1e3 / 19.05),
32 y_offset: props.y_offset * (1000. / 19.05),
33 }
34 })
35 }
36}
37
38impl<'de> Deserialize<'de> for BumpProps {
39 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
40 where
41 D: Deserializer<'de>,
42 {
43 #[derive(Deserialize)]
44 #[serde(rename_all = "kebab-case")]
45 struct RawBumpProps {
46 diameter: f64,
47 y_offset: f64,
48 }
49
50 RawBumpProps::deserialize(deserializer).map(|props| {
51 Self {
53 diameter: props.diameter * (1000. / 19.05),
54 y_offset: props.y_offset * (1000. / 19.05),
55 }
56 })
57 }
58}
59
60impl<'de> Deserialize<'de> for TopSurface {
61 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
62 where
63 D: Deserializer<'de>,
64 {
65 #[derive(Deserialize)]
66 #[serde(rename_all = "kebab-case")]
67 struct RawTopSurface {
68 width: f64,
69 height: f64,
70 radius: f64,
71 y_offset: f64,
72 }
73
74 RawTopSurface::deserialize(deserializer).map(|surface| {
75 Self {
77 size: Size::new(surface.width, surface.height) * (1000. / 19.05),
78 radius: surface.radius * (1000. / 19.05),
79 y_offset: surface.y_offset * (1000. / 19.05),
80 }
81 })
82 }
83}
84
85impl<'de> Deserialize<'de> for BottomSurface {
86 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
87 where
88 D: Deserializer<'de>,
89 {
90 #[derive(Deserialize)]
91 #[serde(rename_all = "kebab-case")]
92 struct RawBottomSurface {
93 width: f64,
94 height: f64,
95 radius: f64,
96 }
97
98 RawBottomSurface::deserialize(deserializer).map(|surface| {
99 Self {
101 size: Size::new(surface.width, surface.height) * (1000. / 19.05),
102 radius: surface.radius * (1000. / 19.05),
103 }
104 })
105 }
106}
107
108#[derive(Debug, Clone, Deserialize)]
109#[serde(rename_all = "kebab-case")]
110struct LegendProps {
111 size: f64,
112 width: f64,
113 height: f64,
114 #[serde(default)]
115 y_offset: f64,
116}
117
118impl LegendProps {
119 fn rect(&self, top_offset: f64) -> Rect {
120 Rect::from_center_size(
121 Point::new(500., 500. + top_offset + self.y_offset),
122 Size::new(self.width, self.height),
123 )
124 }
125}
126
127fn deserialize_legend_map<'de, D>(
128 deserializer: D,
129) -> std::result::Result<HashMap<usize, LegendProps>, D::Error>
130where
131 D: Deserializer<'de>,
132{
133 HashMap::<String, LegendProps>::deserialize(deserializer)?
134 .into_iter()
135 .map(|(s, p)| {
136 let i = s
137 .parse()
138 .map_err(|_| D::Error::invalid_value(Unexpected::Str(&s), &"an integer"))?;
139 let p = LegendProps {
140 size: p.size * (1000. / 19.05),
141 width: p.width * (1000. / 19.05),
142 height: p.height * (1000. / 19.05),
143 y_offset: p.y_offset * (1000. / 19.05),
144 };
145 Ok((i, p))
146 })
147 .collect()
148}
149
150impl<'de> Deserialize<'de> for Profile {
151 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
152 where
153 D: Deserializer<'de>,
154 {
155 #[derive(Deserialize)]
156 struct RawProfileData {
157 #[serde(flatten)]
158 typ: Type,
159 bottom: BottomSurface,
160 top: TopSurface,
161 #[serde(deserialize_with = "deserialize_legend_map")]
162 legend: HashMap<usize, LegendProps>,
163 homing: HomingProps,
164 }
165
166 let raw_data: RawProfileData = RawProfileData::deserialize(deserializer)?;
167
168 let (heights, insets): (HashMap<_, _>, HashMap<_, _>) = raw_data
169 .legend
170 .into_iter()
171 .map(|(i, props)| {
172 let inset = props.rect(raw_data.top.y_offset) - raw_data.top.rect();
173 ((i, props.size), (i, inset))
174 })
175 .unzip();
176
177 Ok(Self {
178 typ: raw_data.typ,
179 bottom: raw_data.bottom,
180 top: raw_data.top,
181 text_margin: TextMargin::new(&insets),
182 text_height: TextHeight::new(&heights),
183 homing: raw_data.homing,
184 })
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use assert_approx_eq::assert_approx_eq;
191
192 use super::*;
193
194 #[test]
195 fn deserialize_bar_props() {
196 let bar_props: BarProps =
197 toml::from_str("width = 3.85\nheight = 0.4\ny-offset = 5.05").unwrap();
198
199 assert_approx_eq!(bar_props.size.width, 202.0, 0.5);
200 assert_approx_eq!(bar_props.size.height, 21.0, 0.5);
201 assert_approx_eq!(bar_props.y_offset, 265.0, 0.5);
202 }
203
204 #[test]
205 fn deserialize_bump_props() {
206 let bar_props: BumpProps = toml::from_str("diameter = 0.4\ny-offset = -0.2").unwrap();
207
208 assert_approx_eq!(bar_props.diameter, 21.0, 0.5);
209 assert_approx_eq!(bar_props.y_offset, -10.5, 0.5);
210 }
211
212 #[test]
213 fn deserialize_top_surface() {
214 let surf: TopSurface =
215 toml::from_str("width = 11.81\nheight = 13.91\nradius = 1.52\ny-offset = -1.62")
216 .unwrap();
217
218 assert_approx_eq!(surf.size.width, 620.0, 0.5);
219 assert_approx_eq!(surf.size.height, 730.0, 0.5);
220 assert_approx_eq!(surf.radius, 80.0, 0.5);
221 assert_approx_eq!(surf.y_offset, -85.0, 0.5);
222 }
223
224 #[test]
225 fn deserialize_bottom_surface() {
226 let surf: BottomSurface =
227 toml::from_str("width = 18.29\nheight = 18.29\nradius = 0.38").unwrap();
228
229 assert_approx_eq!(surf.size.width, 960.0, 0.5);
230 assert_approx_eq!(surf.size.height, 960.0, 0.5);
231 assert_approx_eq!(surf.radius, 20.0, 0.5);
232 }
233}