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> {
if s.trim().eq_ignore_ascii_case("auto") {
return Ok(Length::Auto);
}
parse_suffixed(
s,
"length",
&[
("px", Length::Px),
("vmin", Length::VMin),
("vmax", Length::VMax),
("vw", Length::Vw),
("vh", Length::Vh),
("%", Length::Percent),
],
Length::Px,
)
}
type UnitCtor<T> = fn(f32) -> T;
fn parse_suffixed<T>(
s: &str,
kind: &str,
units: &[(&str, UnitCtor<T>)],
bare: UnitCtor<T>,
) -> Result<T, String> {
let s = s.trim();
let (num, ctor) = units
.iter()
.find_map(|&(suffix, ctor)| s.strip_suffix(suffix).map(|num| (num, ctor)))
.unwrap_or((s, bare));
num.trim()
.parse::<f32>()
.map(ctor)
.map_err(|_| format!("invalid {kind} {s:?}"))
}
fn de_unit<'de, D: Deserializer<'de>, T>(
d: D,
expecting: &'static str,
kind: &'static str,
from_number: fn(f32) -> T,
parse: fn(&str) -> Result<T, String>,
fallback: fn() -> T,
) -> Result<T, D::Error> {
struct UnitVisitor<T> {
expecting: &'static str,
kind: &'static str,
from_number: fn(f32) -> T,
parse: fn(&str) -> Result<T, String>,
fallback: fn() -> T,
}
impl<T> Visitor<'_> for UnitVisitor<T> {
type Value = T;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.expecting)
}
fn visit_f64<E: de::Error>(self, v: f64) -> Result<T, E> {
Ok((self.from_number)(v as f32))
}
fn visit_i64<E: de::Error>(self, v: i64) -> Result<T, E> {
Ok((self.from_number)(v as f32))
}
fn visit_u64<E: de::Error>(self, v: u64) -> Result<T, E> {
Ok((self.from_number)(v as f32))
}
fn visit_str<E: de::Error>(self, s: &str) -> Result<T, E> {
Ok((self.parse)(s).unwrap_or_else(|e| {
decode_warn(self.kind, s, &e);
(self.fallback)()
}))
}
}
d.deserialize_any(UnitVisitor {
expecting,
kind,
from_number,
parse,
fallback,
})
}
impl<'de> Deserialize<'de> for Length {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
de_unit(
d,
"a number (logical pixels) or a CSS length string",
"length",
Length::Px,
parse_length,
Length::default,
)
}
}
#[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<Angle, String> {
use std::f32::consts::{PI, TAU};
parse_suffixed(
s,
"angle",
&[
("deg", |v| Angle(v.to_radians())),
("grad", |v| Angle(v * PI / 200.0)),
("turn", |v| Angle(v * TAU)),
("rad", Angle),
],
|v| Angle(v.to_radians()),
)
}
impl<'de> Deserialize<'de> for Angle {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
de_unit(
d,
"a number (degrees) or a CSS angle string",
"angle",
|v| Angle(v.to_radians()),
parse_angle,
Angle::default,
)
}
}
#[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<Time, String> {
parse_suffixed(
s,
"time",
&[("ms", |v| Time(v / 1000.0)), ("s", Time)],
|v| Time(v / 1000.0),
)
}
impl<'de> Deserialize<'de> for Time {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
de_unit(
d,
"a number (milliseconds) or a CSS time string",
"time",
|v| Time(v / 1000.0),
parse_time,
Time::default,
)
}
}
#[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> {
parse_suffixed(
s,
"fontSize",
&[
("px", FontSize::Px),
("rem", FontSize::Rem),
("vmin", FontSize::VMin),
("vmax", FontSize::VMax),
("vw", FontSize::Vw),
("vh", FontSize::Vh),
],
FontSize::Px,
)
}
impl<'de> Deserialize<'de> for FontSize {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
de_unit(
d,
"a number (logical pixels) or a font-size unit string",
"fontSize",
FontSize::Px,
parse_font_size,
|| FontSize::Px(0.0),
)
}
}
#[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, a {top,right,bottom,left} object, \
or a {horizontal,vertical} axis pair",
)
}
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 horizontal, mut vertical) = (None, None);
let (mut top, mut right, mut bottom, mut left) = (None, None, None, None);
while let Some(key) = map.next_key::<String>()? {
let v = map.next_value::<Length>()?;
match key.as_str() {
"top" => top = Some(v),
"right" => right = Some(v),
"bottom" => bottom = Some(v),
"left" => left = Some(v),
"horizontal" => horizontal = Some(v),
"vertical" => vertical = Some(v),
_ => decode_warn(
"rect",
&key,
&format!(
"unknown rect side {key:?}; ignoring (expected \
top/right/bottom/left or horizontal/vertical)"
),
),
}
}
let mut rect = Rect::default();
if let Some(v) = vertical {
rect.top = v;
rect.bottom = v;
}
if let Some(h) = horizontal {
rect.right = h;
rect.left = h;
}
if let Some(v) = top {
rect.top = v;
}
if let Some(v) = right {
rect.right = v;
}
if let Some(v) = bottom {
rect.bottom = v;
}
if let Some(v) = left {
rect.left = v;
}
Ok(rect)
}
}
d.deserialize_any(RectVisitor)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::animatable::AnimatableField;
use crate::protocol::transform::Transform;
use crate::style::Style;
use crate::style::props::{FONT_SIZE, HEIGHT, PADDING, WIDTH};
#[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.get(&WIDTH).static_val(), Some(Length::default()));
assert_eq!(s.get(&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.get(&FONT_SIZE).copied(), 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.get(&PADDING).copied(), Some(Rect::default()));
let s: Style = serde_json::from_str(r#"{ "padding": "8px 16asd" }"#)
.expect("partial-bad rect must not abort");
assert_eq!(
s.get(&PADDING).copied(),
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.get(&PADDING).copied(),
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.get(&PADDING).copied(), Some(Rect::default()));
}
#[test]
fn rect_axis_pair() {
let rect = |v: &str| {
let s: Style = serde_json::from_str(&format!(r#"{{ "padding": {v} }}"#))
.expect("axis rect must decode");
s.get(&PADDING).copied().expect("padding present")
};
assert_eq!(
rect(r#"{ "horizontal": 8, "vertical": 4 }"#),
Rect {
top: Length::Px(4.0),
bottom: Length::Px(4.0),
right: Length::Px(8.0),
left: Length::Px(8.0),
}
);
assert_eq!(
rect(r#"{ "horizontal": 8 }"#),
Rect {
top: Length::default(),
bottom: Length::default(),
right: Length::Px(8.0),
left: Length::Px(8.0),
}
);
assert_eq!(
rect(r#"{ "horizontal": "50%", "vertical": "auto" }"#),
Rect {
top: Length::Auto,
bottom: Length::Auto,
right: Length::Percent(50.0),
left: Length::Percent(50.0),
}
);
let mixed = Rect {
top: Length::default(),
bottom: Length::default(),
right: Length::Px(8.0),
left: Length::Px(0.0),
};
assert_eq!(rect(r#"{ "horizontal": 8, "left": 0 }"#), mixed);
assert_eq!(rect(r#"{ "left": 0, "horizontal": 8 }"#), mixed);
}
}