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.
This commit is contained in:
Jelle Raaijmakers 2025-11-14 13:41:32 +01:00 committed by Jelle Raaijmakers
parent 911ecf1450
commit e4dc2663ba
Notes: github-actions[bot] 2025-11-18 13:37:43 +00:00
6 changed files with 610 additions and 89 deletions

View file

@ -4,11 +4,11 @@
* Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org>
* Copyright (c) 2022-2023, MacDue <macdue@dueutil.tech>
* Copyright (c) 2024, Steffen T. Larssen <dudedbz@gmail.com>
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "TransformationStyleValue.h"
#include <AK/StringBuilder.h>
#include <LibWeb/CSS/CSSMatrixComponent.h>
#include <LibWeb/CSS/CSSPerspective.h>
@ -23,14 +23,79 @@
#include <LibWeb/CSS/PropertyID.h>
#include <LibWeb/CSS/Serialize.h>
#include <LibWeb/CSS/StyleValues/AngleStyleValue.h>
#include <LibWeb/CSS/StyleValues/KeywordStyleValue.h>
#include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
#include <LibWeb/CSS/StyleValues/TransformationStyleValue.h>
#include <LibWeb/CSS/Transformation.h>
#include <LibWeb/Geometry/DOMMatrix.h>
namespace Web::CSS {
ValueComparingNonnullRefPtr<TransformationStyleValue const> TransformationStyleValue::identity_transformation(
TransformFunction transform_function)
{
// https://drafts.csswg.org/css-transforms-1/#identity-transform-function
// A transform function that is equivalent to a identity 4x4 matrix (see Mathematical Description of Transform
// Functions). Examples for identity transform functions are translate(0), translateX(0), translateY(0), scale(1),
// scaleX(1), scaleY(1), rotate(0), skew(0, 0), skewX(0), skewY(0) and matrix(1, 0, 0, 1, 0, 0).
// https://drafts.csswg.org/css-transforms-2/#identity-transform-function
// In addition to the identity transform function in CSS Transforms, examples for identity transform functions
// include translate3d(0, 0, 0), translateZ(0), scaleZ(1), rotate3d(1, 1, 1, 0), rotateX(0), rotateY(0), rotateZ(0)
// 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).
// The value of m34 becomes infinitesimal small and the transform function is therefore assumed to be equal to the
// identity matrix.
auto identity_parameters = [&] -> StyleValueVector {
auto const number_zero = NumberStyleValue::create(0.);
auto const number_one = NumberStyleValue::create(1.);
switch (transform_function) {
case TransformFunction::Matrix:
return { number_one, number_zero, number_zero, number_one, number_zero, number_zero };
case TransformFunction::Matrix3d:
return { number_one, number_zero, number_zero, number_zero,
number_zero, number_one, number_zero, number_zero,
number_zero, number_zero, number_one, number_zero,
number_zero, number_zero, number_zero, number_one };
case TransformFunction::Perspective:
return { KeywordStyleValue::create(Keyword::None) };
case TransformFunction::Rotate:
case TransformFunction::RotateX:
case TransformFunction::RotateY:
case TransformFunction::RotateZ:
return { AngleStyleValue::create(Angle::make_degrees(0.)) };
case TransformFunction::Rotate3d:
return { number_one, number_one, number_one, AngleStyleValue::create(Angle::make_degrees(0.)) };
case TransformFunction::Skew:
case TransformFunction::SkewX:
case TransformFunction::SkewY:
case TransformFunction::Translate:
case TransformFunction::TranslateX:
case TransformFunction::TranslateY:
case TransformFunction::TranslateZ:
return { LengthStyleValue::create(Length::make_px(0.)) };
case TransformFunction::Translate3d:
return {
LengthStyleValue::create(Length::make_px(0.)),
LengthStyleValue::create(Length::make_px(0.)),
LengthStyleValue::create(Length::make_px(0.)),
};
case TransformFunction::Scale:
case TransformFunction::ScaleX:
case TransformFunction::ScaleY:
case TransformFunction::ScaleZ:
return { number_one };
case TransformFunction::Scale3d:
return { number_one, number_one, number_one };
}
VERIFY_NOT_REACHED();
};
return create(PropertyID::Transform, transform_function, identity_parameters());
}
Transformation TransformationStyleValue::to_transformation() const
{
auto function_metadata = transform_function_metadata(m_properties.transform_function);

View file

@ -22,6 +22,8 @@ public:
}
virtual ~TransformationStyleValue() override = default;
static ValueComparingNonnullRefPtr<TransformationStyleValue const> identity_transformation(TransformFunction);
TransformFunction transform_function() const { return m_properties.transform_function; }
StyleValueVector const& values() const { return m_properties.values; }