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 d = specified::Length::parse_non_negative(context, input)?;
242                             Ok(generic::TransformOperation::Perspective(d))
243                         },
244                         _ => Err(()),
245                     };
246                     result.map_err(|()| {
247                         location.new_custom_error(StyleParseErrorKind::UnexpectedFunction(
248                             function.clone(),
249                         ))
250                     })
251                 })
252             })?
253             .into(),
254         ))
255     }
256 }
257 
258 impl Parse for Transform {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>259     fn parse<'i, 't>(
260         context: &ParserContext,
261         input: &mut Parser<'i, 't>,
262     ) -> Result<Self, ParseError<'i>> {
263         Transform::parse_internal(context, input)
264     }
265 }
266 
267 /// The specified value of a component of a CSS `<transform-origin>`.
268 #[derive(Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
269 pub enum OriginComponent<S> {
270     /// `center`
271     Center,
272     /// `<length-percentage>`
273     Length(LengthPercentage),
274     /// `<side>`
275     Side(S),
276 }
277 
278 impl Parse for TransformOrigin {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>279     fn parse<'i, 't>(
280         context: &ParserContext,
281         input: &mut Parser<'i, 't>,
282     ) -> Result<Self, ParseError<'i>> {
283         let parse_depth = |input: &mut Parser| {
284             input
285                 .try_parse(|i| Length::parse(context, i))
286                 .unwrap_or(Length::zero())
287         };
288         match input.try_parse(|i| OriginComponent::parse(context, i)) {
289             Ok(x_origin @ OriginComponent::Center) => {
290                 if let Ok(y_origin) = input.try_parse(|i| OriginComponent::parse(context, i)) {
291                     let depth = parse_depth(input);
292                     return Ok(Self::new(x_origin, y_origin, depth));
293                 }
294                 let y_origin = OriginComponent::Center;
295                 if let Ok(x_keyword) = input.try_parse(HorizontalPositionKeyword::parse) {
296                     let x_origin = OriginComponent::Side(x_keyword);
297                     let depth = parse_depth(input);
298                     return Ok(Self::new(x_origin, y_origin, depth));
299                 }
300                 let depth = Length::from_px(0.);
301                 return Ok(Self::new(x_origin, y_origin, depth));
302             },
303             Ok(x_origin) => {
304                 if let Ok(y_origin) = input.try_parse(|i| OriginComponent::parse(context, i)) {
305                     let depth = parse_depth(input);
306                     return Ok(Self::new(x_origin, y_origin, depth));
307                 }
308                 let y_origin = OriginComponent::Center;
309                 let depth = Length::from_px(0.);
310                 return Ok(Self::new(x_origin, y_origin, depth));
311             },
312             Err(_) => {},
313         }
314         let y_keyword = VerticalPositionKeyword::parse(input)?;
315         let y_origin = OriginComponent::Side(y_keyword);
316         if let Ok(x_keyword) = input.try_parse(HorizontalPositionKeyword::parse) {
317             let x_origin = OriginComponent::Side(x_keyword);
318             let depth = parse_depth(input);
319             return Ok(Self::new(x_origin, y_origin, depth));
320         }
321         if input
322             .try_parse(|i| i.expect_ident_matching("center"))
323             .is_ok()
324         {
325             let x_origin = OriginComponent::Center;
326             let depth = parse_depth(input);
327             return Ok(Self::new(x_origin, y_origin, depth));
328         }
329         let x_origin = OriginComponent::Center;
330         let depth = Length::from_px(0.);
331         Ok(Self::new(x_origin, y_origin, depth))
332     }
333 }
334 
335 impl<S> ToComputedValue for OriginComponent<S>
336 where
337     S: Side,
338 {
339     type ComputedValue = ComputedLengthPercentage;
340 
to_computed_value(&self, context: &Context) -> Self::ComputedValue341     fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
342         match *self {
343             OriginComponent::Center => {
344                 ComputedLengthPercentage::new_percent(ComputedPercentage(0.5))
345             },
346             OriginComponent::Length(ref length) => length.to_computed_value(context),
347             OriginComponent::Side(ref keyword) => {
348                 let p = ComputedPercentage(if keyword.is_start() { 0. } else { 1. });
349                 ComputedLengthPercentage::new_percent(p)
350             },
351         }
352     }
353 
from_computed_value(computed: &Self::ComputedValue) -> Self354     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
355         OriginComponent::Length(ToComputedValue::from_computed_value(computed))
356     }
357 }
358 
359 impl<S> OriginComponent<S> {
360     /// `0%`
zero() -> Self361     pub fn zero() -> Self {
362         OriginComponent::Length(LengthPercentage::Percentage(ComputedPercentage::zero()))
363     }
364 }
365 
366 /// A specified CSS `rotate`
367 pub type Rotate = generic::Rotate<Number, Angle>;
368 
369 impl Parse for Rotate {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>370     fn parse<'i, 't>(
371         context: &ParserContext,
372         input: &mut Parser<'i, 't>,
373     ) -> Result<Self, ParseError<'i>> {
374         if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
375             return Ok(generic::Rotate::None);
376         }
377 
378         // Parse <angle> or [ x | y | z | <number>{3} ] && <angle>.
379         //
380         // The rotate axis and angle could be in any order, so we parse angle twice to cover
381         // two cases. i.e. `<number>{3} <angle>` or `<angle> <number>{3}`
382         let angle = input
383             .try_parse(|i| specified::Angle::parse(context, i))
384             .ok();
385         let axis = input
386             .try_parse(|i| {
387                 Ok(try_match_ident_ignore_ascii_case! { i,
388                     "x" => (Number::new(1.), Number::new(0.), Number::new(0.)),
389                     "y" => (Number::new(0.), Number::new(1.), Number::new(0.)),
390                     "z" => (Number::new(0.), Number::new(0.), Number::new(1.)),
391                 })
392             })
393             .or_else(|_: ParseError| -> Result<_, ParseError> {
394                 input.try_parse(|i| {
395                     Ok((
396                         Number::parse(context, i)?,
397                         Number::parse(context, i)?,
398                         Number::parse(context, i)?,
399                     ))
400                 })
401             })
402             .ok();
403         let angle = match angle {
404             Some(a) => a,
405             None => specified::Angle::parse(context, input)?,
406         };
407 
408         Ok(match axis {
409             Some((x, y, z)) => generic::Rotate::Rotate3D(x, y, z, angle),
410             None => generic::Rotate::Rotate(angle),
411         })
412     }
413 }
414 
415 /// A specified CSS `translate`
416 pub type Translate = generic::Translate<LengthPercentage, Length>;
417 
418 impl Parse for Translate {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>419     fn parse<'i, 't>(
420         context: &ParserContext,
421         input: &mut Parser<'i, 't>,
422     ) -> Result<Self, ParseError<'i>> {
423         if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
424             return Ok(generic::Translate::None);
425         }
426 
427         let tx = specified::LengthPercentage::parse(context, input)?;
428         if let Ok(ty) = input.try_parse(|i| specified::LengthPercentage::parse(context, i)) {
429             if let Ok(tz) = input.try_parse(|i| specified::Length::parse(context, i)) {
430                 // 'translate: <length-percentage> <length-percentage> <length>'
431                 return Ok(generic::Translate::Translate(tx, ty, tz));
432             }
433 
434             // translate: <length-percentage> <length-percentage>'
435             return Ok(generic::Translate::Translate(
436                 tx,
437                 ty,
438                 specified::Length::zero(),
439             ));
440         }
441 
442         // 'translate: <length-percentage> '
443         Ok(generic::Translate::Translate(
444             tx,
445             specified::LengthPercentage::zero(),
446             specified::Length::zero(),
447         ))
448     }
449 }
450 
451 /// A specified CSS `scale`
452 pub type Scale = generic::Scale<Number>;
453 
454 impl Parse for Scale {
455     /// Scale accepts <number> | <percentage>, so we parse it as NumberOrPercentage,
456     /// and then convert into an Number if it's a Percentage.
457     /// https://github.com/w3c/csswg-drafts/pull/4396
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>458     fn parse<'i, 't>(
459         context: &ParserContext,
460         input: &mut Parser<'i, 't>,
461     ) -> Result<Self, ParseError<'i>> {
462         if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
463             return Ok(generic::Scale::None);
464         }
465 
466         let sx = NumberOrPercentage::parse(context, input)?.to_number();
467         if let Ok(sy) = input.try_parse(|i| NumberOrPercentage::parse(context, i)) {
468             let sy = sy.to_number();
469             if let Ok(sz) = input.try_parse(|i| NumberOrPercentage::parse(context, i)) {
470                 // 'scale: <number> <number> <number>'
471                 return Ok(generic::Scale::Scale(sx, sy, sz.to_number()));
472             }
473 
474             // 'scale: <number> <number>'
475             return Ok(generic::Scale::Scale(sx, sy, Number::new(1.0)));
476         }
477 
478         // 'scale: <number>'
479         Ok(generic::Scale::Scale(sx, sx, Number::new(1.0)))
480     }
481 }
482