keyset_profile/de/
mod.rs

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            // Convert mm to milli units
30            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            // Convert mm to milli units
52            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            // Convert mm to milli units
76            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            // Convert mm to milli units
100            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}