tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

transform.rs (70133B)


      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 //! Animated types for transform.
      6 // There are still some implementation on Matrix3D in animated_properties.mako.rs
      7 // because they still need mako to generate the code.
      8 
      9 use super::animate_multiplicative_factor;
     10 use super::{Animate, Procedure, ToAnimatedZero};
     11 use crate::derives::*;
     12 use crate::values::computed::transform::Rotate as ComputedRotate;
     13 use crate::values::computed::transform::Scale as ComputedScale;
     14 use crate::values::computed::transform::Transform as ComputedTransform;
     15 use crate::values::computed::transform::TransformOperation as ComputedTransformOperation;
     16 use crate::values::computed::transform::Translate as ComputedTranslate;
     17 use crate::values::computed::transform::{DirectionVector, Matrix, Matrix3D};
     18 use crate::values::computed::Angle;
     19 use crate::values::computed::{Length, LengthPercentage};
     20 use crate::values::computed::{Number, Percentage};
     21 use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
     22 use crate::values::generics::transform::{self, Transform, TransformOperation};
     23 use crate::values::generics::transform::{Rotate, Scale, Translate};
     24 use crate::values::CSSFloat;
     25 use crate::Zero;
     26 use std::cmp;
     27 use std::ops::Add;
     28 
     29 // ------------------------------------
     30 // Animations for Matrix/Matrix3D.
     31 // ------------------------------------
     32 /// A 2d matrix for interpolation.
     33 #[derive(Clone, ComputeSquaredDistance, Copy, Debug)]
     34 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
     35 #[allow(missing_docs)]
     36 // FIXME: We use custom derive for ComputeSquaredDistance. However, If possible, we should convert
     37 // the InnerMatrix2D into types with physical meaning. This custom derive computes the squared
     38 // distance from each matrix item, and this makes the result different from that in Gecko if we
     39 // have skew factor in the Matrix3D.
     40 pub struct InnerMatrix2D {
     41    pub m11: CSSFloat,
     42    pub m12: CSSFloat,
     43    pub m21: CSSFloat,
     44    pub m22: CSSFloat,
     45 }
     46 
     47 impl Animate for InnerMatrix2D {
     48    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
     49        Ok(InnerMatrix2D {
     50            m11: animate_multiplicative_factor(self.m11, other.m11, procedure)?,
     51            m12: self.m12.animate(&other.m12, procedure)?,
     52            m21: self.m21.animate(&other.m21, procedure)?,
     53            m22: animate_multiplicative_factor(self.m22, other.m22, procedure)?,
     54        })
     55    }
     56 }
     57 
     58 /// A 2d translation function.
     59 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
     60 #[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug)]
     61 pub struct Translate2D(f32, f32);
     62 
     63 /// A 2d scale function.
     64 #[derive(Clone, ComputeSquaredDistance, Copy, Debug)]
     65 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
     66 pub struct Scale2D(f32, f32);
     67 
     68 impl Animate for Scale2D {
     69    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
     70        Ok(Scale2D(
     71            animate_multiplicative_factor(self.0, other.0, procedure)?,
     72            animate_multiplicative_factor(self.1, other.1, procedure)?,
     73        ))
     74    }
     75 }
     76 
     77 /// A decomposed 2d matrix.
     78 #[derive(Clone, Copy, Debug)]
     79 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
     80 pub struct MatrixDecomposed2D {
     81    /// The translation function.
     82    pub translate: Translate2D,
     83    /// The scale function.
     84    pub scale: Scale2D,
     85    /// The rotation angle.
     86    pub angle: f32,
     87    /// The inner matrix.
     88    pub matrix: InnerMatrix2D,
     89 }
     90 
     91 impl Animate for MatrixDecomposed2D {
     92    /// <https://drafts.csswg.org/css-transforms/#interpolation-of-decomposed-2d-matrix-values>
     93    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
     94        // If x-axis of one is flipped, and y-axis of the other,
     95        // convert to an unflipped rotation.
     96        let mut scale = self.scale;
     97        let mut angle = self.angle;
     98        let mut other_angle = other.angle;
     99        if (scale.0 < 0.0 && other.scale.1 < 0.0) || (scale.1 < 0.0 && other.scale.0 < 0.0) {
    100            scale.0 = -scale.0;
    101            scale.1 = -scale.1;
    102            angle += if angle < 0.0 { 180. } else { -180. };
    103        }
    104 
    105        // Don't rotate the long way around.
    106        if angle == 0.0 {
    107            angle = 360.
    108        }
    109        if other_angle == 0.0 {
    110            other_angle = 360.
    111        }
    112 
    113        if (angle - other_angle).abs() > 180. {
    114            if angle > other_angle {
    115                angle -= 360.
    116            } else {
    117                other_angle -= 360.
    118            }
    119        }
    120 
    121        // Interpolate all values.
    122        let translate = self.translate.animate(&other.translate, procedure)?;
    123        let scale = scale.animate(&other.scale, procedure)?;
    124        let angle = angle.animate(&other_angle, procedure)?;
    125        let matrix = self.matrix.animate(&other.matrix, procedure)?;
    126 
    127        Ok(MatrixDecomposed2D {
    128            translate,
    129            scale,
    130            angle,
    131            matrix,
    132        })
    133    }
    134 }
    135 
    136 impl ComputeSquaredDistance for MatrixDecomposed2D {
    137    #[inline]
    138    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
    139        // Use Radian to compute the distance.
    140        const RAD_PER_DEG: f64 = std::f64::consts::PI / 180.0;
    141        let angle1 = self.angle as f64 * RAD_PER_DEG;
    142        let angle2 = other.angle as f64 * RAD_PER_DEG;
    143        Ok(self.translate.compute_squared_distance(&other.translate)?
    144            + self.scale.compute_squared_distance(&other.scale)?
    145            + angle1.compute_squared_distance(&angle2)?
    146            + self.matrix.compute_squared_distance(&other.matrix)?)
    147    }
    148 }
    149 
    150 impl From<Matrix3D> for MatrixDecomposed2D {
    151    /// Decompose a 2D matrix.
    152    /// <https://drafts.csswg.org/css-transforms/#decomposing-a-2d-matrix>
    153    fn from(matrix: Matrix3D) -> MatrixDecomposed2D {
    154        let mut row0x = matrix.m11;
    155        let mut row0y = matrix.m12;
    156        let mut row1x = matrix.m21;
    157        let mut row1y = matrix.m22;
    158 
    159        let translate = Translate2D(matrix.m41, matrix.m42);
    160        let mut scale = Scale2D(
    161            (row0x * row0x + row0y * row0y).sqrt(),
    162            (row1x * row1x + row1y * row1y).sqrt(),
    163        );
    164 
    165        // If determinant is negative, one axis was flipped.
    166        let determinant = row0x * row1y - row0y * row1x;
    167        if determinant < 0. {
    168            if row0x < row1y {
    169                scale.0 = -scale.0;
    170            } else {
    171                scale.1 = -scale.1;
    172            }
    173        }
    174 
    175        // Renormalize matrix to remove scale.
    176        if scale.0 != 0.0 {
    177            row0x *= 1. / scale.0;
    178            row0y *= 1. / scale.0;
    179        }
    180        if scale.1 != 0.0 {
    181            row1x *= 1. / scale.1;
    182            row1y *= 1. / scale.1;
    183        }
    184 
    185        // Compute rotation and renormalize matrix.
    186        let mut angle = row0y.atan2(row0x);
    187        if angle != 0.0 {
    188            let sn = -row0y;
    189            let cs = row0x;
    190            let m11 = row0x;
    191            let m12 = row0y;
    192            let m21 = row1x;
    193            let m22 = row1y;
    194            row0x = cs * m11 + sn * m21;
    195            row0y = cs * m12 + sn * m22;
    196            row1x = -sn * m11 + cs * m21;
    197            row1y = -sn * m12 + cs * m22;
    198        }
    199 
    200        let m = InnerMatrix2D {
    201            m11: row0x,
    202            m12: row0y,
    203            m21: row1x,
    204            m22: row1y,
    205        };
    206 
    207        // Convert into degrees because our rotation functions expect it.
    208        angle = angle.to_degrees();
    209        MatrixDecomposed2D {
    210            translate: translate,
    211            scale: scale,
    212            angle: angle,
    213            matrix: m,
    214        }
    215    }
    216 }
    217 
    218 impl From<MatrixDecomposed2D> for Matrix3D {
    219    /// Recompose a 2D matrix.
    220    /// <https://drafts.csswg.org/css-transforms/#recomposing-to-a-2d-matrix>
    221    fn from(decomposed: MatrixDecomposed2D) -> Matrix3D {
    222        let mut computed_matrix = Matrix3D::identity();
    223        computed_matrix.m11 = decomposed.matrix.m11;
    224        computed_matrix.m12 = decomposed.matrix.m12;
    225        computed_matrix.m21 = decomposed.matrix.m21;
    226        computed_matrix.m22 = decomposed.matrix.m22;
    227 
    228        // Translate matrix.
    229        computed_matrix.m41 = decomposed.translate.0;
    230        computed_matrix.m42 = decomposed.translate.1;
    231 
    232        // Rotate matrix.
    233        let angle = decomposed.angle.to_radians();
    234        let cos_angle = angle.cos();
    235        let sin_angle = angle.sin();
    236 
    237        let mut rotate_matrix = Matrix3D::identity();
    238        rotate_matrix.m11 = cos_angle;
    239        rotate_matrix.m12 = sin_angle;
    240        rotate_matrix.m21 = -sin_angle;
    241        rotate_matrix.m22 = cos_angle;
    242 
    243        // Multiplication of computed_matrix and rotate_matrix
    244        computed_matrix = rotate_matrix.multiply(&computed_matrix);
    245 
    246        // Scale matrix.
    247        computed_matrix.m11 *= decomposed.scale.0;
    248        computed_matrix.m12 *= decomposed.scale.0;
    249        computed_matrix.m21 *= decomposed.scale.1;
    250        computed_matrix.m22 *= decomposed.scale.1;
    251        computed_matrix
    252    }
    253 }
    254 
    255 impl Animate for Matrix {
    256    #[cfg(feature = "servo")]
    257    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
    258        let this = Matrix3D::from(*self);
    259        let other = Matrix3D::from(*other);
    260        let this = MatrixDecomposed2D::from(this);
    261        let other = MatrixDecomposed2D::from(other);
    262        Matrix3D::from(this.animate(&other, procedure)?).into_2d()
    263    }
    264 
    265    #[cfg(feature = "gecko")]
    266    // Gecko doesn't exactly follow the spec here; we use a different procedure
    267    // to match it
    268    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
    269        let this = Matrix3D::from(*self);
    270        let other = Matrix3D::from(*other);
    271        let from = decompose_2d_matrix(&this)?;
    272        let to = decompose_2d_matrix(&other)?;
    273        Matrix3D::from(from.animate(&to, procedure)?).into_2d()
    274    }
    275 }
    276 
    277 /// A 3d translation.
    278 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
    279 #[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug)]
    280 pub struct Translate3D(pub f32, pub f32, pub f32);
    281 
    282 /// A 3d scale function.
    283 #[derive(Clone, ComputeSquaredDistance, Copy, Debug)]
    284 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
    285 pub struct Scale3D(pub f32, pub f32, pub f32);
    286 
    287 impl Scale3D {
    288    /// Negate self.
    289    fn negate(&mut self) {
    290        self.0 *= -1.0;
    291        self.1 *= -1.0;
    292        self.2 *= -1.0;
    293    }
    294 }
    295 
    296 impl Animate for Scale3D {
    297    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
    298        Ok(Scale3D(
    299            animate_multiplicative_factor(self.0, other.0, procedure)?,
    300            animate_multiplicative_factor(self.1, other.1, procedure)?,
    301            animate_multiplicative_factor(self.2, other.2, procedure)?,
    302        ))
    303    }
    304 }
    305 
    306 /// A 3d skew function.
    307 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
    308 #[derive(Animate, Clone, Copy, Debug)]
    309 pub struct Skew(f32, f32, f32);
    310 
    311 impl ComputeSquaredDistance for Skew {
    312    // We have to use atan() to convert the skew factors into skew angles, so implement
    313    // ComputeSquaredDistance manually.
    314    #[inline]
    315    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
    316        Ok(self.0.atan().compute_squared_distance(&other.0.atan())?
    317            + self.1.atan().compute_squared_distance(&other.1.atan())?
    318            + self.2.atan().compute_squared_distance(&other.2.atan())?)
    319    }
    320 }
    321 
    322 /// A 3d perspective transformation.
    323 #[derive(Clone, ComputeSquaredDistance, Copy, Debug)]
    324 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
    325 pub struct Perspective(pub f32, pub f32, pub f32, pub f32);
    326 
    327 impl Animate for Perspective {
    328    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
    329        Ok(Perspective(
    330            self.0.animate(&other.0, procedure)?,
    331            self.1.animate(&other.1, procedure)?,
    332            self.2.animate(&other.2, procedure)?,
    333            animate_multiplicative_factor(self.3, other.3, procedure)?,
    334        ))
    335    }
    336 }
    337 
    338 /// A quaternion used to represent a rotation.
    339 #[derive(Clone, Copy, Debug)]
    340 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
    341 pub struct Quaternion(f64, f64, f64, f64);
    342 
    343 impl Quaternion {
    344    /// Return a quaternion from a unit direction vector and angle (unit: radian).
    345    #[inline]
    346    fn from_direction_and_angle(vector: &DirectionVector, angle: f64) -> Self {
    347        debug_assert!(
    348            (vector.length() - 1.).abs() < 0.0001,
    349            "Only accept an unit direction vector to create a quaternion"
    350        );
    351 
    352        // Quaternions between the range [360, 720] will treated as rotations at the other
    353        // direction: [-360, 0]. And quaternions between the range [720*k, 720*(k+1)] will be
    354        // treated as rotations [0, 720]. So it does not make sense to use quaternions to rotate
    355        // the element more than ±360deg. Therefore, we have to make sure its range is (-360, 360).
    356        let half_angle = angle
    357            .abs()
    358            .rem_euclid(std::f64::consts::TAU)
    359            .copysign(angle)
    360            / 2.;
    361 
    362        // Reference:
    363        // https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation
    364        //
    365        // if the direction axis is (x, y, z) = xi + yj + zk,
    366        // and the angle is |theta|, this formula can be done using
    367        // an extension of Euler's formula:
    368        //   q = cos(theta/2) + (xi + yj + zk)(sin(theta/2))
    369        //     = cos(theta/2) +
    370        //       x*sin(theta/2)i + y*sin(theta/2)j + z*sin(theta/2)k
    371        Quaternion(
    372            vector.x as f64 * half_angle.sin(),
    373            vector.y as f64 * half_angle.sin(),
    374            vector.z as f64 * half_angle.sin(),
    375            half_angle.cos(),
    376        )
    377    }
    378 
    379    /// Calculate the dot product.
    380    #[inline]
    381    fn dot(&self, other: &Self) -> f64 {
    382        self.0 * other.0 + self.1 * other.1 + self.2 * other.2 + self.3 * other.3
    383    }
    384 
    385    /// Return the scaled quaternion by a factor.
    386    #[inline]
    387    fn scale(&self, factor: f64) -> Self {
    388        Quaternion(
    389            self.0 * factor,
    390            self.1 * factor,
    391            self.2 * factor,
    392            self.3 * factor,
    393        )
    394    }
    395 }
    396 
    397 impl Add for Quaternion {
    398    type Output = Self;
    399 
    400    fn add(self, other: Self) -> Self {
    401        Self(
    402            self.0 + other.0,
    403            self.1 + other.1,
    404            self.2 + other.2,
    405            self.3 + other.3,
    406        )
    407    }
    408 }
    409 
    410 impl Animate for Quaternion {
    411    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
    412        let (this_weight, other_weight) = procedure.weights();
    413        debug_assert!(
    414            // Doule EPSILON since both this_weight and other_weght have calculation errors
    415            // which are approximately equal to EPSILON.
    416            (this_weight + other_weight - 1.0f64).abs() <= f64::EPSILON * 2.0
    417                || other_weight == 1.0f64
    418                || other_weight == 0.0f64,
    419            "animate should only be used for interpolating or accumulating transforms"
    420        );
    421 
    422        // We take a specialized code path for accumulation (where other_weight
    423        // is 1).
    424        if let Procedure::Accumulate { .. } = procedure {
    425            debug_assert_eq!(other_weight, 1.0);
    426            if this_weight == 0.0 {
    427                return Ok(*other);
    428            }
    429 
    430            let clamped_w = self.3.min(1.0).max(-1.0);
    431 
    432            // Determine the scale factor.
    433            let mut theta = clamped_w.acos();
    434            let mut scale = if theta == 0.0 { 0.0 } else { 1.0 / theta.sin() };
    435            theta *= this_weight;
    436            scale *= theta.sin();
    437 
    438            // Scale the self matrix by this_weight.
    439            let mut scaled_self = *self;
    440            scaled_self.0 *= scale;
    441            scaled_self.1 *= scale;
    442            scaled_self.2 *= scale;
    443            scaled_self.3 = theta.cos();
    444 
    445            // Multiply scaled-self by other.
    446            let a = &scaled_self;
    447            let b = other;
    448            return Ok(Quaternion(
    449                a.3 * b.0 + a.0 * b.3 + a.1 * b.2 - a.2 * b.1,
    450                a.3 * b.1 - a.0 * b.2 + a.1 * b.3 + a.2 * b.0,
    451                a.3 * b.2 + a.0 * b.1 - a.1 * b.0 + a.2 * b.3,
    452                a.3 * b.3 - a.0 * b.0 - a.1 * b.1 - a.2 * b.2,
    453            ));
    454        }
    455 
    456        // https://drafts.csswg.org/css-transforms-2/#interpolation-of-decomposed-3d-matrix-values
    457        //
    458        // Dot product, clamped between -1 and 1.
    459        let cos_half_theta =
    460            (self.0 * other.0 + self.1 * other.1 + self.2 * other.2 + self.3 * other.3)
    461                .min(1.0)
    462                .max(-1.0);
    463 
    464        if cos_half_theta.abs() == 1.0 {
    465            return Ok(*self);
    466        }
    467 
    468        let half_theta = cos_half_theta.acos();
    469        let sin_half_theta = (1.0 - cos_half_theta * cos_half_theta).sqrt();
    470 
    471        let right_weight = (other_weight * half_theta).sin() / sin_half_theta;
    472        // The spec would like to use
    473        // "(other_weight * half_theta).cos() - cos_half_theta * right_weight". However, this
    474        // formula may produce some precision issues of floating-point number calculation, e.g.
    475        // when the progress is 100% (i.e. |other_weight| is 1), the |left_weight| may not be
    476        // perfectly equal to 0. It could be something like -2.22e-16, which is approximately equal
    477        // to zero, in the test. And after we recompose the Matrix3D, these approximated zeros
    478        // make us failed to treat this Matrix3D as a Matrix2D, when serializating it.
    479        //
    480        // Therefore, we use another formula to calculate |left_weight| here. Blink and WebKit also
    481        // use this formula, which is defined in:
    482        // https://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/index.htm
    483        // https://github.com/w3c/csswg-drafts/issues/9338
    484        let left_weight = (this_weight * half_theta).sin() / sin_half_theta;
    485 
    486        Ok(self.scale(left_weight) + other.scale(right_weight))
    487    }
    488 }
    489 
    490 impl ComputeSquaredDistance for Quaternion {
    491    #[inline]
    492    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
    493        // Use quaternion vectors to get the angle difference. Both q1 and q2 are unit vectors,
    494        // so we can get their angle difference by:
    495        // cos(theta/2) = (q1 dot q2) / (|q1| * |q2|) = q1 dot q2.
    496        let distance = self.dot(other).max(-1.0).min(1.0).acos() * 2.0;
    497        Ok(SquaredDistance::from_sqrt(distance))
    498    }
    499 }
    500 
    501 /// A decomposed 3d matrix.
    502 #[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug)]
    503 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
    504 pub struct MatrixDecomposed3D {
    505    /// A translation function.
    506    pub translate: Translate3D,
    507    /// A scale function.
    508    pub scale: Scale3D,
    509    /// The skew component of the transformation.
    510    pub skew: Skew,
    511    /// The perspective component of the transformation.
    512    pub perspective: Perspective,
    513    /// The quaternion used to represent the rotation.
    514    pub quaternion: Quaternion,
    515 }
    516 
    517 impl From<MatrixDecomposed3D> for Matrix3D {
    518    /// Recompose a 3D matrix.
    519    /// <https://drafts.csswg.org/css-transforms/#recomposing-to-a-3d-matrix>
    520    fn from(decomposed: MatrixDecomposed3D) -> Matrix3D {
    521        let mut matrix = Matrix3D::identity();
    522 
    523        // Apply perspective
    524        matrix.set_perspective(&decomposed.perspective);
    525 
    526        // Apply translation
    527        matrix.apply_translate(&decomposed.translate);
    528 
    529        // Apply rotation
    530        {
    531            let x = decomposed.quaternion.0;
    532            let y = decomposed.quaternion.1;
    533            let z = decomposed.quaternion.2;
    534            let w = decomposed.quaternion.3;
    535 
    536            // Construct a composite rotation matrix from the quaternion values
    537            // rotationMatrix is a identity 4x4 matrix initially
    538            let mut rotation_matrix = Matrix3D::identity();
    539            rotation_matrix.m11 = 1.0 - 2.0 * (y * y + z * z) as f32;
    540            rotation_matrix.m12 = 2.0 * (x * y + z * w) as f32;
    541            rotation_matrix.m13 = 2.0 * (x * z - y * w) as f32;
    542            rotation_matrix.m21 = 2.0 * (x * y - z * w) as f32;
    543            rotation_matrix.m22 = 1.0 - 2.0 * (x * x + z * z) as f32;
    544            rotation_matrix.m23 = 2.0 * (y * z + x * w) as f32;
    545            rotation_matrix.m31 = 2.0 * (x * z + y * w) as f32;
    546            rotation_matrix.m32 = 2.0 * (y * z - x * w) as f32;
    547            rotation_matrix.m33 = 1.0 - 2.0 * (x * x + y * y) as f32;
    548 
    549            matrix = rotation_matrix.multiply(&matrix);
    550        }
    551 
    552        // Apply skew
    553        {
    554            let mut temp = Matrix3D::identity();
    555            if decomposed.skew.2 != 0.0 {
    556                temp.m32 = decomposed.skew.2;
    557                matrix = temp.multiply(&matrix);
    558                temp.m32 = 0.0;
    559            }
    560 
    561            if decomposed.skew.1 != 0.0 {
    562                temp.m31 = decomposed.skew.1;
    563                matrix = temp.multiply(&matrix);
    564                temp.m31 = 0.0;
    565            }
    566 
    567            if decomposed.skew.0 != 0.0 {
    568                temp.m21 = decomposed.skew.0;
    569                matrix = temp.multiply(&matrix);
    570            }
    571        }
    572 
    573        // Apply scale
    574        matrix.apply_scale(&decomposed.scale);
    575 
    576        matrix
    577    }
    578 }
    579 
    580 /// Decompose a 3D matrix.
    581 /// https://drafts.csswg.org/css-transforms-2/#decomposing-a-3d-matrix
    582 /// http://www.realtimerendering.com/resources/GraphicsGems/gemsii/unmatrix.c
    583 fn decompose_3d_matrix(mut matrix: Matrix3D) -> Result<MatrixDecomposed3D, ()> {
    584    // Combine 2 point.
    585    let combine = |a: [f32; 3], b: [f32; 3], ascl: f32, bscl: f32| {
    586        [
    587            (ascl * a[0]) + (bscl * b[0]),
    588            (ascl * a[1]) + (bscl * b[1]),
    589            (ascl * a[2]) + (bscl * b[2]),
    590        ]
    591    };
    592    // Dot product.
    593    let dot = |a: [f32; 3], b: [f32; 3]| a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
    594    // Cross product.
    595    let cross = |row1: [f32; 3], row2: [f32; 3]| {
    596        [
    597            row1[1] * row2[2] - row1[2] * row2[1],
    598            row1[2] * row2[0] - row1[0] * row2[2],
    599            row1[0] * row2[1] - row1[1] * row2[0],
    600        ]
    601    };
    602 
    603    if matrix.m44 == 0.0 {
    604        return Err(());
    605    }
    606 
    607    let scaling_factor = matrix.m44;
    608 
    609    // Normalize the matrix.
    610    matrix.scale_by_factor(1.0 / scaling_factor);
    611 
    612    // perspective_matrix is used to solve for perspective, but it also provides
    613    // an easy way to test for singularity of the upper 3x3 component.
    614    let mut perspective_matrix = matrix;
    615 
    616    perspective_matrix.m14 = 0.0;
    617    perspective_matrix.m24 = 0.0;
    618    perspective_matrix.m34 = 0.0;
    619    perspective_matrix.m44 = 1.0;
    620 
    621    if perspective_matrix.determinant() == 0.0 {
    622        return Err(());
    623    }
    624 
    625    // First, isolate perspective.
    626    let perspective = if matrix.m14 != 0.0 || matrix.m24 != 0.0 || matrix.m34 != 0.0 {
    627        let right_hand_side: [f32; 4] = [matrix.m14, matrix.m24, matrix.m34, matrix.m44];
    628 
    629        perspective_matrix = perspective_matrix.inverse().unwrap().transpose();
    630        let perspective = perspective_matrix.pre_mul_point4(&right_hand_side);
    631        // NOTE(emilio): Even though the reference algorithm clears the
    632        // fourth column here (matrix.m14..matrix.m44), they're not used below
    633        // so it's not really needed.
    634        Perspective(
    635            perspective[0],
    636            perspective[1],
    637            perspective[2],
    638            perspective[3],
    639        )
    640    } else {
    641        Perspective(0.0, 0.0, 0.0, 1.0)
    642    };
    643 
    644    // Next take care of translation (easy).
    645    let translate = Translate3D(matrix.m41, matrix.m42, matrix.m43);
    646 
    647    // Now get scale and shear. 'row' is a 3 element array of 3 component vectors
    648    let mut row = matrix.get_matrix_3x3_part();
    649 
    650    // Compute X scale factor and normalize first row.
    651    let row0len = (row[0][0] * row[0][0] + row[0][1] * row[0][1] + row[0][2] * row[0][2]).sqrt();
    652    let mut scale = Scale3D(row0len, 0.0, 0.0);
    653    row[0] = [
    654        row[0][0] / row0len,
    655        row[0][1] / row0len,
    656        row[0][2] / row0len,
    657    ];
    658 
    659    // Compute XY shear factor and make 2nd row orthogonal to 1st.
    660    let mut skew = Skew(dot(row[0], row[1]), 0.0, 0.0);
    661    row[1] = combine(row[1], row[0], 1.0, -skew.0);
    662 
    663    // Now, compute Y scale and normalize 2nd row.
    664    let row1len = (row[1][0] * row[1][0] + row[1][1] * row[1][1] + row[1][2] * row[1][2]).sqrt();
    665    scale.1 = row1len;
    666    row[1] = [
    667        row[1][0] / row1len,
    668        row[1][1] / row1len,
    669        row[1][2] / row1len,
    670    ];
    671    skew.0 /= scale.1;
    672 
    673    // Compute XZ and YZ shears, orthogonalize 3rd row
    674    skew.1 = dot(row[0], row[2]);
    675    row[2] = combine(row[2], row[0], 1.0, -skew.1);
    676    skew.2 = dot(row[1], row[2]);
    677    row[2] = combine(row[2], row[1], 1.0, -skew.2);
    678 
    679    // Next, get Z scale and normalize 3rd row.
    680    let row2len = (row[2][0] * row[2][0] + row[2][1] * row[2][1] + row[2][2] * row[2][2]).sqrt();
    681    scale.2 = row2len;
    682    row[2] = [
    683        row[2][0] / row2len,
    684        row[2][1] / row2len,
    685        row[2][2] / row2len,
    686    ];
    687    skew.1 /= scale.2;
    688    skew.2 /= scale.2;
    689 
    690    // At this point, the matrix (in rows) is orthonormal.
    691    // Check for a coordinate system flip.  If the determinant
    692    // is -1, then negate the matrix and the scaling factors.
    693    if dot(row[0], cross(row[1], row[2])) < 0.0 {
    694        scale.negate();
    695        for i in 0..3 {
    696            row[i][0] *= -1.0;
    697            row[i][1] *= -1.0;
    698            row[i][2] *= -1.0;
    699        }
    700    }
    701 
    702    // Now, get the rotations out.
    703    let mut quaternion = Quaternion(
    704        0.5 * ((1.0 + row[0][0] - row[1][1] - row[2][2]).max(0.0) as f64).sqrt(),
    705        0.5 * ((1.0 - row[0][0] + row[1][1] - row[2][2]).max(0.0) as f64).sqrt(),
    706        0.5 * ((1.0 - row[0][0] - row[1][1] + row[2][2]).max(0.0) as f64).sqrt(),
    707        0.5 * ((1.0 + row[0][0] + row[1][1] + row[2][2]).max(0.0) as f64).sqrt(),
    708    );
    709 
    710    if row[2][1] > row[1][2] {
    711        quaternion.0 = -quaternion.0
    712    }
    713    if row[0][2] > row[2][0] {
    714        quaternion.1 = -quaternion.1
    715    }
    716    if row[1][0] > row[0][1] {
    717        quaternion.2 = -quaternion.2
    718    }
    719 
    720    Ok(MatrixDecomposed3D {
    721        translate,
    722        scale,
    723        skew,
    724        perspective,
    725        quaternion,
    726    })
    727 }
    728 
    729 /**
    730 * The relevant section of the transitions specification:
    731 * https://drafts.csswg.org/web-animations-1/#animation-types
    732 * http://dev.w3.org/csswg/css3-transitions/#animation-of-property-types-
    733 * defers all of the details to the 2-D and 3-D transforms specifications.
    734 * For the 2-D transforms specification (all that's relevant for us, right
    735 * now), the relevant section is:
    736 * https://drafts.csswg.org/css-transforms-1/#interpolation-of-transforms
    737 * This, in turn, refers to the unmatrix program in Graphics Gems,
    738 * available from http://graphicsgems.org/ , and in
    739 * particular as the file GraphicsGems/gemsii/unmatrix.c
    740 * in http://graphicsgems.org/AllGems.tar.gz
    741 *
    742 * The unmatrix reference is for general 3-D transform matrices (any of the
    743 * 16 components can have any value).
    744 *
    745 * For CSS 2-D transforms, we have a 2-D matrix with the bottom row constant:
    746 *
    747 * [ A C E ]
    748 * [ B D F ]
    749 * [ 0 0 1 ]
    750 *
    751 * For that case, I believe the algorithm in unmatrix reduces to:
    752 *
    753 *  (1) If A * D - B * C == 0, the matrix is singular.  Fail.
    754 *
    755 *  (2) Set translation components (Tx and Ty) to the translation parts of
    756 *      the matrix (E and F) and then ignore them for the rest of the time.
    757 *      (For us, E and F each actually consist of three constants:  a
    758 *      length, a multiplier for the width, and a multiplier for the
    759 *      height.  This actually requires its own decomposition, but I'll
    760 *      keep that separate.)
    761 *
    762 *  (3) Let the X scale (Sx) be sqrt(A^2 + B^2).  Then divide both A and B
    763 *      by it.
    764 *
    765 *  (4) Let the XY shear (K) be A * C + B * D.  From C, subtract A times
    766 *      the XY shear.  From D, subtract B times the XY shear.
    767 *
    768 *  (5) Let the Y scale (Sy) be sqrt(C^2 + D^2).  Divide C, D, and the XY
    769 *      shear (K) by it.
    770 *
    771 *  (6) At this point, A * D - B * C is either 1 or -1.  If it is -1,
    772 *      negate the XY shear (K), the X scale (Sx), and A, B, C, and D.
    773 *      (Alternatively, we could negate the XY shear (K) and the Y scale
    774 *      (Sy).)
    775 *
    776 *  (7) Let the rotation be R = atan2(B, A).
    777 *
    778 * Then the resulting decomposed transformation is:
    779 *
    780 *   translate(Tx, Ty) rotate(R) skewX(atan(K)) scale(Sx, Sy)
    781 *
    782 * An interesting result of this is that all of the simple transform
    783 * functions (i.e., all functions other than matrix()), in isolation,
    784 * decompose back to themselves except for:
    785 *   'skewY(φ)', which is 'matrix(1, tan(φ), 0, 1, 0, 0)', which decomposes
    786 *   to 'rotate(φ) skewX(φ) scale(sec(φ), cos(φ))' since (ignoring the
    787 *   alternate sign possibilities that would get fixed in step 6):
    788 *     In step 3, the X scale factor is sqrt(1+tan²(φ)) = sqrt(sec²(φ)) =
    789 * sec(φ). Thus, after step 3, A = 1/sec(φ) = cos(φ) and B = tan(φ) / sec(φ) =
    790 * sin(φ). In step 4, the XY shear is sin(φ). Thus, after step 4, C =
    791 * -cos(φ)sin(φ) and D = 1 - sin²(φ) = cos²(φ). Thus, in step 5, the Y scale is
    792 * sqrt(cos²(φ)(sin²(φ) + cos²(φ)) = cos(φ). Thus, after step 5, C = -sin(φ), D
    793 * = cos(φ), and the XY shear is tan(φ). Thus, in step 6, A * D - B * C =
    794 * cos²(φ) + sin²(φ) = 1. In step 7, the rotation is thus φ.
    795 *
    796 *   skew(θ, φ), which is matrix(1, tan(φ), tan(θ), 1, 0, 0), which decomposes
    797 *   to 'rotate(φ) skewX(θ + φ) scale(sec(φ), cos(φ))' since (ignoring
    798 *   the alternate sign possibilities that would get fixed in step 6):
    799 *     In step 3, the X scale factor is sqrt(1+tan²(φ)) = sqrt(sec²(φ)) =
    800 * sec(φ). Thus, after step 3, A = 1/sec(φ) = cos(φ) and B = tan(φ) / sec(φ) =
    801 * sin(φ). In step 4, the XY shear is cos(φ)tan(θ) + sin(φ). Thus, after step 4,
    802 *     C = tan(θ) - cos(φ)(cos(φ)tan(θ) + sin(φ)) = tan(θ)sin²(φ) - cos(φ)sin(φ)
    803 *     D = 1 - sin(φ)(cos(φ)tan(θ) + sin(φ)) = cos²(φ) - sin(φ)cos(φ)tan(θ)
    804 *     Thus, in step 5, the Y scale is sqrt(C² + D²) =
    805 *     sqrt(tan²(θ)(sin⁴(φ) + sin²(φ)cos²(φ)) -
    806 *          2 tan(θ)(sin³(φ)cos(φ) + sin(φ)cos³(φ)) +
    807 *          (sin²(φ)cos²(φ) + cos⁴(φ))) =
    808 *     sqrt(tan²(θ)sin²(φ) - 2 tan(θ)sin(φ)cos(φ) + cos²(φ)) =
    809 *     cos(φ) - tan(θ)sin(φ) (taking the negative of the obvious solution so
    810 *     we avoid flipping in step 6).
    811 *     After step 5, C = -sin(φ) and D = cos(φ), and the XY shear is
    812 *     (cos(φ)tan(θ) + sin(φ)) / (cos(φ) - tan(θ)sin(φ)) =
    813 *     (dividing both numerator and denominator by cos(φ))
    814 *     (tan(θ) + tan(φ)) / (1 - tan(θ)tan(φ)) = tan(θ + φ).
    815 *     (See http://en.wikipedia.org/wiki/List_of_trigonometric_identities .)
    816 *     Thus, in step 6, A * D - B * C = cos²(φ) + sin²(φ) = 1.
    817 *     In step 7, the rotation is thus φ.
    818 *
    819 *     To check this result, we can multiply things back together:
    820 *
    821 *     [ cos(φ) -sin(φ) ] [ 1 tan(θ + φ) ] [ sec(φ)    0   ]
    822 *     [ sin(φ)  cos(φ) ] [ 0      1     ] [   0    cos(φ) ]
    823 *
    824 *     [ cos(φ)      cos(φ)tan(θ + φ) - sin(φ) ] [ sec(φ)    0   ]
    825 *     [ sin(φ)      sin(φ)tan(θ + φ) + cos(φ) ] [   0    cos(φ) ]
    826 *
    827 *     but since tan(θ + φ) = (tan(θ) + tan(φ)) / (1 - tan(θ)tan(φ)),
    828 *     cos(φ)tan(θ + φ) - sin(φ)
    829 *      = cos(φ)(tan(θ) + tan(φ)) - sin(φ) + sin(φ)tan(θ)tan(φ)
    830 *      = cos(φ)tan(θ) + sin(φ) - sin(φ) + sin(φ)tan(θ)tan(φ)
    831 *      = cos(φ)tan(θ) + sin(φ)tan(θ)tan(φ)
    832 *      = tan(θ) (cos(φ) + sin(φ)tan(φ))
    833 *      = tan(θ) sec(φ) (cos²(φ) + sin²(φ))
    834 *      = tan(θ) sec(φ)
    835 *     and
    836 *     sin(φ)tan(θ + φ) + cos(φ)
    837 *      = sin(φ)(tan(θ) + tan(φ)) + cos(φ) - cos(φ)tan(θ)tan(φ)
    838 *      = tan(θ) (sin(φ) - sin(φ)) + sin(φ)tan(φ) + cos(φ)
    839 *      = sec(φ) (sin²(φ) + cos²(φ))
    840 *      = sec(φ)
    841 *     so the above is:
    842 *     [ cos(φ)  tan(θ) sec(φ) ] [ sec(φ)    0   ]
    843 *     [ sin(φ)     sec(φ)     ] [   0    cos(φ) ]
    844 *
    845 *     [    1   tan(θ) ]
    846 *     [ tan(φ)    1   ]
    847 */
    848 
    849 /// Decompose a 2D matrix for Gecko. This implements the above decomposition algorithm.
    850 #[cfg(feature = "gecko")]
    851 fn decompose_2d_matrix(matrix: &Matrix3D) -> Result<MatrixDecomposed3D, ()> {
    852    // The index is column-major, so the equivalent transform matrix is:
    853    // | m11 m21  0 m41 |  =>  | m11 m21 | and translate(m41, m42)
    854    // | m12 m22  0 m42 |      | m12 m22 |
    855    // |   0   0  1   0 |
    856    // |   0   0  0   1 |
    857    let (mut m11, mut m12) = (matrix.m11, matrix.m12);
    858    let (mut m21, mut m22) = (matrix.m21, matrix.m22);
    859    // Check if this is a singular matrix.
    860    if m11 * m22 == m12 * m21 {
    861        return Err(());
    862    }
    863 
    864    let mut scale_x = (m11 * m11 + m12 * m12).sqrt();
    865    m11 /= scale_x;
    866    m12 /= scale_x;
    867 
    868    let mut shear_xy = m11 * m21 + m12 * m22;
    869    m21 -= m11 * shear_xy;
    870    m22 -= m12 * shear_xy;
    871 
    872    let scale_y = (m21 * m21 + m22 * m22).sqrt();
    873    m21 /= scale_y;
    874    m22 /= scale_y;
    875    shear_xy /= scale_y;
    876 
    877    let determinant = m11 * m22 - m12 * m21;
    878    // Determinant should now be 1 or -1.
    879    if 0.99 > determinant.abs() || determinant.abs() > 1.01 {
    880        return Err(());
    881    }
    882 
    883    if determinant < 0. {
    884        m11 = -m11;
    885        m12 = -m12;
    886        shear_xy = -shear_xy;
    887        scale_x = -scale_x;
    888    }
    889 
    890    Ok(MatrixDecomposed3D {
    891        translate: Translate3D(matrix.m41, matrix.m42, 0.),
    892        scale: Scale3D(scale_x, scale_y, 1.),
    893        skew: Skew(shear_xy, 0., 0.),
    894        perspective: Perspective(0., 0., 0., 1.),
    895        quaternion: Quaternion::from_direction_and_angle(
    896            &DirectionVector::new(0., 0., 1.),
    897            m12.atan2(m11) as f64,
    898        ),
    899    })
    900 }
    901 
    902 impl Animate for Matrix3D {
    903    #[cfg(feature = "servo")]
    904    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
    905        if self.is_3d() || other.is_3d() {
    906            let decomposed_from = decompose_3d_matrix(*self);
    907            let decomposed_to = decompose_3d_matrix(*other);
    908            match (decomposed_from, decomposed_to) {
    909                (Ok(this), Ok(other)) => Ok(Matrix3D::from(this.animate(&other, procedure)?)),
    910                // Matrices can be undecomposable due to couple reasons, e.g.,
    911                // non-invertible matrices. In this case, we should report Err
    912                // here, and let the caller do the fallback procedure.
    913                _ => Err(()),
    914            }
    915        } else {
    916            let this = MatrixDecomposed2D::from(*self);
    917            let other = MatrixDecomposed2D::from(*other);
    918            Ok(Matrix3D::from(this.animate(&other, procedure)?))
    919        }
    920    }
    921 
    922    #[cfg(feature = "gecko")]
    923    // Gecko doesn't exactly follow the spec here; we use a different procedure
    924    // to match it
    925    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
    926        let (from, to) = if self.is_3d() || other.is_3d() {
    927            (decompose_3d_matrix(*self)?, decompose_3d_matrix(*other)?)
    928        } else {
    929            (decompose_2d_matrix(self)?, decompose_2d_matrix(other)?)
    930        };
    931        // Matrices can be undecomposable due to couple reasons, e.g.,
    932        // non-invertible matrices. In this case, we should report Err here,
    933        // and let the caller do the fallback procedure.
    934        Ok(Matrix3D::from(from.animate(&to, procedure)?))
    935    }
    936 }
    937 
    938 impl ComputeSquaredDistance for Matrix3D {
    939    #[inline]
    940    #[cfg(feature = "servo")]
    941    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
    942        if self.is_3d() || other.is_3d() {
    943            let from = decompose_3d_matrix(*self)?;
    944            let to = decompose_3d_matrix(*other)?;
    945            from.compute_squared_distance(&to)
    946        } else {
    947            let from = MatrixDecomposed2D::from(*self);
    948            let to = MatrixDecomposed2D::from(*other);
    949            from.compute_squared_distance(&to)
    950        }
    951    }
    952 
    953    #[inline]
    954    #[cfg(feature = "gecko")]
    955    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
    956        let (from, to) = if self.is_3d() || other.is_3d() {
    957            (decompose_3d_matrix(*self)?, decompose_3d_matrix(*other)?)
    958        } else {
    959            (decompose_2d_matrix(self)?, decompose_2d_matrix(other)?)
    960        };
    961        from.compute_squared_distance(&to)
    962    }
    963 }
    964 
    965 // ------------------------------------
    966 // Animation for Transform list.
    967 // ------------------------------------
    968 fn is_matched_operation(
    969    first: &ComputedTransformOperation,
    970    second: &ComputedTransformOperation,
    971 ) -> bool {
    972    match (first, second) {
    973        (&TransformOperation::Matrix(..), &TransformOperation::Matrix(..))
    974        | (&TransformOperation::Matrix3D(..), &TransformOperation::Matrix3D(..))
    975        | (&TransformOperation::Skew(..), &TransformOperation::Skew(..))
    976        | (&TransformOperation::SkewX(..), &TransformOperation::SkewX(..))
    977        | (&TransformOperation::SkewY(..), &TransformOperation::SkewY(..))
    978        | (&TransformOperation::Rotate(..), &TransformOperation::Rotate(..))
    979        | (&TransformOperation::Rotate3D(..), &TransformOperation::Rotate3D(..))
    980        | (&TransformOperation::RotateX(..), &TransformOperation::RotateX(..))
    981        | (&TransformOperation::RotateY(..), &TransformOperation::RotateY(..))
    982        | (&TransformOperation::RotateZ(..), &TransformOperation::RotateZ(..))
    983        | (&TransformOperation::Perspective(..), &TransformOperation::Perspective(..)) => true,
    984        // Match functions that have the same primitive transform function
    985        (a, b) if a.is_translate() && b.is_translate() => true,
    986        (a, b) if a.is_scale() && b.is_scale() => true,
    987        (a, b) if a.is_rotate() && b.is_rotate() => true,
    988        // InterpolateMatrix and AccumulateMatrix are for mismatched transforms
    989        _ => false,
    990    }
    991 }
    992 
    993 /// <https://drafts.csswg.org/css-transforms/#interpolation-of-transforms>
    994 impl Animate for ComputedTransform {
    995    #[inline]
    996    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
    997        use std::borrow::Cow;
    998 
    999        // Addition for transforms simply means appending to the list of
   1000        // transform functions. This is different to how we handle the other
   1001        // animation procedures so we treat it separately here rather than
   1002        // handling it in TransformOperation.
   1003        if procedure == Procedure::Add {
   1004            let result = self.0.iter().chain(&*other.0).cloned().collect();
   1005            return Ok(Transform(result));
   1006        }
   1007 
   1008        let this = Cow::Borrowed(&self.0);
   1009        let other = Cow::Borrowed(&other.0);
   1010 
   1011        // Interpolate the common prefix
   1012        let mut result = this
   1013            .iter()
   1014            .zip(other.iter())
   1015            .take_while(|(this, other)| is_matched_operation(this, other))
   1016            .map(|(this, other)| this.animate(other, procedure))
   1017            .collect::<Result<Vec<_>, _>>()?;
   1018 
   1019        // Deal with the remainders
   1020        let this_remainder = if this.len() > result.len() {
   1021            Some(&this[result.len()..])
   1022        } else {
   1023            None
   1024        };
   1025        let other_remainder = if other.len() > result.len() {
   1026            Some(&other[result.len()..])
   1027        } else {
   1028            None
   1029        };
   1030 
   1031        match (this_remainder, other_remainder) {
   1032            // If there is a remainder from *both* lists we must have had mismatched functions.
   1033            // => Add the remainders to a suitable ___Matrix function.
   1034            (Some(this_remainder), Some(other_remainder)) => {
   1035                result.push(TransformOperation::animate_mismatched_transforms(
   1036                    this_remainder,
   1037                    other_remainder,
   1038                    procedure,
   1039                )?);
   1040            },
   1041            // If there is a remainder from just one list, then one list must be shorter but
   1042            // completely match the type of the corresponding functions in the longer list.
   1043            // => Interpolate the remainder with identity transforms.
   1044            (Some(remainder), None) | (None, Some(remainder)) => {
   1045                let fill_right = this_remainder.is_some();
   1046                result.append(
   1047                    &mut remainder
   1048                        .iter()
   1049                        .map(|transform| {
   1050                            let identity = transform.to_animated_zero().unwrap();
   1051 
   1052                            match transform {
   1053                                TransformOperation::AccumulateMatrix { .. }
   1054                                | TransformOperation::InterpolateMatrix { .. } => {
   1055                                    let (from, to) = if fill_right {
   1056                                        (transform, &identity)
   1057                                    } else {
   1058                                        (&identity, transform)
   1059                                    };
   1060 
   1061                                    TransformOperation::animate_mismatched_transforms(
   1062                                        &[from.clone()],
   1063                                        &[to.clone()],
   1064                                        procedure,
   1065                                    )
   1066                                },
   1067                                _ => {
   1068                                    let (lhs, rhs) = if fill_right {
   1069                                        (transform, &identity)
   1070                                    } else {
   1071                                        (&identity, transform)
   1072                                    };
   1073                                    lhs.animate(rhs, procedure)
   1074                                },
   1075                            }
   1076                        })
   1077                        .collect::<Result<Vec<_>, _>>()?,
   1078                );
   1079            },
   1080            (None, None) => {},
   1081        }
   1082 
   1083        Ok(Transform(result.into()))
   1084    }
   1085 }
   1086 
   1087 impl ComputeSquaredDistance for ComputedTransform {
   1088    #[inline]
   1089    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
   1090        let squared_dist = super::lists::with_zero::squared_distance(&self.0, &other.0);
   1091 
   1092        // Roll back to matrix interpolation if there is any Err(()) in the
   1093        // transform lists, such as mismatched transform functions.
   1094        //
   1095        // FIXME: Using a zero size here seems a bit sketchy but matches the
   1096        // previous behavior.
   1097        if squared_dist.is_err() {
   1098            let rect = euclid::Rect::zero();
   1099            let matrix1: Matrix3D = self.to_transform_3d_matrix(Some(&rect))?.0.into();
   1100            let matrix2: Matrix3D = other.to_transform_3d_matrix(Some(&rect))?.0.into();
   1101            return matrix1.compute_squared_distance(&matrix2);
   1102        }
   1103 
   1104        squared_dist
   1105    }
   1106 }
   1107 
   1108 /// <http://dev.w3.org/csswg/css-transforms/#interpolation-of-transforms>
   1109 impl Animate for ComputedTransformOperation {
   1110    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
   1111        match (self, other) {
   1112            (&TransformOperation::Matrix3D(ref this), &TransformOperation::Matrix3D(ref other)) => {
   1113                Ok(TransformOperation::Matrix3D(
   1114                    this.animate(other, procedure)?,
   1115                ))
   1116            },
   1117            (&TransformOperation::Matrix(ref this), &TransformOperation::Matrix(ref other)) => {
   1118                Ok(TransformOperation::Matrix(this.animate(other, procedure)?))
   1119            },
   1120            (
   1121                &TransformOperation::Skew(ref fx, ref fy),
   1122                &TransformOperation::Skew(ref tx, ref ty),
   1123            ) => Ok(TransformOperation::Skew(
   1124                fx.animate(tx, procedure)?,
   1125                fy.animate(ty, procedure)?,
   1126            )),
   1127            (&TransformOperation::SkewX(ref f), &TransformOperation::SkewX(ref t)) => {
   1128                Ok(TransformOperation::SkewX(f.animate(t, procedure)?))
   1129            },
   1130            (&TransformOperation::SkewY(ref f), &TransformOperation::SkewY(ref t)) => {
   1131                Ok(TransformOperation::SkewY(f.animate(t, procedure)?))
   1132            },
   1133            (
   1134                &TransformOperation::Translate3D(ref fx, ref fy, ref fz),
   1135                &TransformOperation::Translate3D(ref tx, ref ty, ref tz),
   1136            ) => Ok(TransformOperation::Translate3D(
   1137                fx.animate(tx, procedure)?,
   1138                fy.animate(ty, procedure)?,
   1139                fz.animate(tz, procedure)?,
   1140            )),
   1141            (
   1142                &TransformOperation::Translate(ref fx, ref fy),
   1143                &TransformOperation::Translate(ref tx, ref ty),
   1144            ) => Ok(TransformOperation::Translate(
   1145                fx.animate(tx, procedure)?,
   1146                fy.animate(ty, procedure)?,
   1147            )),
   1148            (&TransformOperation::TranslateX(ref f), &TransformOperation::TranslateX(ref t)) => {
   1149                Ok(TransformOperation::TranslateX(f.animate(t, procedure)?))
   1150            },
   1151            (&TransformOperation::TranslateY(ref f), &TransformOperation::TranslateY(ref t)) => {
   1152                Ok(TransformOperation::TranslateY(f.animate(t, procedure)?))
   1153            },
   1154            (&TransformOperation::TranslateZ(ref f), &TransformOperation::TranslateZ(ref t)) => {
   1155                Ok(TransformOperation::TranslateZ(f.animate(t, procedure)?))
   1156            },
   1157            (
   1158                &TransformOperation::Scale3D(ref fx, ref fy, ref fz),
   1159                &TransformOperation::Scale3D(ref tx, ref ty, ref tz),
   1160            ) => Ok(TransformOperation::Scale3D(
   1161                animate_multiplicative_factor(*fx, *tx, procedure)?,
   1162                animate_multiplicative_factor(*fy, *ty, procedure)?,
   1163                animate_multiplicative_factor(*fz, *tz, procedure)?,
   1164            )),
   1165            (&TransformOperation::ScaleX(ref f), &TransformOperation::ScaleX(ref t)) => Ok(
   1166                TransformOperation::ScaleX(animate_multiplicative_factor(*f, *t, procedure)?),
   1167            ),
   1168            (&TransformOperation::ScaleY(ref f), &TransformOperation::ScaleY(ref t)) => Ok(
   1169                TransformOperation::ScaleY(animate_multiplicative_factor(*f, *t, procedure)?),
   1170            ),
   1171            (&TransformOperation::ScaleZ(ref f), &TransformOperation::ScaleZ(ref t)) => Ok(
   1172                TransformOperation::ScaleZ(animate_multiplicative_factor(*f, *t, procedure)?),
   1173            ),
   1174            (
   1175                &TransformOperation::Scale(ref fx, ref fy),
   1176                &TransformOperation::Scale(ref tx, ref ty),
   1177            ) => Ok(TransformOperation::Scale(
   1178                animate_multiplicative_factor(*fx, *tx, procedure)?,
   1179                animate_multiplicative_factor(*fy, *ty, procedure)?,
   1180            )),
   1181            (
   1182                &TransformOperation::Rotate3D(fx, fy, fz, fa),
   1183                &TransformOperation::Rotate3D(tx, ty, tz, ta),
   1184            ) => {
   1185                let animated = Rotate::Rotate3D(fx, fy, fz, fa)
   1186                    .animate(&Rotate::Rotate3D(tx, ty, tz, ta), procedure)?;
   1187                let (fx, fy, fz, fa) = ComputedRotate::resolve(&animated);
   1188                Ok(TransformOperation::Rotate3D(fx, fy, fz, fa))
   1189            },
   1190            (&TransformOperation::RotateX(fa), &TransformOperation::RotateX(ta)) => {
   1191                Ok(TransformOperation::RotateX(fa.animate(&ta, procedure)?))
   1192            },
   1193            (&TransformOperation::RotateY(fa), &TransformOperation::RotateY(ta)) => {
   1194                Ok(TransformOperation::RotateY(fa.animate(&ta, procedure)?))
   1195            },
   1196            (&TransformOperation::RotateZ(fa), &TransformOperation::RotateZ(ta)) => {
   1197                Ok(TransformOperation::RotateZ(fa.animate(&ta, procedure)?))
   1198            },
   1199            (&TransformOperation::Rotate(fa), &TransformOperation::Rotate(ta)) => {
   1200                Ok(TransformOperation::Rotate(fa.animate(&ta, procedure)?))
   1201            },
   1202            (&TransformOperation::Rotate(fa), &TransformOperation::RotateZ(ta)) => {
   1203                Ok(TransformOperation::Rotate(fa.animate(&ta, procedure)?))
   1204            },
   1205            (&TransformOperation::RotateZ(fa), &TransformOperation::Rotate(ta)) => {
   1206                Ok(TransformOperation::Rotate(fa.animate(&ta, procedure)?))
   1207            },
   1208            (
   1209                &TransformOperation::Perspective(ref fd),
   1210                &TransformOperation::Perspective(ref td),
   1211            ) => {
   1212                use crate::values::computed::CSSPixelLength;
   1213                use crate::values::generics::transform::create_perspective_matrix;
   1214 
   1215                // From https://drafts.csswg.org/css-transforms-2/#interpolation-of-transform-functions:
   1216                //
   1217                //    The transform functions matrix(), matrix3d() and
   1218                //    perspective() get converted into 4x4 matrices first and
   1219                //    interpolated as defined in section Interpolation of
   1220                //    Matrices afterwards.
   1221                //
   1222                let from = create_perspective_matrix(fd.infinity_or(|l| l.px()));
   1223                let to = create_perspective_matrix(td.infinity_or(|l| l.px()));
   1224 
   1225                let interpolated = Matrix3D::from(from).animate(&Matrix3D::from(to), procedure)?;
   1226 
   1227                let decomposed = decompose_3d_matrix(interpolated)?;
   1228                let perspective_z = decomposed.perspective.2;
   1229                // Clamp results outside of the -1 to 0 range so that we get perspective
   1230                // function values between 1 and infinity.
   1231                let used_value = if perspective_z >= 0. {
   1232                    transform::PerspectiveFunction::None
   1233                } else {
   1234                    transform::PerspectiveFunction::Length(CSSPixelLength::new(
   1235                        if perspective_z <= -1. {
   1236                            1.
   1237                        } else {
   1238                            -1. / perspective_z
   1239                        },
   1240                    ))
   1241                };
   1242                Ok(TransformOperation::Perspective(used_value))
   1243            },
   1244            _ if self.is_translate() && other.is_translate() => self
   1245                .to_translate_3d()
   1246                .animate(&other.to_translate_3d(), procedure),
   1247            _ if self.is_scale() && other.is_scale() => {
   1248                self.to_scale_3d().animate(&other.to_scale_3d(), procedure)
   1249            },
   1250            _ if self.is_rotate() && other.is_rotate() => self
   1251                .to_rotate_3d()
   1252                .animate(&other.to_rotate_3d(), procedure),
   1253            _ => Err(()),
   1254        }
   1255    }
   1256 }
   1257 
   1258 impl ComputedTransformOperation {
   1259    /// If there are no size dependencies, we try to animate in-place, to avoid
   1260    /// creating deeply nested Interpolate* operations.
   1261    fn try_animate_mismatched_transforms_in_place(
   1262        left: &[Self],
   1263        right: &[Self],
   1264        procedure: Procedure,
   1265    ) -> Result<Self, ()> {
   1266        let (left, _left_3d) = Transform::components_to_transform_3d_matrix(left, None)?;
   1267        let (right, _right_3d) = Transform::components_to_transform_3d_matrix(right, None)?;
   1268        Ok(Self::Matrix3D(
   1269            Matrix3D::from(left).animate(&Matrix3D::from(right), procedure)?,
   1270        ))
   1271    }
   1272 
   1273    fn animate_mismatched_transforms(
   1274        left: &[Self],
   1275        right: &[Self],
   1276        procedure: Procedure,
   1277    ) -> Result<Self, ()> {
   1278        if let Ok(op) = Self::try_animate_mismatched_transforms_in_place(left, right, procedure) {
   1279            return Ok(op);
   1280        }
   1281        let from_list = Transform(left.to_vec().into());
   1282        let to_list = Transform(right.to_vec().into());
   1283        Ok(match procedure {
   1284            Procedure::Add => {
   1285                debug_assert!(false, "Addition should've been handled earlier");
   1286                return Err(());
   1287            },
   1288            Procedure::Interpolate { progress } => Self::InterpolateMatrix {
   1289                from_list,
   1290                to_list,
   1291                progress: Percentage(progress as f32),
   1292            },
   1293            Procedure::Accumulate { count } => Self::AccumulateMatrix {
   1294                from_list,
   1295                to_list,
   1296                count: cmp::min(count, i32::max_value() as u64) as i32,
   1297            },
   1298        })
   1299    }
   1300 }
   1301 
   1302 // This might not be the most useful definition of distance. It might be better, for example,
   1303 // to trace the distance travelled by a point as its transform is interpolated between the two
   1304 // lists. That, however, proves to be quite complicated so we take a simple approach for now.
   1305 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1318591#c0.
   1306 impl ComputeSquaredDistance for ComputedTransformOperation {
   1307    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
   1308        match (self, other) {
   1309            (&TransformOperation::Matrix3D(ref this), &TransformOperation::Matrix3D(ref other)) => {
   1310                this.compute_squared_distance(other)
   1311            },
   1312            (&TransformOperation::Matrix(ref this), &TransformOperation::Matrix(ref other)) => {
   1313                let this: Matrix3D = (*this).into();
   1314                let other: Matrix3D = (*other).into();
   1315                this.compute_squared_distance(&other)
   1316            },
   1317            (
   1318                &TransformOperation::Skew(ref fx, ref fy),
   1319                &TransformOperation::Skew(ref tx, ref ty),
   1320            ) => Ok(fx.compute_squared_distance(&tx)? + fy.compute_squared_distance(&ty)?),
   1321            (&TransformOperation::SkewX(ref f), &TransformOperation::SkewX(ref t))
   1322            | (&TransformOperation::SkewY(ref f), &TransformOperation::SkewY(ref t)) => {
   1323                f.compute_squared_distance(&t)
   1324            },
   1325            (
   1326                &TransformOperation::Translate3D(ref fx, ref fy, ref fz),
   1327                &TransformOperation::Translate3D(ref tx, ref ty, ref tz),
   1328            ) => {
   1329                // For translate, We don't want to require doing layout in order
   1330                // to calculate the result, so drop the percentage part.
   1331                //
   1332                // However, dropping percentage makes us impossible to compute
   1333                // the distance for the percentage-percentage case, but Gecko
   1334                // uses the same formula, so it's fine for now.
   1335                let basis = Length::new(0.);
   1336                let fx = fx.resolve(basis).px();
   1337                let fy = fy.resolve(basis).px();
   1338                let tx = tx.resolve(basis).px();
   1339                let ty = ty.resolve(basis).px();
   1340 
   1341                Ok(fx.compute_squared_distance(&tx)?
   1342                    + fy.compute_squared_distance(&ty)?
   1343                    + fz.compute_squared_distance(&tz)?)
   1344            },
   1345            (
   1346                &TransformOperation::Scale3D(ref fx, ref fy, ref fz),
   1347                &TransformOperation::Scale3D(ref tx, ref ty, ref tz),
   1348            ) => Ok(fx.compute_squared_distance(&tx)?
   1349                + fy.compute_squared_distance(&ty)?
   1350                + fz.compute_squared_distance(&tz)?),
   1351            (
   1352                &TransformOperation::Rotate3D(fx, fy, fz, fa),
   1353                &TransformOperation::Rotate3D(tx, ty, tz, ta),
   1354            ) => Rotate::Rotate3D(fx, fy, fz, fa)
   1355                .compute_squared_distance(&Rotate::Rotate3D(tx, ty, tz, ta)),
   1356            (&TransformOperation::RotateX(fa), &TransformOperation::RotateX(ta))
   1357            | (&TransformOperation::RotateY(fa), &TransformOperation::RotateY(ta))
   1358            | (&TransformOperation::RotateZ(fa), &TransformOperation::RotateZ(ta))
   1359            | (&TransformOperation::Rotate(fa), &TransformOperation::Rotate(ta)) => {
   1360                fa.compute_squared_distance(&ta)
   1361            },
   1362            (
   1363                &TransformOperation::Perspective(ref fd),
   1364                &TransformOperation::Perspective(ref td),
   1365            ) => fd
   1366                .infinity_or(|l| l.px())
   1367                .compute_squared_distance(&td.infinity_or(|l| l.px())),
   1368            (&TransformOperation::Perspective(ref p), &TransformOperation::Matrix3D(ref m))
   1369            | (&TransformOperation::Matrix3D(ref m), &TransformOperation::Perspective(ref p)) => {
   1370                // FIXME(emilio): Is this right? Why interpolating this with
   1371                // Perspective but not with anything else?
   1372                let mut p_matrix = Matrix3D::identity();
   1373                let p = p.infinity_or(|p| p.px());
   1374                if p >= 0. {
   1375                    p_matrix.m34 = -1. / p.max(1.);
   1376                }
   1377                p_matrix.compute_squared_distance(&m)
   1378            },
   1379            // Gecko cross-interpolates amongst all translate and all scale
   1380            // functions (See ToPrimitive in layout/style/StyleAnimationValue.cpp)
   1381            // without falling back to InterpolateMatrix
   1382            _ if self.is_translate() && other.is_translate() => self
   1383                .to_translate_3d()
   1384                .compute_squared_distance(&other.to_translate_3d()),
   1385            _ if self.is_scale() && other.is_scale() => self
   1386                .to_scale_3d()
   1387                .compute_squared_distance(&other.to_scale_3d()),
   1388            _ if self.is_rotate() && other.is_rotate() => self
   1389                .to_rotate_3d()
   1390                .compute_squared_distance(&other.to_rotate_3d()),
   1391            _ => Err(()),
   1392        }
   1393    }
   1394 }
   1395 
   1396 // ------------------------------------
   1397 // Individual transforms.
   1398 // ------------------------------------
   1399 /// <https://drafts.csswg.org/css-transforms-2/#propdef-rotate>
   1400 impl ComputedRotate {
   1401    fn resolve(&self) -> (Number, Number, Number, Angle) {
   1402        // According to the spec:
   1403        // https://drafts.csswg.org/css-transforms-2/#individual-transforms
   1404        //
   1405        // If the axis is unspecified, it defaults to "0 0 1"
   1406        match *self {
   1407            Rotate::None => (0., 0., 1., Angle::zero()),
   1408            Rotate::Rotate3D(rx, ry, rz, angle) => (rx, ry, rz, angle),
   1409            Rotate::Rotate(angle) => (0., 0., 1., angle),
   1410        }
   1411    }
   1412 }
   1413 
   1414 impl Animate for ComputedRotate {
   1415    #[inline]
   1416    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
   1417        use euclid::approxeq::ApproxEq;
   1418        match (self, other) {
   1419            (&Rotate::None, &Rotate::None) => Ok(Rotate::None),
   1420            (&Rotate::Rotate3D(fx, fy, fz, fa), &Rotate::None) => {
   1421                // We always normalize direction vector for rotate3d() first, so we should also
   1422                // apply the same rule for rotate property. In other words, we promote none into
   1423                // a 3d rotate, and normalize both direction vector first, and then do
   1424                // interpolation.
   1425                let (fx, fy, fz, fa) = transform::get_normalized_vector_and_angle(fx, fy, fz, fa);
   1426                Ok(Rotate::Rotate3D(
   1427                    fx,
   1428                    fy,
   1429                    fz,
   1430                    fa.animate(&Angle::zero(), procedure)?,
   1431                ))
   1432            },
   1433            (&Rotate::None, &Rotate::Rotate3D(tx, ty, tz, ta)) => {
   1434                // Normalize direction vector first.
   1435                let (tx, ty, tz, ta) = transform::get_normalized_vector_and_angle(tx, ty, tz, ta);
   1436                Ok(Rotate::Rotate3D(
   1437                    tx,
   1438                    ty,
   1439                    tz,
   1440                    Angle::zero().animate(&ta, procedure)?,
   1441                ))
   1442            },
   1443            (&Rotate::Rotate3D(_, ..), _) | (_, &Rotate::Rotate3D(_, ..)) => {
   1444                // https://drafts.csswg.org/css-transforms-2/#interpolation-of-transform-functions
   1445 
   1446                let (from, to) = (self.resolve(), other.resolve());
   1447                // For interpolations with the primitive rotate3d(), the direction vectors of the
   1448                // transform functions get normalized first.
   1449                let (fx, fy, fz, fa) =
   1450                    transform::get_normalized_vector_and_angle(from.0, from.1, from.2, from.3);
   1451                let (tx, ty, tz, ta) =
   1452                    transform::get_normalized_vector_and_angle(to.0, to.1, to.2, to.3);
   1453 
   1454                // The rotation angle gets interpolated numerically and the rotation vector of the
   1455                // non-zero angle is used or (0, 0, 1) if both angles are zero.
   1456                //
   1457                // Note: the normalization may get two different vectors because of the
   1458                // floating-point precision, so we have to use approx_eq to compare two
   1459                // vectors.
   1460                let fv = DirectionVector::new(fx, fy, fz);
   1461                let tv = DirectionVector::new(tx, ty, tz);
   1462                if fa.is_zero() || ta.is_zero() || fv.approx_eq(&tv) {
   1463                    let (x, y, z) = if fa.is_zero() && ta.is_zero() {
   1464                        (0., 0., 1.)
   1465                    } else if fa.is_zero() {
   1466                        (tx, ty, tz)
   1467                    } else {
   1468                        // ta.is_zero() or both vectors are equal.
   1469                        (fx, fy, fz)
   1470                    };
   1471                    return Ok(Rotate::Rotate3D(x, y, z, fa.animate(&ta, procedure)?));
   1472                }
   1473 
   1474                // Slerp algorithm doesn't work well for Procedure::Add, which makes both
   1475                // |this_weight| and |other_weight| be 1.0, and this may make the cosine value of
   1476                // the angle be out of the range (i.e. the 4th component of the quaternion vector).
   1477                // (See Quaternion::animate() for more details about the Slerp formula.)
   1478                // Therefore, if the cosine value is out of range, we get an NaN after applying
   1479                // acos() on it, and so the result is invalid.
   1480                // Note: This is specialized for `rotate` property. The addition of `transform`
   1481                // property has been handled in `ComputedTransform::animate()` by merging two list
   1482                // directly.
   1483                let rq = if procedure == Procedure::Add {
   1484                    // In Transform::animate(), it converts two rotations into transform matrices,
   1485                    // and do matrix multiplication. This match the spec definition for the
   1486                    // addition.
   1487                    // https://drafts.csswg.org/css-transforms-2/#combining-transform-lists
   1488                    let f = ComputedTransformOperation::Rotate3D(fx, fy, fz, fa);
   1489                    let t = ComputedTransformOperation::Rotate3D(tx, ty, tz, ta);
   1490                    let v =
   1491                        Transform(vec![f].into()).animate(&Transform(vec![t].into()), procedure)?;
   1492                    let (m, _) = v.to_transform_3d_matrix(None)?;
   1493                    // Decompose the matrix and retrive the quaternion vector.
   1494                    decompose_3d_matrix(Matrix3D::from(m))?.quaternion
   1495                } else {
   1496                    // If the normalized vectors are not equal and both rotation angles are
   1497                    // non-zero the transform functions get converted into 4x4 matrices first and
   1498                    // interpolated as defined in section Interpolation of Matrices afterwards.
   1499                    // However, per the spec issue [1], we prefer to converting the rotate3D into
   1500                    // quaternion vectors directly, and then apply Slerp algorithm.
   1501                    //
   1502                    // Both ways should be identical, and converting rotate3D into quaternion
   1503                    // vectors directly can avoid redundant math operations, e.g. the generation of
   1504                    // the equivalent matrix3D and the unnecessary decomposition parts of
   1505                    // translation, scale, skew, and persepctive in the matrix3D.
   1506                    //
   1507                    // [1] https://github.com/w3c/csswg-drafts/issues/9278
   1508                    let fq = Quaternion::from_direction_and_angle(&fv, fa.radians64());
   1509                    let tq = Quaternion::from_direction_and_angle(&tv, ta.radians64());
   1510                    Quaternion::animate(&fq, &tq, procedure)?
   1511                };
   1512 
   1513                let (x, y, z, angle) = transform::get_normalized_vector_and_angle(
   1514                    rq.0 as f32,
   1515                    rq.1 as f32,
   1516                    rq.2 as f32,
   1517                    // Due to floating point precision issues, the quaternion may contain values
   1518                    // slightly larger out of the [-1.0, 1.0] range - Clamp to avoid NaN.
   1519                    rq.3.clamp(-1.0, 1.0).acos() as f32 * 2.0,
   1520                );
   1521 
   1522                Ok(Rotate::Rotate3D(x, y, z, Angle::from_radians(angle)))
   1523            },
   1524            (&Rotate::Rotate(_), _) | (_, &Rotate::Rotate(_)) => {
   1525                // If this is a 2D rotation, we just animate the <angle>
   1526                let (from, to) = (self.resolve().3, other.resolve().3);
   1527                Ok(Rotate::Rotate(from.animate(&to, procedure)?))
   1528            },
   1529        }
   1530    }
   1531 }
   1532 
   1533 impl ComputeSquaredDistance for ComputedRotate {
   1534    #[inline]
   1535    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
   1536        use euclid::approxeq::ApproxEq;
   1537        match (self, other) {
   1538            (&Rotate::None, &Rotate::None) => Ok(SquaredDistance::from_sqrt(0.)),
   1539            (&Rotate::Rotate3D(_, _, _, a), &Rotate::None)
   1540            | (&Rotate::None, &Rotate::Rotate3D(_, _, _, a)) => {
   1541                a.compute_squared_distance(&Angle::zero())
   1542            },
   1543            (&Rotate::Rotate3D(_, ..), _) | (_, &Rotate::Rotate3D(_, ..)) => {
   1544                let (from, to) = (self.resolve(), other.resolve());
   1545                let (mut fx, mut fy, mut fz, angle1) =
   1546                    transform::get_normalized_vector_and_angle(from.0, from.1, from.2, from.3);
   1547                let (mut tx, mut ty, mut tz, angle2) =
   1548                    transform::get_normalized_vector_and_angle(to.0, to.1, to.2, to.3);
   1549 
   1550                if angle1.is_zero() && angle2.is_zero() {
   1551                    (fx, fy, fz) = (0., 0., 1.);
   1552                    (tx, ty, tz) = (0., 0., 1.);
   1553                } else if angle1.is_zero() {
   1554                    (fx, fy, fz) = (tx, ty, tz);
   1555                } else if angle2.is_zero() {
   1556                    (tx, ty, tz) = (fx, fy, fz);
   1557                }
   1558 
   1559                let v1 = DirectionVector::new(fx, fy, fz);
   1560                let v2 = DirectionVector::new(tx, ty, tz);
   1561                if v1.approx_eq(&v2) {
   1562                    angle1.compute_squared_distance(&angle2)
   1563                } else {
   1564                    let q1 = Quaternion::from_direction_and_angle(&v1, angle1.radians64());
   1565                    let q2 = Quaternion::from_direction_and_angle(&v2, angle2.radians64());
   1566                    q1.compute_squared_distance(&q2)
   1567                }
   1568            },
   1569            (&Rotate::Rotate(_), _) | (_, &Rotate::Rotate(_)) => self
   1570                .resolve()
   1571                .3
   1572                .compute_squared_distance(&other.resolve().3),
   1573        }
   1574    }
   1575 }
   1576 
   1577 /// <https://drafts.csswg.org/css-transforms-2/#propdef-translate>
   1578 impl ComputedTranslate {
   1579    fn resolve(&self) -> (LengthPercentage, LengthPercentage, Length) {
   1580        // According to the spec:
   1581        // https://drafts.csswg.org/css-transforms-2/#individual-transforms
   1582        //
   1583        // Unspecified translations default to 0px
   1584        match *self {
   1585            Translate::None => (
   1586                LengthPercentage::zero(),
   1587                LengthPercentage::zero(),
   1588                Length::zero(),
   1589            ),
   1590            Translate::Translate(ref tx, ref ty, ref tz) => (tx.clone(), ty.clone(), tz.clone()),
   1591        }
   1592    }
   1593 }
   1594 
   1595 impl Animate for ComputedTranslate {
   1596    #[inline]
   1597    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
   1598        match (self, other) {
   1599            (&Translate::None, &Translate::None) => Ok(Translate::None),
   1600            (&Translate::Translate(_, ..), _) | (_, &Translate::Translate(_, ..)) => {
   1601                let (from, to) = (self.resolve(), other.resolve());
   1602                Ok(Translate::Translate(
   1603                    from.0.animate(&to.0, procedure)?,
   1604                    from.1.animate(&to.1, procedure)?,
   1605                    from.2.animate(&to.2, procedure)?,
   1606                ))
   1607            },
   1608        }
   1609    }
   1610 }
   1611 
   1612 impl ComputeSquaredDistance for ComputedTranslate {
   1613    #[inline]
   1614    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
   1615        let (from, to) = (self.resolve(), other.resolve());
   1616        Ok(from.0.compute_squared_distance(&to.0)?
   1617            + from.1.compute_squared_distance(&to.1)?
   1618            + from.2.compute_squared_distance(&to.2)?)
   1619    }
   1620 }
   1621 
   1622 /// <https://drafts.csswg.org/css-transforms-2/#propdef-scale>
   1623 impl ComputedScale {
   1624    fn resolve(&self) -> (Number, Number, Number) {
   1625        // According to the spec:
   1626        // https://drafts.csswg.org/css-transforms-2/#individual-transforms
   1627        //
   1628        // Unspecified scales default to 1
   1629        match *self {
   1630            Scale::None => (1.0, 1.0, 1.0),
   1631            Scale::Scale(sx, sy, sz) => (sx, sy, sz),
   1632        }
   1633    }
   1634 }
   1635 
   1636 impl Animate for ComputedScale {
   1637    #[inline]
   1638    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
   1639        match (self, other) {
   1640            (&Scale::None, &Scale::None) => Ok(Scale::None),
   1641            (&Scale::Scale(_, ..), _) | (_, &Scale::Scale(_, ..)) => {
   1642                let (from, to) = (self.resolve(), other.resolve());
   1643                // For transform lists, we add by appending to the list of
   1644                // transform functions. However, ComputedScale cannot be
   1645                // simply concatenated, so we have to calculate the additive
   1646                // result here.
   1647                if procedure == Procedure::Add {
   1648                    // scale(x1,y1,z1)*scale(x2,y2,z2) = scale(x1*x2, y1*y2, z1*z2)
   1649                    return Ok(Scale::Scale(from.0 * to.0, from.1 * to.1, from.2 * to.2));
   1650                }
   1651                Ok(Scale::Scale(
   1652                    animate_multiplicative_factor(from.0, to.0, procedure)?,
   1653                    animate_multiplicative_factor(from.1, to.1, procedure)?,
   1654                    animate_multiplicative_factor(from.2, to.2, procedure)?,
   1655                ))
   1656            },
   1657        }
   1658    }
   1659 }
   1660 
   1661 impl ComputeSquaredDistance for ComputedScale {
   1662    #[inline]
   1663    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
   1664        let (from, to) = (self.resolve(), other.resolve());
   1665        Ok(from.0.compute_squared_distance(&to.0)?
   1666            + from.1.compute_squared_distance(&to.1)?
   1667            + from.2.compute_squared_distance(&to.2)?)
   1668    }
   1669 }