1use std::fmt;
5
6use serde::Deserialize;
7use serde::de::{self, Deserializer, MapAccess, Visitor};
8
9use super::decode_warn;
10use super::units::{Angle, Length};
11
12#[derive(Debug, Clone, Default, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct OutlineSpec {
16 #[serde(default)]
17 pub width: Option<Length>,
18 #[serde(default)]
19 pub offset: Option<Length>,
20 #[serde(default)]
21 pub color: Option<String>,
22}
23
24#[derive(Debug, Clone, Default, Deserialize)]
26#[serde(rename_all = "camelCase")]
27pub struct BoxShadowSpec {
28 #[serde(default)]
29 pub color: Option<String>,
30 #[serde(default)]
31 pub x_offset: Option<Length>,
32 #[serde(default)]
33 pub y_offset: Option<Length>,
34 #[serde(default)]
35 pub spread_radius: Option<Length>,
36 #[serde(default)]
37 pub blur_radius: Option<Length>,
38}
39
40#[derive(Debug, Clone, Deserialize)]
42#[serde(untagged)]
43pub enum BoxShadowList {
44 One(BoxShadowSpec),
45 Many(Vec<BoxShadowSpec>),
46}
47
48#[derive(Debug, Clone, Deserialize)]
52#[serde(untagged)]
53pub enum LineHeightSpec {
54 Relative(f32),
55 Px { px: f32 },
56 Str(String),
57}
58
59#[derive(Debug, Clone, Deserialize)]
63#[serde(untagged)]
64pub enum LetterSpacingSpec {
65 Px(f32),
66 Rem { rem: f32 },
67 Str(String),
68}
69
70#[derive(Debug, Clone, Default, Deserialize)]
74#[serde(rename_all = "camelCase")]
75pub struct TextShadowSpec {
76 #[serde(default)]
77 pub color: Option<String>,
78 #[serde(default)]
79 pub offset_x: Option<f32>,
80 #[serde(default)]
81 pub offset_y: Option<f32>,
82}
83
84#[derive(Debug, Clone, Deserialize)]
89#[serde(rename_all = "camelCase")]
90pub struct GradientStop {
91 pub color: String,
92 #[serde(default)]
93 pub position: Option<Length>,
94 #[serde(default)]
95 pub hint: Option<f32>,
96}
97
98#[derive(Debug, Clone, Deserialize)]
101#[serde(rename_all = "camelCase")]
102pub struct AngularStop {
103 pub color: String,
104 #[serde(default)]
105 pub angle: Option<Angle>,
106 #[serde(default)]
107 pub hint: Option<f32>,
108}
109
110pub type GradientPosition = String;
113
114pub type ColorSpace = String;
118
119#[derive(Debug, Clone, Deserialize)]
123#[serde(rename_all = "camelCase")]
124pub enum RadialShapeSpec {
125 Keyword(String),
126 Circle { circle: Length },
127 Ellipse { ellipse: [Length; 2] },
128}
129
130#[derive(Debug, Clone, Default, Deserialize)]
131#[serde(rename_all = "camelCase")]
132pub struct LinearGradientSpec {
133 #[serde(default)]
136 pub angle: Option<Angle>,
137 #[serde(default)]
138 pub stops: Vec<GradientStop>,
139 #[serde(default)]
140 pub color_space: Option<ColorSpace>,
141}
142
143#[derive(Debug, Clone, Default, Deserialize)]
144#[serde(rename_all = "camelCase")]
145pub struct RadialGradientSpec {
146 #[serde(default)]
147 pub position: Option<GradientPosition>,
148 #[serde(default)]
149 pub shape: Option<RadialShapeSpec>,
150 #[serde(default)]
151 pub stops: Vec<GradientStop>,
152 #[serde(default)]
153 pub color_space: Option<ColorSpace>,
154}
155
156#[derive(Debug, Clone, Default, Deserialize)]
157#[serde(rename_all = "camelCase")]
158pub struct ConicGradientSpec {
159 #[serde(default)]
161 pub start: Option<Angle>,
162 #[serde(default)]
163 pub position: Option<GradientPosition>,
164 #[serde(default)]
165 pub stops: Vec<AngularStop>,
166 #[serde(default)]
167 pub color_space: Option<ColorSpace>,
168}
169
170#[derive(Debug, Clone, Deserialize)]
172#[serde(tag = "type", rename_all = "camelCase")]
173pub enum GradientSpec {
174 Linear(LinearGradientSpec),
175 Radial(RadialGradientSpec),
176 Conic(ConicGradientSpec),
177}
178
179#[derive(Debug, Clone, Deserialize)]
181#[serde(untagged)]
182pub enum GradientList {
183 One(GradientSpec),
184 Many(Vec<GradientSpec>),
185}
186
187#[derive(Debug, Clone, PartialEq, Default)]
195pub struct BorderColorSpec {
196 pub top: Option<String>,
197 pub right: Option<String>,
198 pub bottom: Option<String>,
199 pub left: Option<String>,
200}
201
202impl BorderColorSpec {
203 fn uniform(s: String) -> Self {
205 BorderColorSpec {
206 top: Some(s.clone()),
207 right: Some(s.clone()),
208 bottom: Some(s.clone()),
209 left: Some(s),
210 }
211 }
212}
213
214impl<'de> Deserialize<'de> for BorderColorSpec {
215 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
216 struct BorderColorVisitor;
217 impl<'de> Visitor<'de> for BorderColorVisitor {
218 type Value = BorderColorSpec;
219 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
220 f.write_str("a CSS color string or a {top,right,bottom,left} object of colors")
221 }
222 fn visit_str<E: de::Error>(self, s: &str) -> Result<BorderColorSpec, E> {
223 Ok(BorderColorSpec::uniform(s.to_owned()))
224 }
225 fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<BorderColorSpec, A::Error> {
226 let mut spec = BorderColorSpec::default();
227 while let Some(key) = map.next_key::<String>()? {
228 let v = map.next_value::<String>()?;
229 match key.as_str() {
230 "top" => spec.top = Some(v),
231 "right" => spec.right = Some(v),
232 "bottom" => spec.bottom = Some(v),
233 "left" => spec.left = Some(v),
234 _ => decode_warn(
237 "borderColor",
238 &key,
239 &format!(
240 "unknown borderColor side {key:?}; ignoring (expected top/right/bottom/left)"
241 ),
242 ),
243 }
244 }
245 Ok(spec)
246 }
247 }
248 d.deserialize_any(BorderColorVisitor)
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use crate::protocol::animatable::AnimatableField;
255 use crate::protocol::style::Style;
256
257 #[test]
260 fn border_color_scalar_and_per_side() {
261 let uniform: Style =
263 serde_json::from_str(r#"{ "borderColor": "white" }"#).expect("scalar decodes");
264 let bc = uniform
265 .border_color
266 .static_ref()
267 .expect("border_color present");
268 assert_eq!(bc.top.as_deref(), Some("white"));
269 assert_eq!(bc.right.as_deref(), Some("white"));
270 assert_eq!(bc.bottom.as_deref(), Some("white"));
271 assert_eq!(bc.left.as_deref(), Some("white"));
272
273 let sided: Style =
275 serde_json::from_str(r##"{ "borderColor": { "top": "#f00", "left": "blue" } }"##)
276 .expect("object decodes");
277 let bc = sided
278 .border_color
279 .static_ref()
280 .expect("border_color present");
281 assert_eq!(bc.top.as_deref(), Some("#f00"));
282 assert_eq!(bc.left.as_deref(), Some("blue"));
283 assert_eq!(bc.right, None);
284 assert_eq!(bc.bottom, None);
285
286 let bogus: Style =
290 serde_json::from_str(r#"{ "borderColor": { "middle": "red", "top": "blue" } }"#)
291 .expect("unknown side key must not abort deserialization");
292 let bc = bogus
293 .border_color
294 .static_ref()
295 .expect("border_color present");
296 assert_eq!(bc.top.as_deref(), Some("blue"));
297 assert_eq!(bc.right, None);
298 assert_eq!(bc.bottom, None);
299 assert_eq!(bc.left, None);
300 }
301}