1use std::fmt;
5
6use serde::Deserialize;
7use serde::de::{self, Deserializer, MapAccess, Visitor};
8
9use super::decode_warn;
10
11#[derive(Debug, Clone, Copy, PartialEq)]
14pub enum Length {
15 Auto,
16 Px(f32),
17 Percent(f32),
18 Vw(f32),
19 Vh(f32),
20 VMin(f32),
21 VMax(f32),
22}
23
24impl Default for Length {
25 fn default() -> Self {
26 Length::Px(0.0)
27 }
28}
29
30fn parse_length(s: &str) -> Result<Length, String> {
32 let s = s.trim();
33 if s.eq_ignore_ascii_case("auto") {
34 return Ok(Length::Auto);
35 }
36 type LengthCtor = fn(f32) -> Length;
39 let units: [(&str, LengthCtor); 6] = [
40 ("px", Length::Px),
41 ("vmin", Length::VMin),
42 ("vmax", Length::VMax),
43 ("vw", Length::Vw),
44 ("vh", Length::Vh),
45 ("%", Length::Percent),
46 ];
47 for (suffix, ctor) in units {
48 if let Some(num) = s.strip_suffix(suffix) {
49 let v: f32 = num
50 .trim()
51 .parse()
52 .map_err(|_| format!("invalid length {s:?}"))?;
53 return Ok(ctor(v));
54 }
55 }
56 s.parse::<f32>()
57 .map(Length::Px)
58 .map_err(|_| format!("invalid length {s:?}"))
59}
60
61impl<'de> Deserialize<'de> for Length {
62 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
63 struct LengthVisitor;
64 impl<'de> Visitor<'de> for LengthVisitor {
65 type Value = Length;
66 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
67 f.write_str("a number (logical pixels) or a CSS length string")
68 }
69 fn visit_f64<E: de::Error>(self, v: f64) -> Result<Length, E> {
70 Ok(Length::Px(v as f32))
71 }
72 fn visit_i64<E: de::Error>(self, v: i64) -> Result<Length, E> {
73 Ok(Length::Px(v as f32))
74 }
75 fn visit_u64<E: de::Error>(self, v: u64) -> Result<Length, E> {
76 Ok(Length::Px(v as f32))
77 }
78 fn visit_str<E: de::Error>(self, s: &str) -> Result<Length, E> {
79 Ok(parse_length(s).unwrap_or_else(|e| {
80 decode_warn("length", s, &e);
81 Length::default()
82 }))
83 }
84 }
85 d.deserialize_any(LengthVisitor)
86 }
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Default)]
93pub struct Angle(f32);
94
95impl Angle {
96 pub fn radians(self) -> f32 {
98 self.0
99 }
100
101 pub fn from_radians(radians: f32) -> Self {
104 Angle(radians)
105 }
106}
107
108fn parse_angle(s: &str) -> Result<f32, String> {
112 use std::f32::consts::{PI, TAU};
113 let s = s.trim();
114 type AngleConv = fn(f32) -> f32;
115 let units: [(&str, AngleConv); 4] = [
116 ("deg", f32::to_radians),
117 ("grad", |v| v * PI / 200.0),
118 ("turn", |v| v * TAU),
119 ("rad", |v| v),
120 ];
121 for (suffix, conv) in units {
122 if let Some(num) = s.strip_suffix(suffix) {
123 let v: f32 = num
124 .trim()
125 .parse()
126 .map_err(|_| format!("invalid angle {s:?}"))?;
127 return Ok(conv(v));
128 }
129 }
130 s.parse::<f32>()
131 .map(f32::to_radians)
132 .map_err(|_| format!("invalid angle {s:?}"))
133}
134
135impl<'de> Deserialize<'de> for Angle {
136 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
137 struct AngleVisitor;
138 impl Visitor<'_> for AngleVisitor {
139 type Value = Angle;
140 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
141 f.write_str("a number (degrees) or a CSS angle string")
142 }
143 fn visit_f64<E: de::Error>(self, v: f64) -> Result<Angle, E> {
144 Ok(Angle((v as f32).to_radians()))
145 }
146 fn visit_i64<E: de::Error>(self, v: i64) -> Result<Angle, E> {
147 Ok(Angle((v as f32).to_radians()))
148 }
149 fn visit_u64<E: de::Error>(self, v: u64) -> Result<Angle, E> {
150 Ok(Angle((v as f32).to_radians()))
151 }
152 fn visit_str<E: de::Error>(self, s: &str) -> Result<Angle, E> {
153 Ok(parse_angle(s).map(Angle).unwrap_or_else(|e| {
154 decode_warn("angle", s, &e);
155 Angle::default()
156 }))
157 }
158 }
159 d.deserialize_any(AngleVisitor)
160 }
161}
162
163#[derive(Debug, Clone, Copy, PartialEq, Default)]
167pub struct Time(f32);
168
169impl Time {
170 pub fn from_secs(secs: f32) -> Self {
172 Time(secs)
173 }
174 pub fn seconds(self) -> f32 {
176 self.0
177 }
178}
179
180fn parse_time(s: &str) -> Result<f32, String> {
184 let s = s.trim();
185 if let Some(num) = s.strip_suffix("ms") {
186 return num
187 .trim()
188 .parse::<f32>()
189 .map(|v| v / 1000.0)
190 .map_err(|_| format!("invalid time {s:?}"));
191 }
192 if let Some(num) = s.strip_suffix('s') {
193 return num
194 .trim()
195 .parse::<f32>()
196 .map_err(|_| format!("invalid time {s:?}"));
197 }
198 s.parse::<f32>()
199 .map(|v| v / 1000.0)
200 .map_err(|_| format!("invalid time {s:?}"))
201}
202
203impl<'de> Deserialize<'de> for Time {
204 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
205 struct TimeVisitor;
206 impl Visitor<'_> for TimeVisitor {
207 type Value = Time;
208 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
209 f.write_str("a number (milliseconds) or a CSS time string")
210 }
211 fn visit_f64<E: de::Error>(self, v: f64) -> Result<Time, E> {
212 Ok(Time(v as f32 / 1000.0))
213 }
214 fn visit_i64<E: de::Error>(self, v: i64) -> Result<Time, E> {
215 Ok(Time(v as f32 / 1000.0))
216 }
217 fn visit_u64<E: de::Error>(self, v: u64) -> Result<Time, E> {
218 Ok(Time(v as f32 / 1000.0))
219 }
220 fn visit_str<E: de::Error>(self, s: &str) -> Result<Time, E> {
221 Ok(parse_time(s).map(Time).unwrap_or_else(|e| {
222 decode_warn("time", s, &e);
223 Time::default()
224 }))
225 }
226 }
227 d.deserialize_any(TimeVisitor)
228 }
229}
230
231#[derive(Debug, Clone, Copy, PartialEq)]
236pub enum FontSize {
237 Px(f32),
238 Vw(f32),
239 Vh(f32),
240 VMin(f32),
241 VMax(f32),
242 Rem(f32),
243}
244
245fn parse_font_size(s: &str) -> Result<FontSize, String> {
249 let s = s.trim();
250 type FsCtor = fn(f32) -> FontSize;
251 let units: [(&str, FsCtor); 6] = [
252 ("px", FontSize::Px),
253 ("rem", FontSize::Rem),
254 ("vmin", FontSize::VMin),
255 ("vmax", FontSize::VMax),
256 ("vw", FontSize::Vw),
257 ("vh", FontSize::Vh),
258 ];
259 for (suffix, ctor) in units {
260 if let Some(num) = s.strip_suffix(suffix) {
261 let v: f32 = num
262 .trim()
263 .parse()
264 .map_err(|_| format!("invalid fontSize {s:?}"))?;
265 return Ok(ctor(v));
266 }
267 }
268 s.parse::<f32>()
269 .map(FontSize::Px)
270 .map_err(|_| format!("invalid fontSize {s:?}"))
271}
272
273impl<'de> Deserialize<'de> for FontSize {
274 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
275 struct FontSizeVisitor;
276 impl Visitor<'_> for FontSizeVisitor {
277 type Value = FontSize;
278 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
279 f.write_str("a number (logical pixels) or a font-size unit string")
280 }
281 fn visit_f64<E: de::Error>(self, v: f64) -> Result<FontSize, E> {
282 Ok(FontSize::Px(v as f32))
283 }
284 fn visit_i64<E: de::Error>(self, v: i64) -> Result<FontSize, E> {
285 Ok(FontSize::Px(v as f32))
286 }
287 fn visit_u64<E: de::Error>(self, v: u64) -> Result<FontSize, E> {
288 Ok(FontSize::Px(v as f32))
289 }
290 fn visit_str<E: de::Error>(self, s: &str) -> Result<FontSize, E> {
291 Ok(parse_font_size(s).unwrap_or_else(|e| {
292 decode_warn("fontSize", s, &e);
293 FontSize::Px(0.0)
294 }))
295 }
296 }
297 d.deserialize_any(FontSizeVisitor)
298 }
299}
300
301#[derive(Debug, Clone, Copy, PartialEq, Default)]
304pub struct Rect {
305 pub top: Length,
306 pub right: Length,
307 pub bottom: Length,
308 pub left: Length,
309}
310
311impl Rect {
312 fn uniform(v: Length) -> Self {
313 Rect {
314 top: v,
315 right: v,
316 bottom: v,
317 left: v,
318 }
319 }
320
321 fn from_shorthand(values: &[Length]) -> Result<Self, String> {
323 Ok(match values {
324 [a] => Rect::uniform(*a),
325 [a, b] => Rect {
326 top: *a,
327 bottom: *a,
328 right: *b,
329 left: *b,
330 },
331 [a, b, c] => Rect {
332 top: *a,
333 right: *b,
334 left: *b,
335 bottom: *c,
336 },
337 [a, b, c, d] => Rect {
338 top: *a,
339 right: *b,
340 bottom: *c,
341 left: *d,
342 },
343 _ => return Err("expected 1–4 length values".into()),
344 })
345 }
346}
347
348impl<'de> Deserialize<'de> for Rect {
349 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
350 struct RectVisitor;
351 impl<'de> Visitor<'de> for RectVisitor {
352 type Value = Rect;
353 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
354 f.write_str("a number, a CSS shorthand string, or a {top,right,bottom,left} object")
355 }
356 fn visit_f64<E: de::Error>(self, v: f64) -> Result<Rect, E> {
357 Ok(Rect::uniform(Length::Px(v as f32)))
358 }
359 fn visit_i64<E: de::Error>(self, v: i64) -> Result<Rect, E> {
360 Ok(Rect::uniform(Length::Px(v as f32)))
361 }
362 fn visit_u64<E: de::Error>(self, v: u64) -> Result<Rect, E> {
363 Ok(Rect::uniform(Length::Px(v as f32)))
364 }
365 fn visit_str<E: de::Error>(self, s: &str) -> Result<Rect, E> {
366 let values: Vec<Length> = s
369 .split_whitespace()
370 .map(|tok| {
371 parse_length(tok).unwrap_or_else(|e| {
372 decode_warn("rect", tok, &e);
373 Length::default()
374 })
375 })
376 .collect();
377 Ok(Rect::from_shorthand(&values).unwrap_or_else(|e| {
378 decode_warn("rect", s, &format!("invalid rect {s:?}: {e}"));
379 Rect::default()
380 }))
381 }
382 fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Rect, A::Error> {
383 let mut rect = Rect::default();
384 while let Some(key) = map.next_key::<String>()? {
385 let v = map.next_value::<Length>()?;
386 match key.as_str() {
387 "top" => rect.top = v,
388 "right" => rect.right = v,
389 "bottom" => rect.bottom = v,
390 "left" => rect.left = v,
391 _ => decode_warn(
394 "rect",
395 &key,
396 &format!(
397 "unknown rect side {key:?}; ignoring (expected top/right/bottom/left)"
398 ),
399 ),
400 }
401 }
402 Ok(rect)
403 }
404 }
405 d.deserialize_any(RectVisitor)
406 }
407}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412 use crate::protocol::animatable::AnimatableField;
413 use crate::protocol::style::Style;
414 use crate::protocol::transform::Transform;
415
416 #[test]
419 fn angle_units() {
420 use std::f32::consts::{PI, TAU};
421 let parse = |v: serde_json::Value| serde_json::from_value::<Angle>(v).unwrap().radians();
422 assert!((parse(serde_json::json!(180)) - PI).abs() < 1e-5);
423 assert!((parse(serde_json::json!("180deg")) - PI).abs() < 1e-5);
424 assert!((parse(serde_json::json!("3.14159rad")) - PI).abs() < 1e-4);
425 assert!((parse(serde_json::json!("0.5turn")) - PI).abs() < 1e-5);
426 assert!((parse(serde_json::json!("400grad")) - TAU).abs() < 1e-5);
427 }
428
429 #[test]
433 fn bad_unit_values_fall_back_instead_of_aborting() {
434 let s: Style = serde_json::from_str(r#"{ "width": "100pixels", "height": "40px" }"#)
436 .expect("a bad length must not abort deserialization");
437 assert_eq!(s.width.static_val(), Some(Length::default()));
438 assert_eq!(s.height.static_val(), Some(Length::Px(40.0)));
439
440 let s: Style = serde_json::from_str(r#"{ "fontSize": "16pxx" }"#)
442 .expect("bad fontSize must not abort");
443 assert_eq!(s.font_size, Some(FontSize::Px(0.0)));
444
445 let t: Transform = serde_json::from_str(r#"{ "rotate": "45degg", "translateX": "50%" }"#)
447 .expect("bad angle must not abort");
448 assert_eq!(t.rotate.static_val(), Some(Angle::default()));
449 assert_eq!(t.translate_x.static_val(), Some(Length::Percent(50.0)));
450
451 let s: Style =
455 serde_json::from_str(r#"{ "padding": "16asd" }"#).expect("bad rect must not abort");
456 assert_eq!(s.padding, Some(Rect::default()));
457
458 let s: Style = serde_json::from_str(r#"{ "padding": "8px 16asd" }"#)
459 .expect("partial-bad rect must not abort");
460 assert_eq!(
462 s.padding,
463 Some(Rect {
464 top: Length::Px(8.0),
465 bottom: Length::Px(8.0),
466 right: Length::default(),
467 left: Length::default(),
468 })
469 );
470
471 let s: Style = serde_json::from_str(r#"{ "padding": "8px 16px" }"#)
472 .expect("valid two-value shorthand decodes");
473 assert_eq!(
474 s.padding,
475 Some(Rect {
476 top: Length::Px(8.0),
477 bottom: Length::Px(8.0),
478 right: Length::Px(16.0),
479 left: Length::Px(16.0),
480 })
481 );
482
483 let s: Style = serde_json::from_str(r#"{ "padding": "1px 2px 3px 4px 5px" }"#)
485 .expect("bad value-count must not abort");
486 assert_eq!(s.padding, Some(Rect::default()));
487 }
488}