use std::fmt;
use serde::Deserialize;
use serde::de::{self, Deserializer, MapAccess, Visitor};
use super::decode_warn;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Length {
Auto,
Px(f32),
Percent(f32),
Vw(f32),
Vh(f32),
VMin(f32),
VMax(f32),
}
impl Default for Length {
fn default() -> Self {
Length::Px(0.0)
}
}
fn parse_length(s: &str) -> Result<Length, String> {
let s = s.trim();
if s.eq_ignore_ascii_case("auto") {
return Ok(Length::Auto);
}
type LengthCtor = fn(f32) -> Length;
let units: [(&str, LengthCtor); 6] = [
("px", Length::Px),
("vmin", Length::VMin),
("vmax", Length::VMax),
("vw", Length::Vw),
("vh", Length::Vh),
("%", Length::Percent),
];
for (suffix, ctor) in units {
if let Some(num) = s.strip_suffix(suffix) {
let v: f32 = num
.trim()
.parse()
.map_err(|_| format!("invalid length {s:?}"))?;
return Ok(ctor(v));
}
}
s.parse::<f32>()
.map(Length::Px)
.map_err(|_| format!("invalid length {s:?}"))
}
impl<'de> Deserialize<'de> for Length {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
struct LengthVisitor;
impl<'de> Visitor<'de> for LengthVisitor {
type Value = Length;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("a number (logical pixels) or a CSS length string")
}
fn visit_f64<E: de::Error>(self, v: f64) -> Result<Length, E> {
Ok(Length::Px(v as f32))
}
fn visit_i64<E: de::Error>(self, v: i64) -> Result<Length, E> {
Ok(Length::Px(v as f32))
}
fn visit_u64<E: de::Error>(self, v: u64) -> Result<Length, E> {
Ok(Length::Px(v as f32))
}
fn visit_str<E: de::Error>(self, s: &str) -> Result<Length, E> {
Ok(parse_length(s).unwrap_or_else(|e| {
decode_warn("length", s, &e);
Length::default()
}))
}
}
d.deserialize_any(LengthVisitor)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Angle(f32);
impl Angle {
pub fn radians(self) -> f32 {
self.0
}
pub fn from_radians(radians: f32) -> Self {
Angle(radians)
}
}
fn parse_angle(s: &str) -> Result<f32, String> {
use std::f32::consts::{PI, TAU};
let s = s.trim();
type AngleConv = fn(f32) -> f32;
let units: [(&str, AngleConv); 4] = [
("deg", f32::to_radians),
("grad", |v| v * PI / 200.0),
("turn", |v| v * TAU),
("rad", |v| v),
];
for (suffix, conv) in units {
if let Some(num) = s.strip_suffix(suffix) {
let v: f32 = num
.trim()
.parse()
.map_err(|_| format!("invalid angle {s:?}"))?;
return Ok(conv(v));
}
}
s.parse::<f32>()
.map(f32::to_radians)
.map_err(|_| format!("invalid angle {s:?}"))
}
impl<'de> Deserialize<'de> for Angle {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
struct AngleVisitor;
impl Visitor<'_> for AngleVisitor {
type Value = Angle;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("a number (degrees) or a CSS angle string")
}
fn visit_f64<E: de::Error>(self, v: f64) -> Result<Angle, E> {
Ok(Angle((v as f32).to_radians()))
}
fn visit_i64<E: de::Error>(self, v: i64) -> Result<Angle, E> {
Ok(Angle((v as f32).to_radians()))
}
fn visit_u64<E: de::Error>(self, v: u64) -> Result<Angle, E> {
Ok(Angle((v as f32).to_radians()))
}
fn visit_str<E: de::Error>(self, s: &str) -> Result<Angle, E> {
Ok(parse_angle(s).map(Angle).unwrap_or_else(|e| {
decode_warn("angle", s, &e);
Angle::default()
}))
}
}
d.deserialize_any(AngleVisitor)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Time(f32);
impl Time {
pub fn from_secs(secs: f32) -> Self {
Time(secs)
}
pub fn seconds(self) -> f32 {
self.0
}
}
fn parse_time(s: &str) -> Result<f32, String> {
let s = s.trim();
if let Some(num) = s.strip_suffix("ms") {
return num
.trim()
.parse::<f32>()
.map(|v| v / 1000.0)
.map_err(|_| format!("invalid time {s:?}"));
}
if let Some(num) = s.strip_suffix('s') {
return num
.trim()
.parse::<f32>()
.map_err(|_| format!("invalid time {s:?}"));
}
s.parse::<f32>()
.map(|v| v / 1000.0)
.map_err(|_| format!("invalid time {s:?}"))
}
impl<'de> Deserialize<'de> for Time {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
struct TimeVisitor;
impl Visitor<'_> for TimeVisitor {
type Value = Time;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("a number (milliseconds) or a CSS time string")
}
fn visit_f64<E: de::Error>(self, v: f64) -> Result<Time, E> {
Ok(Time(v as f32 / 1000.0))
}
fn visit_i64<E: de::Error>(self, v: i64) -> Result<Time, E> {
Ok(Time(v as f32 / 1000.0))
}
fn visit_u64<E: de::Error>(self, v: u64) -> Result<Time, E> {
Ok(Time(v as f32 / 1000.0))
}
fn visit_str<E: de::Error>(self, s: &str) -> Result<Time, E> {
Ok(parse_time(s).map(Time).unwrap_or_else(|e| {
decode_warn("time", s, &e);
Time::default()
}))
}
}
d.deserialize_any(TimeVisitor)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FontSize {
Px(f32),
Vw(f32),
Vh(f32),
VMin(f32),
VMax(f32),
Rem(f32),
}
fn parse_font_size(s: &str) -> Result<FontSize, String> {
let s = s.trim();
type FsCtor = fn(f32) -> FontSize;
let units: [(&str, FsCtor); 6] = [
("px", FontSize::Px),
("rem", FontSize::Rem),
("vmin", FontSize::VMin),
("vmax", FontSize::VMax),
("vw", FontSize::Vw),
("vh", FontSize::Vh),
];
for (suffix, ctor) in units {
if let Some(num) = s.strip_suffix(suffix) {
let v: f32 = num
.trim()
.parse()
.map_err(|_| format!("invalid fontSize {s:?}"))?;
return Ok(ctor(v));
}
}
s.parse::<f32>()
.map(FontSize::Px)
.map_err(|_| format!("invalid fontSize {s:?}"))
}
impl<'de> Deserialize<'de> for FontSize {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
struct FontSizeVisitor;
impl Visitor<'_> for FontSizeVisitor {
type Value = FontSize;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("a number (logical pixels) or a font-size unit string")
}
fn visit_f64<E: de::Error>(self, v: f64) -> Result<FontSize, E> {
Ok(FontSize::Px(v as f32))
}
fn visit_i64<E: de::Error>(self, v: i64) -> Result<FontSize, E> {
Ok(FontSize::Px(v as f32))
}
fn visit_u64<E: de::Error>(self, v: u64) -> Result<FontSize, E> {
Ok(FontSize::Px(v as f32))
}
fn visit_str<E: de::Error>(self, s: &str) -> Result<FontSize, E> {
Ok(parse_font_size(s).unwrap_or_else(|e| {
decode_warn("fontSize", s, &e);
FontSize::Px(0.0)
}))
}
}
d.deserialize_any(FontSizeVisitor)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Rect {
pub top: Length,
pub right: Length,
pub bottom: Length,
pub left: Length,
}
impl Rect {
fn uniform(v: Length) -> Self {
Rect {
top: v,
right: v,
bottom: v,
left: v,
}
}
fn from_shorthand(values: &[Length]) -> Result<Self, String> {
Ok(match values {
[a] => Rect::uniform(*a),
[a, b] => Rect {
top: *a,
bottom: *a,
right: *b,
left: *b,
},
[a, b, c] => Rect {
top: *a,
right: *b,
left: *b,
bottom: *c,
},
[a, b, c, d] => Rect {
top: *a,
right: *b,
bottom: *c,
left: *d,
},
_ => return Err("expected 1–4 length values".into()),
})
}
}
impl<'de> Deserialize<'de> for Rect {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
struct RectVisitor;
impl<'de> Visitor<'de> for RectVisitor {
type Value = Rect;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("a number, a CSS shorthand string, or a {top,right,bottom,left} object")
}
fn visit_f64<E: de::Error>(self, v: f64) -> Result<Rect, E> {
Ok(Rect::uniform(Length::Px(v as f32)))
}
fn visit_i64<E: de::Error>(self, v: i64) -> Result<Rect, E> {
Ok(Rect::uniform(Length::Px(v as f32)))
}
fn visit_u64<E: de::Error>(self, v: u64) -> Result<Rect, E> {
Ok(Rect::uniform(Length::Px(v as f32)))
}
fn visit_str<E: de::Error>(self, s: &str) -> Result<Rect, E> {
let values: Vec<Length> = s
.split_whitespace()
.map(|tok| {
parse_length(tok).unwrap_or_else(|e| {
decode_warn("rect", tok, &e);
Length::default()
})
})
.collect();
Ok(Rect::from_shorthand(&values).unwrap_or_else(|e| {
decode_warn("rect", s, &format!("invalid rect {s:?}: {e}"));
Rect::default()
}))
}
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Rect, A::Error> {
let mut rect = Rect::default();
while let Some(key) = map.next_key::<String>()? {
let v = map.next_value::<Length>()?;
match key.as_str() {
"top" => rect.top = v,
"right" => rect.right = v,
"bottom" => rect.bottom = v,
"left" => rect.left = v,
_ => decode_warn(
"rect",
&key,
&format!(
"unknown rect side {key:?}; ignoring (expected top/right/bottom/left)"
),
),
}
}
Ok(rect)
}
}
d.deserialize_any(RectVisitor)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::animatable::AnimatableField;
use crate::protocol::style::Style;
use crate::protocol::transform::Transform;
#[test]
fn angle_units() {
use std::f32::consts::{PI, TAU};
let parse = |v: serde_json::Value| serde_json::from_value::<Angle>(v).unwrap().radians();
assert!((parse(serde_json::json!(180)) - PI).abs() < 1e-5);
assert!((parse(serde_json::json!("180deg")) - PI).abs() < 1e-5);
assert!((parse(serde_json::json!("3.14159rad")) - PI).abs() < 1e-4);
assert!((parse(serde_json::json!("0.5turn")) - PI).abs() < 1e-5);
assert!((parse(serde_json::json!("400grad")) - TAU).abs() < 1e-5);
}
#[test]
fn bad_unit_values_fall_back_instead_of_aborting() {
let s: Style = serde_json::from_str(r#"{ "width": "100pixels", "height": "40px" }"#)
.expect("a bad length must not abort deserialization");
assert_eq!(s.width.static_val(), Some(Length::default()));
assert_eq!(s.height.static_val(), Some(Length::Px(40.0)));
let s: Style = serde_json::from_str(r#"{ "fontSize": "16pxx" }"#)
.expect("bad fontSize must not abort");
assert_eq!(s.font_size, Some(FontSize::Px(0.0)));
let t: Transform = serde_json::from_str(r#"{ "rotate": "45degg", "translateX": "50%" }"#)
.expect("bad angle must not abort");
assert_eq!(t.rotate.static_val(), Some(Angle::default()));
assert_eq!(t.translate_x.static_val(), Some(Length::Percent(50.0)));
let s: Style =
serde_json::from_str(r#"{ "padding": "16asd" }"#).expect("bad rect must not abort");
assert_eq!(s.padding, Some(Rect::default()));
let s: Style = serde_json::from_str(r#"{ "padding": "8px 16asd" }"#)
.expect("partial-bad rect must not abort");
assert_eq!(
s.padding,
Some(Rect {
top: Length::Px(8.0),
bottom: Length::Px(8.0),
right: Length::default(),
left: Length::default(),
})
);
let s: Style = serde_json::from_str(r#"{ "padding": "8px 16px" }"#)
.expect("valid two-value shorthand decodes");
assert_eq!(
s.padding,
Some(Rect {
top: Length::Px(8.0),
bottom: Length::Px(8.0),
right: Length::Px(16.0),
left: Length::Px(16.0),
})
);
let s: Style = serde_json::from_str(r#"{ "padding": "1px 2px 3px 4px 5px" }"#)
.expect("bad value-count must not abort");
assert_eq!(s.padding, Some(Rect::default()));
}
}