1mod error;
4
5use geom::{Point, Size};
6use kle_serial as kle;
7
8use crate::{Homing, Key, Legend, Shape};
9pub use error::{Error, Result};
10
11fn shape_from_kle(key: &kle::Key) -> Result<Shape> {
12 const STEP_CAPS: [f64; 6] = [1.25, 1.0, 0.0, 0.0, 1.75, 1.0];
13 const ISO_VERT: [f64; 6] = [1.25, 2.0, -0.25, 0.0, 1.5, 1.0];
14 const ISO_HORIZ: [f64; 6] = [1.5, 1.0, 0.25, 0.0, 1.25, 2.0];
15
16 fn is_close<const N: usize>(a: &[f64; N], b: &[f64; N]) -> bool {
17 a.iter().zip(b).all(|(a, b)| (b - a).abs() < 1e-2)
18 }
19
20 let &kle::Key {
21 width: w,
22 height: h,
23 x2,
24 y2,
25 width2: w2,
26 height2: h2,
27 ..
28 } = key;
29
30 let is_normal = is_close(&[x2, y2, w2, h2], &[0.0, 0.0, w, h]);
31 let is_1u = is_normal && is_close(&[w, h], &[1.0, 1.0]);
32
33 let dims = [w, h, x2, y2, w2, h2];
34
35 if is_1u && (key.profile.contains("scoop") || key.profile.contains("dish")) {
36 Ok(Shape::Homing(Some(Homing::Scoop)))
37 } else if is_1u && key.profile.contains("bar") {
38 Ok(Shape::Homing(Some(Homing::Bar)))
39 } else if is_1u && (key.profile.contains("bump") || key.profile.contains("dot")) {
40 Ok(Shape::Homing(Some(Homing::Bump)))
41 } else if is_normal && key.profile.contains("space") {
42 Ok(Shape::Space(Size::new(w, h)))
43 } else if is_1u && key.homing {
44 Ok(Shape::Homing(None))
45 } else if key.decal {
46 Ok(Shape::None(Size::new(w, h)))
47 } else if is_normal {
48 Ok(Shape::Normal(Size::new(w, h)))
49 } else if is_close(&dims, &STEP_CAPS) {
50 Ok(Shape::SteppedCaps)
51 } else if is_close(&dims, &ISO_VERT) {
52 Ok(Shape::IsoVertical)
53 } else if is_close(&dims, &ISO_HORIZ) {
54 Ok(Shape::IsoHorizontal)
55 } else {
56 Err(Error::UnsupportedKeySize {
58 w,
59 h,
60 x2,
61 y2,
62 w2,
63 h2,
64 })
65 }
66}
67
68impl From<kle::Legend> for Legend {
69 fn from(legend: kle::Legend) -> Self {
70 let kle::Legend { text, size, color } = legend;
71 Self {
72 text,
73 size_idx: size,
74 color: color.rgb().into(),
75 }
76 }
77}
78
79impl TryFrom<kle::Key> for Key {
80 type Error = Error;
81
82 fn try_from(mut key: kle::Key) -> Result<Self> {
83 let position = Point::new(key.x + key.x2.min(0.), key.y + key.y2.min(0.));
84 let shape = shape_from_kle(&key)?;
85 let color = key.color.rgb().into();
86 let legends = {
87 let mut arr = <[Option<kle::Legend>; 9]>::default();
88 arr.swap_with_slice(&mut key.legends[..9]);
89 arr
90 };
91 let legends = legends.map(|l| l.map(Legend::from)).into();
92 Ok(Self {
93 position,
94 shape,
95 color,
96 legends,
97 })
98 }
99}
100
101pub fn from_json(json: &str) -> Result<Vec<Key>> {
107 let key_iter: kle::KeyIterator = serde_json::from_str(json)?;
108 key_iter.map(Key::try_from).collect()
109}
110
111#[cfg(test)]
112mod tests {
113 use assert_matches::assert_matches;
114 use unindent::unindent;
115
116 use super::*;
117
118 #[test]
119 fn key_shape_from_kle() {
120 let default_key = shape_from_kle(&kle::Key::default()).unwrap();
121 let decal = shape_from_kle(&kle::Key {
122 decal: true,
123 ..Default::default()
124 })
125 .unwrap();
126 let space = shape_from_kle(&kle::Key {
127 profile: "space".into(),
128 ..Default::default()
129 })
130 .unwrap();
131 let homing_default = shape_from_kle(&kle::Key {
132 homing: true,
133 ..Default::default()
134 })
135 .unwrap();
136 let homing_scoop = shape_from_kle(&kle::Key {
137 profile: "scoop".into(),
138 ..Default::default()
139 })
140 .unwrap();
141 let homing_bar = shape_from_kle(&kle::Key {
142 profile: "bar".into(),
143 ..Default::default()
144 })
145 .unwrap();
146 let homing_bump = shape_from_kle(&kle::Key {
147 profile: "bump".into(),
148 ..Default::default()
149 })
150 .unwrap();
151 let regular_key = shape_from_kle(&kle::Key {
152 width: 2.25,
153 height: 1.,
154 x2: 0.,
155 y2: 0.,
156 width2: 2.25,
157 height2: 1.,
158 ..Default::default()
159 })
160 .unwrap();
161 let iso_horiz = shape_from_kle(&kle::Key {
162 width: 1.5,
163 height: 1.,
164 x2: 0.25,
165 y2: 0.,
166 width2: 1.25,
167 height2: 2.,
168 ..Default::default()
169 })
170 .unwrap();
171 let iso_vert = shape_from_kle(&kle::Key {
172 width: 1.25,
173 height: 2.,
174 x2: -0.25,
175 y2: 0.,
176 width2: 1.5,
177 height2: 1.,
178 ..Default::default()
179 })
180 .unwrap();
181 let step_caps = shape_from_kle(&kle::Key {
182 width: 1.25,
183 height: 1.,
184 x2: 0.,
185 y2: 0.,
186 width2: 1.75,
187 height2: 1.,
188 ..Default::default()
189 })
190 .unwrap();
191
192 assert_matches!(default_key, Shape::Normal(size) if size == Size::new(1.0, 1.0));
193 assert_matches!(regular_key, Shape::Normal(size) if size == Size::new(2.25, 1.0));
194 assert_matches!(decal, Shape::None(size) if size == Size::new(1.0, 1.0));
195 assert_matches!(space, Shape::Space(size) if size == Size::new(1.0, 1.0));
196 assert_matches!(homing_default, Shape::Homing(None));
197 assert_matches!(homing_scoop, Shape::Homing(Some(Homing::Scoop)));
198 assert_matches!(homing_bar, Shape::Homing(Some(Homing::Bar)));
199 assert_matches!(homing_bump, Shape::Homing(Some(Homing::Bump)));
200 assert_matches!(iso_horiz, Shape::IsoHorizontal);
201 assert_matches!(iso_vert, Shape::IsoVertical);
202 assert_matches!(step_caps, Shape::SteppedCaps);
203 }
204
205 #[test]
206 fn key_shape_from_kle_invalid() {
207 let invalid = shape_from_kle(&kle::Key {
208 width: 1.,
209 height: 1.,
210 x2: -0.25,
211 y2: 0.,
212 width2: 1.5,
213 height2: 1.,
214 ..Default::default()
215 });
216
217 assert!(invalid.is_err());
218 assert_eq!(
219 format!("{}", invalid.unwrap_err()),
220 format!(concat!(
221 "unsupported non-standard key size (w: 1.00, h: 1.00, ",
222 "x2: -0.25, y2: 0.00, w2: 1.50, h2: 1.00). Note only ISO enter and stepped caps ",
223 "are supported as special cases"
224 ))
225 );
226 }
227
228 #[test]
229 fn kle_from_json() {
230 let result1: Vec<_> = from_json(&unindent(
231 r#"[
232 {
233 "meta": "data"
234 },
235 [
236 {
237 "a": 4,
238 "unknown": "key"
239 },
240 "A",
241 "B",
242 {
243 "x": -0.5,
244 "y": 0.25
245 },
246 "C"
247 ],
248 [
249 "D"
250 ]
251 ]"#,
252 ))
253 .unwrap();
254
255 assert_eq!(result1.len(), 4);
256 assert_eq!(result1[0].position.x, 0.0);
257 assert_eq!(result1[1].position.x, 1.0);
258 assert_eq!(result1[2].position.x, 1.5);
259 assert_eq!(result1[3].position.x, 0.0);
260
261 let result2: Vec<_> = from_json(&unindent(
262 r#"[
263 [
264 "A"
265 ]
266 ]"#,
267 ))
268 .unwrap();
269
270 assert_eq!(result2.len(), 1);
271 }
272}