layout: Add support for basic transform css properties (#35926)

Signed-off-by: Chocolate Pie <106949016+chocolate-pie@users.noreply.github.com>
Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
chocolate-pie 2025-03-14 23:46:20 +09:00 committed by GitHub
parent 7f2f51b59d
commit 455f4202c6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 57 additions and 63 deletions

View file

@ -19,10 +19,11 @@ use style::computed_values::mix_blend_mode::T as ComputedMixBlendMode;
use style::computed_values::overflow_x::T as ComputedOverflow;
use style::computed_values::position::T as ComputedPosition;
use style::properties::ComputedValues;
use style::values::computed::angle::Angle;
use style::values::computed::basic_shape::ClipPath;
use style::values::computed::{ClipRectOrAuto, Length};
use style::values::generics::box_::Perspective;
use style::values::generics::transform;
use style::values::generics::transform::{self, GenericRotate, GenericScale, GenericTranslate};
use style::values::specified::box_::DisplayOutside;
use webrender_api::units::{LayoutPoint, LayoutRect, LayoutTransform, LayoutVector2D};
use webrender_api::{self as wr, BorderRadius};
@ -39,7 +40,7 @@ use crate::fragment_tree::{
PositioningFragment, SpecificLayoutInfo,
};
use crate::geom::{AuOrAuto, PhysicalRect, PhysicalSides};
use crate::style_ext::ComputedValuesExt;
use crate::style_ext::{ComputedValuesExt, TransformExt};
#[derive(Clone)]
pub(crate) struct ContainingBlock {
@ -1635,17 +1636,37 @@ impl BoxFragment {
/// Returns the 4D matrix representing this fragment's transform.
pub fn calculate_transform_matrix(&self, border_rect: &Rect<Au>) -> Option<LayoutTransform> {
let list = &self.style.get_box().transform;
let length_rect = au_rect_to_length_rect(border_rect);
// https://drafts.csswg.org/css-transforms-2/#individual-transforms
let rotate = match self.style.clone_rotate() {
GenericRotate::Rotate(angle) => (0., 0., 1., angle),
GenericRotate::Rotate3D(x, y, z, angle) => (x, y, z, angle),
GenericRotate::None => (0., 0., 1., Angle::zero()),
};
let scale = match self.style.clone_scale() {
GenericScale::Scale(sx, sy, sz) => (sx, sy, sz),
GenericScale::None => (1., 1., 1.),
};
let translation = match self.style.clone_translate() {
GenericTranslate::Translate(x, y, z) => LayoutTransform::translation(
x.resolve(length_rect.size.width).px(),
y.resolve(length_rect.size.height).px(),
z.px(),
),
GenericTranslate::None => LayoutTransform::identity(),
};
let transform = LayoutTransform::from_untyped(
&list
.to_transform_3d_matrix(Some(&au_rect_to_length_rect(border_rect)))
.ok()?
.0,
);
let angle = euclid::Angle::radians(rotate.3.radians());
let transform_base = list.to_transform_3d_matrix(Some(&length_rect)).ok()?;
let transform = LayoutTransform::from_untyped(&transform_base.0)
.then_rotate(rotate.0, rotate.1, rotate.2, angle)
.then_scale(scale.0, scale.1, scale.2)
.then(&translation);
// WebRender will end up dividing by the scale value of this transform, so we
// want to ensure we don't feed it a divisor of 0.
assert_ne!(transform.m11, 0.);
assert_ne!(transform.m22, 0.);
if transform.m11 == 0. || transform.m22 == 0. {
return Some(LayoutTransform::identity());
}
let transform_origin = &self.style.get_box().transform_origin;
let transform_origin_x = transform_origin
@ -1658,18 +1679,7 @@ impl BoxFragment {
.to_f32_px();
let transform_origin_z = transform_origin.depth.px();
let pre_transform = LayoutTransform::translation(
transform_origin_x,
transform_origin_y,
transform_origin_z,
);
let post_transform = LayoutTransform::translation(
-transform_origin_x,
-transform_origin_y,
-transform_origin_z,
);
Some(post_transform.then(&transform).then(&pre_transform))
Some(transform.change_basis(transform_origin_x, transform_origin_y, transform_origin_z))
}
/// Returns the 4D matrix representing this fragment's perspective.
@ -1688,20 +1698,15 @@ impl BoxFragment {
.px(),
);
let pre_transform =
LayoutTransform::translation(perspective_origin.x, perspective_origin.y, 0.0);
let post_transform =
LayoutTransform::translation(-perspective_origin.x, -perspective_origin.y, 0.0);
let perspective_matrix = LayoutTransform::from_untyped(
&transform::create_perspective_matrix(length.px()),
);
Some(
post_transform
.then(&perspective_matrix)
.then(&pre_transform),
)
Some(perspective_matrix.change_basis(
perspective_origin.x,
perspective_origin.y,
0.0,
))
},
Perspective::None => None,
}

View file

@ -24,9 +24,11 @@ use style::values::computed::image::Image as ComputedImageLayer;
use style::values::computed::{AlignItems, BorderStyle, Color, Inset, LengthPercentage, Margin};
use style::values::generics::box_::Perspective;
use style::values::generics::position::{GenericAspectRatio, PreferredRatio};
use style::values::generics::transform::{GenericRotate, GenericScale, GenericTranslate};
use style::values::specified::align::AlignFlags;
use style::values::specified::{Overflow, WillChangeBits, box_ as stylo};
use webrender_api as wr;
use webrender_api::units::LayoutTransform;
use crate::dom_traversal::Contents;
use crate::fragment_tree::FragmentFlags;
@ -501,6 +503,9 @@ impl ComputedValuesExt for ComputedValues {
fn has_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool {
self.is_transformable(fragment_flags) &&
(!self.get_box().transform.0.is_empty() ||
self.get_box().scale != GenericScale::None ||
self.get_box().rotate != GenericRotate::None ||
self.get_box().translate != GenericTranslate::None ||
self.get_box().perspective != Perspective::None)
}
@ -1181,3 +1186,16 @@ impl Clamp for Au {
self.clamp_below_max(max).max(min)
}
}
pub(crate) trait TransformExt {
fn change_basis(&self, x: f32, y: f32, z: f32) -> Self;
}
impl TransformExt for LayoutTransform {
/// <https://drafts.csswg.org/css-transforms/#transformation-matrix-computation>
fn change_basis(&self, x: f32, y: f32, z: f32) -> Self {
let pre_translation = Self::translation(x, y, z);
let post_translation = Self::translation(-x, -y, -z);
post_translation.then(self).then(&pre_translation)
}
}

View file

@ -0,0 +1,2 @@
[flexbox-safe-overflow-position-006.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[change-translate-property.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[individual-transform-1.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[individual-transform-2a.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[individual-transform-2b.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[individual-transform-2c.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[individual-transform-2d.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[individual-transform-2e.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[scale-animation-with-var-001.html]
expected: FAIL

View file

@ -1,15 +0,0 @@
[transform-hit-testing.html]
[hit testing of rectangle with 'translate' and 'rotate']
expected: FAIL
[hit testing of rectangle with 'transform']
expected: FAIL
[hit testing of rectangle with 'translate' and 'rotate' and 'scale' and 'transform']
expected: FAIL
[hit testing of square with 'rotate']
expected: FAIL
[hit testing of square with 'scale']
expected: FAIL