mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-12-08 06:09:58 +00:00
A value of `perspective(none)` should result in the identity matrix. But instead of creating that identity matrix explicitly, just break and default to the identity matrix at the bottom of this method.
234 lines
9.6 KiB
C++
234 lines
9.6 KiB
C++
/*
|
|
* 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_box_rect();
|
|
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();
|
|
}
|
|
|
|
}
|