1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 //! Specified types for CSS values that are related to transformations.
6 
7 use cssparser::Parser;
8 use parser::{Parse, ParserContext};
9 use selectors::parser::SelectorParseErrorKind;
10 use style_traits::{ParseError, StyleParseErrorKind};
11 use values::computed::{Context, LengthOrPercentage as ComputedLengthOrPercentage};
12 use values::computed::{Percentage as ComputedPercentage, ToComputedValue};
13 use values::computed::transform::TimingFunction as ComputedTimingFunction;
14 use values::generics::transform::{Matrix3D, Transform as GenericTransform};
15 use values::generics::transform::{StepPosition, TimingFunction as GenericTimingFunction, Matrix};
16 use values::generics::transform::{TimingKeyword, TransformOrigin as GenericTransformOrigin};
17 use values::generics::transform::Rotate as GenericRotate;
18 use values::generics::transform::Scale as GenericScale;
19 use values::generics::transform::TransformOperation as GenericTransformOperation;
20 use values::generics::transform::Translate as GenericTranslate;
21 use values::specified::{self, Angle, Number, Length, Integer, LengthOrPercentage};
22 use values::specified::position::{Side, X, Y};
23 
24 pub use values::generics::transform::TransformStyle;
25 
26 /// A single operation in a specified CSS `transform`
27 pub type TransformOperation = GenericTransformOperation<
28     Angle,
29     Number,
30     Length,
31     Integer,
32     LengthOrPercentage,
33 >;
34 
35 /// A specified CSS `transform`
36 pub type Transform = GenericTransform<TransformOperation>;
37 
38 /// The specified value of a CSS `<transform-origin>`
39 pub type TransformOrigin = GenericTransformOrigin<OriginComponent<X>, OriginComponent<Y>, Length>;
40 
41 impl Transform {
42     /// Internal parse function for deciding if we wish to accept prefixed values or not
43     ///
44     /// `transform` allows unitless zero angles as an exception, see:
45     /// https://github.com/w3c/csswg-drafts/issues/1162
parse_internal<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>46     fn parse_internal<'i, 't>(
47         context: &ParserContext,
48         input: &mut Parser<'i, 't>,
49     ) -> Result<Self, ParseError<'i>> {
50         use style_traits::{Separator, Space};
51 
52         if input
53             .try(|input| input.expect_ident_matching("none"))
54             .is_ok()
55         {
56             return Ok(GenericTransform(Vec::new()));
57         }
58 
59         Ok(GenericTransform(Space::parse(input, |input| {
60             let function = input.expect_function()?.clone();
61             input.parse_nested_block(|input| {
62                 let location = input.current_source_location();
63                 let result =
64                     match_ignore_ascii_case! { &function,
65                     "matrix" => {
66                         let a = Number::parse(context, input)?;
67                         input.expect_comma()?;
68                         let b = Number::parse(context, input)?;
69                         input.expect_comma()?;
70                         let c = Number::parse(context, input)?;
71                         input.expect_comma()?;
72                         let d = Number::parse(context, input)?;
73                         input.expect_comma()?;
74                         // Standard matrix parsing.
75                         let e = Number::parse(context, input)?;
76                         input.expect_comma()?;
77                         let f = Number::parse(context, input)?;
78                         Ok(GenericTransformOperation::Matrix(Matrix { a, b, c, d, e, f }))
79                     },
80                     "matrix3d" => {
81                         let m11 = Number::parse(context, input)?;
82                         input.expect_comma()?;
83                         let m12 = Number::parse(context, input)?;
84                         input.expect_comma()?;
85                         let m13 = Number::parse(context, input)?;
86                         input.expect_comma()?;
87                         let m14 = Number::parse(context, input)?;
88                         input.expect_comma()?;
89                         let m21 = Number::parse(context, input)?;
90                         input.expect_comma()?;
91                         let m22 = Number::parse(context, input)?;
92                         input.expect_comma()?;
93                         let m23 = Number::parse(context, input)?;
94                         input.expect_comma()?;
95                         let m24 = Number::parse(context, input)?;
96                         input.expect_comma()?;
97                         let m31 = Number::parse(context, input)?;
98                         input.expect_comma()?;
99                         let m32 = Number::parse(context, input)?;
100                         input.expect_comma()?;
101                         let m33 = Number::parse(context, input)?;
102                         input.expect_comma()?;
103                         let m34 = Number::parse(context, input)?;
104                         input.expect_comma()?;
105                         // Standard matrix3d parsing.
106                         let m41 = Number::parse(context, input)?;
107                         input.expect_comma()?;
108                         let m42 = Number::parse(context, input)?;
109                         input.expect_comma()?;
110                         let m43 = Number::parse(context, input)?;
111                         input.expect_comma()?;
112                         let m44 = Number::parse(context, input)?;
113                         Ok(GenericTransformOperation::Matrix3D(Matrix3D {
114                             m11, m12, m13, m14,
115                             m21, m22, m23, m24,
116                             m31, m32, m33, m34,
117                             m41, m42, m43, m44,
118                         }))
119                     },
120                     "translate" => {
121                         let sx = specified::LengthOrPercentage::parse(context, input)?;
122                         if input.try(|input| input.expect_comma()).is_ok() {
123                             let sy = specified::LengthOrPercentage::parse(context, input)?;
124                             Ok(GenericTransformOperation::Translate(sx, Some(sy)))
125                         } else {
126                             Ok(GenericTransformOperation::Translate(sx, None))
127                         }
128                     },
129                     "translatex" => {
130                         let tx = specified::LengthOrPercentage::parse(context, input)?;
131                         Ok(GenericTransformOperation::TranslateX(tx))
132                     },
133                     "translatey" => {
134                         let ty = specified::LengthOrPercentage::parse(context, input)?;
135                         Ok(GenericTransformOperation::TranslateY(ty))
136                     },
137                     "translatez" => {
138                         let tz = specified::Length::parse(context, input)?;
139                         Ok(GenericTransformOperation::TranslateZ(tz))
140                     },
141                     "translate3d" => {
142                         let tx = specified::LengthOrPercentage::parse(context, input)?;
143                         input.expect_comma()?;
144                         let ty = specified::LengthOrPercentage::parse(context, input)?;
145                         input.expect_comma()?;
146                         let tz = specified::Length::parse(context, input)?;
147                         Ok(GenericTransformOperation::Translate3D(tx, ty, tz))
148                     },
149                     "scale" => {
150                         let sx = Number::parse(context, input)?;
151                         if input.try(|input| input.expect_comma()).is_ok() {
152                             let sy = Number::parse(context, input)?;
153                             Ok(GenericTransformOperation::Scale(sx, Some(sy)))
154                         } else {
155                             Ok(GenericTransformOperation::Scale(sx, None))
156                         }
157                     },
158                     "scalex" => {
159                         let sx = Number::parse(context, input)?;
160                         Ok(GenericTransformOperation::ScaleX(sx))
161                     },
162                     "scaley" => {
163                         let sy = Number::parse(context, input)?;
164                         Ok(GenericTransformOperation::ScaleY(sy))
165                     },
166                     "scalez" => {
167                         let sz = Number::parse(context, input)?;
168                         Ok(GenericTransformOperation::ScaleZ(sz))
169                     },
170                     "scale3d" => {
171                         let sx = Number::parse(context, input)?;
172                         input.expect_comma()?;
173                         let sy = Number::parse(context, input)?;
174                         input.expect_comma()?;
175                         let sz = Number::parse(context, input)?;
176                         Ok(GenericTransformOperation::Scale3D(sx, sy, sz))
177                     },
178                     "rotate" => {
179                         let theta = specified::Angle::parse_with_unitless(context, input)?;
180                         Ok(GenericTransformOperation::Rotate(theta))
181                     },
182                     "rotatex" => {
183                         let theta = specified::Angle::parse_with_unitless(context, input)?;
184                         Ok(GenericTransformOperation::RotateX(theta))
185                     },
186                     "rotatey" => {
187                         let theta = specified::Angle::parse_with_unitless(context, input)?;
188                         Ok(GenericTransformOperation::RotateY(theta))
189                     },
190                     "rotatez" => {
191                         let theta = specified::Angle::parse_with_unitless(context, input)?;
192                         Ok(GenericTransformOperation::RotateZ(theta))
193                     },
194                     "rotate3d" => {
195                         let ax = Number::parse(context, input)?;
196                         input.expect_comma()?;
197                         let ay = Number::parse(context, input)?;
198                         input.expect_comma()?;
199                         let az = Number::parse(context, input)?;
200                         input.expect_comma()?;
201                         let theta = specified::Angle::parse_with_unitless(context, input)?;
202                         // TODO(gw): Check that the axis can be normalized.
203                         Ok(GenericTransformOperation::Rotate3D(ax, ay, az, theta))
204                     },
205                     "skew" => {
206                         let ax = specified::Angle::parse_with_unitless(context, input)?;
207                         if input.try(|input| input.expect_comma()).is_ok() {
208                             let ay = specified::Angle::parse_with_unitless(context, input)?;
209                             Ok(GenericTransformOperation::Skew(ax, Some(ay)))
210                         } else {
211                             Ok(GenericTransformOperation::Skew(ax, None))
212                         }
213                     },
214                     "skewx" => {
215                         let theta = specified::Angle::parse_with_unitless(context, input)?;
216                         Ok(GenericTransformOperation::SkewX(theta))
217                     },
218                     "skewy" => {
219                         let theta = specified::Angle::parse_with_unitless(context, input)?;
220                         Ok(GenericTransformOperation::SkewY(theta))
221                     },
222                     "perspective" => {
223                         let d = specified::Length::parse_non_negative(context, input)?;
224                         Ok(GenericTransformOperation::Perspective(d))
225                     },
226                     _ => Err(()),
227                 };
228                 result
229                     .map_err(|()| location.new_custom_error(StyleParseErrorKind::UnexpectedFunction(function.clone())))
230             })
231         })?))
232     }
233 }
234 
235 impl Parse for Transform {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't> ) -> Result<Self, ParseError<'i>>236     fn parse<'i, 't>(
237         context: &ParserContext,
238         input: &mut Parser<'i, 't>
239     ) -> Result<Self, ParseError<'i>> {
240         Transform::parse_internal(context, input)
241     }
242 }
243 
244 /// The specified value of a component of a CSS `<transform-origin>`.
245 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)]
246 pub enum OriginComponent<S> {
247     /// `center`
248     Center,
249     /// `<lop>`
250     Length(LengthOrPercentage),
251     /// `<side>`
252     Side(S),
253 }
254 
255 /// A specified timing function.
256 pub type TimingFunction = GenericTimingFunction<Integer, Number>;
257 
258 impl Parse for TransformOrigin {
parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>>259     fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
260         let parse_depth = |input: &mut Parser| {
261             input.try(|i| Length::parse(context, i)).unwrap_or(
262                 Length::from_px(0.),
263             )
264         };
265         match input.try(|i| OriginComponent::parse(context, i)) {
266             Ok(x_origin @ OriginComponent::Center) => {
267                 if let Ok(y_origin) = input.try(|i| OriginComponent::parse(context, i)) {
268                     let depth = parse_depth(input);
269                     return Ok(Self::new(x_origin, y_origin, depth));
270                 }
271                 let y_origin = OriginComponent::Center;
272                 if let Ok(x_keyword) = input.try(X::parse) {
273                     let x_origin = OriginComponent::Side(x_keyword);
274                     let depth = parse_depth(input);
275                     return Ok(Self::new(x_origin, y_origin, depth));
276                 }
277                 let depth = Length::from_px(0.);
278                 return Ok(Self::new(x_origin, y_origin, depth));
279             },
280             Ok(x_origin) => {
281                 if let Ok(y_origin) = input.try(|i| OriginComponent::parse(context, i)) {
282                     let depth = parse_depth(input);
283                     return Ok(Self::new(x_origin, y_origin, depth));
284                 }
285                 let y_origin = OriginComponent::Center;
286                 let depth = Length::from_px(0.);
287                 return Ok(Self::new(x_origin, y_origin, depth));
288             },
289             Err(_) => {},
290         }
291         let y_keyword = Y::parse(input)?;
292         let y_origin = OriginComponent::Side(y_keyword);
293         if let Ok(x_keyword) = input.try(X::parse) {
294             let x_origin = OriginComponent::Side(x_keyword);
295             let depth = parse_depth(input);
296             return Ok(Self::new(x_origin, y_origin, depth));
297         }
298         if input.try(|i| i.expect_ident_matching("center")).is_ok() {
299             let x_origin = OriginComponent::Center;
300             let depth = parse_depth(input);
301             return Ok(Self::new(x_origin, y_origin, depth));
302         }
303         let x_origin = OriginComponent::Center;
304         let depth = Length::from_px(0.);
305         Ok(Self::new(x_origin, y_origin, depth))
306     }
307 }
308 
309 impl<S> Parse for OriginComponent<S>
310 where
311     S: Parse,
312 {
parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>>313     fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
314         if input.try(|i| i.expect_ident_matching("center")).is_ok() {
315             return Ok(OriginComponent::Center);
316         }
317         if let Ok(lop) = input.try(|i| LengthOrPercentage::parse(context, i)) {
318             return Ok(OriginComponent::Length(lop));
319         }
320         let keyword = S::parse(context, input)?;
321         Ok(OriginComponent::Side(keyword))
322     }
323 }
324 
325 impl<S> ToComputedValue for OriginComponent<S>
326 where
327     S: Side,
328 {
329     type ComputedValue = ComputedLengthOrPercentage;
330 
to_computed_value(&self, context: &Context) -> Self::ComputedValue331     fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
332         match *self {
333             OriginComponent::Center => ComputedLengthOrPercentage::Percentage(ComputedPercentage(0.5)),
334             OriginComponent::Length(ref length) => length.to_computed_value(context),
335             OriginComponent::Side(ref keyword) => {
336                 let p = ComputedPercentage(if keyword.is_start() { 0. } else { 1. });
337                 ComputedLengthOrPercentage::Percentage(p)
338             },
339         }
340     }
341 
from_computed_value(computed: &Self::ComputedValue) -> Self342     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
343         OriginComponent::Length(ToComputedValue::from_computed_value(computed))
344     }
345 }
346 
347 impl<S> OriginComponent<S> {
348     /// `0%`
zero() -> Self349     pub fn zero() -> Self {
350         OriginComponent::Length(LengthOrPercentage::Percentage(ComputedPercentage::zero()))
351     }
352 }
353 
354 #[cfg(feature = "gecko")]
355 #[inline]
allow_frames_timing() -> bool356 fn allow_frames_timing() -> bool {
357     use gecko_bindings::structs::mozilla;
358     unsafe { mozilla::StylePrefs_sFramesTimingFunctionEnabled }
359 }
360 
361 #[cfg(feature = "servo")]
362 #[inline]
allow_frames_timing() -> bool363 fn allow_frames_timing() -> bool {
364     true
365 }
366 
367 impl Parse for TimingFunction {
parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>>368     fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
369         if let Ok(keyword) = input.try(TimingKeyword::parse) {
370             return Ok(GenericTimingFunction::Keyword(keyword));
371         }
372         if let Ok(ident) = input.try(|i| i.expect_ident_cloned()) {
373             let position =
374                 match_ignore_ascii_case! { &ident,
375                 "step-start" => StepPosition::Start,
376                 "step-end" => StepPosition::End,
377                 _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone()))),
378             };
379             return Ok(GenericTimingFunction::Steps(Integer::new(1), position));
380         }
381         let location = input.current_source_location();
382         let function = input.expect_function()?.clone();
383         input.parse_nested_block(move |i| {
384             (match_ignore_ascii_case! { &function,
385                 "cubic-bezier" => {
386                     let x1 = Number::parse(context, i)?;
387                     i.expect_comma()?;
388                     let y1 = Number::parse(context, i)?;
389                     i.expect_comma()?;
390                     let x2 = Number::parse(context, i)?;
391                     i.expect_comma()?;
392                     let y2 = Number::parse(context, i)?;
393 
394                     if x1.get() < 0.0 || x1.get() > 1.0 || x2.get() < 0.0 || x2.get() > 1.0 {
395                         return Err(i.new_custom_error(StyleParseErrorKind::UnspecifiedError));
396                     }
397 
398                     Ok(GenericTimingFunction::CubicBezier { x1, y1, x2, y2 })
399                 },
400                 "steps" => {
401                     let steps = Integer::parse_positive(context, i)?;
402                     let position = i.try(|i| {
403                         i.expect_comma()?;
404                         StepPosition::parse(i)
405                     }).unwrap_or(StepPosition::End);
406                     Ok(GenericTimingFunction::Steps(steps, position))
407                 },
408                 "frames" => {
409                     if allow_frames_timing() {
410                         let frames = Integer::parse_with_minimum(context, i, 2)?;
411                         Ok(GenericTimingFunction::Frames(frames))
412                     } else {
413                         Err(())
414                     }
415                 },
416                 _ => Err(()),
417             }).map_err(|()| location.new_custom_error(StyleParseErrorKind::UnexpectedFunction(function.clone())))
418         })
419     }
420 }
421 
422 impl ToComputedValue for TimingFunction {
423     type ComputedValue = ComputedTimingFunction;
424 
425     #[inline]
to_computed_value(&self, context: &Context) -> Self::ComputedValue426     fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
427         match *self {
428             GenericTimingFunction::Keyword(keyword) => GenericTimingFunction::Keyword(keyword),
429             GenericTimingFunction::CubicBezier {
430                 x1,
431                 y1,
432                 x2,
433                 y2,
434             } => {
435                 GenericTimingFunction::CubicBezier {
436                     x1: x1.to_computed_value(context),
437                     y1: y1.to_computed_value(context),
438                     x2: x2.to_computed_value(context),
439                     y2: y2.to_computed_value(context),
440                 }
441             },
442             GenericTimingFunction::Steps(steps, position) => {
443                 GenericTimingFunction::Steps(steps.to_computed_value(context) as u32, position)
444             },
445             GenericTimingFunction::Frames(frames) => {
446                 GenericTimingFunction::Frames(frames.to_computed_value(context) as u32)
447             },
448         }
449     }
450 
451     #[inline]
from_computed_value(computed: &Self::ComputedValue) -> Self452     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
453         match *computed {
454             GenericTimingFunction::Keyword(keyword) => GenericTimingFunction::Keyword(keyword),
455             GenericTimingFunction::CubicBezier {
456                 ref x1,
457                 ref y1,
458                 ref x2,
459                 ref y2,
460             } => {
461                 GenericTimingFunction::CubicBezier {
462                     x1: Number::from_computed_value(x1),
463                     y1: Number::from_computed_value(y1),
464                     x2: Number::from_computed_value(x2),
465                     y2: Number::from_computed_value(y2),
466                 }
467             },
468             GenericTimingFunction::Steps(steps, position) => {
469                 GenericTimingFunction::Steps(Integer::from_computed_value(&(steps as i32)), position)
470             },
471             GenericTimingFunction::Frames(frames) => {
472                 GenericTimingFunction::Frames(Integer::from_computed_value(&(frames as i32)))
473             },
474         }
475     }
476 }
477 
478 /// A specified CSS `rotate`
479 pub type Rotate = GenericRotate<Number, Angle>;
480 
481 impl Parse for Rotate {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't> ) -> Result<Self, ParseError<'i>>482     fn parse<'i, 't>(
483         context: &ParserContext,
484         input: &mut Parser<'i, 't>
485     ) -> Result<Self, ParseError<'i>> {
486         if input.try(|i| i.expect_ident_matching("none")).is_ok() {
487             return Ok(GenericRotate::None);
488         }
489 
490         if let Ok(rx) = input.try(|i| Number::parse(context, i)) {
491             // 'rotate: <number>{3} <angle>'
492             let ry = Number::parse(context, input)?;
493             let rz = Number::parse(context, input)?;
494             let angle = specified::Angle::parse(context, input)?;
495             return Ok(GenericRotate::Rotate3D(rx, ry, rz, angle));
496         }
497 
498         // 'rotate: <angle>'
499         let angle = specified::Angle::parse(context, input)?;
500         Ok(GenericRotate::Rotate(angle))
501     }
502 }
503 
504 /// A specified CSS `translate`
505 pub type Translate = GenericTranslate<LengthOrPercentage, Length>;
506 
507 impl Parse for Translate {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't> ) -> Result<Self, ParseError<'i>>508     fn parse<'i, 't>(
509         context: &ParserContext,
510         input: &mut Parser<'i, 't>
511     ) -> Result<Self, ParseError<'i>> {
512         if input.try(|i| i.expect_ident_matching("none")).is_ok() {
513             return Ok(GenericTranslate::None);
514         }
515 
516         let tx = specified::LengthOrPercentage::parse(context, input)?;
517         if let Ok(ty) = input.try(|i| specified::LengthOrPercentage::parse(context, i)) {
518             if let Ok(tz) = input.try(|i| specified::Length::parse(context, i)) {
519                 // 'translate: <length-percentage> <length-percentage> <length>'
520                 return Ok(GenericTranslate::Translate3D(tx, ty, tz));
521             }
522 
523             // translate: <length-percentage> <length-percentage>'
524             return Ok(GenericTranslate::Translate(tx, ty));
525         }
526 
527         // 'translate: <length-percentage> '
528         Ok(GenericTranslate::TranslateX(tx))
529     }
530 }
531 
532 /// A specified CSS `scale`
533 pub type Scale = GenericScale<Number>;
534 
535 impl Parse for Scale {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't> ) -> Result<Self, ParseError<'i>>536     fn parse<'i, 't>(
537         context: &ParserContext,
538         input: &mut Parser<'i, 't>
539     ) -> Result<Self, ParseError<'i>> {
540         if input.try(|i| i.expect_ident_matching("none")).is_ok() {
541             return Ok(GenericScale::None);
542         }
543 
544         let sx = Number::parse(context, input)?;
545         if let Ok(sy) = input.try(|i| Number::parse(context, i)) {
546             if let Ok(sz) = input.try(|i| Number::parse(context, i)) {
547                 // 'scale: <number> <number> <number>'
548                 return Ok(GenericScale::Scale3D(sx, sy, sz));
549             }
550 
551             // 'scale: <number> <number>'
552             return Ok(GenericScale::Scale(sx, sy));
553         }
554 
555         // 'scale: <number>'
556         Ok(GenericScale::ScaleX(sx))
557     }
558 }
559