/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Generic types for CSS values that are related to transformations.
usecrate::derives::*; usecrate::typed_om::{
KeywordValue, MatrixComponent, NumericValue, PerspectiveComponent, PerspectiveValue,
RotateComponent, ScaleComponent, SkewComponent, ToTyped, TransformComponent,
TranslateComponent, TypedValue,
}; usecrate::values::computed::length::Length as ComputedLength; usecrate::values::computed::length::LengthPercentage as ComputedLengthPercentage; usecrate::values::computed::transform::Matrix3D as ComputedMatrix3D; usecrate::values::specified::angle::Angle as SpecifiedAngle; usecrate::values::specified::length::Length as SpecifiedLength; usecrate::values::specified::length::LengthPercentage as SpecifiedLengthPercentage; usecrate::values::specified::number::Number as SpecifiedNumber; usecrate::values::{computed, CSSFloat}; usecrate::{One, Zero, ZeroNoPercent}; use euclid::default::{Rect, Transform3D}; use std::fmt::{self, Write}; use std::ops::Neg; use style_traits::{CssString, CssWriter, ToCss}; use thin_vec::ThinVec;
fn is_same<N: PartialEq>(x: &N, y: &N) -> bool {
x == y
}
/// A value for the `perspective()` transform function, which is either a /// non-negative `<length>` or `none`. #[derive(
Clone,
Debug,
Deserialize,
MallocSizeOf,
PartialEq,
Serialize,
SpecifiedValueInfo,
ToAnimatedValue,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
ToTyped,
)] #[repr(C, u8)] pubenum GenericPerspectiveFunction<L> { /// `none`
None, /// A `<length>`.
Length(L),
}
impl<L> GenericPerspectiveFunction<L> { /// Returns `f32::INFINITY` or the result of a function on the length value. pubfn infinity_or(&self, f: impl FnOnce(&L) -> f32) -> f32 { match *self { Self::None => f32::INFINITY, Self::Length(ref l) => f(l),
}
}
}
pubuseself::GenericPerspectiveFunction as PerspectiveFunction;
#[derive(
Clone,
Debug,
Deserialize,
MallocSizeOf,
PartialEq,
Serialize,
SpecifiedValueInfo,
ToAnimatedValue,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)] #[repr(C, u8)] /// A single operation in the list of a `transform` value pubenum GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage> where
Angle: Zero,
LengthPercentage: Zero + ZeroNoPercent,
Number: PartialEq,
{ /// Represents a 2D 2x3 matrix.
Matrix(GenericMatrix<Number>), /// Represents a 3D 4x4 matrix.
Matrix3D(GenericMatrix3D<Number>), /// A 2D skew. /// /// If the second angle is not provided it is assumed zero. /// /// Syntax can be skew(angle) or skew(angle, angle) #[css(comma, function)]
Skew(Angle, #[css(skip_if = "Zero::is_zero")] Angle), /// skewX(angle) #[css(function = "skewX")]
SkewX(Angle), /// skewY(angle) #[css(function = "skewY")]
SkewY(Angle), /// translate(x, y) or translate(x) #[css(comma, function)]
Translate(
LengthPercentage, #[css(skip_if = "ZeroNoPercent::is_zero_no_percent")] LengthPercentage,
), /// translateX(x) #[css(function = "translateX")]
TranslateX(LengthPercentage), /// translateY(y) #[css(function = "translateY")]
TranslateY(LengthPercentage), /// translateZ(z) #[css(function = "translateZ")]
TranslateZ(Length), /// translate3d(x, y, z) #[css(comma, function = "translate3d")]
Translate3D(LengthPercentage, LengthPercentage, Length), /// A 2D scaling factor. /// /// Syntax can be scale(factor) or scale(factor, factor) #[css(comma, function)]
Scale(Number, #[css(contextual_skip_if = "is_same")] Number), /// scaleX(factor) #[css(function = "scaleX")]
ScaleX(Number), /// scaleY(factor) #[css(function = "scaleY")]
ScaleY(Number), /// scaleZ(factor) #[css(function = "scaleZ")]
ScaleZ(Number), /// scale3D(factorX, factorY, factorZ) #[css(comma, function = "scale3d")]
Scale3D(Number, Number, Number), /// Describes a 2D Rotation. /// /// In a 3D scene `rotate(angle)` is equivalent to `rotateZ(angle)`. #[css(function)]
Rotate(Angle), /// Rotation in 3D space around the x-axis. #[css(function = "rotateX")]
RotateX(Angle), /// Rotation in 3D space around the y-axis. #[css(function = "rotateY")]
RotateY(Angle), /// Rotation in 3D space around the z-axis. #[css(function = "rotateZ")]
RotateZ(Angle), /// Rotation in 3D space. /// /// Generalization of rotateX, rotateY and rotateZ. #[css(comma, function = "rotate3d")]
Rotate3D(Number, Number, Number, Angle), /// Specifies a perspective projection matrix. /// /// Part of CSS Transform Module Level 2 and defined at /// [§ 13.1. 3D Transform Function](https://drafts.csswg.org/css-transforms-2/#funcdef-perspective). /// /// The value must be greater than or equal to zero. #[css(function)]
Perspective(GenericPerspectiveFunction<Length>), /// A intermediate type for interpolation of mismatched transform lists. #[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,
}, /// A intermediate type for accumulation of mismatched transform lists. #[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,
},
}
pubuseself::GenericTransformOperation as TransformOperation;
/// Converts a transform operation into a transform component. pubtrait ToTransformComponent { /// Attempt to convert `self` into a transform component. /// /// Implementations append the resulting component to `dest`. Returning /// `Err(())` indicates that the transform operation cannot currently be /// represented as a transform component. fn to_transform_component(&self, _dest: &mut ThinVec<TransformComponent>) -> Result<(), ()>;
}
impl<Angle, Number, Length, Integer, LengthPercentage>
TransformOperation<Angle, Number, Length, Integer, LengthPercentage> where
Angle: Zero,
LengthPercentage: Zero + ZeroNoPercent,
Number: PartialEq,
{ /// Check if it is any rotate function. pubfn is_rotate(&self) -> bool { useself::TransformOperation::*;
matches!(
*self,
Rotate(..) | Rotate3D(..) | RotateX(..) | RotateY(..) | RotateZ(..)
)
}
/// Check if it is any translate function pubfn is_translate(&self) -> bool { useself::TransformOperation::*; match *self {
Translate(..) | Translate3D(..) | TranslateX(..) | TranslateY(..) | TranslateZ(..) => { true
},
_ => false,
}
}
/// Check if it is any scale function pubfn is_scale(&self) -> bool { useself::TransformOperation::*; match *self {
Scale(..) | Scale3D(..) | ScaleX(..) | ScaleY(..) | ScaleZ(..) => true,
_ => false,
}
}
}
/// Convert a length type into the absolute lengths. pubtrait ToAbsoluteLength { /// Returns the absolute length as pixel value. fn to_pixel_length(&self, containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()>;
}
impl ToAbsoluteLength for SpecifiedLength { // This returns Err(()) if there is any relative length or percentage. We use this when // parsing a transform list of DOMMatrix because we want to return a DOM Exception // if there is relative length. #[inline] fn to_pixel_length(&self, _containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()> { self.to_computed_pixel_length_without_context()
}
}
impl ToAbsoluteLength for SpecifiedLengthPercentage { // This returns Err(()) if there is any relative length or percentage. We use this when // parsing a transform list of DOMMatrix because we want to return a DOM Exception // if there is relative length. #[inline] fn to_pixel_length(&self, _containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()> { useself::SpecifiedLengthPercentage::*; match *self {
Length(len) => len.to_computed_pixel_length_without_context(),
Calc(ref calc) => calc.to_computed_pixel_length_without_context(),
Percentage(..) => Err(()),
}
}
}
/// Support the conversion to a 3d matrix. pubtrait ToMatrix { /// Check if it is a 3d transform function. fn is_3d(&self) -> bool;
/// Return the equivalent 3d matrix. fn to_3d_matrix(
&self,
reference_box: Option<&Rect<ComputedLength>>,
) -> Result<Transform3D<f64>, ()>;
}
/// A little helper to deal with both specified and computed angles. pubtrait ToRadians { /// Return the radians value as a 64-bit floating point value. fn radians64(&self) -> Result<f64, ()>;
}
impl ToRadians for SpecifiedAngle { #[inline] fn radians64(&self) -> Result<f64, ()> { let degrees = self.degrees().ok_or(())?;
Ok(computed::angle::Angle::from_degrees(degrees).radians64())
}
}
/// Convert a number type into a float. pubtrait ToFloat { /// Return the number as an f32, or Err(()) if the conversion is not possible. fn to_f32(&self) -> Result<f32, ()>;
/// Return the number as an f64, or Err(()) if the conversion is not possible. fn to_f64(&self) -> Result<f64, ()>;
}
/// If |reference_box| is None, we will drop the percent part from translate because /// we cannot resolve it without the layout info, for computed TransformOperation. /// However, for specified TransformOperation, we will return Err(()) if there is any relative /// lengths because the only caller, DOMMatrix, doesn't accept relative lengths. #[inline] fn to_3d_matrix(
&self,
reference_box: Option<&Rect<ComputedLength>>,
) -> Result<Transform3D<f64>, ()> { useself::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(ref ax, ref ay, ref az, ref theta) => { let theta = theta.radians64()?; let (ax, ay, az, theta) = get_normalized_vector_and_angle(
ax.to_f32()?,
ay.to_f32()?,
az.to_f32()?,
theta,
);
Transform3D::rotation(
ax as f64,
ay as f64,
az as f64,
euclid::Angle::radians(theta),
)
},
RotateX(ref theta) => { let theta = euclid::Angle::radians(theta.radians64()?);
Transform3D::rotation(1., 0., 0., theta)
},
RotateY(ref theta) => { let theta = euclid::Angle::radians(theta.radians64()?);
Transform3D::rotation(0., 1., 0., theta)
},
RotateZ(ref theta) | Rotate(ref 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(ref sx, ref sy, ref sz) => {
Transform3D::scale(sx.to_f64()?, sy.to_f64()?, sz.to_f64()?)
},
Scale(ref sx, ref sy) => Transform3D::scale(sx.to_f64()?, sy.to_f64()?, 1.),
ScaleX(ref s) => Transform3D::scale(s.to_f64()?, 1., 1.),
ScaleY(ref s) => Transform3D::scale(1., s.to_f64()?, 1.),
ScaleZ(ref s) => Transform3D::scale(1., 1., s.to_f64()?),
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(ref theta_x, ref theta_y) => Transform3D::skew(
euclid::Angle::radians(theta_x.radians64()?),
euclid::Angle::radians(theta_y.radians64()?),
),
SkewX(ref theta) => Transform3D::skew(
euclid::Angle::radians(theta.radians64()?),
euclid::Angle::radians(0.),
),
SkewY(ref theta) => Transform3D::skew(
euclid::Angle::radians(0.),
euclid::Angle::radians(theta.radians64()?),
),
Matrix3D(ref m) => m.clone().try_into()?,
Matrix(ref m) => m.clone().try_into()?,
InterpolateMatrix { .. } | AccumulateMatrix { .. } => { // TODO: Convert InterpolateMatrix/AccumulateMatrix into a valid Transform3D by // the reference box and do interpolation on these two Transform3D matrices. // Both Gecko and Servo don't support this for computing distance, and Servo // doesn't support animations on InterpolateMatrix/AccumulateMatrix, so // return an identity matrix. // Note: DOMMatrix doesn't go into this arm.
Transform3D::identity()
},
};
Ok(matrix)
}
}
impl<T: ToMatrix> Transform<T> { /// Return the equivalent 3d matrix of this transform list. /// /// We return a pair: the first one is the transform matrix, and the second one /// indicates if there is any 3d transform function in this transform list. #[cfg_attr(rustfmt, rustfmt_skip)] pubfn to_transform_3d_matrix(
&self,
reference_box: Option<&Rect<ComputedLength>>
) -> Result<(Transform3D<CSSFloat>, bool), ()> { Self::components_to_transform_3d_matrix(&self.0, reference_box)
}
/// Converts a series of components to a 3d matrix. #[cfg_attr(rustfmt, rustfmt_skip)] pubfn 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))
}
/// Same as Transform::to_transform_3d_matrix but a f64 version. pubfn 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)
}
/// Same as Transform::components_to_transform_3d_matrix but a f64 version. fn components_to_transform_3d_matrix_f64(
ops: &[T],
reference_box: Option<&Rect<ComputedLength>>,
) -> Result<(Transform3D<f64>, bool), ()> { // We intentionally use Transform3D<f64> during computation to avoid // error propagation because using f32 to compute triangle functions // (e.g. in rotation()) is not accurate enough. In Gecko, we also use // "double" to compute the triangle functions. Therefore, let's use // Transform3D<f64> during matrix computation and cast it into f32 in // the end. letmut transform = Transform3D::<f64>::identity(); letmut 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))
}
}
/// Return the transform matrix from a perspective length. #[inline] pubfn create_perspective_matrix(d: CSSFloat) -> Transform3D<CSSFloat> { if d.is_finite() {
Transform3D::perspective(d.max(1.))
} else {
Transform3D::identity()
}
}
/// Return the normalized direction vector and its angle for Rotate3D. pubfn get_normalized_vector_and_angle<T: Zero>(
x: CSSFloat,
y: CSSFloat,
z: CSSFloat,
angle: T,
) -> (CSSFloat, CSSFloat, CSSFloat, T) { usecrate::values::computed::transform::DirectionVector; use euclid::approxeq::ApproxEq; let vector = DirectionVector::new(x, y, z); if vector.square_length().approx_eq(&f32::zero()) { // https://www.w3.org/TR/css-transforms-1/#funcdef-rotate3d // A direction vector that cannot be normalized, such as [0, 0, 0], will cause the // rotation to not be applied, so we use identity matrix (i.e. rotate3d(0, 0, 1, 0)).
(0., 0., 1., T::zero())
} else { let vector = vector.robust_normalize();
(vector.x, vector.y, vector.z, angle)
}
}
/// A trait to check if the current 3D vector is parallel to the DirectionVector. /// This is especially for serialization on Rotate. pubtrait IsParallelTo { /// Returns true if this is parallel to the vector. fn is_parallel_to(&self, vector: &computed::transform::DirectionVector) -> bool;
}
impl<Number, Angle> ToCss for Rotate<Number, Angle> where
Number: Clone + PartialOrd + ToCss + Zero,
Angle: Clone + Neg<Output = Angle> + ToCss + Zero,
(Number, Number, Number): IsParallelTo,
{ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where
W: fmt::Write,
{ usecrate::values::computed::transform::DirectionVector; match *self {
Rotate::None => dest.write_str("none"),
Rotate::Rotate(ref angle) => angle.to_css(dest),
Rotate::Rotate3D(ref x, ref y, ref z, ref angle) => { // If the axis is parallel with the x or y axes, it must serialize as the // appropriate keyword. If a rotation about the z axis (that is, in 2D) is // specified, the property must serialize as just an <angle>. // // Note that if the axis is parallel to x/y/z but pointing in the opposite // direction, we need to negate the angle to maintain the correct meaning. // // https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization let v = (x.clone(), y.clone(), z.clone()); let (axis, angle) = if v.0.is_zero() && v.1.is_zero() && v.2.is_zero() { // The zero length vector is parallel to every other vector, so // is_parallel_to() returns true for it. However, it is definitely different // from x axis, y axis, or z axis, and it's meaningless to perform a rotation // using that direction vector. So we *have* to serialize it using that same // vector - we can't simplify to some theoretically parallel axis-aligned // vector.
(None, angle.clone())
} elseif v.is_parallel_to(&DirectionVector::new(1., 0., 0.)) {
(
Some("x "), if v.0 < Number::zero() {
-angle.clone()
} else {
angle.clone()
},
)
} elseif v.is_parallel_to(&DirectionVector::new(0., 1., 0.)) {
(
Some("y "), if v.1 < Number::zero() {
-angle.clone()
} else {
angle.clone()
},
)
} elseif v.is_parallel_to(&DirectionVector::new(0., 0., 1.)) { // When we're parallel to the z-axis, we can just serialize the angle. let angle = if v.2 < Number::zero() {
-angle.clone()
} else {
angle.clone()
}; return angle.to_css(dest);
} else {
(None, angle.clone())
}; 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,
ToTyped,
)] #[repr(C, u8)] /// A value of the `Scale` property /// /// <https://drafts.csswg.org/css-transforms-2/#individual-transforms> pubenum GenericScale<Number> { /// 'none'
None, /// '<number>{1,3}'
Scale(Number, Number, Number),
}
pubuseself::GenericScale as Scale;
impl<Number> ToCss for Scale<Number> where
Number: ToCss + PartialEq + Clone + ToFloat,
{ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where
W: fmt::Write,
{ match *self {
Scale::None => dest.write_str("none"),
Scale::Scale(ref x, ref y, ref z) => {
x.to_css(dest)?;
let serialize_z = z.to_f32() != Ok(1.0); if serialize_z || x != y {
dest.write_char(' ')?;
y.to_css(dest)?;
}
#[derive(
Clone,
Debug,
Deserialize,
MallocSizeOf,
PartialEq,
Serialize,
SpecifiedValueInfo,
ToAnimatedValue,
ToAnimatedZero,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
ToTyped,
)] #[repr(C, u8)] /// A value of the `translate` property /// /// https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization: /// /// If a 2d translation is specified, the property must serialize with only one /// or two values (per usual, if the second value is 0px, the default, it must /// be omitted when serializing; however if 0% is the second value, it is included). /// /// If a 3d translation is specified and the value can be expressed as 2d, we treat as 2d and /// serialize accoringly. Otherwise, we serialize all three values. /// https://github.com/w3c/csswg-drafts/issues/3305 /// /// <https://drafts.csswg.org/css-transforms-2/#individual-transforms> pubenum GenericTranslate<LengthPercentage, Length> where
LengthPercentage: Zero + ZeroNoPercent,
Length: Zero,
{ /// 'none'
None, /// <length-percentage> [ <length-percentage> <length>? ]?
Translate(
LengthPercentage, #[css(contextual_skip_if = "y_axis_and_z_axis_are_zero")] LengthPercentage, #[css(skip_if = "Zero::is_zero")] Length,
),
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.