transform.rs (29334B)
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 //! Generic types for CSS values that are related to transformations. 6 7 use crate::derives::*; 8 use crate::values::computed::length::Length as ComputedLength; 9 use crate::values::computed::length::LengthPercentage as ComputedLengthPercentage; 10 use crate::values::specified::angle::Angle as SpecifiedAngle; 11 use crate::values::specified::length::Length as SpecifiedLength; 12 use crate::values::specified::length::LengthPercentage as SpecifiedLengthPercentage; 13 use crate::values::{computed, CSSFloat}; 14 use crate::{Zero, ZeroNoPercent}; 15 use euclid::default::{Rect, Transform3D}; 16 use std::fmt::{self, Write}; 17 use std::ops::Neg; 18 use style_traits::{CssWriter, ToCss}; 19 20 /// A generic 2D transformation matrix. 21 #[allow(missing_docs)] 22 #[derive( 23 Clone, 24 Copy, 25 Debug, 26 Deserialize, 27 MallocSizeOf, 28 PartialEq, 29 Serialize, 30 SpecifiedValueInfo, 31 ToAnimatedValue, 32 ToComputedValue, 33 ToCss, 34 ToResolvedValue, 35 ToShmem, 36 )] 37 #[css(comma, function = "matrix")] 38 #[repr(C)] 39 pub struct GenericMatrix<T> { 40 pub a: T, 41 pub b: T, 42 pub c: T, 43 pub d: T, 44 pub e: T, 45 pub f: T, 46 } 47 48 pub use self::GenericMatrix as Matrix; 49 50 #[allow(missing_docs)] 51 #[cfg_attr(rustfmt, rustfmt_skip)] 52 #[derive( 53 Clone, 54 Copy, 55 Debug, 56 Deserialize, 57 MallocSizeOf, 58 PartialEq, 59 Serialize, 60 SpecifiedValueInfo, 61 ToAnimatedValue, 62 ToComputedValue, 63 ToCss, 64 ToResolvedValue, 65 ToShmem, 66 )] 67 #[css(comma, function = "matrix3d")] 68 #[repr(C)] 69 pub struct GenericMatrix3D<T> { 70 pub m11: T, pub m12: T, pub m13: T, pub m14: T, 71 pub m21: T, pub m22: T, pub m23: T, pub m24: T, 72 pub m31: T, pub m32: T, pub m33: T, pub m34: T, 73 pub m41: T, pub m42: T, pub m43: T, pub m44: T, 74 } 75 76 pub use self::GenericMatrix3D as Matrix3D; 77 78 #[cfg_attr(rustfmt, rustfmt_skip)] 79 impl<T: Into<f64>> From<Matrix<T>> for Transform3D<f64> { 80 #[inline] 81 fn from(m: Matrix<T>) -> Self { 82 Transform3D::new( 83 m.a.into(), m.b.into(), 0.0, 0.0, 84 m.c.into(), m.d.into(), 0.0, 0.0, 85 0.0, 0.0, 1.0, 0.0, 86 m.e.into(), m.f.into(), 0.0, 1.0, 87 ) 88 } 89 } 90 91 #[cfg_attr(rustfmt, rustfmt_skip)] 92 impl<T: Into<f64>> From<Matrix3D<T>> for Transform3D<f64> { 93 #[inline] 94 fn from(m: Matrix3D<T>) -> Self { 95 Transform3D::new( 96 m.m11.into(), m.m12.into(), m.m13.into(), m.m14.into(), 97 m.m21.into(), m.m22.into(), m.m23.into(), m.m24.into(), 98 m.m31.into(), m.m32.into(), m.m33.into(), m.m34.into(), 99 m.m41.into(), m.m42.into(), m.m43.into(), m.m44.into(), 100 ) 101 } 102 } 103 104 /// A generic transform origin. 105 #[derive( 106 Animate, 107 Clone, 108 ComputeSquaredDistance, 109 Copy, 110 Debug, 111 MallocSizeOf, 112 PartialEq, 113 SpecifiedValueInfo, 114 ToAnimatedValue, 115 ToAnimatedZero, 116 ToComputedValue, 117 ToCss, 118 ToResolvedValue, 119 ToShmem, 120 ToTyped, 121 )] 122 #[repr(C)] 123 pub struct GenericTransformOrigin<H, V, Depth> { 124 /// The horizontal origin. 125 pub horizontal: H, 126 /// The vertical origin. 127 pub vertical: V, 128 /// The depth. 129 pub depth: Depth, 130 } 131 132 pub use self::GenericTransformOrigin as TransformOrigin; 133 134 impl<H, V, D> TransformOrigin<H, V, D> { 135 /// Returns a new transform origin. 136 pub fn new(horizontal: H, vertical: V, depth: D) -> Self { 137 Self { 138 horizontal, 139 vertical, 140 depth, 141 } 142 } 143 } 144 145 fn is_same<N: PartialEq>(x: &N, y: &N) -> bool { 146 x == y 147 } 148 149 /// A value for the `perspective()` transform function, which is either a 150 /// non-negative `<length>` or `none`. 151 #[derive( 152 Clone, 153 Debug, 154 Deserialize, 155 MallocSizeOf, 156 PartialEq, 157 Serialize, 158 SpecifiedValueInfo, 159 ToAnimatedValue, 160 ToComputedValue, 161 ToCss, 162 ToResolvedValue, 163 ToShmem, 164 )] 165 #[repr(C, u8)] 166 pub enum GenericPerspectiveFunction<L> { 167 /// `none` 168 None, 169 /// A `<length>`. 170 Length(L), 171 } 172 173 impl<L> GenericPerspectiveFunction<L> { 174 /// Returns `f32::INFINITY` or the result of a function on the length value. 175 pub fn infinity_or(&self, f: impl FnOnce(&L) -> f32) -> f32 { 176 match *self { 177 Self::None => f32::INFINITY, 178 Self::Length(ref l) => f(l), 179 } 180 } 181 } 182 183 pub use self::GenericPerspectiveFunction as PerspectiveFunction; 184 185 #[derive( 186 Clone, 187 Debug, 188 Deserialize, 189 MallocSizeOf, 190 PartialEq, 191 Serialize, 192 SpecifiedValueInfo, 193 ToAnimatedValue, 194 ToComputedValue, 195 ToCss, 196 ToResolvedValue, 197 ToShmem, 198 )] 199 #[repr(C, u8)] 200 /// A single operation in the list of a `transform` value 201 pub enum GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage> 202 where 203 Angle: Zero, 204 LengthPercentage: Zero + ZeroNoPercent, 205 Number: PartialEq, 206 { 207 /// Represents a 2D 2x3 matrix. 208 Matrix(GenericMatrix<Number>), 209 /// Represents a 3D 4x4 matrix. 210 Matrix3D(GenericMatrix3D<Number>), 211 /// A 2D skew. 212 /// 213 /// If the second angle is not provided it is assumed zero. 214 /// 215 /// Syntax can be skew(angle) or skew(angle, angle) 216 #[css(comma, function)] 217 Skew(Angle, #[css(skip_if = "Zero::is_zero")] Angle), 218 /// skewX(angle) 219 #[css(function = "skewX")] 220 SkewX(Angle), 221 /// skewY(angle) 222 #[css(function = "skewY")] 223 SkewY(Angle), 224 /// translate(x, y) or translate(x) 225 #[css(comma, function)] 226 Translate( 227 LengthPercentage, 228 #[css(skip_if = "ZeroNoPercent::is_zero_no_percent")] LengthPercentage, 229 ), 230 /// translateX(x) 231 #[css(function = "translateX")] 232 TranslateX(LengthPercentage), 233 /// translateY(y) 234 #[css(function = "translateY")] 235 TranslateY(LengthPercentage), 236 /// translateZ(z) 237 #[css(function = "translateZ")] 238 TranslateZ(Length), 239 /// translate3d(x, y, z) 240 #[css(comma, function = "translate3d")] 241 Translate3D(LengthPercentage, LengthPercentage, Length), 242 /// A 2D scaling factor. 243 /// 244 /// Syntax can be scale(factor) or scale(factor, factor) 245 #[css(comma, function)] 246 Scale(Number, #[css(contextual_skip_if = "is_same")] Number), 247 /// scaleX(factor) 248 #[css(function = "scaleX")] 249 ScaleX(Number), 250 /// scaleY(factor) 251 #[css(function = "scaleY")] 252 ScaleY(Number), 253 /// scaleZ(factor) 254 #[css(function = "scaleZ")] 255 ScaleZ(Number), 256 /// scale3D(factorX, factorY, factorZ) 257 #[css(comma, function = "scale3d")] 258 Scale3D(Number, Number, Number), 259 /// Describes a 2D Rotation. 260 /// 261 /// In a 3D scene `rotate(angle)` is equivalent to `rotateZ(angle)`. 262 #[css(function)] 263 Rotate(Angle), 264 /// Rotation in 3D space around the x-axis. 265 #[css(function = "rotateX")] 266 RotateX(Angle), 267 /// Rotation in 3D space around the y-axis. 268 #[css(function = "rotateY")] 269 RotateY(Angle), 270 /// Rotation in 3D space around the z-axis. 271 #[css(function = "rotateZ")] 272 RotateZ(Angle), 273 /// Rotation in 3D space. 274 /// 275 /// Generalization of rotateX, rotateY and rotateZ. 276 #[css(comma, function = "rotate3d")] 277 Rotate3D(Number, Number, Number, Angle), 278 /// Specifies a perspective projection matrix. 279 /// 280 /// Part of CSS Transform Module Level 2 and defined at 281 /// [ยง 13.1. 3D Transform Function](https://drafts.csswg.org/css-transforms-2/#funcdef-perspective). 282 /// 283 /// The value must be greater than or equal to zero. 284 #[css(function)] 285 Perspective(GenericPerspectiveFunction<Length>), 286 /// A intermediate type for interpolation of mismatched transform lists. 287 #[allow(missing_docs)] 288 #[css(comma, function = "interpolatematrix")] 289 InterpolateMatrix { 290 from_list: GenericTransform< 291 GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>, 292 >, 293 to_list: GenericTransform< 294 GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>, 295 >, 296 progress: computed::Percentage, 297 }, 298 /// A intermediate type for accumulation of mismatched transform lists. 299 #[allow(missing_docs)] 300 #[css(comma, function = "accumulatematrix")] 301 AccumulateMatrix { 302 from_list: GenericTransform< 303 GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>, 304 >, 305 to_list: GenericTransform< 306 GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>, 307 >, 308 count: Integer, 309 }, 310 } 311 312 pub use self::GenericTransformOperation as TransformOperation; 313 314 #[derive( 315 Clone, 316 Debug, 317 Deserialize, 318 MallocSizeOf, 319 PartialEq, 320 Serialize, 321 SpecifiedValueInfo, 322 ToAnimatedValue, 323 ToComputedValue, 324 ToCss, 325 ToResolvedValue, 326 ToShmem, 327 ToTyped, 328 )] 329 #[repr(C)] 330 /// A value of the `transform` property 331 pub struct GenericTransform<T>(#[css(if_empty = "none", iterable)] pub crate::OwnedSlice<T>); 332 333 pub use self::GenericTransform as Transform; 334 335 impl<Angle, Number, Length, Integer, LengthPercentage> 336 TransformOperation<Angle, Number, Length, Integer, LengthPercentage> 337 where 338 Angle: Zero, 339 LengthPercentage: Zero + ZeroNoPercent, 340 Number: PartialEq, 341 { 342 /// Check if it is any rotate function. 343 pub fn is_rotate(&self) -> bool { 344 use self::TransformOperation::*; 345 matches!( 346 *self, 347 Rotate(..) | Rotate3D(..) | RotateX(..) | RotateY(..) | RotateZ(..) 348 ) 349 } 350 351 /// Check if it is any translate function 352 pub fn is_translate(&self) -> bool { 353 use self::TransformOperation::*; 354 match *self { 355 Translate(..) | Translate3D(..) | TranslateX(..) | TranslateY(..) | TranslateZ(..) => { 356 true 357 }, 358 _ => false, 359 } 360 } 361 362 /// Check if it is any scale function 363 pub fn is_scale(&self) -> bool { 364 use self::TransformOperation::*; 365 match *self { 366 Scale(..) | Scale3D(..) | ScaleX(..) | ScaleY(..) | ScaleZ(..) => true, 367 _ => false, 368 } 369 } 370 } 371 372 /// Convert a length type into the absolute lengths. 373 pub trait ToAbsoluteLength { 374 /// Returns the absolute length as pixel value. 375 fn to_pixel_length(&self, containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()>; 376 } 377 378 impl ToAbsoluteLength for SpecifiedLength { 379 // This returns Err(()) if there is any relative length or percentage. We use this when 380 // parsing a transform list of DOMMatrix because we want to return a DOM Exception 381 // if there is relative length. 382 #[inline] 383 fn to_pixel_length(&self, _containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()> { 384 match *self { 385 SpecifiedLength::NoCalc(len) => len.to_computed_pixel_length_without_context(), 386 SpecifiedLength::Calc(ref calc) => calc.to_computed_pixel_length_without_context(), 387 } 388 } 389 } 390 391 impl ToAbsoluteLength for SpecifiedLengthPercentage { 392 // This returns Err(()) if there is any relative length or percentage. We use this when 393 // parsing a transform list of DOMMatrix because we want to return a DOM Exception 394 // if there is relative length. 395 #[inline] 396 fn to_pixel_length(&self, _containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()> { 397 use self::SpecifiedLengthPercentage::*; 398 match *self { 399 Length(len) => len.to_computed_pixel_length_without_context(), 400 Calc(ref calc) => calc.to_computed_pixel_length_without_context(), 401 Percentage(..) => Err(()), 402 } 403 } 404 } 405 406 impl ToAbsoluteLength for ComputedLength { 407 #[inline] 408 fn to_pixel_length(&self, _containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()> { 409 Ok(self.px()) 410 } 411 } 412 413 impl ToAbsoluteLength for ComputedLengthPercentage { 414 #[inline] 415 fn to_pixel_length(&self, containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()> { 416 Ok(self 417 .maybe_percentage_relative_to(containing_len) 418 .ok_or(())? 419 .px()) 420 } 421 } 422 423 /// Support the conversion to a 3d matrix. 424 pub trait ToMatrix { 425 /// Check if it is a 3d transform function. 426 fn is_3d(&self) -> bool; 427 428 /// Return the equivalent 3d matrix. 429 fn to_3d_matrix( 430 &self, 431 reference_box: Option<&Rect<ComputedLength>>, 432 ) -> Result<Transform3D<f64>, ()>; 433 } 434 435 /// A little helper to deal with both specified and computed angles. 436 pub trait ToRadians { 437 /// Return the radians value as a 64-bit floating point value. 438 fn radians64(&self) -> f64; 439 } 440 441 impl ToRadians for computed::angle::Angle { 442 #[inline] 443 fn radians64(&self) -> f64 { 444 computed::angle::Angle::radians64(self) 445 } 446 } 447 448 impl ToRadians for SpecifiedAngle { 449 #[inline] 450 fn radians64(&self) -> f64 { 451 computed::angle::Angle::from_degrees(self.degrees()).radians64() 452 } 453 } 454 455 impl<Angle, Number, Length, Integer, LoP> ToMatrix 456 for TransformOperation<Angle, Number, Length, Integer, LoP> 457 where 458 Angle: Zero + ToRadians + Copy, 459 Number: PartialEq + Copy + Into<f32> + Into<f64>, 460 Length: ToAbsoluteLength, 461 LoP: Zero + ToAbsoluteLength + ZeroNoPercent, 462 { 463 #[inline] 464 fn is_3d(&self) -> bool { 465 use self::TransformOperation::*; 466 match *self { 467 Translate3D(..) | TranslateZ(..) | Rotate3D(..) | RotateX(..) | RotateY(..) 468 | RotateZ(..) | Scale3D(..) | ScaleZ(..) | Perspective(..) | Matrix3D(..) => true, 469 _ => false, 470 } 471 } 472 473 /// If |reference_box| is None, we will drop the percent part from translate because 474 /// we cannot resolve it without the layout info, for computed TransformOperation. 475 /// However, for specified TransformOperation, we will return Err(()) if there is any relative 476 /// lengths because the only caller, DOMMatrix, doesn't accept relative lengths. 477 #[inline] 478 fn to_3d_matrix( 479 &self, 480 reference_box: Option<&Rect<ComputedLength>>, 481 ) -> Result<Transform3D<f64>, ()> { 482 use self::TransformOperation::*; 483 484 let reference_width = reference_box.map(|v| v.size.width); 485 let reference_height = reference_box.map(|v| v.size.height); 486 let matrix = match *self { 487 Rotate3D(ax, ay, az, theta) => { 488 let theta = theta.radians64(); 489 let (ax, ay, az, theta) = 490 get_normalized_vector_and_angle(ax.into(), ay.into(), az.into(), theta); 491 Transform3D::rotation( 492 ax as f64, 493 ay as f64, 494 az as f64, 495 euclid::Angle::radians(theta), 496 ) 497 }, 498 RotateX(theta) => { 499 let theta = euclid::Angle::radians(theta.radians64()); 500 Transform3D::rotation(1., 0., 0., theta) 501 }, 502 RotateY(theta) => { 503 let theta = euclid::Angle::radians(theta.radians64()); 504 Transform3D::rotation(0., 1., 0., theta) 505 }, 506 RotateZ(theta) | Rotate(theta) => { 507 let theta = euclid::Angle::radians(theta.radians64()); 508 Transform3D::rotation(0., 0., 1., theta) 509 }, 510 Perspective(ref p) => { 511 let px = match p { 512 PerspectiveFunction::None => f32::INFINITY, 513 PerspectiveFunction::Length(ref p) => p.to_pixel_length(None)?, 514 }; 515 create_perspective_matrix(px).cast() 516 }, 517 Scale3D(sx, sy, sz) => Transform3D::scale(sx.into(), sy.into(), sz.into()), 518 Scale(sx, sy) => Transform3D::scale(sx.into(), sy.into(), 1.), 519 ScaleX(s) => Transform3D::scale(s.into(), 1., 1.), 520 ScaleY(s) => Transform3D::scale(1., s.into(), 1.), 521 ScaleZ(s) => Transform3D::scale(1., 1., s.into()), 522 Translate3D(ref tx, ref ty, ref tz) => { 523 let tx = tx.to_pixel_length(reference_width)? as f64; 524 let ty = ty.to_pixel_length(reference_height)? as f64; 525 Transform3D::translation(tx, ty, tz.to_pixel_length(None)? as f64) 526 }, 527 Translate(ref tx, ref ty) => { 528 let tx = tx.to_pixel_length(reference_width)? as f64; 529 let ty = ty.to_pixel_length(reference_height)? as f64; 530 Transform3D::translation(tx, ty, 0.) 531 }, 532 TranslateX(ref t) => { 533 let t = t.to_pixel_length(reference_width)? as f64; 534 Transform3D::translation(t, 0., 0.) 535 }, 536 TranslateY(ref t) => { 537 let t = t.to_pixel_length(reference_height)? as f64; 538 Transform3D::translation(0., t, 0.) 539 }, 540 TranslateZ(ref z) => Transform3D::translation(0., 0., z.to_pixel_length(None)? as f64), 541 Skew(theta_x, theta_y) => Transform3D::skew( 542 euclid::Angle::radians(theta_x.radians64()), 543 euclid::Angle::radians(theta_y.radians64()), 544 ), 545 SkewX(theta) => Transform3D::skew( 546 euclid::Angle::radians(theta.radians64()), 547 euclid::Angle::radians(0.), 548 ), 549 SkewY(theta) => Transform3D::skew( 550 euclid::Angle::radians(0.), 551 euclid::Angle::radians(theta.radians64()), 552 ), 553 Matrix3D(m) => m.into(), 554 Matrix(m) => m.into(), 555 InterpolateMatrix { .. } | AccumulateMatrix { .. } => { 556 // TODO: Convert InterpolateMatrix/AccumulateMatrix into a valid Transform3D by 557 // the reference box and do interpolation on these two Transform3D matrices. 558 // Both Gecko and Servo don't support this for computing distance, and Servo 559 // doesn't support animations on InterpolateMatrix/AccumulateMatrix, so 560 // return an identity matrix. 561 // Note: DOMMatrix doesn't go into this arm. 562 Transform3D::identity() 563 }, 564 }; 565 Ok(matrix) 566 } 567 } 568 569 impl<T> Transform<T> { 570 /// `none` 571 pub fn none() -> Self { 572 Transform(Default::default()) 573 } 574 } 575 576 impl<T: ToMatrix> Transform<T> { 577 /// Return the equivalent 3d matrix of this transform list. 578 /// 579 /// We return a pair: the first one is the transform matrix, and the second one 580 /// indicates if there is any 3d transform function in this transform list. 581 #[cfg_attr(rustfmt, rustfmt_skip)] 582 pub fn to_transform_3d_matrix( 583 &self, 584 reference_box: Option<&Rect<ComputedLength>> 585 ) -> Result<(Transform3D<CSSFloat>, bool), ()> { 586 Self::components_to_transform_3d_matrix(&self.0, reference_box) 587 } 588 589 /// Converts a series of components to a 3d matrix. 590 #[cfg_attr(rustfmt, rustfmt_skip)] 591 pub fn components_to_transform_3d_matrix( 592 ops: &[T], 593 reference_box: Option<&Rect<ComputedLength>>, 594 ) -> Result<(Transform3D<CSSFloat>, bool), ()> { 595 let cast_3d_transform = |m: Transform3D<f64>| -> Transform3D<CSSFloat> { 596 use std::{f32, f64}; 597 let cast = |v: f64| v.min(f32::MAX as f64).max(f32::MIN as f64) as f32; 598 Transform3D::new( 599 cast(m.m11), cast(m.m12), cast(m.m13), cast(m.m14), 600 cast(m.m21), cast(m.m22), cast(m.m23), cast(m.m24), 601 cast(m.m31), cast(m.m32), cast(m.m33), cast(m.m34), 602 cast(m.m41), cast(m.m42), cast(m.m43), cast(m.m44), 603 ) 604 }; 605 606 let (m, is_3d) = Self::components_to_transform_3d_matrix_f64(ops, reference_box)?; 607 Ok((cast_3d_transform(m), is_3d)) 608 } 609 610 /// Same as Transform::to_transform_3d_matrix but a f64 version. 611 pub fn to_transform_3d_matrix_f64( 612 &self, 613 reference_box: Option<&Rect<ComputedLength>>, 614 ) -> Result<(Transform3D<f64>, bool), ()> { 615 Self::components_to_transform_3d_matrix_f64(&self.0, reference_box) 616 } 617 618 /// Same as Transform::components_to_transform_3d_matrix but a f64 version. 619 fn components_to_transform_3d_matrix_f64( 620 ops: &[T], 621 reference_box: Option<&Rect<ComputedLength>>, 622 ) -> Result<(Transform3D<f64>, bool), ()> { 623 // We intentionally use Transform3D<f64> during computation to avoid 624 // error propagation because using f32 to compute triangle functions 625 // (e.g. in rotation()) is not accurate enough. In Gecko, we also use 626 // "double" to compute the triangle functions. Therefore, let's use 627 // Transform3D<f64> during matrix computation and cast it into f32 in 628 // the end. 629 let mut transform = Transform3D::<f64>::identity(); 630 let mut contain_3d = false; 631 632 for operation in ops { 633 let matrix = operation.to_3d_matrix(reference_box)?; 634 contain_3d = contain_3d || operation.is_3d(); 635 transform = matrix.then(&transform); 636 } 637 638 Ok((transform, contain_3d)) 639 } 640 } 641 642 /// Return the transform matrix from a perspective length. 643 #[inline] 644 pub fn create_perspective_matrix(d: CSSFloat) -> Transform3D<CSSFloat> { 645 if d.is_finite() { 646 Transform3D::perspective(d.max(1.)) 647 } else { 648 Transform3D::identity() 649 } 650 } 651 652 /// Return the normalized direction vector and its angle for Rotate3D. 653 pub fn get_normalized_vector_and_angle<T: Zero>( 654 x: CSSFloat, 655 y: CSSFloat, 656 z: CSSFloat, 657 angle: T, 658 ) -> (CSSFloat, CSSFloat, CSSFloat, T) { 659 use crate::values::computed::transform::DirectionVector; 660 use euclid::approxeq::ApproxEq; 661 let vector = DirectionVector::new(x, y, z); 662 if vector.square_length().approx_eq(&f32::zero()) { 663 // https://www.w3.org/TR/css-transforms-1/#funcdef-rotate3d 664 // A direction vector that cannot be normalized, such as [0, 0, 0], will cause the 665 // rotation to not be applied, so we use identity matrix (i.e. rotate3d(0, 0, 1, 0)). 666 (0., 0., 1., T::zero()) 667 } else { 668 let vector = vector.robust_normalize(); 669 (vector.x, vector.y, vector.z, angle) 670 } 671 } 672 673 #[derive( 674 Clone, 675 Copy, 676 Debug, 677 Deserialize, 678 MallocSizeOf, 679 PartialEq, 680 Serialize, 681 SpecifiedValueInfo, 682 ToAnimatedValue, 683 ToAnimatedZero, 684 ToComputedValue, 685 ToResolvedValue, 686 ToShmem, 687 ToTyped, 688 )] 689 #[repr(C, u8)] 690 /// A value of the `Rotate` property 691 /// 692 /// <https://drafts.csswg.org/css-transforms-2/#individual-transforms> 693 pub enum GenericRotate<Number, Angle> { 694 /// 'none' 695 None, 696 /// '<angle>' 697 Rotate(Angle), 698 /// '<number>{3} <angle>' 699 Rotate3D(Number, Number, Number, Angle), 700 } 701 702 pub use self::GenericRotate as Rotate; 703 704 /// A trait to check if the current 3D vector is parallel to the DirectionVector. 705 /// This is especially for serialization on Rotate. 706 pub trait IsParallelTo { 707 /// Returns true if this is parallel to the vector. 708 fn is_parallel_to(&self, vector: &computed::transform::DirectionVector) -> bool; 709 } 710 711 impl<Number, Angle> ToCss for Rotate<Number, Angle> 712 where 713 Number: Copy + PartialOrd + ToCss + Zero, 714 Angle: Copy + Neg<Output = Angle> + ToCss + Zero, 715 (Number, Number, Number): IsParallelTo, 716 { 717 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 718 where 719 W: fmt::Write, 720 { 721 use crate::values::computed::transform::DirectionVector; 722 match *self { 723 Rotate::None => dest.write_str("none"), 724 Rotate::Rotate(ref angle) => angle.to_css(dest), 725 Rotate::Rotate3D(x, y, z, angle) => { 726 // If the axis is parallel with the x or y axes, it must serialize as the 727 // appropriate keyword. If a rotation about the z axis (that is, in 2D) is 728 // specified, the property must serialize as just an <angle>. 729 // 730 // Note that if the axis is parallel to x/y/z but pointing in the opposite 731 // direction, we need to negate the angle to maintain the correct meaning. 732 // 733 // https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization 734 let v = (x, y, z); 735 let (axis, angle) = if x.is_zero() && y.is_zero() && z.is_zero() { 736 // The zero length vector is parallel to every other vector, so 737 // is_parallel_to() returns true for it. However, it is definitely different 738 // from x axis, y axis, or z axis, and it's meaningless to perform a rotation 739 // using that direction vector. So we *have* to serialize it using that same 740 // vector - we can't simplify to some theoretically parallel axis-aligned 741 // vector. 742 (None, angle) 743 } else if v.is_parallel_to(&DirectionVector::new(1., 0., 0.)) { 744 ( 745 Some("x "), 746 if v.0 < Number::zero() { -angle } else { angle }, 747 ) 748 } else if v.is_parallel_to(&DirectionVector::new(0., 1., 0.)) { 749 ( 750 Some("y "), 751 if v.1 < Number::zero() { -angle } else { angle }, 752 ) 753 } else if v.is_parallel_to(&DirectionVector::new(0., 0., 1.)) { 754 // When we're parallel to the z-axis, we can just serialize the angle. 755 let angle = if v.2 < Number::zero() { -angle } else { angle }; 756 return angle.to_css(dest); 757 } else { 758 (None, angle) 759 }; 760 match axis { 761 Some(a) => dest.write_str(a)?, 762 None => { 763 x.to_css(dest)?; 764 dest.write_char(' ')?; 765 y.to_css(dest)?; 766 dest.write_char(' ')?; 767 z.to_css(dest)?; 768 dest.write_char(' ')?; 769 }, 770 } 771 angle.to_css(dest) 772 }, 773 } 774 } 775 } 776 777 #[derive( 778 Clone, 779 Copy, 780 Debug, 781 Deserialize, 782 MallocSizeOf, 783 PartialEq, 784 Serialize, 785 SpecifiedValueInfo, 786 ToAnimatedValue, 787 ToAnimatedZero, 788 ToComputedValue, 789 ToResolvedValue, 790 ToShmem, 791 ToTyped, 792 )] 793 #[repr(C, u8)] 794 /// A value of the `Scale` property 795 /// 796 /// <https://drafts.csswg.org/css-transforms-2/#individual-transforms> 797 pub enum GenericScale<Number> { 798 /// 'none' 799 None, 800 /// '<number>{1,3}' 801 Scale(Number, Number, Number), 802 } 803 804 pub use self::GenericScale as Scale; 805 806 impl<Number> ToCss for Scale<Number> 807 where 808 Number: ToCss + PartialEq + Copy, 809 f32: From<Number>, 810 { 811 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 812 where 813 W: fmt::Write, 814 f32: From<Number>, 815 { 816 match *self { 817 Scale::None => dest.write_str("none"), 818 Scale::Scale(ref x, ref y, ref z) => { 819 x.to_css(dest)?; 820 821 let is_3d = f32::from(*z) != 1.0; 822 if is_3d || x != y { 823 dest.write_char(' ')?; 824 y.to_css(dest)?; 825 } 826 827 if is_3d { 828 dest.write_char(' ')?; 829 z.to_css(dest)?; 830 } 831 Ok(()) 832 }, 833 } 834 } 835 } 836 837 #[inline] 838 fn y_axis_and_z_axis_are_zero<LengthPercentage: Zero + ZeroNoPercent, Length: Zero>( 839 _: &LengthPercentage, 840 y: &LengthPercentage, 841 z: &Length, 842 ) -> bool { 843 y.is_zero_no_percent() && z.is_zero() 844 } 845 846 #[derive( 847 Clone, 848 Debug, 849 Deserialize, 850 MallocSizeOf, 851 PartialEq, 852 Serialize, 853 SpecifiedValueInfo, 854 ToAnimatedValue, 855 ToAnimatedZero, 856 ToComputedValue, 857 ToCss, 858 ToResolvedValue, 859 ToShmem, 860 ToTyped, 861 )] 862 #[repr(C, u8)] 863 /// A value of the `translate` property 864 /// 865 /// https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization: 866 /// 867 /// If a 2d translation is specified, the property must serialize with only one 868 /// or two values (per usual, if the second value is 0px, the default, it must 869 /// be omitted when serializing; however if 0% is the second value, it is included). 870 /// 871 /// If a 3d translation is specified and the value can be expressed as 2d, we treat as 2d and 872 /// serialize accoringly. Otherwise, we serialize all three values. 873 /// https://github.com/w3c/csswg-drafts/issues/3305 874 /// 875 /// <https://drafts.csswg.org/css-transforms-2/#individual-transforms> 876 pub enum GenericTranslate<LengthPercentage, Length> 877 where 878 LengthPercentage: Zero + ZeroNoPercent, 879 Length: Zero, 880 { 881 /// 'none' 882 None, 883 /// <length-percentage> [ <length-percentage> <length>? ]? 884 Translate( 885 LengthPercentage, 886 #[css(contextual_skip_if = "y_axis_and_z_axis_are_zero")] LengthPercentage, 887 #[css(skip_if = "Zero::is_zero")] Length, 888 ), 889 } 890 891 pub use self::GenericTranslate as Translate; 892 893 #[allow(missing_docs)] 894 #[derive( 895 Clone, 896 Copy, 897 Debug, 898 MallocSizeOf, 899 Parse, 900 PartialEq, 901 SpecifiedValueInfo, 902 ToComputedValue, 903 ToCss, 904 ToResolvedValue, 905 ToShmem, 906 ToTyped, 907 )] 908 #[repr(u8)] 909 pub enum TransformStyle { 910 Flat, 911 #[css(keyword = "preserve-3d")] 912 Preserve3d, 913 }