1use crate::parser::{Parse, ParserContext};
7use crate::piecewise_linear::{PiecewiseLinearFunction, PiecewiseLinearFunctionBuilder};
8use crate::values::computed::easing::TimingFunction as ComputedTimingFunction;
9use crate::values::computed::{Context, ToComputedValue};
10use crate::values::generics::easing::TimingFunction as GenericTimingFunction;
11use crate::values::generics::easing::{StepPosition, TimingKeyword};
12use crate::values::specified::{AnimationName, Integer, Number, Percentage};
13use cssparser::{Delimiter, Parser, Token};
14use selectors::parser::SelectorParseErrorKind;
15use style_traits::{ParseError, StyleParseErrorKind};
16
17pub type TimingFunction = GenericTimingFunction<Integer, Number, PiecewiseLinearFunction>;
19
20impl Parse for TimingFunction {
21 fn parse<'i, 't>(
22 context: &ParserContext,
23 input: &mut Parser<'i, 't>,
24 ) -> Result<Self, ParseError<'i>> {
25 if let Ok(keyword) = input.try_parse(TimingKeyword::parse) {
26 return Ok(GenericTimingFunction::Keyword(keyword));
27 }
28 if let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
29 let position = match_ignore_ascii_case! { &ident,
30 "step-start" => StepPosition::Start,
31 "step-end" => StepPosition::End,
32 _ => {
33 return Err(input.new_custom_error(
34 SelectorParseErrorKind::UnexpectedIdent(ident.clone())
35 ));
36 },
37 };
38 return Ok(GenericTimingFunction::Steps(Integer::new(1), position));
39 }
40 let location = input.current_source_location();
41 let function = input.expect_function()?.clone();
42 input.parse_nested_block(move |i| {
43 match_ignore_ascii_case! { &function,
44 "cubic-bezier" => Self::parse_cubic_bezier(context, i),
45 "steps" => Self::parse_steps(context, i),
46 "linear" => Self::parse_linear_function(context, i),
47 _ => Err(location.new_custom_error(StyleParseErrorKind::UnexpectedFunction(function.clone()))),
48 }
49 })
50 }
51}
52
53impl TimingFunction {
54 fn parse_cubic_bezier<'i, 't>(
55 context: &ParserContext,
56 input: &mut Parser<'i, 't>,
57 ) -> Result<Self, ParseError<'i>> {
58 let x1 = Number::parse(context, input)?;
59 input.expect_comma()?;
60 let y1 = Number::parse(context, input)?;
61 input.expect_comma()?;
62 let x2 = Number::parse(context, input)?;
63 input.expect_comma()?;
64 let y2 = Number::parse(context, input)?;
65
66 if x1.get() < 0.0 || x1.get() > 1.0 || x2.get() < 0.0 || x2.get() > 1.0 {
67 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
68 }
69
70 Ok(GenericTimingFunction::CubicBezier { x1, y1, x2, y2 })
71 }
72
73 fn parse_steps<'i, 't>(
74 context: &ParserContext,
75 input: &mut Parser<'i, 't>,
76 ) -> Result<Self, ParseError<'i>> {
77 let steps = Integer::parse_positive(context, input)?;
78 let position = input
79 .try_parse(|i| {
80 i.expect_comma()?;
81 StepPosition::parse(context, i)
82 })
83 .unwrap_or(StepPosition::End);
84
85 if position == StepPosition::JumpNone && steps.value() <= 1 {
92 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
93 }
94 Ok(GenericTimingFunction::Steps(steps, position))
95 }
96
97 fn parse_linear_function<'i, 't>(
98 context: &ParserContext,
99 input: &mut Parser<'i, 't>,
100 ) -> Result<Self, ParseError<'i>> {
101 let mut builder = PiecewiseLinearFunctionBuilder::default();
102 let mut num_specified_stops = 0;
103 loop {
105 input.parse_until_before(Delimiter::Comma, |i| {
106 let builder = &mut builder;
107 let mut input_start = i.try_parse(|i| Percentage::parse(context, i)).ok();
108 let mut input_end = i.try_parse(|i| Percentage::parse(context, i)).ok();
109
110 let output = Number::parse(context, i)?;
111 if input_start.is_none() {
112 debug_assert!(input_end.is_none(), "Input end parsed without input start?");
113 input_start = i.try_parse(|i| Percentage::parse(context, i)).ok();
114 input_end = i.try_parse(|i| Percentage::parse(context, i)).ok();
115 }
116 builder.push(output.into(), input_start.map(|v| v.get()).into());
117 num_specified_stops += 1;
118 if input_end.is_some() {
119 debug_assert!(
120 input_start.is_some(),
121 "Input end valid but not input start?"
122 );
123 builder.push(output.into(), input_end.map(|v| v.get()).into());
124 }
125
126 Ok(())
127 })?;
128
129 match input.next() {
130 Err(_) => break,
131 Ok(&Token::Comma) => continue,
132 Ok(_) => unreachable!(),
133 }
134 }
135 if num_specified_stops < 2 {
138 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
139 }
140
141 Ok(GenericTimingFunction::LinearFunction(builder.build()))
142 }
143
144 #[inline]
146 pub fn match_keywords(name: &AnimationName) -> bool {
147 if let Some(name) = name.as_atom() {
148 #[cfg(feature = "gecko")]
149 return name.with_str(|n| TimingKeyword::from_ident(n).is_ok());
150 #[cfg(feature = "servo")]
151 return TimingKeyword::from_ident(name).is_ok();
152 }
153 false
154 }
155}
156
157impl TimingFunction {
161 pub fn to_computed_value_without_context(&self) -> ComputedTimingFunction {
163 match &self {
164 GenericTimingFunction::Steps(steps, pos) => {
165 GenericTimingFunction::Steps(steps.value(), *pos)
166 },
167 GenericTimingFunction::CubicBezier { x1, y1, x2, y2 } => {
168 GenericTimingFunction::CubicBezier {
169 x1: x1.get(),
170 y1: y1.get(),
171 x2: x2.get(),
172 y2: y2.get(),
173 }
174 },
175 GenericTimingFunction::Keyword(keyword) => GenericTimingFunction::Keyword(*keyword),
176 GenericTimingFunction::LinearFunction(function) => {
177 GenericTimingFunction::LinearFunction(function.clone())
178 },
179 }
180 }
181}
182
183impl ToComputedValue for TimingFunction {
184 type ComputedValue = ComputedTimingFunction;
185 fn to_computed_value(&self, _: &Context) -> Self::ComputedValue {
186 self.to_computed_value_without_context()
187 }
188
189 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
190 match &computed {
191 ComputedTimingFunction::Steps(steps, pos) => Self::Steps(Integer::new(*steps), *pos),
192 ComputedTimingFunction::CubicBezier { x1, y1, x2, y2 } => Self::CubicBezier {
193 x1: Number::new(*x1),
194 y1: Number::new(*y1),
195 x2: Number::new(*x2),
196 y2: Number::new(*y2),
197 },
198 ComputedTimingFunction::Keyword(keyword) => GenericTimingFunction::Keyword(*keyword),
199 ComputedTimingFunction::LinearFunction(function) => {
200 GenericTimingFunction::LinearFunction(function.clone())
201 },
202 }
203 }
204}