use crate::values::computed::length::Length as ComputedLength;
use crate::values::computed::length::LengthPercentage as ComputedLengthPercentage;
use crate::values::specified::angle::Angle as SpecifiedAngle;
use crate::values::specified::length::Length as SpecifiedLength;
use crate::values::specified::length::LengthPercentage as SpecifiedLengthPercentage;
use crate::values::{computed, CSSFloat};
use crate::{Zero, ZeroNoPercent};
use euclid::default::{Rect, Transform3D};
use std::fmt::{self, Write};
use style_traits::{CssWriter, ToCss};
#[allow(missing_docs)]
#[derive(
Clone,
Copy,
Debug,
Deserialize,
MallocSizeOf,
PartialEq,
Serialize,
SpecifiedValueInfo,
ToAnimatedValue,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[css(comma, function = "matrix")]
#[repr(C)]
pub struct GenericMatrix<T> {
pub a: T,
pub b: T,
pub c: T,
pub d: T,
pub e: T,
pub f: T,
}
pub use self::GenericMatrix as Matrix;
#[allow(missing_docs)]
#[cfg_attr(rustfmt, rustfmt_skip)]
#[derive(
Clone,
Copy,
Debug,
Deserialize,
MallocSizeOf,
PartialEq,
Serialize,
SpecifiedValueInfo,
ToAnimatedValue,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[css(comma, function = "matrix3d")]
#[repr(C)]
pub struct GenericMatrix3D<T> {
pub m11: T, pub m12: T, pub m13: T, pub m14: T,
pub m21: T, pub m22: T, pub m23: T, pub m24: T,
pub m31: T, pub m32: T, pub m33: T, pub m34: T,
pub m41: T, pub m42: T, pub m43: T, pub m44: T,
}
pub use self::GenericMatrix3D as Matrix3D;
#[cfg_attr(rustfmt, rustfmt_skip)]
impl<T: Into<f64>> From<Matrix<T>> for Transform3D<f64> {
#[inline]
fn from(m: Matrix<T>) -> Self {
Transform3D::new(
m.a.into(), m.b.into(), 0.0, 0.0,
m.c.into(), m.d.into(), 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
m.e.into(), m.f.into(), 0.0, 1.0,
)
}
}
#[cfg_attr(rustfmt, rustfmt_skip)]
impl<T: Into<f64>> From<Matrix3D<T>> for Transform3D<f64> {
#[inline]
fn from(m: Matrix3D<T>) -> Self {
Transform3D::new(
m.m11.into(), m.m12.into(), m.m13.into(), m.m14.into(),
m.m21.into(), m.m22.into(), m.m23.into(), m.m24.into(),
m.m31.into(), m.m32.into(), m.m33.into(), m.m34.into(),
m.m41.into(), m.m42.into(), m.m43.into(), m.m44.into(),
)
}
}
#[derive(
Animate,
Clone,
ComputeSquaredDistance,
Copy,
Debug,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToAnimatedValue,
ToAnimatedZero,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(C)]
pub struct GenericTransformOrigin<H, V, Depth> {
pub horizontal: H,
pub vertical: V,
pub depth: Depth,
}
pub use self::GenericTransformOrigin as TransformOrigin;
impl<H, V, D> TransformOrigin<H, V, D> {
pub fn new(horizontal: H, vertical: V, depth: D) -> Self {
Self {
horizontal,
vertical,
depth,
}
}
}
fn is_same<N: PartialEq>(x: &N, y: &N) -> bool {
x == y
}
#[derive(
Clone,
Debug,
Deserialize,
MallocSizeOf,
PartialEq,
Serialize,
SpecifiedValueInfo,
ToAnimatedValue,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(C, u8)]
pub enum GenericPerspectiveFunction<L> {
None,
Length(L),
}
impl<L> GenericPerspectiveFunction<L> {
pub fn infinity_or(&self, f: impl FnOnce(&L) -> f32) -> f32 {
match *self {
Self::None => f32::INFINITY,
Self::Length(ref l) => f(l),
}
}
}
pub use self::GenericPerspectiveFunction as PerspectiveFunction;
#[derive(
Clone,
Debug,
Deserialize,
MallocSizeOf,
PartialEq,
Serialize,
SpecifiedValueInfo,
ToAnimatedValue,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(C, u8)]
pub enum GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>
where
Angle: Zero,
LengthPercentage: Zero + ZeroNoPercent,
Number: PartialEq,
{
Matrix(GenericMatrix<Number>),
Matrix3D(GenericMatrix3D<Number>),
#[css(comma, function)]
Skew(Angle, #[css(skip_if = "Zero::is_zero")] Angle),
#[css(function = "skewX")]
SkewX(Angle),
#[css(function = "skewY")]
SkewY(Angle),
#[css(comma, function)]
Translate(
LengthPercentage,
#[css(skip_if = "ZeroNoPercent::is_zero_no_percent")] LengthPercentage,
),
#[css(function = "translateX")]
TranslateX(LengthPercentage),
#[css(function = "translateY")]
TranslateY(LengthPercentage),
#[css(function = "translateZ")]
TranslateZ(Length),
#[css(comma, function = "translate3d")]
Translate3D(LengthPercentage, LengthPercentage, Length),
#[css(comma, function)]
Scale(Number, #[css(contextual_skip_if = "is_same")] Number),
#[css(function = "scaleX")]
ScaleX(Number),
#[css(function = "scaleY")]
ScaleY(Number),
#[css(function = "scaleZ")]
ScaleZ(Number),
#[css(comma, function = "scale3d")]
Scale3D(Number, Number, Number),
#[css(function)]
Rotate(Angle),
#[css(function = "rotateX")]
RotateX(Angle),
#[css(function = "rotateY")]
RotateY(Angle),
#[css(function = "rotateZ")]
RotateZ(Angle),
#[css(comma, function = "rotate3d")]
Rotate3D(Number, Number, Number, Angle),
#[css(function)]
Perspective(GenericPerspectiveFunction<Length>),
#[allow(missing_docs)]
#[css(comma, function = "interpolatematrix")]
InterpolateMatrix {
from_list: GenericTransform<
GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>,
>,
to_list: GenericTransform<
GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>,
>,
progress: computed::Percentage,
},
#[allow(missing_docs)]
#[css(comma, function = "accumulatematrix")]
AccumulateMatrix {
from_list: GenericTransform<
GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>,
>,
to_list: GenericTransform<
GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>,
>,
count: Integer,
},
}
pub use self::GenericTransformOperation as TransformOperation;
#[derive(
Clone,
Debug,
Deserialize,
MallocSizeOf,
PartialEq,
Serialize,
SpecifiedValueInfo,
ToAnimatedValue,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(C)]
pub struct GenericTransform<T>(#[css(if_empty = "none", iterable)] pub crate::OwnedSlice<T>);
pub use self::GenericTransform as Transform;
impl<Angle, Number, Length, Integer, LengthPercentage>
TransformOperation<Angle, Number, Length, Integer, LengthPercentage>
where
Angle: Zero,
LengthPercentage: Zero + ZeroNoPercent,
Number: PartialEq,
{
pub fn is_rotate(&self) -> bool {
use self::TransformOperation::*;
matches!(
*self,
Rotate(..) | Rotate3D(..) | RotateX(..) | RotateY(..) | RotateZ(..)
)
}
pub fn is_translate(&self) -> bool {
use self::TransformOperation::*;
match *self {
Translate(..) | Translate3D(..) | TranslateX(..) | TranslateY(..) | TranslateZ(..) => {
true
},
_ => false,
}
}
pub fn is_scale(&self) -> bool {
use self::TransformOperation::*;
match *self {
Scale(..) | Scale3D(..) | ScaleX(..) | ScaleY(..) | ScaleZ(..) => true,
_ => false,
}
}
}
pub trait ToAbsoluteLength {
fn to_pixel_length(&self, containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()>;
}
impl ToAbsoluteLength for SpecifiedLength {
#[inline]
fn to_pixel_length(&self, _containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()> {
match *self {
SpecifiedLength::NoCalc(len) => len.to_computed_pixel_length_without_context(),
SpecifiedLength::Calc(ref calc) => calc.to_computed_pixel_length_without_context(),
}
}
}
impl ToAbsoluteLength for SpecifiedLengthPercentage {
#[inline]
fn to_pixel_length(&self, _containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()> {
use self::SpecifiedLengthPercentage::*;
match *self {
Length(len) => len.to_computed_pixel_length_without_context(),
Calc(ref calc) => calc.to_computed_pixel_length_without_context(),
Percentage(..) => Err(()),
}
}
}
impl ToAbsoluteLength for ComputedLength {
#[inline]
fn to_pixel_length(&self, _containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()> {
Ok(self.px())
}
}
impl ToAbsoluteLength for ComputedLengthPercentage {
#[inline]
fn to_pixel_length(&self, containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()> {
Ok(self
.maybe_percentage_relative_to(containing_len)
.ok_or(())?
.px())
}
}
pub trait ToMatrix {
fn is_3d(&self) -> bool;
fn to_3d_matrix(
&self,
reference_box: Option<&Rect<ComputedLength>>,
) -> Result<Transform3D<f64>, ()>;
}
pub trait ToRadians {
fn radians64(&self) -> f64;
}
impl ToRadians for computed::angle::Angle {
#[inline]
fn radians64(&self) -> f64 {
computed::angle::Angle::radians64(self)
}
}
impl ToRadians for SpecifiedAngle {
#[inline]
fn radians64(&self) -> f64 {
computed::angle::Angle::from_degrees(self.degrees()).radians64()
}
}
impl<Angle, Number, Length, Integer, LoP> ToMatrix
for TransformOperation<Angle, Number, Length, Integer, LoP>
where
Angle: Zero + ToRadians + Copy,
Number: PartialEq + Copy + Into<f32> + Into<f64>,
Length: ToAbsoluteLength,
LoP: Zero + ToAbsoluteLength + ZeroNoPercent,
{
#[inline]
fn is_3d(&self) -> bool {
use self::TransformOperation::*;
match *self {
Translate3D(..) | TranslateZ(..) | Rotate3D(..) | RotateX(..) | RotateY(..) |
RotateZ(..) | Scale3D(..) | ScaleZ(..) | Perspective(..) | Matrix3D(..) => true,
_ => false,
}
}
#[inline]
fn to_3d_matrix(
&self,
reference_box: Option<&Rect<ComputedLength>>,
) -> Result<Transform3D<f64>, ()> {
use self::TransformOperation::*;
let reference_width = reference_box.map(|v| v.size.width);
let reference_height = reference_box.map(|v| v.size.height);
let matrix = match *self {
Rotate3D(ax, ay, az, theta) => {
let theta = theta.radians64();
let (ax, ay, az, theta) =
get_normalized_vector_and_angle(ax.into(), ay.into(), az.into(), theta);
Transform3D::rotation(
ax as f64,
ay as f64,
az as f64,
euclid::Angle::radians(theta),
)
},
RotateX(theta) => {
let theta = euclid::Angle::radians(theta.radians64());
Transform3D::rotation(1., 0., 0., theta)
},
RotateY(theta) => {
let theta = euclid::Angle::radians(theta.radians64());
Transform3D::rotation(0., 1., 0., theta)
},
RotateZ(theta) | Rotate(theta) => {
let theta = euclid::Angle::radians(theta.radians64());
Transform3D::rotation(0., 0., 1., theta)
},
Perspective(ref p) => {
let px = match p {
PerspectiveFunction::None => f32::INFINITY,
PerspectiveFunction::Length(ref p) => p.to_pixel_length(None)?,
};
create_perspective_matrix(px).cast()
},
Scale3D(sx, sy, sz) => Transform3D::scale(sx.into(), sy.into(), sz.into()),
Scale(sx, sy) => Transform3D::scale(sx.into(), sy.into(), 1.),
ScaleX(s) => Transform3D::scale(s.into(), 1., 1.),
ScaleY(s) => Transform3D::scale(1., s.into(), 1.),
ScaleZ(s) => Transform3D::scale(1., 1., s.into()),
Translate3D(ref tx, ref ty, ref tz) => {
let tx = tx.to_pixel_length(reference_width)? as f64;
let ty = ty.to_pixel_length(reference_height)? as f64;
Transform3D::translation(tx, ty, tz.to_pixel_length(None)? as f64)
},
Translate(ref tx, ref ty) => {
let tx = tx.to_pixel_length(reference_width)? as f64;
let ty = ty.to_pixel_length(reference_height)? as f64;
Transform3D::translation(tx, ty, 0.)
},
TranslateX(ref t) => {
let t = t.to_pixel_length(reference_width)? as f64;
Transform3D::translation(t, 0., 0.)
},
TranslateY(ref t) => {
let t = t.to_pixel_length(reference_height)? as f64;
Transform3D::translation(0., t, 0.)
},
TranslateZ(ref z) => Transform3D::translation(0., 0., z.to_pixel_length(None)? as f64),
Skew(theta_x, theta_y) => Transform3D::skew(
euclid::Angle::radians(theta_x.radians64()),
euclid::Angle::radians(theta_y.radians64()),
),
SkewX(theta) => Transform3D::skew(
euclid::Angle::radians(theta.radians64()),
euclid::Angle::radians(0.),
),
SkewY(theta) => Transform3D::skew(
euclid::Angle::radians(0.),
euclid::Angle::radians(theta.radians64()),
),
Matrix3D(m) => m.into(),
Matrix(m) => m.into(),
InterpolateMatrix { .. } | AccumulateMatrix { .. } => {
Transform3D::identity()
},
};
Ok(matrix)
}
}
impl<T> Transform<T> {
pub fn none() -> Self {
Transform(Default::default())
}
}
impl<T: ToMatrix> Transform<T> {
#[cfg_attr(rustfmt, rustfmt_skip)]
pub fn to_transform_3d_matrix(
&self,
reference_box: Option<&Rect<ComputedLength>>
) -> Result<(Transform3D<CSSFloat>, bool), ()> {
Self::components_to_transform_3d_matrix(&self.0, reference_box)
}
#[cfg_attr(rustfmt, rustfmt_skip)]
pub fn components_to_transform_3d_matrix(
ops: &[T],
reference_box: Option<&Rect<ComputedLength>>,
) -> Result<(Transform3D<CSSFloat>, bool), ()> {
let cast_3d_transform = |m: Transform3D<f64>| -> Transform3D<CSSFloat> {
use std::{f32, f64};
let cast = |v: f64| v.min(f32::MAX as f64).max(f32::MIN as f64) as f32;
Transform3D::new(
cast(m.m11), cast(m.m12), cast(m.m13), cast(m.m14),
cast(m.m21), cast(m.m22), cast(m.m23), cast(m.m24),
cast(m.m31), cast(m.m32), cast(m.m33), cast(m.m34),
cast(m.m41), cast(m.m42), cast(m.m43), cast(m.m44),
)
};
let (m, is_3d) = Self::components_to_transform_3d_matrix_f64(ops, reference_box)?;
Ok((cast_3d_transform(m), is_3d))
}
pub fn to_transform_3d_matrix_f64(
&self,
reference_box: Option<&Rect<ComputedLength>>
) -> Result<(Transform3D<f64>, bool), ()> {
Self::components_to_transform_3d_matrix_f64(&self.0, reference_box)
}
fn components_to_transform_3d_matrix_f64(
ops: &[T],
reference_box: Option<&Rect<ComputedLength>>,
) -> Result<(Transform3D<f64>, bool), ()> {
let mut transform = Transform3D::<f64>::identity();
let mut contain_3d = false;
for operation in ops {
let matrix = operation.to_3d_matrix(reference_box)?;
contain_3d = contain_3d || operation.is_3d();
transform = matrix.then(&transform);
}
Ok((transform, contain_3d))
}
}
#[inline]
pub fn create_perspective_matrix(d: CSSFloat) -> Transform3D<CSSFloat> {
if d.is_finite() {
Transform3D::perspective(d.max(1.))
} else {
Transform3D::identity()
}
}
pub fn get_normalized_vector_and_angle<T: Zero>(
x: CSSFloat,
y: CSSFloat,
z: CSSFloat,
angle: T,
) -> (CSSFloat, CSSFloat, CSSFloat, T) {
use crate::values::computed::transform::DirectionVector;
use euclid::approxeq::ApproxEq;
let vector = DirectionVector::new(x, y, z);
if vector.square_length().approx_eq(&f32::zero()) {
(0., 0., 1., T::zero())
} else {
let vector = vector.robust_normalize();
(vector.x, vector.y, vector.z, angle)
}
}
#[derive(
Clone,
Copy,
Debug,
Deserialize,
MallocSizeOf,
PartialEq,
Serialize,
SpecifiedValueInfo,
ToAnimatedValue,
ToAnimatedZero,
ToComputedValue,
ToResolvedValue,
ToShmem,
)]
#[repr(C, u8)]
pub enum GenericRotate<Number, Angle> {
None,
Rotate(Angle),
Rotate3D(Number, Number, Number, Angle),
}
pub use self::GenericRotate as Rotate;
pub trait IsParallelTo {
fn is_parallel_to(&self, vector: &computed::transform::DirectionVector) -> bool;
}
impl<Number, Angle> ToCss for Rotate<Number, Angle>
where
Number: Copy + ToCss + Zero,
Angle: ToCss,
(Number, Number, Number): IsParallelTo,
{
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: fmt::Write,
{
use crate::values::computed::transform::DirectionVector;
match *self {
Rotate::None => dest.write_str("none"),
Rotate::Rotate(ref angle) => angle.to_css(dest),
Rotate::Rotate3D(x, y, z, ref angle) => {
let v = (x, y, z);
let axis = if x.is_zero() && y.is_zero() && z.is_zero() {
None
} else if v.is_parallel_to(&DirectionVector::new(1., 0., 0.)) {
Some("x ")
} else if v.is_parallel_to(&DirectionVector::new(0., 1., 0.)) {
Some("y ")
} else if v.is_parallel_to(&DirectionVector::new(0., 0., 1.)) {
return angle.to_css(dest);
} else {
None
};
match axis {
Some(a) => dest.write_str(a)?,
None => {
x.to_css(dest)?;
dest.write_char(' ')?;
y.to_css(dest)?;
dest.write_char(' ')?;
z.to_css(dest)?;
dest.write_char(' ')?;
},
}
angle.to_css(dest)
},
}
}
}
#[derive(
Clone,
Copy,
Debug,
Deserialize,
MallocSizeOf,
PartialEq,
Serialize,
SpecifiedValueInfo,
ToAnimatedValue,
ToAnimatedZero,
ToComputedValue,
ToResolvedValue,
ToShmem,
)]
#[repr(C, u8)]
pub enum GenericScale<Number> {
None,
Scale(Number, Number, Number),
}
pub use self::GenericScale as Scale;
impl<Number> ToCss for Scale<Number>
where
Number: ToCss + PartialEq + Copy,
f32: From<Number>,
{
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: fmt::Write,
f32: From<Number>,
{
match *self {
Scale::None => dest.write_str("none"),
Scale::Scale(ref x, ref y, ref z) => {
x.to_css(dest)?;
let is_3d = f32::from(*z) != 1.0;
if is_3d || x != y {
dest.write_char(' ')?;
y.to_css(dest)?;
}
if is_3d {
dest.write_char(' ')?;
z.to_css(dest)?;
}
Ok(())
},
}
}
}
#[inline]
fn y_axis_and_z_axis_are_zero<LengthPercentage: Zero + ZeroNoPercent, Length: Zero>(
_: &LengthPercentage,
y: &LengthPercentage,
z: &Length,
) -> bool {
y.is_zero_no_percent() && z.is_zero()
}
#[derive(
Clone,
Debug,
Deserialize,
MallocSizeOf,
PartialEq,
Serialize,
SpecifiedValueInfo,
ToAnimatedValue,
ToAnimatedZero,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(C, u8)]
pub enum GenericTranslate<LengthPercentage, Length>
where
LengthPercentage: Zero + ZeroNoPercent,
Length: Zero,
{
None,
Translate(
LengthPercentage,
#[css(contextual_skip_if = "y_axis_and_z_axis_are_zero")] LengthPercentage,
#[css(skip_if = "Zero::is_zero")] Length,
),
}
pub use self::GenericTranslate as Translate;
#[allow(missing_docs)]
#[derive(
Clone,
Copy,
Debug,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(u8)]
pub enum TransformStyle {
Flat,
#[css(keyword = "preserve-3d")]
Preserve3d,
}