Skip to content

Commit e4dc266

Browse files
committed
LibWeb: Reimplement transform interpolation according to spec
We had a partial implementation of transformation function interpolation that did not support numerical interpolation of simple functions (e.g. `scale(0)` -> `scale(1)`). This refactors the interpolation to follow the spec more closely. Gains us 267 WPT subtest passes in `css/css-transforms`. Fixes #6774.
1 parent 911ecf1 commit e4dc266

File tree

6 files changed

+610
-89
lines changed

6 files changed

+610
-89
lines changed

Libraries/LibWeb/CSS/Interpolation.cpp

Lines changed: 363 additions & 87 deletions
Large diffs are not rendered by default.

Libraries/LibWeb/CSS/Interpolation.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Optional<LengthPercentageOrAuto> interpolate_length_percentage_or_auto(Calculati
2929
RefPtr<StyleValue const> interpolate_value(DOM::Element&, CalculationContext const&, StyleValue const& from, StyleValue const& to, float delta, AllowDiscrete);
3030
RefPtr<StyleValue const> interpolate_repeatable_list(DOM::Element&, CalculationContext const&, StyleValue const& from, StyleValue const& to, float delta, AllowDiscrete);
3131
RefPtr<StyleValue const> interpolate_box_shadow(DOM::Element&, CalculationContext const&, StyleValue const& from, StyleValue const& to, float delta, AllowDiscrete);
32-
RefPtr<StyleValue const> interpolate_transform(DOM::Element&, StyleValue const& from, StyleValue const& to, float delta, AllowDiscrete);
32+
RefPtr<StyleValue const> interpolate_transform(DOM::Element&, CalculationContext const&, StyleValue const& from, StyleValue const& to, float delta, AllowDiscrete);
3333

3434
Color interpolate_color(Color from, Color to, float delta, ColorSyntax syntax);
3535

Libraries/LibWeb/CSS/StyleValues/TransformationStyleValue.cpp

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
* Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org>
55
* Copyright (c) 2022-2023, MacDue <macdue@dueutil.tech>
66
* Copyright (c) 2024, Steffen T. Larssen <dudedbz@gmail.com>
7+
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
78
*
89
* SPDX-License-Identifier: BSD-2-Clause
910
*/
1011

11-
#include "TransformationStyleValue.h"
1212
#include <AK/StringBuilder.h>
1313
#include <LibWeb/CSS/CSSMatrixComponent.h>
1414
#include <LibWeb/CSS/CSSPerspective.h>
@@ -23,14 +23,79 @@
2323
#include <LibWeb/CSS/PropertyID.h>
2424
#include <LibWeb/CSS/Serialize.h>
2525
#include <LibWeb/CSS/StyleValues/AngleStyleValue.h>
26+
#include <LibWeb/CSS/StyleValues/KeywordStyleValue.h>
2627
#include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
2728
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
2829
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
30+
#include <LibWeb/CSS/StyleValues/TransformationStyleValue.h>
2931
#include <LibWeb/CSS/Transformation.h>
3032
#include <LibWeb/Geometry/DOMMatrix.h>
3133

3234
namespace Web::CSS {
3335

36+
ValueComparingNonnullRefPtr<TransformationStyleValue const> TransformationStyleValue::identity_transformation(
37+
TransformFunction transform_function)
38+
{
39+
// https://drafts.csswg.org/css-transforms-1/#identity-transform-function
40+
// A transform function that is equivalent to a identity 4x4 matrix (see Mathematical Description of Transform
41+
// Functions). Examples for identity transform functions are translate(0), translateX(0), translateY(0), scale(1),
42+
// scaleX(1), scaleY(1), rotate(0), skew(0, 0), skewX(0), skewY(0) and matrix(1, 0, 0, 1, 0, 0).
43+
44+
// https://drafts.csswg.org/css-transforms-2/#identity-transform-function
45+
// In addition to the identity transform function in CSS Transforms, examples for identity transform functions
46+
// include translate3d(0, 0, 0), translateZ(0), scaleZ(1), rotate3d(1, 1, 1, 0), rotateX(0), rotateY(0), rotateZ(0)
47+
// and matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1). A special case is perspective: perspective(none).
48+
// The value of m34 becomes infinitesimal small and the transform function is therefore assumed to be equal to the
49+
// identity matrix.
50+
51+
auto identity_parameters = [&] -> StyleValueVector {
52+
auto const number_zero = NumberStyleValue::create(0.);
53+
auto const number_one = NumberStyleValue::create(1.);
54+
55+
switch (transform_function) {
56+
case TransformFunction::Matrix:
57+
return { number_one, number_zero, number_zero, number_one, number_zero, number_zero };
58+
case TransformFunction::Matrix3d:
59+
return { number_one, number_zero, number_zero, number_zero,
60+
number_zero, number_one, number_zero, number_zero,
61+
number_zero, number_zero, number_one, number_zero,
62+
number_zero, number_zero, number_zero, number_one };
63+
case TransformFunction::Perspective:
64+
return { KeywordStyleValue::create(Keyword::None) };
65+
case TransformFunction::Rotate:
66+
case TransformFunction::RotateX:
67+
case TransformFunction::RotateY:
68+
case TransformFunction::RotateZ:
69+
return { AngleStyleValue::create(Angle::make_degrees(0.)) };
70+
case TransformFunction::Rotate3d:
71+
return { number_one, number_one, number_one, AngleStyleValue::create(Angle::make_degrees(0.)) };
72+
case TransformFunction::Skew:
73+
case TransformFunction::SkewX:
74+
case TransformFunction::SkewY:
75+
case TransformFunction::Translate:
76+
case TransformFunction::TranslateX:
77+
case TransformFunction::TranslateY:
78+
case TransformFunction::TranslateZ:
79+
return { LengthStyleValue::create(Length::make_px(0.)) };
80+
case TransformFunction::Translate3d:
81+
return {
82+
LengthStyleValue::create(Length::make_px(0.)),
83+
LengthStyleValue::create(Length::make_px(0.)),
84+
LengthStyleValue::create(Length::make_px(0.)),
85+
};
86+
case TransformFunction::Scale:
87+
case TransformFunction::ScaleX:
88+
case TransformFunction::ScaleY:
89+
case TransformFunction::ScaleZ:
90+
return { number_one };
91+
case TransformFunction::Scale3d:
92+
return { number_one, number_one, number_one };
93+
}
94+
VERIFY_NOT_REACHED();
95+
};
96+
return create(PropertyID::Transform, transform_function, identity_parameters());
97+
}
98+
3499
Transformation TransformationStyleValue::to_transformation() const
35100
{
36101
auto function_metadata = transform_function_metadata(m_properties.transform_function);

Libraries/LibWeb/CSS/StyleValues/TransformationStyleValue.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ class TransformationStyleValue final : public StyleValueWithDefaultOperators<Tra
2222
}
2323
virtual ~TransformationStyleValue() override = default;
2424

25+
static ValueComparingNonnullRefPtr<TransformationStyleValue const> identity_transformation(TransformFunction);
26+
2527
TransformFunction transform_function() const { return m_properties.transform_function; }
2628
StyleValueVector const& values() const { return m_properties.values; }
2729

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
Harness status: OK
2+
3+
Found 82 tests
4+
5+
64 Pass
6+
18 Fail
7+
Pass Interpolation between translateX(0px) and translateX(50px) gives the correct computed value halfway according to computedStyleMap.
8+
Pass Interpolation between translateX(0px) and translateX(50px) gives the correct computed value halfway according to computedStyleMap with zoom active.
9+
Pass Interpolation between translateX(0%) and translateX(50%) gives the correct computed value halfway according to computedStyleMap.
10+
Pass Interpolation between translateX(0%) and translateX(50%) gives the correct computed value halfway according to computedStyleMap with zoom active.
11+
Fail Interpolation between translateY(0%) and translateX(50%) gives the correct computed value halfway according to computedStyleMap.
12+
Fail Interpolation between translateY(0%) and translateX(50%) gives the correct computed value halfway according to computedStyleMap with zoom active.
13+
Pass Interpolation between translateX(50px) and translateY(50px) gives the correct computed value halfway according to computedStyleMap.
14+
Pass Interpolation between translateX(50px) and translateY(50px) gives the correct computed value halfway according to computedStyleMap with zoom active.
15+
Pass Interpolation between translateX(50px) and translateZ(50px) gives the correct computed value halfway according to computedStyleMap.
16+
Pass Interpolation between translateX(50px) and translateZ(50px) gives the correct computed value halfway according to computedStyleMap with zoom active.
17+
Pass Interpolation between translateZ(50px) and translateX(50px) gives the correct computed value halfway according to computedStyleMap.
18+
Pass Interpolation between translateZ(50px) and translateX(50px) gives the correct computed value halfway according to computedStyleMap with zoom active.
19+
Pass Interpolation between translateZ(-50px) and translateZ(50px) gives the correct computed value halfway according to computedStyleMap.
20+
Pass Interpolation between translateZ(-50px) and translateZ(50px) gives the correct computed value halfway according to computedStyleMap with zoom active.
21+
Pass Interpolation between translate(0%) and translate(50%) gives the correct computed value halfway according to computedStyleMap.
22+
Pass Interpolation between translate(0%) and translate(50%) gives the correct computed value halfway according to computedStyleMap with zoom active.
23+
Pass Interpolation between translate(50%) and translate(100%, 50%) gives the correct computed value halfway according to computedStyleMap.
24+
Pass Interpolation between translate(50%) and translate(100%, 50%) gives the correct computed value halfway according to computedStyleMap with zoom active.
25+
Pass Interpolation between translate(0%, 50%) and translate(50%, 100%) gives the correct computed value halfway according to computedStyleMap.
26+
Pass Interpolation between translate(0%, 50%) and translate(50%, 100%) gives the correct computed value halfway according to computedStyleMap with zoom active.
27+
Pass Interpolation between translate3d(0,0,-50px) and translateZ(50px) gives the correct computed value halfway according to computedStyleMap.
28+
Pass Interpolation between translate3d(0,0,-50px) and translateZ(50px) gives the correct computed value halfway according to computedStyleMap with zoom active.
29+
Pass Interpolation between translate(50px, 0px) and translate(100px, 0px) gives the correct computed value halfway according to computedStyleMap.
30+
Pass Interpolation between translate(50px, 0px) and translate(100px, 0px) gives the correct computed value halfway according to computedStyleMap with zoom active.
31+
Pass Interpolation between translate(50px, -50px) and translate(100px, 50px) gives the correct computed value halfway according to computedStyleMap.
32+
Pass Interpolation between translate(50px, -50px) and translate(100px, 50px) gives the correct computed value halfway according to computedStyleMap with zoom active.
33+
Pass Interpolation between rotate(30deg) and rotate(90deg) gives the correct computed value halfway according to computedStyleMap.
34+
Pass Interpolation between rotate(30deg) and rotate(90deg) gives the correct computed value halfway according to computedStyleMap with zoom active.
35+
Pass Interpolation between rotateZ(30deg) and rotateZ(90deg) gives the correct computed value halfway according to computedStyleMap.
36+
Pass Interpolation between rotateZ(30deg) and rotateZ(90deg) gives the correct computed value halfway according to computedStyleMap with zoom active.
37+
Fail Interpolation between rotate(0deg) and rotateZ(90deg) gives the correct computed value halfway according to computedStyleMap.
38+
Fail Interpolation between rotate(0deg) and rotateZ(90deg) gives the correct computed value halfway according to computedStyleMap with zoom active.
39+
Fail Interpolation between rotateX(0deg) and rotateX(90deg) gives the correct computed value halfway according to computedStyleMap.
40+
Fail Interpolation between rotateX(0deg) and rotateX(90deg) gives the correct computed value halfway according to computedStyleMap with zoom active.
41+
Fail Interpolation between rotate(0deg) and rotateX(90deg) gives the correct computed value halfway according to computedStyleMap.
42+
Fail Interpolation between rotate(0deg) and rotateX(90deg) gives the correct computed value halfway according to computedStyleMap with zoom active.
43+
Fail Interpolation between scale(1) and scale(2) gives the correct computed value halfway according to computedStyleMap.
44+
Fail Interpolation between scale(1) and scale(2) gives the correct computed value halfway according to computedStyleMap with zoom active.
45+
Pass Interpolation between scale(1, 3) and scale(2) gives the correct computed value halfway according to computedStyleMap.
46+
Pass Interpolation between scale(1, 3) and scale(2) gives the correct computed value halfway according to computedStyleMap with zoom active.
47+
Pass Interpolation between scaleX(1) and scaleX(2) gives the correct computed value halfway according to computedStyleMap.
48+
Pass Interpolation between scaleX(1) and scaleX(2) gives the correct computed value halfway according to computedStyleMap with zoom active.
49+
Pass Interpolation between scaleY(1) and scaleY(2) gives the correct computed value halfway according to computedStyleMap.
50+
Pass Interpolation between scaleY(1) and scaleY(2) gives the correct computed value halfway according to computedStyleMap with zoom active.
51+
Pass Interpolation between scaleZ(1) and scaleZ(2) gives the correct computed value halfway according to computedStyleMap.
52+
Pass Interpolation between scaleZ(1) and scaleZ(2) gives the correct computed value halfway according to computedStyleMap with zoom active.
53+
Fail Interpolation between scaleX(2) and scaleY(2) gives the correct computed value halfway according to computedStyleMap.
54+
Fail Interpolation between scaleX(2) and scaleY(2) gives the correct computed value halfway according to computedStyleMap with zoom active.
55+
Pass Interpolation between scaleX(2) and scaleY(3) gives the correct computed value halfway according to computedStyleMap.
56+
Pass Interpolation between scaleX(2) and scaleY(3) gives the correct computed value halfway according to computedStyleMap with zoom active.
57+
Pass Interpolation between scaleZ(1) and scale(2) gives the correct computed value halfway according to computedStyleMap.
58+
Pass Interpolation between scaleZ(1) and scale(2) gives the correct computed value halfway according to computedStyleMap with zoom active.
59+
Pass Interpolation between scale(1, 2) and scale(3, 4) gives the correct computed value halfway according to computedStyleMap.
60+
Pass Interpolation between scale(1, 2) and scale(3, 4) gives the correct computed value halfway according to computedStyleMap with zoom active.
61+
Pass Interpolation between scale3d(1, 2, 3) and scale3d(4, 5, 6) gives the correct computed value halfway according to computedStyleMap.
62+
Pass Interpolation between scale3d(1, 2, 3) and scale3d(4, 5, 6) gives the correct computed value halfway according to computedStyleMap with zoom active.
63+
Pass Interpolation between scale3d(1, 2, 3) and scale(4, 5) gives the correct computed value halfway according to computedStyleMap.
64+
Pass Interpolation between scale3d(1, 2, 3) and scale(4, 5) gives the correct computed value halfway according to computedStyleMap with zoom active.
65+
Pass Interpolation between scale(1, 2) and scale3d(3, 4, 5) gives the correct computed value halfway according to computedStyleMap.
66+
Pass Interpolation between scale(1, 2) and scale3d(3, 4, 5) gives the correct computed value halfway according to computedStyleMap with zoom active.
67+
Pass Interpolation between skewX(0deg) and skewX(60deg) gives the correct computed value halfway according to computedStyleMap.
68+
Pass Interpolation between skewX(0deg) and skewX(60deg) gives the correct computed value halfway according to computedStyleMap with zoom active.
69+
Pass Interpolation between skewX(0deg) and skewX(90deg) gives the correct computed value halfway according to computedStyleMap.
70+
Pass Interpolation between skewX(0deg) and skewX(90deg) gives the correct computed value halfway according to computedStyleMap with zoom active.
71+
Pass Interpolation between skewX(0deg) and skewX(180deg) gives the correct computed value halfway according to computedStyleMap.
72+
Pass Interpolation between skewX(0deg) and skewX(180deg) gives the correct computed value halfway according to computedStyleMap with zoom active.
73+
Pass Interpolation between skew(0deg, 0deg) and skew(60deg, 60deg) gives the correct computed value halfway according to computedStyleMap.
74+
Pass Interpolation between skew(0deg, 0deg) and skew(60deg, 60deg) gives the correct computed value halfway according to computedStyleMap with zoom active.
75+
Pass Interpolation between skew(45deg, 0deg) and skew(0deg, 45deg) gives the correct computed value halfway according to computedStyleMap.
76+
Pass Interpolation between skew(45deg, 0deg) and skew(0deg, 45deg) gives the correct computed value halfway according to computedStyleMap with zoom active.
77+
Fail Interpolation between perspective(10px) and perspective(2.5px) gives the correct computed value halfway according to computedStyleMap.
78+
Fail Interpolation between perspective(10px) and perspective(2.5px) gives the correct computed value halfway according to computedStyleMap with zoom active.
79+
Fail Interpolation between perspective(10px) and perspective(none) gives the correct computed value halfway according to computedStyleMap.
80+
Fail Interpolation between perspective(10px) and perspective(none) gives the correct computed value halfway according to computedStyleMap with zoom active.
81+
Fail Interpolation between perspective(none) and perspective(none) gives the correct computed value halfway according to computedStyleMap.
82+
Fail Interpolation between perspective(none) and perspective(none) gives the correct computed value halfway according to computedStyleMap with zoom active.
83+
Pass Interpolation between matrix(2, 0, 0, 2, 10, 30) and matrix(4, 0, 0, 6, 14, 10) gives the correct computed value halfway according to computedStyleMap.
84+
Pass Interpolation between matrix(2, 0, 0, 2, 10, 30) and matrix(4, 0, 0, 6, 14, 10) gives the correct computed value halfway according to computedStyleMap with zoom active.
85+
Pass Interpolation between matrix3d(1, 0, 0, 0, 0, 4, 0, 0, 0, 0, 1, 0, 5, 10, 4, 1) and matrix3d(3, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, -11, 2, 2, 1) gives the correct computed value halfway according to computedStyleMap.
86+
Pass Interpolation between matrix3d(1, 0, 0, 0, 0, 4, 0, 0, 0, 0, 1, 0, 5, 10, 4, 1) and matrix3d(3, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, -11, 2, 2, 1) gives the correct computed value halfway according to computedStyleMap with zoom active.
87+
Pass Interpolation between matrix3d(1, 0, 0, 3, 0, 1, 0, 2, 0, 0, 1, 8, 0, 0, 0, 1) and matrix3d(1, 0, 0, 5, 0, 1, 0, 8, 0, 0, 1, 14, 0, 0, 0, 1) gives the correct computed value halfway according to computedStyleMap.
88+
Pass Interpolation between matrix3d(1, 0, 0, 3, 0, 1, 0, 2, 0, 0, 1, 8, 0, 0, 0, 1) and matrix3d(1, 0, 0, 5, 0, 1, 0, 8, 0, 0, 1, 14, 0, 0, 0, 1) gives the correct computed value halfway according to computedStyleMap with zoom active.

0 commit comments

Comments
 (0)