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::overflow_x::T as ComputedOverflow;
use style::computed_values::position::T as ComputedPosition; use style::computed_values::position::T as ComputedPosition;
use style::properties::ComputedValues; use style::properties::ComputedValues;
use style::values::computed::angle::Angle;
use style::values::computed::basic_shape::ClipPath; use style::values::computed::basic_shape::ClipPath;
use style::values::computed::{ClipRectOrAuto, Length}; use style::values::computed::{ClipRectOrAuto, Length};
use style::values::generics::box_::Perspective; 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 style::values::specified::box_::DisplayOutside;
use webrender_api::units::{LayoutPoint, LayoutRect, LayoutTransform, LayoutVector2D}; use webrender_api::units::{LayoutPoint, LayoutRect, LayoutTransform, LayoutVector2D};
use webrender_api::{self as wr, BorderRadius}; use webrender_api::{self as wr, BorderRadius};
@ -39,7 +40,7 @@ use crate::fragment_tree::{
PositioningFragment, SpecificLayoutInfo, PositioningFragment, SpecificLayoutInfo,
}; };
use crate::geom::{AuOrAuto, PhysicalRect, PhysicalSides}; use crate::geom::{AuOrAuto, PhysicalRect, PhysicalSides};
use crate::style_ext::ComputedValuesExt; use crate::style_ext::{ComputedValuesExt, TransformExt};
#[derive(Clone)] #[derive(Clone)]
pub(crate) struct ContainingBlock { pub(crate) struct ContainingBlock {
@ -1635,17 +1636,37 @@ impl BoxFragment {
/// Returns the 4D matrix representing this fragment's transform. /// Returns the 4D matrix representing this fragment's transform.
pub fn calculate_transform_matrix(&self, border_rect: &Rect<Au>) -> Option<LayoutTransform> { pub fn calculate_transform_matrix(&self, border_rect: &Rect<Au>) -> Option<LayoutTransform> {
let list = &self.style.get_box().transform; 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( let angle = euclid::Angle::radians(rotate.3.radians());
&list let transform_base = list.to_transform_3d_matrix(Some(&length_rect)).ok()?;
.to_transform_3d_matrix(Some(&au_rect_to_length_rect(border_rect))) let transform = LayoutTransform::from_untyped(&transform_base.0)
.ok()? .then_rotate(rotate.0, rotate.1, rotate.2, angle)
.0, .then_scale(scale.0, scale.1, scale.2)
); .then(&translation);
// WebRender will end up dividing by the scale value of this transform, so we // 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. // want to ensure we don't feed it a divisor of 0.
assert_ne!(transform.m11, 0.); if transform.m11 == 0. || transform.m22 == 0. {
assert_ne!(transform.m22, 0.); return Some(LayoutTransform::identity());
}
let transform_origin = &self.style.get_box().transform_origin; let transform_origin = &self.style.get_box().transform_origin;
let transform_origin_x = transform_origin let transform_origin_x = transform_origin
@ -1658,18 +1679,7 @@ impl BoxFragment {
.to_f32_px(); .to_f32_px();
let transform_origin_z = transform_origin.depth.px(); let transform_origin_z = transform_origin.depth.px();
let pre_transform = LayoutTransform::translation( Some(transform.change_basis(transform_origin_x, transform_origin_y, transform_origin_z))
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))
} }
/// Returns the 4D matrix representing this fragment's perspective. /// Returns the 4D matrix representing this fragment's perspective.
@ -1688,20 +1698,15 @@ impl BoxFragment {
.px(), .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( let perspective_matrix = LayoutTransform::from_untyped(
&transform::create_perspective_matrix(length.px()), &transform::create_perspective_matrix(length.px()),
); );
Some( Some(perspective_matrix.change_basis(
post_transform perspective_origin.x,
.then(&perspective_matrix) perspective_origin.y,
.then(&pre_transform), 0.0,
) ))
}, },
Perspective::None => None, 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::computed::{AlignItems, BorderStyle, Color, Inset, LengthPercentage, Margin};
use style::values::generics::box_::Perspective; use style::values::generics::box_::Perspective;
use style::values::generics::position::{GenericAspectRatio, PreferredRatio}; 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::align::AlignFlags;
use style::values::specified::{Overflow, WillChangeBits, box_ as stylo}; use style::values::specified::{Overflow, WillChangeBits, box_ as stylo};
use webrender_api as wr; use webrender_api as wr;
use webrender_api::units::LayoutTransform;
use crate::dom_traversal::Contents; use crate::dom_traversal::Contents;
use crate::fragment_tree::FragmentFlags; use crate::fragment_tree::FragmentFlags;
@ -501,6 +503,9 @@ impl ComputedValuesExt for ComputedValues {
fn has_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool { fn has_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool {
self.is_transformable(fragment_flags) && self.is_transformable(fragment_flags) &&
(!self.get_box().transform.0.is_empty() || (!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) self.get_box().perspective != Perspective::None)
} }
@ -1181,3 +1186,16 @@ impl Clamp for Au {
self.clamp_below_max(max).max(min) 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