Skip to main content

takumi_css/
keyframes.rs

1//! Shared keyframe input parsing used by external bindings.
2
3use cssparser::{ParseError, ParseErrorKind, Parser, ParserInput, Token};
4use serde::{
5  Deserialize, Deserializer,
6  de::{self, MapAccess, SeqAccess, Visitor},
7};
8
9use crate::style::{KeyframeRule, KeyframesRule, StyleDeclarationBlock};
10
11struct StageMap(Vec<(String, StyleDeclarationBlock)>);
12
13impl<'de> Deserialize<'de> for StageMap {
14  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
15  where
16    D: Deserializer<'de>,
17  {
18    struct StageMapVisitor;
19
20    impl<'de> Visitor<'de> for StageMapVisitor {
21      type Value = StageMap;
22
23      fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        formatter.write_str("a map from keyframe selector to declaration block")
25      }
26
27      fn visit_map<A>(self, mut access: A) -> Result<Self::Value, A::Error>
28      where
29        A: MapAccess<'de>,
30      {
31        let mut stages = Vec::with_capacity(access.size_hint().unwrap_or(0));
32        while let Some((selector, declarations)) =
33          access.next_entry::<String, StyleDeclarationBlock>()?
34        {
35          stages.push((selector, declarations));
36        }
37        stages.sort_unstable_by(|(left, _), (right, _)| left.cmp(right));
38        Ok(StageMap(stages))
39      }
40    }
41
42    deserializer.deserialize_map(StageMapVisitor)
43  }
44}
45
46struct KeyframesVec(Vec<KeyframesRule>);
47
48impl<'de> Deserialize<'de> for KeyframesVec {
49  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
50  where
51    D: Deserializer<'de>,
52  {
53    struct KeyframesVecVisitor;
54
55    impl<'de> Visitor<'de> for KeyframesVecVisitor {
56      type Value = KeyframesVec;
57
58      fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59        formatter.write_str("keyframes rules array or shorthand keyframes object")
60      }
61
62      fn visit_seq<A>(self, mut access: A) -> Result<Self::Value, A::Error>
63      where
64        A: SeqAccess<'de>,
65      {
66        let mut rules = Vec::with_capacity(access.size_hint().unwrap_or(0));
67        while let Some(rule) = access.next_element::<KeyframesRule>()? {
68          rules.push(rule);
69        }
70        Ok(KeyframesVec(rules))
71      }
72
73      fn visit_map<A>(self, mut access: A) -> Result<Self::Value, A::Error>
74      where
75        A: MapAccess<'de>,
76      {
77        let mut shorthand = Vec::with_capacity(access.size_hint().unwrap_or(0));
78        while let Some((name, stages)) = access.next_entry::<String, StageMap>()? {
79          shorthand.push((name, stages));
80        }
81        shorthand.sort_unstable_by(|(left, _), (right, _)| left.cmp(right));
82
83        let rules = shorthand
84          .into_iter()
85          .map(|(name, StageMap(stages))| {
86            let keyframes = stages
87              .into_iter()
88              .map(|(selector, declarations)| {
89                Ok(KeyframeRule {
90                  offsets: parse_keyframe_offsets(&selector).map_err(de::Error::custom)?,
91                  declarations,
92                })
93              })
94              .collect::<Result<Vec<_>, _>>()?;
95
96            Ok(KeyframesRule {
97              name,
98              keyframes,
99              media_queries: Vec::new(),
100            })
101          })
102          .collect::<Result<Vec<_>, _>>()?;
103
104        Ok(KeyframesVec(rules))
105      }
106    }
107
108    deserializer.deserialize_any(KeyframesVecVisitor)
109  }
110}
111
112/// Deserializes either structured keyframes or shorthand keyframe maps.
113pub fn deserialize_keyframes<'de, D>(deserializer: D) -> Result<Vec<KeyframesRule>, D::Error>
114where
115  D: Deserializer<'de>,
116{
117  Ok(KeyframesVec::deserialize(deserializer)?.0)
118}
119
120/// Deserializes optional keyframes while preserving missing-field behavior.
121pub fn deserialize_optional_keyframes<'de, D>(
122  deserializer: D,
123) -> Result<Option<Vec<KeyframesRule>>, D::Error>
124where
125  D: Deserializer<'de>,
126{
127  Ok(Option::<KeyframesVec>::deserialize(deserializer)?.map(|keyframes| keyframes.0))
128}
129
130fn parse_keyframe_offsets(selector: &str) -> Result<Vec<f32>, String> {
131  if selector
132    .trim_matches(|c: char| c.is_ascii_whitespace() || c == ',')
133    .is_empty()
134  {
135    return Err(
136      "empty keyframe selector; expected at least one of `from`, `to`, or percentage values"
137        .to_owned(),
138    );
139  }
140
141  let mut input = ParserInput::new(selector);
142  let mut parser = Parser::new(&mut input);
143  parse_keyframe_prelude::<KeyframePreludeParseError<'_>>(&mut parser).map_err(|error| match error
144    .kind
145  {
146    ParseErrorKind::Custom(KeyframePreludeParseError::InvalidPercentage(part)) => {
147      format!("invalid keyframe percentage `{part}`; expected a value in 0%..=100%")
148    }
149    ParseErrorKind::Custom(KeyframePreludeParseError::InvalidSelector(part)) => {
150      unsupported_keyframe_selector(part)
151    }
152    ParseErrorKind::Basic(_) => unsupported_keyframe_selector(selector.trim()),
153  })
154}
155
156pub(crate) fn parse_keyframe_prelude<'i, E>(
157  input: &mut Parser<'i, '_>,
158) -> Result<Vec<f32>, ParseError<'i, E>>
159where
160  KeyframePreludeParseError<'i>: Into<E>,
161{
162  input.parse_comma_separated(parse_keyframe_offset)
163}
164
165#[derive(Clone, Copy)]
166pub(crate) enum KeyframePreludeParseError<'i> {
167  InvalidSelector(&'i str),
168  InvalidPercentage(&'i str),
169}
170
171fn parse_keyframe_offset<'i, E>(input: &mut Parser<'i, '_>) -> Result<f32, ParseError<'i, E>>
172where
173  KeyframePreludeParseError<'i>: Into<E>,
174{
175  if input
176    .try_parse(|parser| parser.expect_ident_matching("from"))
177    .is_ok()
178  {
179    return Ok(0.0);
180  }
181
182  if input
183    .try_parse(|parser| parser.expect_ident_matching("to"))
184    .is_ok()
185  {
186    return Ok(1.0);
187  }
188
189  let start_position = input.position();
190  let offset = match input.next() {
191    Ok(Token::Percentage { unit_value, .. }) => *unit_value,
192    Ok(_) | Err(_) => {
193      let part = input.slice_from(start_position).trim();
194      return Err(input.new_custom_error(KeyframePreludeParseError::InvalidSelector(part)));
195    }
196  };
197
198  if !(0.0..=1.0).contains(&offset) {
199    let part = input.slice_from(start_position).trim();
200    return Err(input.new_custom_error(KeyframePreludeParseError::InvalidPercentage(part)));
201  }
202
203  Ok(offset)
204}
205
206fn unsupported_keyframe_selector(selector: &str) -> String {
207  format!(
208    "unsupported keyframe selector `{selector}`; use `from`, `to`, or percentage values like `50%`"
209  )
210}
211
212#[cfg(test)]
213mod tests {
214  use std::assert_matches;
215
216  use serde::Deserialize;
217  use serde_json::from_value;
218
219  use super::{deserialize_keyframes, deserialize_optional_keyframes};
220  use crate::style::KeyframesRule;
221
222  #[derive(Debug, Deserialize)]
223  struct KeyframesDocument {
224    #[serde(deserialize_with = "deserialize_keyframes")]
225    keyframes: Vec<KeyframesRule>,
226  }
227
228  #[derive(Debug, Deserialize)]
229  struct OptionalKeyframesDocument {
230    #[serde(default, deserialize_with = "deserialize_optional_keyframes")]
231    keyframes: Option<Vec<KeyframesRule>>,
232  }
233
234  #[test]
235  fn rejects_empty_keyframe_selector() {
236    let result = from_value::<KeyframesDocument>(serde_json::json!({
237      "keyframes": {
238        "fade": {
239          " , ": {
240            "opacity": 0
241          }
242        }
243      }
244    }));
245
246    assert!(
247      result.is_err(),
248      "expected empty selector to fail: {result:?}"
249    );
250    assert_matches!(
251      result.as_ref(),
252      Err(error) if error.to_string().contains("empty keyframe selector")
253    );
254  }
255
256  #[test]
257  fn parses_shorthand_keyframes() {
258    let result = from_value::<KeyframesDocument>(serde_json::json!({
259      "keyframes": {
260        "fade": {
261          "from, 50%, to": {
262            "opacity": 1
263          }
264        }
265      }
266    }));
267
268    assert!(
269      result.is_ok(),
270      "expected valid shorthand keyframes: {result:?}"
271    );
272    let keyframes = result.as_ref().ok();
273
274    assert_eq!(keyframes.map(|value| value.keyframes.len()), Some(1));
275    assert_eq!(
276      keyframes.map(|value| value.keyframes[0].keyframes[0].offsets.clone()),
277      Some(vec![0.0, 0.5, 1.0])
278    );
279  }
280
281  #[test]
282  fn keeps_missing_optional_keyframes_as_none() {
283    let result = from_value::<OptionalKeyframesDocument>(serde_json::json!({}));
284
285    assert!(
286      result.is_ok(),
287      "expected missing keyframes to deserialize: {result:?}"
288    );
289    assert_eq!(result.ok().and_then(|document| document.keyframes), None);
290  }
291}