LibWeb/CSS: Remove Transformation in favor of TransformationStyleValue

The Transformation class wasn't really accomplishing anything. It still
had to store StyleValues, so it was basically the same as
TransformationStyleValue, with extra steps to convert from one to the
other. So... let's just use TransformationStyleValue instead!

Apart from moving code around, the behavior has changed a bit. We now
actually acknowledge unresolvable parameters and return an error when
we try to produce a matrix from them. Previously we just skipped over
them, which was pretty wrong. This gets us an extra pass in the
typed-om test.

We also get some slightly different results with our transform
serialization, because we're not converting to CSSPixels and back.
This commit is contained in:
Sam Atkins 2025-12-17 16:40:48 +00:00 committed by Jelle Raaijmakers
parent 6b5836644a
commit c446281844
21 changed files with 344 additions and 397 deletions

View file

@ -289,7 +289,6 @@ set(SOURCES
CSS/SyntaxHighlighter/SyntaxHighlighter.cpp
CSS/SystemColor.cpp
CSS/Time.cpp
CSS/Transformation.cpp
CSS/TransitionEvent.cpp
CSS/URL.cpp
CSS/ValueType.cpp

View file

@ -811,8 +811,8 @@ RefPtr<StyleValue const> CSSStyleProperties::style_value_for_computed_property(L
// 2. Post-multiply all <transform-function>s in <transform-list> to transform.
VERIFY(layout_node.first_paintable());
auto const& paintable_box = as<Painting::PaintableBox const>(*layout_node.first_paintable());
for (auto transformation : transformations) {
transform = transform * transformation.to_matrix(paintable_box).release_value();
for (auto const& transformation : transformations) {
transform = transform * transformation->to_matrix(paintable_box).release_value();
}
// https://drafts.csswg.org/css-transforms-1/#2d-matrix

View file

@ -788,7 +788,7 @@ JustifySelf ComputedProperties::justify_self() const
return keyword_to_justify_self(value.to_keyword()).release_value();
}
Vector<Transformation> ComputedProperties::transformations_for_style_value(StyleValue const& value)
Vector<NonnullRefPtr<TransformationStyleValue const>> ComputedProperties::transformations_for_style_value(StyleValue const& value)
{
if (value.is_keyword() && value.to_keyword() == Keyword::None)
return {};
@ -797,43 +797,41 @@ Vector<Transformation> ComputedProperties::transformations_for_style_value(Style
return {};
auto& list = value.as_value_list();
Vector<Transformation> transformations;
for (auto& it : list.values()) {
if (!it->is_transformation())
return {};
transformations.append(it->as_transformation().to_transformation());
Vector<NonnullRefPtr<TransformationStyleValue const>> transformations;
for (auto const& transform_value : list.values()) {
VERIFY(transform_value->is_transformation());
transformations.append(transform_value->as_transformation());
}
return transformations;
}
Vector<Transformation> ComputedProperties::transformations() const
Vector<NonnullRefPtr<TransformationStyleValue const>> ComputedProperties::transformations() const
{
return transformations_for_style_value(property(PropertyID::Transform));
}
Optional<Transformation> ComputedProperties::rotate() const
RefPtr<TransformationStyleValue const> ComputedProperties::rotate() const
{
auto const& value = property(PropertyID::Rotate);
if (!value.is_transformation())
return {};
return value.as_transformation().to_transformation();
return value.as_transformation();
}
Optional<Transformation> ComputedProperties::translate() const
RefPtr<TransformationStyleValue const> ComputedProperties::translate() const
{
auto const& value = property(PropertyID::Translate);
if (!value.is_transformation())
return {};
return value.as_transformation().to_transformation();
return value.as_transformation();
}
Optional<Transformation> ComputedProperties::scale() const
RefPtr<TransformationStyleValue const> ComputedProperties::scale() const
{
auto const& value = property(PropertyID::Scale);
if (!value.is_transformation())
return {};
return value.as_transformation().to_transformation();
return value.as_transformation();
}
TransformBox ComputedProperties::transform_box() const

View file

@ -209,14 +209,14 @@ public:
Display display_before_box_type_transformation() const;
void set_display_before_box_type_transformation(Display value);
static Vector<Transformation> transformations_for_style_value(StyleValue const& value);
Vector<Transformation> transformations() const;
static Vector<NonnullRefPtr<TransformationStyleValue const>> transformations_for_style_value(StyleValue const& value);
Vector<NonnullRefPtr<TransformationStyleValue const>> transformations() const;
TransformBox transform_box() const;
TransformOrigin transform_origin() const;
TransformStyle transform_style() const;
Optional<Transformation> rotate() const;
Optional<Transformation> translate() const;
Optional<Transformation> scale() const;
RefPtr<TransformationStyleValue const> rotate() const;
RefPtr<TransformationStyleValue const> translate() const;
RefPtr<TransformationStyleValue const> scale() const;
Optional<CSSPixels> perspective() const;
Position perspective_origin() const;

View file

@ -10,7 +10,6 @@
#include <AK/FlyString.h>
#include <AK/HashMap.h>
#include <AK/Optional.h>
#include <LibGfx/Filter.h>
#include <LibGfx/Font/FontVariant.h>
#include <LibGfx/FontCascadeList.h>
#include <LibGfx/ScalingMode.h>
@ -32,7 +31,7 @@
#include <LibWeb/CSS/StyleValues/CursorStyleValue.h>
#include <LibWeb/CSS/StyleValues/ImageStyleValue.h>
#include <LibWeb/CSS/StyleValues/ShadowStyleValue.h>
#include <LibWeb/CSS/Transformation.h>
#include <LibWeb/CSS/StyleValues/TransformationStyleValue.h>
#include <LibWeb/CSS/URL.h>
namespace Web::CSS {
@ -648,13 +647,13 @@ public:
LengthPercentage const& x() const { return m_noninherited.x; }
LengthPercentage const& y() const { return m_noninherited.y; }
Vector<Transformation> const& transformations() const { return m_noninherited.transformations; }
Vector<NonnullRefPtr<TransformationStyleValue const>> const& transformations() const { return m_noninherited.transformations; }
TransformBox const& transform_box() const { return m_noninherited.transform_box; }
TransformOrigin const& transform_origin() const { return m_noninherited.transform_origin; }
TransformStyle const& transform_style() const { return m_noninherited.transform_style; }
Optional<Transformation> const& rotate() const { return m_noninherited.rotate; }
Optional<Transformation> const& translate() const { return m_noninherited.translate; }
Optional<Transformation> const& scale() const { return m_noninherited.scale; }
RefPtr<TransformationStyleValue const> const& rotate() const { return m_noninherited.rotate; }
RefPtr<TransformationStyleValue const> const& translate() const { return m_noninherited.translate; }
RefPtr<TransformationStyleValue const> const& scale() const { return m_noninherited.scale; }
Optional<CSSPixels> const& perspective() const { return m_noninherited.perspective; }
Position const& perspective_origin() const { return m_noninherited.perspective_origin; }
@ -813,7 +812,7 @@ protected:
Overflow overflow_y { InitialValues::overflow() };
float opacity { InitialValues::opacity() };
Vector<ShadowData> box_shadow {};
Vector<Transformation> transformations {};
Vector<NonnullRefPtr<TransformationStyleValue const>> transformations {};
TransformBox transform_box { InitialValues::transform_box() };
TransformOrigin transform_origin {};
TransformStyle transform_style { InitialValues::transform_style() };
@ -856,9 +855,9 @@ protected:
Optional<FlyString> view_transition_name;
TouchActionData touch_action;
Optional<Transformation> rotate;
Optional<Transformation> translate;
Optional<Transformation> scale;
RefPtr<TransformationStyleValue const> rotate;
RefPtr<TransformationStyleValue const> translate;
RefPtr<TransformationStyleValue const> scale;
Optional<CSSPixels> perspective;
Position perspective_origin;
@ -1011,15 +1010,15 @@ public:
void set_justify_items(JustifyItems value) { m_noninherited.justify_items = value; }
void set_justify_self(JustifySelf value) { m_noninherited.justify_self = value; }
void set_box_shadow(Vector<ShadowData>&& value) { m_noninherited.box_shadow = move(value); }
void set_rotate(Transformation value) { m_noninherited.rotate = move(value); }
void set_scale(Transformation value) { m_noninherited.scale = move(value); }
void set_rotate(RefPtr<TransformationStyleValue const> value) { m_noninherited.rotate = move(value); }
void set_scale(RefPtr<TransformationStyleValue const> value) { m_noninherited.scale = move(value); }
void set_perspective(Optional<CSSPixels> value) { m_noninherited.perspective = move(value); }
void set_perspective_origin(Position value) { m_noninherited.perspective_origin = move(value); }
void set_transformations(Vector<Transformation> value) { m_noninherited.transformations = move(value); }
void set_transformations(Vector<NonnullRefPtr<TransformationStyleValue const>> value) { m_noninherited.transformations = move(value); }
void set_transform_box(TransformBox value) { m_noninherited.transform_box = value; }
void set_transform_origin(TransformOrigin value) { m_noninherited.transform_origin = move(value); }
void set_transform_origin(TransformOrigin value) { m_noninherited.transform_origin = value; }
void set_transform_style(TransformStyle value) { m_noninherited.transform_style = value; }
void set_translate(Transformation value) { m_noninherited.translate = move(value); }
void set_translate(RefPtr<TransformationStyleValue const> value) { m_noninherited.translate = move(value); }
void set_box_sizing(BoxSizing value) { m_noninherited.box_sizing = value; }
void set_vertical_align(Variant<VerticalAlign, LengthPercentage> value) { m_noninherited.vertical_align = move(value); }
void set_visibility(Visibility value) { m_inherited.visibility = value; }

View file

@ -36,7 +36,6 @@
#include <LibWeb/CSS/StyleValues/TextIndentStyleValue.h>
#include <LibWeb/CSS/StyleValues/TimeStyleValue.h>
#include <LibWeb/CSS/StyleValues/TransformationStyleValue.h>
#include <LibWeb/CSS/Transformation.h>
#include <LibWeb/DOM/Element.h>
#include <LibWeb/Layout/Node.h>
#include <LibWeb/Painting/PaintableBox.h>
@ -1170,7 +1169,7 @@ RefPtr<StyleValue const> interpolate_transform(DOM::Element& element, Calculatio
return *box;
return {};
}();
parameters = matrix_to_style_value_vector(MUST(transform->to_transformation().to_matrix(paintable_box)));
parameters = matrix_to_style_value_vector(MUST(transform->to_matrix(paintable_box)));
}
return TransformationStyleValue::create(PropertyID::Transform, generic_function, move(parameters));
};
@ -1274,8 +1273,7 @@ RefPtr<StyleValue const> interpolate_transform(DOM::Element& element, Calculatio
auto post_multiply_remaining_transformations = [&paintable_box](size_t start_index, Vector<NonnullRefPtr<TransformationStyleValue const>> const& transformations) {
FloatMatrix4x4 result = FloatMatrix4x4::identity();
for (auto index = start_index; index < transformations.size(); ++index) {
auto transformation = transformations[index]->to_transformation();
auto transformation_matrix = transformation.to_matrix(paintable_box);
auto transformation_matrix = transformations[index]->to_matrix(paintable_box);
if (transformation_matrix.is_error()) {
dbgln("Unable to interpret a transformation's matrix; bailing out of interpolation.");
break;

View file

@ -4,6 +4,7 @@
* 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) 2024-2025, Bastiaan van der Plaat <bastiaan.v.d.plaat@gmail.com>
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
@ -23,13 +24,14 @@
#include <LibWeb/CSS/PropertyID.h>
#include <LibWeb/CSS/Serialize.h>
#include <LibWeb/CSS/StyleValues/AngleStyleValue.h>
#include <LibWeb/CSS/StyleValues/CalculatedStyleValue.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>
#include <LibWeb/Painting/PaintableBox.h>
namespace Web::CSS {
@ -96,50 +98,265 @@ ValueComparingNonnullRefPtr<TransformationStyleValue const> TransformationStyleV
return create(PropertyID::Transform, transform_function, identity_parameters());
}
Transformation TransformationStyleValue::to_transformation() const
ErrorOr<FloatMatrix4x4> TransformationStyleValue::to_matrix(Optional<Painting::PaintableBox const&> paintable_box) const
{
auto count = m_properties.values.size();
auto function_metadata = transform_function_metadata(m_properties.transform_function);
Vector<TransformValue> values;
values.ensure_capacity(m_properties.values.size());
size_t argument_index = 0;
for (auto& transformation_value : m_properties.values) {
auto const function_type = function_metadata.parameters[argument_index++].type;
if (transformation_value->is_calculated()) {
auto& calculated = transformation_value->as_calculated();
if (calculated.resolves_to_length()) {
values.unchecked_append(LengthPercentage { calculated });
} else if (calculated.resolves_to_number() || calculated.resolves_to_percentage()) {
values.unchecked_append(NumberPercentage { calculated });
} else if (calculated.resolves_to_angle()) {
values.unchecked_append(AngleOrCalculated { calculated });
} else {
dbgln("FIXME: Unsupported calc value in transform! {}", calculated.to_string(SerializationMode::Normal));
auto length_to_px = [&](Length const& length) -> ErrorOr<float> {
if (paintable_box.has_value())
return length.to_px(paintable_box->layout_node()).to_float();
if (length.is_absolute())
return length.absolute_length_to_px().to_float();
return Error::from_string_literal("Transform contains non absolute units");
};
auto get_value = [&](size_t argument_index, Optional<CSSPixels> reference_length = {}) -> ErrorOr<float> {
auto& transformation_value = *m_properties.values[argument_index];
CalculationResolutionContext context;
if (paintable_box.has_value())
context.length_resolution_context = Length::ResolutionContext::for_layout_node(paintable_box->layout_node());
if (reference_length.has_value())
context.percentage_basis = Length::make_px(reference_length.value());
if (transformation_value.is_calculated()) {
auto& calculated = transformation_value.as_calculated();
switch (function_metadata.parameters[argument_index].type) {
case TransformFunctionParameterType::Angle: {
if (!calculated.resolves_to_angle())
return Error::from_string_literal("Calculated angle parameter to transform function doesn't resolve to an angle.");
if (auto resolved = calculated.resolve_angle(context); resolved.has_value())
return resolved->to_radians();
return Error::from_string_literal("Couldn't resolve calculated angle.");
}
} else if (transformation_value->is_keyword()) {
if (function_type == TransformFunctionParameterType::LengthNone
&& transformation_value->as_keyword().keyword() == Keyword::None) {
// We don't add 'none' to the list of values.
} else {
dbgln("FIXME: Unsupported keyword value '{}' in transform", transformation_value->to_string(SerializationMode::Normal));
case TransformFunctionParameterType::Length:
case TransformFunctionParameterType::LengthNone: {
if (!calculated.resolves_to_length())
return Error::from_string_literal("Calculated length parameter to transform function doesn't resolve to a length.");
if (auto resolved = calculated.resolve_length(context); resolved.has_value())
return length_to_px(resolved.value());
return Error::from_string_literal("Couldn't resolve calculated length.");
}
case TransformFunctionParameterType::LengthPercentage: {
if (!calculated.resolves_to_length_percentage())
return Error::from_string_literal("Calculated length-percentage parameter to transform function doesn't resolve to a length-percentage.");
if (auto resolved = calculated.resolve_length(context); resolved.has_value())
return length_to_px(resolved.value());
return Error::from_string_literal("Couldn't resolve calculated length-percentage.");
}
case TransformFunctionParameterType::Number: {
if (!calculated.resolves_to_number())
return Error::from_string_literal("Calculated number parameter to transform function doesn't resolve to a number.");
if (auto resolved = calculated.resolve_number(context); resolved.has_value())
return resolved.release_value();
return Error::from_string_literal("Couldn't resolve calculated number.");
}
case TransformFunctionParameterType::NumberPercentage: {
if (calculated.resolves_to_number()) {
if (auto resolved = calculated.resolve_number(context); resolved.has_value())
return calculated.resolve_number(context).value();
return Error::from_string_literal("Couldn't resolve calculated number.");
}
if (calculated.resolves_to_percentage()) {
if (auto resolved = calculated.resolve_percentage(context); resolved.has_value())
return calculated.resolve_percentage(context).value().as_fraction();
return Error::from_string_literal("Couldn't resolve calculated percentage.");
}
return Error::from_string_literal("Calculated number/percentage parameter to transform function doesn't resolve to a number or percentage.");
}
} else if (transformation_value->is_length()) {
values.unchecked_append({ transformation_value->as_length().length() });
} else if (transformation_value->is_percentage()) {
if (function_type == TransformFunctionParameterType::NumberPercentage) {
values.unchecked_append(NumberPercentage { transformation_value->as_percentage().percentage() });
} else {
values.unchecked_append(LengthPercentage { transformation_value->as_percentage().percentage() });
}
} else if (transformation_value->is_number()) {
values.unchecked_append({ Number(Number::Type::Number, transformation_value->as_number().number()) });
} else if (transformation_value->is_angle()) {
values.unchecked_append({ transformation_value->as_angle().angle() });
} else {
dbgln("FIXME: Unsupported value in transform! {}", transformation_value->to_string(SerializationMode::Normal));
}
if (transformation_value.is_length())
return length_to_px(transformation_value.as_length().length());
if (transformation_value.is_percentage()) {
if (function_metadata.parameters[argument_index].type == TransformFunctionParameterType::NumberPercentage) {
return transformation_value.as_percentage().percentage().as_fraction();
}
if (!reference_length.has_value())
return Error::from_string_literal("Can't resolve percentage to length without a reference value.");
return length_to_px(Length::make_px(reference_length.value()).percentage_of(transformation_value.as_percentage().percentage()));
}
if (transformation_value.is_number())
return transformation_value.as_number().number();
if (transformation_value.is_angle())
return transformation_value.as_angle().angle().to_radians();
dbgln("FIXME: Unsupported value in transform! {}", transformation_value.to_string(SerializationMode::Normal));
return Error::from_string_literal("Unsupported value in transform function");
};
Optional<CSSPixels> width;
Optional<CSSPixels> height;
if (paintable_box.has_value()) {
auto reference_box = paintable_box->transform_reference_box();
width = reference_box.width();
height = reference_box.height();
}
return Transformation { m_properties.transform_function, move(values) };
switch (m_properties.transform_function) {
case TransformFunction::Perspective:
// https://drafts.csswg.org/css-transforms-2/#perspective
if (count == 1) {
if (m_properties.values.first()->to_keyword() == Keyword::None)
return FloatMatrix4x4::identity();
// FIXME: Add support for the 'perspective-origin' CSS property.
auto distance = TRY(get_value(0));
// If the depth value is less than '1px', it must be treated as '1px' for the purpose of rendering, for
// computing the resolved value of 'transform', and when used as the endpoint of interpolation.
// Note: The intent of the above rules on values less than '1px' is that they cover the cases where
// the 'perspective()' function needs to be converted into a matrix.
if (distance < 1)
distance = 1;
return FloatMatrix4x4(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, -1 / distance, 1);
}
break;
case TransformFunction::Matrix:
if (count == 6)
return FloatMatrix4x4(TRY(get_value(0)), TRY(get_value(2)), 0, TRY(get_value(4)),
TRY(get_value(1)), TRY(get_value(3)), 0, TRY(get_value(5)),
0, 0, 1, 0,
0, 0, 0, 1);
break;
case TransformFunction::Matrix3d:
if (count == 16)
return FloatMatrix4x4(TRY(get_value(0)), TRY(get_value(4)), TRY(get_value(8)), TRY(get_value(12)),
TRY(get_value(1)), TRY(get_value(5)), TRY(get_value(9)), TRY(get_value(13)),
TRY(get_value(2)), TRY(get_value(6)), TRY(get_value(10)), TRY(get_value(14)),
TRY(get_value(3)), TRY(get_value(7)), TRY(get_value(11)), TRY(get_value(15)));
break;
case TransformFunction::Translate:
if (count == 1)
return FloatMatrix4x4(1, 0, 0, TRY(get_value(0, width)),
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
if (count == 2)
return FloatMatrix4x4(1, 0, 0, TRY(get_value(0, width)),
0, 1, 0, TRY(get_value(1, height)),
0, 0, 1, 0,
0, 0, 0, 1);
break;
case TransformFunction::Translate3d:
return FloatMatrix4x4(1, 0, 0, TRY(get_value(0, width)),
0, 1, 0, TRY(get_value(1, height)),
0, 0, 1, TRY(get_value(2)),
0, 0, 0, 1);
break;
case TransformFunction::TranslateX:
if (count == 1)
return FloatMatrix4x4(1, 0, 0, TRY(get_value(0, width)),
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
break;
case TransformFunction::TranslateY:
if (count == 1)
return FloatMatrix4x4(1, 0, 0, 0,
0, 1, 0, TRY(get_value(0, height)),
0, 0, 1, 0,
0, 0, 0, 1);
break;
case TransformFunction::TranslateZ:
if (count == 1)
return FloatMatrix4x4(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, TRY(get_value(0)),
0, 0, 0, 1);
break;
case TransformFunction::Scale:
if (count == 1)
return FloatMatrix4x4(TRY(get_value(0)), 0, 0, 0,
0, TRY(get_value(0)), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
if (count == 2)
return FloatMatrix4x4(TRY(get_value(0)), 0, 0, 0,
0, TRY(get_value(1)), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
break;
case TransformFunction::Scale3d:
if (count == 3)
return FloatMatrix4x4(TRY(get_value(0)), 0, 0, 0,
0, TRY(get_value(1)), 0, 0,
0, 0, TRY(get_value(2)), 0,
0, 0, 0, 1);
break;
case TransformFunction::ScaleX:
if (count == 1)
return FloatMatrix4x4(TRY(get_value(0)), 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
break;
case TransformFunction::ScaleY:
if (count == 1)
return FloatMatrix4x4(1, 0, 0, 0,
0, TRY(get_value(0)), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
break;
case TransformFunction::ScaleZ:
if (count == 1)
return FloatMatrix4x4(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, TRY(get_value(0)), 0,
0, 0, 0, 1);
break;
case TransformFunction::Rotate3d:
if (count == 4)
return Gfx::rotation_matrix({ TRY(get_value(0)), TRY(get_value(1)), TRY(get_value(2)) }, TRY(get_value(3)));
break;
case TransformFunction::RotateX:
if (count == 1)
return Gfx::rotation_matrix({ 1.0f, 0.0f, 0.0f }, TRY(get_value(0)));
break;
case TransformFunction::RotateY:
if (count == 1)
return Gfx::rotation_matrix({ 0.0f, 1.0f, 0.0f }, TRY(get_value(0)));
break;
case TransformFunction::Rotate:
case TransformFunction::RotateZ:
if (count == 1)
return Gfx::rotation_matrix({ 0.0f, 0.0f, 1.0f }, TRY(get_value(0)));
break;
case TransformFunction::Skew:
if (count == 1)
return FloatMatrix4x4(1, tanf(TRY(get_value(0))), 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
if (count == 2)
return FloatMatrix4x4(1, tanf(TRY(get_value(0))), 0, 0,
tanf(TRY(get_value(1))), 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
break;
case TransformFunction::SkewX:
if (count == 1)
return FloatMatrix4x4(1, tanf(TRY(get_value(0))), 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
break;
case TransformFunction::SkewY:
if (count == 1)
return FloatMatrix4x4(1, 0, 0, 0,
tanf(TRY(get_value(0))), 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
break;
}
dbgln_if(LIBWEB_CSS_DEBUG, "FIXME: Unhandled transformation function {} with {} arguments", CSS::to_string(m_properties.transform_function), m_properties.values.size());
return FloatMatrix4x4::identity();
}
String TransformationStyleValue::to_string(SerializationMode mode) const
@ -318,7 +535,7 @@ ErrorOr<GC::Ref<CSSTransformComponent>> TransformationStyleValue::reify_a_transf
// same information as func, and whose is2D internal slot is true if func is matrix(), and false otherwise.
case TransformFunction::Matrix:
case TransformFunction::Matrix3d: {
auto transform_as_matrix = TRY(to_transformation().to_matrix({}));
auto transform_as_matrix = TRY(to_matrix({}));
auto matrix = Geometry::DOMMatrix::create(realm);
matrix->set_m11(transform_as_matrix[0, 0]);
matrix->set_m12(transform_as_matrix[1, 0]);

View file

@ -9,6 +9,7 @@
#pragma once
#include <LibGfx/Matrix4x4.h>
#include <LibWeb/CSS/StyleValues/StyleValue.h>
#include <LibWeb/CSS/TransformFunctions.h>
@ -27,7 +28,7 @@ public:
TransformFunction transform_function() const { return m_properties.transform_function; }
StyleValueVector const& values() const { return m_properties.values; }
Transformation to_transformation() const;
ErrorOr<FloatMatrix4x4> to_matrix(Optional<Painting::PaintableBox const&>) const;
virtual String to_string(SerializationMode) const override;
ErrorOr<GC::Ref<CSSTransformComponent>> reify_a_transform_function(JS::Realm&) const;

View file

@ -1,234 +0,0 @@
/*
* Copyright (c) 2020-2022, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2024-2025, Bastiaan van der Plaat <bastiaan.v.d.plaat@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Transformation.h"
#include <LibWeb/Painting/PaintableBox.h>
namespace Web::CSS {
Transformation::Transformation(TransformFunction function, Vector<TransformValue>&& values)
: m_function(function)
, m_values(move(values))
{
}
ErrorOr<Gfx::FloatMatrix4x4> Transformation::to_matrix(Optional<Painting::PaintableBox const&> paintable_box) const
{
auto count = m_values.size();
auto value = [&](size_t index, CSSPixels const& reference_length = 0) -> ErrorOr<float> {
CalculationResolutionContext context {};
if (paintable_box.has_value())
context.length_resolution_context = Length::ResolutionContext::for_layout_node(paintable_box->layout_node());
return m_values[index].visit(
[&](CSS::AngleOrCalculated const& value) -> ErrorOr<float> {
if (!value.is_calculated())
return fmod(value.value().to_radians(), 2 * AK::Pi<double>);
if (auto resolved = value.resolved(context); resolved.has_value())
return fmod(resolved->to_radians(), 2 * AK::Pi<double>);
return Error::from_string_literal("Transform contains non absolute units");
},
[&](CSS::LengthPercentage const& value) -> ErrorOr<float> {
context.percentage_basis = Length::make_px(reference_length);
if (paintable_box.has_value())
return value.resolved(paintable_box->layout_node(), reference_length).to_px(paintable_box->layout_node()).to_float();
if (value.is_length()) {
if (auto const& length = value.length(); length.is_absolute())
return length.absolute_length_to_px().to_float();
}
if (value.is_calculated() && value.calculated()->resolves_to_length()) {
if (auto const& resolved = value.calculated()->resolve_length(context); resolved.has_value() && resolved->is_absolute())
return resolved->absolute_length_to_px().to_float();
}
return Error::from_string_literal("Transform contains non absolute units");
},
[&](CSS::NumberPercentage const& value) -> ErrorOr<float> {
if (value.is_number())
return value.number().value();
if (value.is_percentage())
return value.percentage().as_fraction();
if (value.is_calculated()) {
if (value.calculated()->resolves_to_number())
if (auto resolved_number = value.calculated()->resolve_number(context); resolved_number.has_value())
return resolved_number.value();
if (value.calculated()->resolves_to_percentage())
if (auto resolved_percentage = value.calculated()->resolve_percentage(context); resolved_percentage.has_value())
return resolved_percentage->as_fraction();
}
return Error::from_string_literal("Transform contains non absolute units");
});
};
CSSPixels width = 1;
CSSPixels height = 1;
if (paintable_box.has_value()) {
auto reference_box = paintable_box->transform_reference_box();
width = reference_box.width();
height = reference_box.height();
}
switch (m_function) {
case CSS::TransformFunction::Perspective:
// https://drafts.csswg.org/css-transforms-2/#funcdef-perspective
// Count is zero when null parameter
if (count == 1) {
auto distance = TRY(value(0));
// If the depth value is less than '1px', it must be treated as '1px' for the purpose of rendering, for
// computing the resolved value of 'transform', and when used as the endpoint of interpolation.
// Note: The intent of the above rules on values less than '1px' is that they cover the cases where
// the 'perspective()' function needs to be converted into a matrix.
if (distance < 1)
distance = 1;
return Gfx::FloatMatrix4x4(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, -1 / distance, 1);
}
break;
case CSS::TransformFunction::Matrix:
if (count == 6)
return Gfx::FloatMatrix4x4(TRY(value(0)), TRY(value(2)), 0, TRY(value(4)),
TRY(value(1)), TRY(value(3)), 0, TRY(value(5)),
0, 0, 1, 0,
0, 0, 0, 1);
break;
case CSS::TransformFunction::Matrix3d:
if (count == 16)
return Gfx::FloatMatrix4x4(TRY(value(0)), TRY(value(4)), TRY(value(8)), TRY(value(12)),
TRY(value(1)), TRY(value(5)), TRY(value(9)), TRY(value(13)),
TRY(value(2)), TRY(value(6)), TRY(value(10)), TRY(value(14)),
TRY(value(3)), TRY(value(7)), TRY(value(11)), TRY(value(15)));
break;
case CSS::TransformFunction::Translate:
if (count == 1)
return Gfx::FloatMatrix4x4(1, 0, 0, TRY(value(0, width)),
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
if (count == 2)
return Gfx::FloatMatrix4x4(1, 0, 0, TRY(value(0, width)),
0, 1, 0, TRY(value(1, height)),
0, 0, 1, 0,
0, 0, 0, 1);
break;
case CSS::TransformFunction::Translate3d:
return Gfx::FloatMatrix4x4(1, 0, 0, TRY(value(0, width)),
0, 1, 0, TRY(value(1, height)),
0, 0, 1, TRY(value(2)),
0, 0, 0, 1);
case CSS::TransformFunction::TranslateX:
if (count == 1)
return Gfx::FloatMatrix4x4(1, 0, 0, TRY(value(0, width)),
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
break;
case CSS::TransformFunction::TranslateY:
if (count == 1)
return Gfx::FloatMatrix4x4(1, 0, 0, 0,
0, 1, 0, TRY(value(0, height)),
0, 0, 1, 0,
0, 0, 0, 1);
break;
case CSS::TransformFunction::TranslateZ:
if (count == 1)
return Gfx::FloatMatrix4x4(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, TRY(value(0)),
0, 0, 0, 1);
break;
case CSS::TransformFunction::Scale:
if (count == 1)
return Gfx::FloatMatrix4x4(TRY(value(0)), 0, 0, 0,
0, TRY(value(0)), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
if (count == 2)
return Gfx::FloatMatrix4x4(TRY(value(0)), 0, 0, 0,
0, TRY(value(1)), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
break;
case CSS::TransformFunction::Scale3d:
if (count == 3)
return Gfx::FloatMatrix4x4(TRY(value(0)), 0, 0, 0,
0, TRY(value(1)), 0, 0,
0, 0, TRY(value(2)), 0,
0, 0, 0, 1);
break;
case CSS::TransformFunction::ScaleX:
if (count == 1)
return Gfx::FloatMatrix4x4(TRY(value(0)), 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
break;
case CSS::TransformFunction::ScaleY:
if (count == 1)
return Gfx::FloatMatrix4x4(1, 0, 0, 0,
0, TRY(value(0)), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
break;
case CSS::TransformFunction::ScaleZ:
if (count == 1)
return Gfx::FloatMatrix4x4(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, TRY(value(0)), 0,
0, 0, 0, 1);
break;
case CSS::TransformFunction::Rotate3d:
if (count == 4)
return Gfx::rotation_matrix({ TRY(value(0)), TRY(value(1)), TRY(value(2)) }, TRY(value(3)));
break;
case CSS::TransformFunction::RotateX:
if (count == 1)
return Gfx::rotation_matrix({ 1.0f, 0.0f, 0.0f }, TRY(value(0)));
break;
case CSS::TransformFunction::RotateY:
if (count == 1)
return Gfx::rotation_matrix({ 0.0f, 1.0f, 0.0f }, TRY(value(0)));
break;
case CSS::TransformFunction::Rotate:
case CSS::TransformFunction::RotateZ:
if (count == 1)
return Gfx::rotation_matrix({ 0.0f, 0.0f, 1.0f }, TRY(value(0)));
break;
case CSS::TransformFunction::Skew:
if (count == 1)
return Gfx::FloatMatrix4x4(1, tanf(TRY(value(0))), 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
if (count == 2)
return Gfx::FloatMatrix4x4(1, tanf(TRY(value(0))), 0, 0,
tanf(TRY(value(1))), 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
break;
case CSS::TransformFunction::SkewX:
if (count == 1)
return Gfx::FloatMatrix4x4(1, tanf(TRY(value(0))), 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
break;
case CSS::TransformFunction::SkewY:
if (count == 1)
return Gfx::FloatMatrix4x4(1, 0, 0, 0,
tanf(TRY(value(0))), 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
break;
}
dbgln_if(LIBWEB_CSS_DEBUG, "FIXME: Unhandled transformation function {} with {} arguments", to_string(m_function), m_values.size());
return Gfx::FloatMatrix4x4::identity();
}
}

View file

@ -1,34 +0,0 @@
/*
* Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibGfx/Matrix4x4.h>
#include <LibWeb/CSS/Angle.h>
#include <LibWeb/CSS/CalculatedOr.h>
#include <LibWeb/CSS/Length.h>
#include <LibWeb/CSS/PercentageOr.h>
#include <LibWeb/CSS/TransformFunctions.h>
#include <LibWeb/Export.h>
namespace Web::CSS {
using TransformValue = Variant<AngleOrCalculated, LengthPercentage, NumberPercentage>;
class WEB_API Transformation {
public:
Transformation(TransformFunction function, Vector<TransformValue>&& values);
TransformFunction function() const { return m_function; }
ErrorOr<Gfx::FloatMatrix4x4> to_matrix(Optional<Painting::PaintableBox const&>) const;
private:
TransformFunction m_function;
Vector<TransformValue> m_values;
};
}

View file

@ -390,7 +390,6 @@ class Time;
class TimeOrCalculated;
class TimePercentage;
class TimeStyleValue;
class Transformation;
class TransformationStyleValue;
class TreeCountingFunctionStyleValue;
class UnicodeRangeStyleValue;

View file

@ -12,6 +12,7 @@
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/StyleValues/KeywordStyleValue.h>
#include <LibWeb/CSS/StyleValues/ShorthandStyleValue.h>
#include <LibWeb/CSS/StyleValues/TransformationStyleValue.h>
#include <LibWeb/Geometry/DOMMatrix.h>
#include <LibWeb/Geometry/DOMMatrixReadOnly.h>
#include <LibWeb/Geometry/DOMPoint.h>
@ -967,26 +968,21 @@ WebIDL::ExceptionOr<ParsedMatrix> parse_dom_matrix_init_string(JS::Realm& realm,
bool is_2d_transform = true;
for (auto const& transform : parsed_value) {
// https://www.w3.org/TR/css-transforms-1/#two-d-transform-functions
if (transform.function() != CSS::TransformFunction::Matrix
&& transform.function() != CSS::TransformFunction::Translate
&& transform.function() != CSS::TransformFunction::TranslateX
&& transform.function() != CSS::TransformFunction::TranslateY
&& transform.function() != CSS::TransformFunction::Scale
&& transform.function() != CSS::TransformFunction::ScaleX
&& transform.function() != CSS::TransformFunction::ScaleY
&& transform.function() != CSS::TransformFunction::Rotate
&& transform.function() != CSS::TransformFunction::Skew
&& transform.function() != CSS::TransformFunction::SkewX
&& transform.function() != CSS::TransformFunction::SkewY)
if (!first_is_one_of(transform->as_transformation().transform_function(),
CSS::TransformFunction::Matrix,
CSS::TransformFunction::Translate, CSS::TransformFunction::TranslateX, CSS::TransformFunction::TranslateY,
CSS::TransformFunction::Scale, CSS::TransformFunction::ScaleX, CSS::TransformFunction::ScaleY,
CSS::TransformFunction::Rotate,
CSS::TransformFunction::Skew, CSS::TransformFunction::SkewX, CSS::TransformFunction::SkewY))
is_2d_transform = false;
}
// 5. Transform all <transform-function>s to 4x4 abstract matrices by following the “Mathematical Description of Transform Functions”. [CSS3-TRANSFORMS]
// 6. Let matrix be a 4x4 abstract matrix as shown in the initial figure of this section. Post-multiply all matrices from left to right and set matrix to this product.
for (auto const& transform : parsed_value) {
auto const& transform_matrix = transform.to_matrix({});
auto const& transform_matrix = transform->as_transformation().to_matrix({});
if (transform_matrix.is_error())
return WebIDL::SyntaxError::create(realm, "Failed to parse CSS transform string."_utf16);
return WebIDL::SyntaxError::create(realm, Utf16String::formatted("Failed to parse CSS transform string: {}", transform_matrix.error()));
matrix = matrix * transform_matrix.value();
}

View file

@ -100,11 +100,11 @@ bool Node::can_contain_boxes_with_position_absolute() const
// Any computed value other than none for the transform affects containing block and stacking context
if (!computed_values.transformations().is_empty() || will_change_property(CSS::PropertyID::Transform))
return true;
if (computed_values.translate().has_value() || will_change_property(CSS::PropertyID::Translate))
if (computed_values.translate() || will_change_property(CSS::PropertyID::Translate))
return true;
if (computed_values.rotate().has_value() || will_change_property(CSS::PropertyID::Rotate))
if (computed_values.rotate() || will_change_property(CSS::PropertyID::Rotate))
return true;
if (computed_values.scale().has_value() || will_change_property(CSS::PropertyID::Scale))
if (computed_values.scale() || will_change_property(CSS::PropertyID::Scale))
return true;
// https://drafts.csswg.org/css-transforms-2/#propdef-perspective
@ -230,13 +230,13 @@ bool Node::establishes_stacking_context() const
if (!computed_values.transformations().is_empty() || will_change_property(CSS::PropertyID::Transform))
return true;
if (computed_values.translate().has_value() || will_change_property(CSS::PropertyID::Translate))
if (computed_values.translate() || will_change_property(CSS::PropertyID::Translate))
return true;
if (computed_values.rotate().has_value() || will_change_property(CSS::PropertyID::Rotate))
if (computed_values.rotate() || will_change_property(CSS::PropertyID::Rotate))
return true;
if (computed_values.scale().has_value() || will_change_property(CSS::PropertyID::Scale))
if (computed_values.scale() || will_change_property(CSS::PropertyID::Scale))
return true;
// Element that is a child of a flex container, with z-index value other than auto.
@ -572,14 +572,14 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style)
computed_values.set_box_shadow(computed_style.box_shadow(*this));
if (auto rotate_value = computed_style.rotate(); rotate_value.has_value())
computed_values.set_rotate(rotate_value.release_value());
if (auto rotate_value = computed_style.rotate())
computed_values.set_rotate(rotate_value.release_nonnull());
if (auto translate_value = computed_style.translate(); translate_value.has_value())
computed_values.set_translate(translate_value.release_value());
if (auto translate_value = computed_style.translate())
computed_values.set_translate(translate_value.release_nonnull());
if (auto scale_value = computed_style.scale(); scale_value.has_value())
computed_values.set_scale(scale_value.release_value());
if (auto scale_value = computed_style.scale())
computed_values.set_scale(scale_value.release_nonnull());
computed_values.set_transformations(computed_style.transformations());
computed_values.set_transform_box(computed_style.transform_box());

View file

@ -188,9 +188,9 @@ public:
{
auto const& computed_values = this->computed_values();
return !computed_values.transformations().is_empty()
|| computed_values.rotate().has_value()
|| computed_values.translate().has_value()
|| computed_values.scale().has_value();
|| computed_values.rotate()
|| computed_values.translate()
|| computed_values.scale();
}
// https://drafts.csswg.org/css-ui/#propdef-user-select

View file

@ -12,6 +12,7 @@
#include <LibGfx/Font/Font.h>
#include <LibGfx/ImmutableBitmap.h>
#include <LibUnicode/CharacterTypes.h>
#include <LibWeb/CSS/PropertyID.h>
#include <LibWeb/CSS/SystemColor.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Position.h>
@ -1536,14 +1537,14 @@ void PaintableBox::resolve_paint_properties()
auto const& rotate = computed_values.rotate();
auto const& scale = computed_values.scale();
auto matrix = Gfx::FloatMatrix4x4::identity();
if (translate.has_value())
if (translate)
matrix = matrix * translate->to_matrix(*this).release_value();
if (rotate.has_value())
if (rotate)
matrix = matrix * rotate->to_matrix(*this).release_value();
if (scale.has_value())
if (scale)
matrix = matrix * scale->to_matrix(*this).release_value();
for (auto const& transform : transformations)
matrix = matrix * transform.to_matrix(*this).release_value();
matrix = matrix * transform->to_matrix(*this).release_value();
set_transform(matrix);
auto const& transform_origin = computed_values.transform_origin();
@ -1568,7 +1569,8 @@ void PaintableBox::resolve_paint_properties()
// 3. Multiply by the matrix that would be obtained from the 'perspective()' transform function, where the
// length is provided by the value of the perspective property
// NB: Length values less than 1px being clamped to 1px is handled by the perspective() function already.
m_perspective_matrix = m_perspective_matrix.value() * CSS::Transformation(CSS::TransformFunction::Perspective, Vector<CSS::TransformValue>({ CSS::TransformValue(CSS::Length::make_px(perspective.value())) })).to_matrix({}).release_value();
// FIXME: Create the matrix directly.
m_perspective_matrix = m_perspective_matrix.value() * CSS::TransformationStyleValue::create(CSS::PropertyID::Transform, CSS::TransformFunction::Perspective, CSS::StyleValueVector { CSS::LengthStyleValue::create(CSS::Length::make_px(perspective.value())) })->to_matrix({}).release_value();
// 4. Translate by the negated computed X and Y values of 'perspective-origin'
m_perspective_matrix = m_perspective_matrix.value() * Gfx::translation_matrix(Vector3<float>(-computed_x, -computed_y, 0));

View file

@ -119,9 +119,9 @@ public:
{
auto const& computed_values = this->computed_values();
return !computed_values.transformations().is_empty()
|| computed_values.rotate().has_value()
|| computed_values.translate().has_value()
|| computed_values.scale().has_value();
|| computed_values.rotate()
|| computed_values.translate()
|| computed_values.scale();
}
[[nodiscard]] Optional<CSSPixelRect> scrollable_overflow_rect() const

View file

@ -7,6 +7,7 @@
#include <LibGfx/ImmutableBitmap.h>
#include <LibJS/Runtime/Realm.h>
#include <LibWeb/CSS/CSSKeyframesRule.h>
#include <LibWeb/CSS/PropertyID.h>
#include <LibWeb/HTML/EventLoop/EventLoop.h>
#include <LibWeb/HTML/Navigable.h>
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
@ -302,7 +303,11 @@ ErrorOr<void> ViewTransition::capture_the_old_state()
// 6. Set captures old transform to a <transform-function> that would map elements border box from the
// snapshot containing block origin to its current visual position.
// FIXME: Actually compute the right transform here.
capture->old_transform = CSS::Transformation(CSS::TransformFunction::Translate, Vector<CSS::TransformValue>({ CSS::TransformValue(CSS::Length(0, CSS::LengthUnit::Px)), CSS::TransformValue(CSS::Length(0, CSS::LengthUnit::Px)) }));
capture->old_transform = CSS::TransformationStyleValue::create(CSS::PropertyID::Transform, CSS::TransformFunction::Translate,
CSS::StyleValueVector {
CSS::LengthStyleValue::create(CSS::Length(0, CSS::LengthUnit::Px)),
CSS::LengthStyleValue::create(CSS::Length(0, CSS::LengthUnit::Px)),
});
// 7. Set captures old writing-mode to the computed value of writing-mode on element.
capture->old_writing_mode = element.layout_node()->computed_values().writing_mode();
@ -807,7 +812,7 @@ ErrorOr<void> ViewTransition::update_pseudo_element_styles()
// colorScheme be null.
Optional<CSSPixels> width = {};
Optional<CSSPixels> height = {};
Optional<CSS::Transformation> transform = {};
RefPtr<CSS::TransformationStyleValue const> transform = {};
Optional<CSS::WritingMode> writing_mode = {};
Optional<CSS::Direction> direction = {};
// FIXME: Implement this once we have text-orientation.
@ -880,7 +885,11 @@ ErrorOr<void> ViewTransition::update_pseudo_element_styles()
// 5. Set transform to a transform that would map newRect from the snapshot containing block origin
// to its current visual position.
auto offset = new_rect.location() - captured_element->new_element->navigable()->snapshot_containing_block().location();
transform = CSS::Transformation(CSS::TransformFunction::Translate, Vector<CSS::TransformValue>({ CSS::TransformValue(CSS::Length::make_px(offset.x())), CSS::TransformValue(CSS::Length::make_px(offset.y())) }));
transform = CSS::TransformationStyleValue::create(CSS::PropertyID::Transform, CSS::TransformFunction::Translate,
CSS::StyleValueVector {
CSS::LengthStyleValue::create(CSS::Length::make_px(offset.x())),
CSS::LengthStyleValue::create(CSS::Length::make_px(offset.y())),
});
// 6. Set writingMode to the computed value of writing-mode on capturedElements new element.
writing_mode = captured_element->new_element->layout_node()->computed_values().writing_mode();

View file

@ -7,14 +7,13 @@
#pragma once
#include <AK/HashMap.h>
#include <AK/Tuple.h>
#include <LibGfx/Forward.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Bindings/ViewTransitionPrototype.h>
#include <LibWeb/CSS/Enums.h>
#include <LibWeb/CSS/Filter.h>
#include <LibWeb/CSS/PreferredColorScheme.h>
#include <LibWeb/CSS/Transformation.h>
#include <LibWeb/CSS/StyleValues/TransformationStyleValue.h>
#include <LibWeb/DOM/PseudoElement.h>
#include <LibWeb/Forward.h>
#include <LibWeb/PixelUnits.h>
@ -57,7 +56,7 @@ struct CapturedElement : public JS::Cell {
CSSPixels old_width = 0;
CSSPixels old_height = 0;
// FIXME: Make this an identity transform function by default.
CSS::Transformation old_transform = CSS::Transformation(CSS::TransformFunction::Translate, Vector<CSS::TransformValue>());
NonnullRefPtr<CSS::TransformationStyleValue const> old_transform = CSS::TransformationStyleValue::identity_transformation(CSS::TransformFunction::Translate);
Optional<CSS::WritingMode> old_writing_mode {};
Optional<CSS::Direction> old_direction {};
// FIXME: old_text_orientation

View file

@ -74,7 +74,6 @@ source_set("CSS") {
"Supports.cpp",
"SystemColor.cpp",
"Time.cpp",
"Transformation.cpp",
"VisualViewport.cpp",
]
}

View file

@ -4,8 +4,8 @@ matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) => matrix3d(1, 2
perspective(none) => matrix(1, 0, 0, 1, 0, 0)
perspective(0) => matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -1, 0, 0, 0, 1)
perspective(4cm) => matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -0.006614, 0, 0, 0, 1)
translate(1%, 2px) => matrix(1, 0, 0, 1, 7.828125, 2)
translate3d(1%, 2px, 3em) => matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 7.828125, 2, 48, 1)
translate(1%, 2px) => matrix(1, 0, 0, 1, 7.84375, 2)
translate3d(1%, 2px, 3em) => matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 7.84375, 2, 48, 1)
translateX(1px) => matrix(1, 0, 0, 1, 1, 0)
translateY(1%) => matrix(1, 0, 0, 1, 0, 0)
scale(1, 2) => matrix(1, 0, 0, 2, 0, 0)

View file

@ -2,8 +2,7 @@ Harness status: OK
Found 33 tests
32 Pass
1 Fail
33 Pass
Pass Can set 'transform' to CSS-wide keywords: initial
Pass Can set 'transform' to CSS-wide keywords: inherit
Pass Can set 'transform' to CSS-wide keywords: unset
@ -36,4 +35,4 @@ Pass Setting 'transform' to a number: 0 throws TypeError
Pass Setting 'transform' to a number: -3.14 throws TypeError
Pass Setting 'transform' to a number: 3.14 throws TypeError
Pass Setting 'transform' to a number: calc(2 + 3) throws TypeError
Fail 'transform' does not support 'matrix(sibling-index(), 2, 3, 4, 5 ,6)'
Pass 'transform' does not support 'matrix(sibling-index(), 2, 3, 4, 5 ,6)'