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 https://mozilla.org/MPL/2.0/. */
4 
5 //! Specified types for CSS values that are related to transformations.
6 
7 use crate::parser::{Parse, ParserContext};
8 use crate::values::computed::{Context, LengthPercentage as ComputedLengthPercentage};
9 use crate::values::computed::{Percentage as ComputedPercentage, ToComputedValue};
10 use crate::values::generics::transform as generic;
11 use crate::values::generics::transform::{Matrix, Matrix3D};
12 use crate::values::specified::position::{
13     HorizontalPositionKeyword, Side, VerticalPositionKeyword,
14 };
15 use crate::values::specified::{
16     self, Angle, Integer, Length, LengthPercentage, Number, NumberOrPercentage,
17 };
18 use crate::Zero;
19 use cssparser::Parser;
20 use style_traits::{ParseError, StyleParseErrorKind};
21 
22 pub use crate::values::generics::transform::TransformStyle;
23 
24 /// A single operation in a specified CSS `transform`
25 pub type TransformOperation =
26     generic::TransformOperation<Angle, Number, Length, Integer, LengthPercentage>;
27 
28 /// A specified CSS `transform`
29 pub type Transform = generic::Transform<TransformOperation>;
30 
31 /// The specified value of a CSS `<transform-origin>`
32 pub type TransformOrigin = generic::TransformOrigin<
33     OriginComponent<HorizontalPositionKeyword>,
34     OriginComponent<VerticalPositionKeyword>,
35     Length,
36 >;
37 
38 impl TransformOrigin {
39     /// Returns the initial specified value for `transform-origin`.
40     #[inline]
initial_value() -> Self41     pub fn initial_value() -> Self {
42         Self::new(
43             OriginComponent::Length(LengthPercentage::Percentage(ComputedPercentage(0.5))),
44             OriginComponent::Length(LengthPercentage::Percentage(ComputedPercentage(0.5))),
45             Length::zero(),
46         )
47     }
48 
49     /// Returns the `0 0` value.
zero_zero() -> Self50     pub fn zero_zero() -> Self {
51         Self::new(
52             OriginComponent::Length(LengthPercentage::zero()),
53             OriginComponent::Length(LengthPercentage::zero()),
54             Length::zero(),
55         )
56     }
57 }
58 
59 impl Transform {
60     /// Internal parse function for deciding if we wish to accept prefixed values or not
61     ///
62     /// `transform` allows unitless zero angles as an exception, see:
63     /// https://github.com/w3c/csswg-drafts/issues/1162
parse_internal<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>64     fn parse_internal<'i, 't>(
65         context: &ParserContext,
66         input: &mut Parser<'i, 't>,
67     ) -> Result<Self, ParseError<'i>> {
68         use style_traits::{Separator, Space};
69 
70         if input
71             .try_parse(|input| input.expect_ident_matching("none"))
72             .is_ok()
73         {
74             return Ok(generic::Transform::none());
75         }
76 
77         Ok(generic::Transform(
78             Space::parse(input, |input| {
79                 let function = input.expect_function()?.clone();
80                 input.parse_nested_block(|input| {
81                     let location = input.current_source_location();
82                     let result = match_ignore_ascii_case! { &function,
83                         "matrix" => {
84                             let a = Number::parse(context, input)?;
85                             input.expect_comma()?;
86                             let b = Number::parse(context, input)?;
87                             input.expect_comma()?;
88                             let c = Number::parse(context, input)?;
89                             input.expect_comma()?;
90                             let d = Number::parse(context, input)?;
91                             input.expect_comma()?;
92                             // Standard matrix parsing.
93                             let e = Number::parse(context, input)?;
94                             input.expect_comma()?;
95                             let f = Number::parse(context, input)?;
96                             Ok(generic::TransformOperation::Matrix(Matrix { a, b, c, d, e, f }))
97                         },
98                         "matrix3d" => {
99                             let m11 = Number::parse(context, input)?;
100                             input.expect_comma()?;
101                             let m12 = Number::parse(context, input)?;
102                             input.expect_comma()?;
103                             let m13 = Number::parse(context, input)?;
104                             input.expect_comma()?;
105                             let m14 = Number::parse(context, input)?;
106                             input.expect_comma()?;
107                             let m21 = Number::parse(context, input)?;
108                             input.expect_comma()?;
109                             let m22 = Number::parse(context, input)?;
110                             input.expect_comma()?;
111                             let m23 = Number::parse(context, input)?;
112                             input.expect_comma()?;
113                             let m24 = Number::parse(context, input)?;
114                             input.expect_comma()?;
115                             let m31 = Number::parse(context, input)?;
116                             input.expect_comma()?;
117                             let m32 = Number::parse(context, input)?;
118                             input.expect_comma()?;
119                             let m33 = Number::parse(context, input)?;
120                             input.expect_comma()?;
121                             let m34 = Number::parse(context, input)?;
122                             input.expect_comma()?;
123                             // Standard matrix3d parsing.
124                             let m41 = Number::parse(context, input)?;
125                             input.expect_comma()?;
126                             let m42 = Number::parse(context, input)?;
127                             input.expect_comma()?;
128                             let m43 = Number::parse(context, input)?;
129                             input.expect_comma()?;
130                             let m44 = Number::parse(context, input)?;
131                             Ok(generic::TransformOperation::Matrix3D(Matrix3D {
132                                 m11, m12, m13, m14,
133                                 m21, m22, m23, m24,
134                                 m31, m32, m33, m34,
135                                 m41, m42, m43, m44,
136                             }))
137                         },
138                         "translate" => {
139                             let sx = specified::LengthPercentage::parse(context, input)?;
140                             if input.try_parse(|input| input.expect_comma()).is_ok() {
141                                 let sy = specified::LengthPercentage::parse(context, input)?;
142                                 Ok(generic::TransformOperation::Translate(sx, sy))
143                             } else {
144                                 Ok(generic::TransformOperation::Translate(sx, Zero::zero()))
145                             }
146                         },
147                         "translatex" => {
148                             let tx = specified::LengthPercentage::parse(context, input)?;
149                             Ok(generic::TransformOperation::TranslateX(tx))
150                         },
151                         "translatey" => {
152                             let ty = specified::LengthPercentage::parse(context, input)?;
153                             Ok(generic::TransformOperation::TranslateY(ty))
154                         },
155                         "translatez" => {
156                             let tz = specified::Length::parse(context, input)?;
157                             Ok(generic::TransformOperation::TranslateZ(tz))
158                         },
159                         "translate3d" => {
160                             let tx = specified::LengthPercentage::parse(context, input)?;
161                             input.expect_comma()?;
162                             let ty = specified::LengthPercentage::parse(context, input)?;
163                             input.expect_comma()?;
164                             let tz = specified::Length::parse(context, input)?;
165                             Ok(generic::TransformOperation::Translate3D(tx, ty, tz))
166                         },
167                         "scale" => {
168                             let sx = NumberOrPercentage::parse(context, input)?.to_number();
169                             if input.try_parse(|input| input.expect_comma()).is_ok() {
170                                 let sy = NumberOrPercentage::parse(context, input)?.to_number();
171                                 Ok(generic::TransformOperation::Scale(sx, sy))
172                             } else {
173                                 Ok(generic::TransformOperation::Scale(sx, sx))
174                             }
175                         },
176                         "scalex" => {
177                             let sx = NumberOrPercentage::parse(context, input)?.to_number();
178                             Ok(generic::TransformOperation::ScaleX(sx))
179                         },
180                         "scaley" => {
181                             let sy = NumberOrPercentage::parse(context, input)?.to_number();
182                             Ok(generic::TransformOperation::ScaleY(sy))
183                         },
184                         "scalez" => {
185                             let sz = NumberOrPercentage::parse(context, input)?.to_number();
186                             Ok(generic::TransformOperation::ScaleZ(sz))
187                         },
188                         "scale3d" => {
189                             let sx = NumberOrPercentage::parse(context, input)?.to_number();
190                             input.expect_comma()?;
191                             let sy = NumberOrPercentage::parse(context, input)?.to_number();
192                             input.expect_comma()?;
193                             let sz = NumberOrPercentage::parse(context, input)?.to_number();
194                             Ok(generic::TransformOperation::Scale3D(sx, sy, sz))
195                         },
196                         "rotate" => {
197                             let theta = specified::Angle::parse_with_unitless(context, input)?;
198                             Ok(generic::TransformOperation::Rotate(theta))
199                         },
200                         "rotatex" => {
201                             let theta = specified::Angle::parse_with_unitless(context, input)?;
202                             Ok(generic::TransformOperation::RotateX(theta))
203                         },
204                         "rotatey" => {
205                             let theta = specified::Angle::parse_with_unitless(context, input)?;
206                             Ok(generic::TransformOperation::RotateY(theta))
207                         },
208                         "rotatez" => {
209                             let theta = specified::Angle::parse_with_unitless(context, input)?;
210                             Ok(generic::TransformOperation::RotateZ(theta))
211                         },
212                         "rotate3d" => {
213                             let ax = Number::parse(context, input)?;
214                             input.expect_comma()?;
215                             let ay = Number::parse(context, input)?;
216                             input.expect_comma()?;
217                             let az = Number::parse(context, input)?;
218                             input.expect_comma()?;
219                             let theta = specified::Angle::parse_with_unitless(context, input)?;
220                             // TODO(gw): Check that the axis can be normalized.
221                             Ok(generic::TransformOperation::Rotate3D(ax, ay, az, theta))
222                         },
223                         "skew" => {
224                             let ax = specified::Angle::parse_with_unitless(context, input)?;
225                             if input.try_parse(|input| input.expect_comma()).is_ok() {
226                                 let ay = specified::Angle::parse_with_unitless(context, input)?;
227                                 Ok(generic::TransformOperation::Skew(ax, ay))
228                             } else {
229                                 Ok(generic::TransformOperation::Skew(ax, Zero::zero()))
230                             }
231                         },
232                         "skewx" => {
233                             let theta = specified::Angle::parse_with_unitless(context, input)?;
234                             Ok(generic::TransformOperation::SkewX(theta))
235                         },
236                         "skewy" => {
237                             let theta = specified::Angle::parse_with_unitless(context, input)?;
238                             Ok(generic::TransformOperation::SkewY(theta))
239                         },
240                         "perspective" => {
241                             let p = match input.try_parse(|input| specified::Length::parse_non_negative(context, input)) {
242                                 Ok(p) => generic::PerspectiveFunction::Length(p),
243                                 Err(..) => {
244                                     input.expect_ident_matching("none")?;
245                                     generic::PerspectiveFunction::None
246                                 }
247                             };
248                             Ok(generic::TransformOperation::Perspective(p))
249                         },
250                         _ => Err(()),
251                     };
252                     result.map_err(|()| {
253                         location.new_custom_error(StyleParseErrorKind::UnexpectedFunction(
254                             function.clone(),
255                         ))
256                     })
257                 })
258             })?
259             .into(),
260         ))
261     }
262 }
263 
264 impl Parse for Transform {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>265     fn parse<'i, 't>(
266         context: &ParserContext,
267         input: &mut Parser<'i, 't>,
268     ) -> Result<Self, ParseError<'i>> {
269         Transform::parse_internal(context, input)
270     }
271 }
272 
273 /// The specified value of a component of a CSS `<transform-origin>`.
274 #[derive(Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
275 pub enum OriginComponent<S> {
276     /// `center`
277     Center,
278     /// `<length-percentage>`
279     Length(LengthPercentage),
280     /// `<side>`
281     Side(S),
282 }
283 
284 impl Parse for TransformOrigin {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>285     fn parse<'i, 't>(
286         context: &ParserContext,
287         input: &mut Parser<'i, 't>,
288     ) -> Result<Self, ParseError<'i>> {
289         let parse_depth = |input: &mut Parser| {
290             input
291                 .try_parse(|i| Length::parse(context, i))
292                 .unwrap_or(Length::zero())
293         };
294         match input.try_parse(|i| OriginComponent::parse(context, i)) {
295             Ok(x_origin @ OriginComponent::Center) => {
296                 if let Ok(y_origin) = input.try_parse(|i| OriginComponent::parse(context, i)) {
297                     let depth = parse_depth(input);
298                     return Ok(Self::new(x_origin, y_origin, depth));
299                 }
300                 let y_origin = OriginComponent::Center;
301                 if let Ok(x_keyword) = input.try_parse(HorizontalPositionKeyword::parse) {
302                     let x_origin = OriginComponent::Side(x_keyword);
303                     let depth = parse_depth(input);
304                     return Ok(Self::new(x_origin, y_origin, depth));
305                 }
306                 let depth = Length::from_px(0.);
307                 return Ok(Self::new(x_origin, y_origin, depth));
308             },
309             Ok(x_origin) => {
310                 if let Ok(y_origin) = input.try_parse(|i| OriginComponent::parse(context, i)) {
311                     let depth = parse_depth(input);
312                     return Ok(Self::new(x_origin, y_origin, depth));
313                 }
314                 let y_origin = OriginComponent::Center;
315                 let depth = Length::from_px(0.);
316                 return Ok(Self::new(x_origin, y_origin, depth));
317             },
318             Err(_) => {},
319         }
320         let y_keyword = VerticalPositionKeyword::parse(input)?;
321         let y_origin = OriginComponent::Side(y_keyword);
322         if let Ok(x_keyword) = input.try_parse(HorizontalPositionKeyword::parse) {
323             let x_origin = OriginComponent::Side(x_keyword);
324             let depth = parse_depth(input);
325             return Ok(Self::new(x_origin, y_origin, depth));
326         }
327         if input
328             .try_parse(|i| i.expect_ident_matching("center"))
329             .is_ok()
330         {
331             let x_origin = OriginComponent::Center;
332             let depth = parse_depth(input);
333             return Ok(Self::new(x_origin, y_origin, depth));
334         }
335         let x_origin = OriginComponent::Center;
336         let depth = Length::from_px(0.);
337         Ok(Self::new(x_origin, y_origin, depth))
338     }
339 }
340 
341 impl<S> ToComputedValue for OriginComponent<S>
342 where
343     S: Side,
344 {
345     type ComputedValue = ComputedLengthPercentage;
346 
to_computed_value(&self, context: &Context) -> Self::ComputedValue347     fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
348         match *self {
349             OriginComponent::Center => {
350                 ComputedLengthPercentage::new_percent(ComputedPercentage(0.5))
351             },
352             OriginComponent::Length(ref length) => length.to_computed_value(context),
353             OriginComponent::Side(ref keyword) => {
354                 let p = ComputedPercentage(if keyword.is_start() { 0. } else { 1. });
355                 ComputedLengthPercentage::new_percent(p)
356             },
357         }
358     }
359 
from_computed_value(computed: &Self::ComputedValue) -> Self360     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
361         OriginComponent::Length(ToComputedValue::from_computed_value(computed))
362     }
363 }
364 
365 impl<S> OriginComponent<S> {
366     /// `0%`
zero() -> Self367     pub fn zero() -> Self {
368         OriginComponent::Length(LengthPercentage::Percentage(ComputedPercentage::zero()))
369     }
370 }
371 
372 /// A specified CSS `rotate`
373 pub type Rotate = generic::Rotate<Number, Angle>;
374 
375 impl Parse for Rotate {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>376     fn parse<'i, 't>(
377         context: &ParserContext,
378         input: &mut Parser<'i, 't>,
379     ) -> Result<Self, ParseError<'i>> {
380         if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
381             return Ok(generic::Rotate::None);
382         }
383 
384         // Parse <angle> or [ x | y | z | <number>{3} ] && <angle>.
385         //
386         // The rotate axis and angle could be in any order, so we parse angle twice to cover
387         // two cases. i.e. `<number>{3} <angle>` or `<angle> <number>{3}`
388         let angle = input
389             .try_parse(|i| specified::Angle::parse(context, i))
390             .ok();
391         let axis = input
392             .try_parse(|i| {
393                 Ok(try_match_ident_ignore_ascii_case! { i,
394                     "x" => (Number::new(1.), Number::new(0.), Number::new(0.)),
395                     "y" => (Number::new(0.), Number::new(1.), Number::new(0.)),
396                     "z" => (Number::new(0.), Number::new(0.), Number::new(1.)),
397                 })
398             })
399             .or_else(|_: ParseError| -> Result<_, ParseError> {
400                 input.try_parse(|i| {
401                     Ok((
402                         Number::parse(context, i)?,
403                         Number::parse(context, i)?,
404                         Number::parse(context, i)?,
405                     ))
406                 })
407             })
408             .ok();
409         let angle = match angle {
410             Some(a) => a,
411             None => specified::Angle::parse(context, input)?,
412         };
413 
414         Ok(match axis {
415             Some((x, y, z)) => generic::Rotate::Rotate3D(x, y, z, angle),
416             None => generic::Rotate::Rotate(angle),
417         })
418     }
419 }
420 
421 /// A specified CSS `translate`
422 pub type Translate = generic::Translate<LengthPercentage, Length>;
423 
424 impl Parse for Translate {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>425     fn parse<'i, 't>(
426         context: &ParserContext,
427         input: &mut Parser<'i, 't>,
428     ) -> Result<Self, ParseError<'i>> {
429         if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
430             return Ok(generic::Translate::None);
431         }
432 
433         let tx = specified::LengthPercentage::parse(context, input)?;
434         if let Ok(ty) = input.try_parse(|i| specified::LengthPercentage::parse(context, i)) {
435             if let Ok(tz) = input.try_parse(|i| specified::Length::parse(context, i)) {
436                 // 'translate: <length-percentage> <length-percentage> <length>'
437                 return Ok(generic::Translate::Translate(tx, ty, tz));
438             }
439 
440             // translate: <length-percentage> <length-percentage>'
441             return Ok(generic::Translate::Translate(
442                 tx,
443                 ty,
444                 specified::Length::zero(),
445             ));
446         }
447 
448         // 'translate: <length-percentage> '
449         Ok(generic::Translate::Translate(
450             tx,
451             specified::LengthPercentage::zero(),
452             specified::Length::zero(),
453         ))
454     }
455 }
456 
457 /// A specified CSS `scale`
458 pub type Scale = generic::Scale<Number>;
459 
460 impl Parse for Scale {
461     /// Scale accepts <number> | <percentage>, so we parse it as NumberOrPercentage,
462     /// and then convert into an Number if it's a Percentage.
463     /// https://github.com/w3c/csswg-drafts/pull/4396
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>464     fn parse<'i, 't>(
465         context: &ParserContext,
466         input: &mut Parser<'i, 't>,
467     ) -> Result<Self, ParseError<'i>> {
468         if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
469             return Ok(generic::Scale::None);
470         }
471 
472         let sx = NumberOrPercentage::parse(context, input)?.to_number();
473         if let Ok(sy) = input.try_parse(|i| NumberOrPercentage::parse(context, i)) {
474             let sy = sy.to_number();
475             if let Ok(sz) = input.try_parse(|i| NumberOrPercentage::parse(context, i)) {
476                 // 'scale: <number> <number> <number>'
477                 return Ok(generic::Scale::Scale(sx, sy, sz.to_number()));
478             }
479 
480             // 'scale: <number> <number>'
481             return Ok(generic::Scale::Scale(sx, sy, Number::new(1.0)));
482         }
483 
484         // 'scale: <number>'
485         Ok(generic::Scale::Scale(sx, sx, Number::new(1.0)))
486     }
487 }
488