Coverage Report

Created: 2025-12-18 07:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibWeb/CSS/Transformation.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2020-2022, Andreas Kling <kling@serenityos.org>
3
 * Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
4
 *
5
 * SPDX-License-Identifier: BSD-2-Clause
6
 */
7
8
#include "Transformation.h"
9
#include <LibWeb/Painting/PaintableBox.h>
10
11
namespace Web::CSS {
12
13
Transformation::Transformation(TransformFunction function, Vector<TransformValue>&& values)
14
0
    : m_function(function)
15
0
    , m_values(move(values))
16
0
{
17
0
}
18
19
ErrorOr<Gfx::FloatMatrix4x4> Transformation::to_matrix(Optional<Painting::PaintableBox const&> paintable_box) const
20
0
{
21
0
    auto count = m_values.size();
22
0
    auto value = [&](size_t index, CSSPixels const& reference_length = 0) -> ErrorOr<float> {
23
0
        return m_values[index].visit(
24
0
            [&](CSS::LengthPercentage const& value) -> ErrorOr<float> {
25
0
                if (paintable_box.has_value())
26
0
                    return value.resolved(paintable_box->layout_node(), reference_length).to_px(paintable_box->layout_node()).to_float();
27
0
                if (value.is_length()) {
28
0
                    if (auto const& length = value.length(); length.is_absolute())
29
0
                        return length.absolute_length_to_px().to_float();
30
0
                }
31
0
                return Error::from_string_literal("Transform contains non absolute units");
32
0
            },
33
0
            [&](CSS::AngleOrCalculated const& value) -> ErrorOr<float> {
34
0
                if (paintable_box.has_value())
35
0
                    return value.resolved(paintable_box->layout_node()).to_radians();
36
0
                if (!value.is_calculated())
37
0
                    return value.value().to_radians();
38
0
                return Error::from_string_literal("Transform contains non absolute units");
39
0
            },
40
0
            [&](CSS::NumberPercentage const& value) -> ErrorOr<float> {
41
0
                if (value.is_percentage())
42
0
                    return value.percentage().as_fraction();
43
0
                return value.number().value();
44
0
            });
45
0
    };
46
47
0
    CSSPixels width = 1;
48
0
    CSSPixels height = 1;
49
0
    if (paintable_box.has_value()) {
50
0
        auto reference_box = paintable_box->absolute_padding_box_rect();
51
0
        width = reference_box.width();
52
0
        height = reference_box.height();
53
0
    }
54
55
0
    switch (m_function) {
56
0
    case CSS::TransformFunction::Perspective:
57
        // https://drafts.csswg.org/css-transforms-2/#perspective
58
        // Count is zero when null parameter
59
0
        if (count == 1) {
60
            // FIXME: Add support for the 'perspective-origin' CSS property.
61
0
            auto distance = TRY(value(0));
62
0
            return Gfx::FloatMatrix4x4(1, 0, 0, 0,
63
0
                0, 1, 0, 0,
64
0
                0, 0, 1, 0,
65
0
                0, 0, -1 / (distance <= 0 ? 1 : distance), 1);
66
0
        }
67
0
        return Gfx::FloatMatrix4x4(1, 0, 0, 0,
68
0
            0, 1, 0, 0,
69
0
            0, 0, 1, 0,
70
0
            0, 0, 0, 1);
71
0
    case CSS::TransformFunction::Matrix:
72
0
        if (count == 6)
73
0
            return Gfx::FloatMatrix4x4(TRY(value(0)), TRY(value(2)), 0, TRY(value(4)),
74
0
                TRY(value(1)), TRY(value(3)), 0, TRY(value(5)),
75
0
                0, 0, 1, 0,
76
0
                0, 0, 0, 1);
77
0
        break;
78
0
    case CSS::TransformFunction::Matrix3d:
79
0
        if (count == 16)
80
0
            return Gfx::FloatMatrix4x4(TRY(value(0)), TRY(value(4)), TRY(value(8)), TRY(value(12)),
81
0
                TRY(value(1)), TRY(value(5)), TRY(value(9)), TRY(value(13)),
82
0
                TRY(value(2)), TRY(value(6)), TRY(value(10)), TRY(value(14)),
83
0
                TRY(value(3)), TRY(value(7)), TRY(value(11)), TRY(value(15)));
84
0
        break;
85
0
    case CSS::TransformFunction::Translate:
86
0
        if (count == 1)
87
0
            return Gfx::FloatMatrix4x4(1, 0, 0, TRY(value(0, width)),
88
0
                0, 1, 0, 0,
89
0
                0, 0, 1, 0,
90
0
                0, 0, 0, 1);
91
0
        if (count == 2)
92
0
            return Gfx::FloatMatrix4x4(1, 0, 0, TRY(value(0, width)),
93
0
                0, 1, 0, TRY(value(1, height)),
94
0
                0, 0, 1, 0,
95
0
                0, 0, 0, 1);
96
0
        break;
97
0
    case CSS::TransformFunction::Translate3d:
98
0
        return Gfx::FloatMatrix4x4(1, 0, 0, TRY(value(0, width)),
99
0
            0, 1, 0, TRY(value(1, height)),
100
0
            0, 0, 1, TRY(value(2)),
101
0
            0, 0, 0, 1);
102
0
    case CSS::TransformFunction::TranslateX:
103
0
        if (count == 1)
104
0
            return Gfx::FloatMatrix4x4(1, 0, 0, TRY(value(0, width)),
105
0
                0, 1, 0, 0,
106
0
                0, 0, 1, 0,
107
0
                0, 0, 0, 1);
108
0
        break;
109
0
    case CSS::TransformFunction::TranslateY:
110
0
        if (count == 1)
111
0
            return Gfx::FloatMatrix4x4(1, 0, 0, 0,
112
0
                0, 1, 0, TRY(value(0, height)),
113
0
                0, 0, 1, 0,
114
0
                0, 0, 0, 1);
115
0
        break;
116
0
    case CSS::TransformFunction::TranslateZ:
117
0
        if (count == 1)
118
0
            return Gfx::FloatMatrix4x4(1, 0, 0, 0,
119
0
                0, 1, 0, 0,
120
0
                0, 0, 1, TRY(value(0)),
121
0
                0, 0, 0, 1);
122
0
        break;
123
0
    case CSS::TransformFunction::Scale:
124
0
        if (count == 1)
125
0
            return Gfx::FloatMatrix4x4(TRY(value(0)), 0, 0, 0,
126
0
                0, TRY(value(0)), 0, 0,
127
0
                0, 0, 1, 0,
128
0
                0, 0, 0, 1);
129
0
        if (count == 2)
130
0
            return Gfx::FloatMatrix4x4(TRY(value(0)), 0, 0, 0,
131
0
                0, TRY(value(1)), 0, 0,
132
0
                0, 0, 1, 0,
133
0
                0, 0, 0, 1);
134
0
        break;
135
0
    case CSS::TransformFunction::Scale3d:
136
0
        if (count == 3)
137
0
            return Gfx::FloatMatrix4x4(TRY(value(0)), 0, 0, 0,
138
0
                0, TRY(value(1)), 0, 0,
139
0
                0, 0, TRY(value(2)), 0,
140
0
                0, 0, 0, 1);
141
0
        break;
142
0
    case CSS::TransformFunction::ScaleX:
143
0
        if (count == 1)
144
0
            return Gfx::FloatMatrix4x4(TRY(value(0)), 0, 0, 0,
145
0
                0, 1, 0, 0,
146
0
                0, 0, 1, 0,
147
0
                0, 0, 0, 1);
148
0
        break;
149
0
    case CSS::TransformFunction::ScaleY:
150
0
        if (count == 1)
151
0
            return Gfx::FloatMatrix4x4(1, 0, 0, 0,
152
0
                0, TRY(value(0)), 0, 0,
153
0
                0, 0, 1, 0,
154
0
                0, 0, 0, 1);
155
0
        break;
156
0
    case CSS::TransformFunction::ScaleZ:
157
0
        if (count == 1)
158
0
            return Gfx::FloatMatrix4x4(1, 0, 0, 0,
159
0
                0, 1, 0, 0,
160
0
                0, 0, TRY(value(0)), 0,
161
0
                0, 0, 0, 1);
162
0
        break;
163
0
    case CSS::TransformFunction::Rotate3d:
164
0
        if (count == 4)
165
0
            return Gfx::rotation_matrix({ TRY(value(0)), TRY(value(1)), TRY(value(2)) }, TRY(value(3)));
166
0
        break;
167
0
    case CSS::TransformFunction::RotateX:
168
0
        if (count == 1)
169
0
            return Gfx::rotation_matrix({ 1.0f, 0.0f, 0.0f }, TRY(value(0)));
170
0
        break;
171
0
    case CSS::TransformFunction::RotateY:
172
0
        if (count == 1)
173
0
            return Gfx::rotation_matrix({ 0.0f, 1.0f, 0.0f }, TRY(value(0)));
174
0
        break;
175
0
    case CSS::TransformFunction::Rotate:
176
0
    case CSS::TransformFunction::RotateZ:
177
0
        if (count == 1)
178
0
            return Gfx::rotation_matrix({ 0.0f, 0.0f, 1.0f }, TRY(value(0)));
179
0
        break;
180
0
    case CSS::TransformFunction::Skew:
181
0
        if (count == 1)
182
0
            return Gfx::FloatMatrix4x4(1, tanf(TRY(value(0))), 0, 0,
183
0
                0, 1, 0, 0,
184
0
                0, 0, 1, 0,
185
0
                0, 0, 0, 1);
186
0
        if (count == 2)
187
0
            return Gfx::FloatMatrix4x4(1, tanf(TRY(value(0))), 0, 0,
188
0
                tanf(TRY(value(1))), 1, 0, 0,
189
0
                0, 0, 1, 0,
190
0
                0, 0, 0, 1);
191
0
        break;
192
0
    case CSS::TransformFunction::SkewX:
193
0
        if (count == 1)
194
0
            return Gfx::FloatMatrix4x4(1, tanf(TRY(value(0))), 0, 0,
195
0
                0, 1, 0, 0,
196
0
                0, 0, 1, 0,
197
0
                0, 0, 0, 1);
198
0
        break;
199
0
    case CSS::TransformFunction::SkewY:
200
0
        if (count == 1)
201
0
            return Gfx::FloatMatrix4x4(1, 0, 0, 0,
202
0
                tanf(TRY(value(0))), 1, 0, 0,
203
0
                0, 0, 1, 0,
204
0
                0, 0, 0, 1);
205
0
        break;
206
0
    }
207
0
    dbgln_if(LIBWEB_CSS_DEBUG, "FIXME: Unhandled transformation function {} with {} arguments", to_string(m_function), m_values.size());
208
0
    return Gfx::FloatMatrix4x4::identity();
209
0
}
210
211
}