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::percentage::ToPercentage;
13use crate::values::specified::{AnimationName, Integer, Number, Percentage};
14use cssparser::{match_ignore_ascii_case, Delimiter, Parser, Token};
15use selectors::parser::SelectorParseErrorKind;
16use style_traits::{ParseError, StyleParseErrorKind};
17
18pub type TimingFunction = GenericTimingFunction<Integer, Number, PiecewiseLinearFunction>;
20
21impl Parse for TimingFunction {
22 fn parse<'i, 't>(
23 context: &ParserContext,
24 input: &mut Parser<'i, 't>,
25 ) -> Result<Self, ParseError<'i>> {
26 if let Ok(keyword) = input.try_parse(TimingKeyword::parse) {
27 return Ok(GenericTimingFunction::Keyword(keyword));
28 }
29 if let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
30 let position = match_ignore_ascii_case! { &ident,
31 "step-start" => StepPosition::Start,
32 "step-end" => StepPosition::End,
33 _ => {
34 return Err(input.new_custom_error(
35 SelectorParseErrorKind::UnexpectedIdent(ident.clone())
36 ));
37 },
38 };
39 return Ok(GenericTimingFunction::Steps(Integer::new(1), position));
40 }
41 let location = input.current_source_location();
42 let function = input.expect_function()?.clone();
43 input.parse_nested_block(move |i| {
44 match_ignore_ascii_case! { &function,
45 "cubic-bezier" => Self::parse_cubic_bezier(context, i),
46 "steps" => Self::parse_steps(context, i),
47 "linear" => Self::parse_linear_function(context, i),
48 _ => Err(location.new_custom_error(StyleParseErrorKind::UnexpectedFunction(function.clone()))),
49 }
50 })
51 }
52}
53
54impl TimingFunction {
55 fn parse_cubic_bezier<'i, 't>(
56 context: &ParserContext,
57 input: &mut Parser<'i, 't>,
58 ) -> Result<Self, ParseError<'i>> {
59 let x1 = Number::parse(context, input)?;
60 input.expect_comma()?;
61 let y1 = Number::parse(context, input)?;
62 input.expect_comma()?;
63 let x2 = Number::parse(context, input)?;
64 input.expect_comma()?;
65 let y2 = Number::parse(context, input)?;
66
67 if let (Some(x1), Some(_), Some(x2), Some(_)) =
70 (x1.resolve(), y1.resolve(), x2.resolve(), y2.resolve())
71 {
72 if x1 < 0.0 || x1 > 1.0 || x2 < 0.0 || x2 > 1.0 {
73 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
74 }
75 } else {
76 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
77 }
78
79 Ok(GenericTimingFunction::CubicBezier { x1, y1, x2, y2 })
80 }
81
82 fn parse_steps<'i, 't>(
83 context: &ParserContext,
84 input: &mut Parser<'i, 't>,
85 ) -> Result<Self, ParseError<'i>> {
86 let steps = Integer::parse_positive(context, input)?;
87 let position = input
88 .try_parse(|i| {
89 i.expect_comma()?;
90 StepPosition::parse(i)
91 })
92 .unwrap_or(StepPosition::End);
93
94 let num_steps = match steps.resolve() {
97 Some(v) => v,
98 None => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
99 };
100
101 if position == StepPosition::JumpNone && num_steps <= 1 {
108 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
109 }
110 Ok(GenericTimingFunction::Steps(steps, position))
111 }
112
113 fn parse_linear_function<'i, 't>(
114 context: &ParserContext,
115 input: &mut Parser<'i, 't>,
116 ) -> Result<Self, ParseError<'i>> {
117 let mut builder = PiecewiseLinearFunctionBuilder::default();
118 let mut num_specified_stops = 0;
119 loop {
121 input.parse_until_before(Delimiter::Comma, |i| {
122 let builder = &mut builder;
123 let mut input_start = i.try_parse(|i| Percentage::parse(context, i)).ok();
124 let mut input_end = i.try_parse(|i| Percentage::parse(context, i)).ok();
125
126 let output = Number::parse(context, i)?;
127 if input_start.is_none() {
128 debug_assert!(input_end.is_none(), "Input end parsed without input start?");
129 input_start = i.try_parse(|i| Percentage::parse(context, i)).ok();
130 input_end = i.try_parse(|i| Percentage::parse(context, i)).ok();
131 }
132
133 let output = match output.resolve() {
136 Some(v) => v,
137 None => return Err(i.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
138 };
139 if matches!(input_start.as_ref().or(input_end.as_ref()), Some(p) if p.resolve().is_none()) {
140 return Err(i.new_custom_error(StyleParseErrorKind::UnspecifiedError));
141 }
142
143 let has_input_start = input_start.is_some();
144 builder.push(
145 output,
146 input_start.map(|v| v.to_percentage().unwrap()).into(),
147 );
148 num_specified_stops += 1;
149 if input_end.is_some() {
150 debug_assert!(has_input_start, "Input end valid but not input start?");
151 builder.push(output, input_end.map(|v| v.to_percentage().unwrap()).into());
152 }
153
154 Ok(())
155 })?;
156
157 match input.next() {
158 Err(_) => break,
159 Ok(&Token::Comma) => continue,
160 Ok(_) => unreachable!(),
161 }
162 }
163 if num_specified_stops < 2 {
166 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
167 }
168
169 Ok(GenericTimingFunction::LinearFunction(builder.build()))
170 }
171
172 #[inline]
174 pub fn match_keywords(name: &AnimationName) -> bool {
175 if let Some(name) = name.as_atom() {
176 #[cfg(feature = "gecko")]
177 return name.with_str(|n| TimingKeyword::from_ident(n).is_ok());
178 #[cfg(feature = "servo")]
179 return TimingKeyword::from_ident(name).is_ok();
180 }
181 false
182 }
183}
184
185impl TimingFunction {
189 pub fn to_computed_value_without_context(&self) -> ComputedTimingFunction {
191 match &self {
192 GenericTimingFunction::Steps(steps, pos) => {
193 GenericTimingFunction::Steps(steps.resolve().unwrap(), *pos)
195 },
196 GenericTimingFunction::CubicBezier { x1, y1, x2, y2 } => {
197 GenericTimingFunction::CubicBezier {
199 x1: x1.resolve().unwrap(),
200 y1: y1.resolve().unwrap(),
201 x2: x2.resolve().unwrap(),
202 y2: y2.resolve().unwrap(),
203 }
204 },
205 GenericTimingFunction::Keyword(keyword) => GenericTimingFunction::Keyword(*keyword),
206 GenericTimingFunction::LinearFunction(function) => {
207 GenericTimingFunction::LinearFunction(function.clone())
209 },
210 }
211 }
212}
213
214impl ToComputedValue for TimingFunction {
215 type ComputedValue = ComputedTimingFunction;
216 fn to_computed_value(&self, _: &Context) -> Self::ComputedValue {
217 self.to_computed_value_without_context()
218 }
219
220 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
221 match &computed {
222 ComputedTimingFunction::Steps(steps, pos) => Self::Steps(Integer::new(*steps), *pos),
223 ComputedTimingFunction::CubicBezier { x1, y1, x2, y2 } => Self::CubicBezier {
224 x1: Number::new(*x1),
225 y1: Number::new(*y1),
226 x2: Number::new(*x2),
227 y2: Number::new(*y2),
228 },
229 ComputedTimingFunction::Keyword(keyword) => GenericTimingFunction::Keyword(*keyword),
230 ComputedTimingFunction::LinearFunction(function) => {
231 GenericTimingFunction::LinearFunction(function.clone())
232 },
233 }
234 }
235}