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