1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-28 18:45:10 +00:00

LibWeb: Add DOMMatrix string constructor and set matrix value

This commit is contained in:
Bastiaan van der Plaat 2024-01-06 18:05:21 +01:00 committed by Andreas Kling
parent 903d3c92c8
commit be7538961b
15 changed files with 336 additions and 120 deletions

View file

@ -8,3 +8,11 @@
8. {"a":10,"b":20,"c":50,"d":60,"e":130,"f":140,"m11":10,"m12":20,"m13":30,"m14":40,"m21":50,"m22":60,"m23":70,"m24":80,"m31":90,"m32":100,"m33":110,"m34":120,"m41":130,"m42":140,"m43":150,"m44":160,"is2D":false,"isIdentity":false}
9. Exception: TypeError
10. Exception: TypeError
11. {"a":1,"b":0,"c":0,"d":1,"e":10,"f":10,"m11":1,"m12":0,"m13":0,"m14":0,"m21":0,"m22":1,"m23":0,"m24":0,"m31":0,"m32":0,"m33":1,"m34":0,"m41":10,"m42":10,"m43":0,"m44":1,"is2D":true,"isIdentity":false}
12. {"a":10,"b":0,"c":0,"d":10,"e":100,"f":100,"m11":10,"m12":0,"m13":0,"m14":0,"m21":0,"m22":10,"m23":0,"m24":0,"m31":0,"m32":0,"m33":1,"m34":0,"m41":100,"m42":100,"m43":0,"m44":1,"is2D":true,"isIdentity":false}
13. {"a":2,"b":0,"c":0,"d":2,"e":0,"f":0,"m11":2,"m12":0,"m13":0,"m14":0,"m21":0,"m22":2,"m23":0,"m24":0,"m31":0,"m32":0,"m33":1,"m34":0,"m41":0,"m42":0,"m43":0,"m44":1,"is2D":true,"isIdentity":false}
14. {"a":0.9396926164627075,"b":0.3420201241970062,"c":-0.3420201241970062,"d":0.9396926164627075,"e":5.976724624633789,"f":12.817127227783203,"m11":0.9396926164627075,"m12":0.3420201241970062,"m13":0,"m14":0,"m21":-0.3420201241970062,"m22":0.9396926164627075,"m23":0,"m24":0,"m31":0,"m32":0,"m33":1,"m34":0,"m41":5.976724624633789,"m42":12.817127227783203,"m43":0,"m44":1,"is2D":true,"isIdentity":false}
15. Exception: SyntaxError
16. Exception: SyntaxError
17. Exception: SyntaxError
18. Exception: SyntaxError

View file

@ -0,0 +1,6 @@
1. {"a":1,"b":0,"c":0,"d":1,"e":0,"f":0,"m11":1,"m12":0,"m13":0,"m14":0,"m21":0,"m22":1,"m23":0,"m24":0,"m31":0,"m32":0,"m33":1,"m34":0,"m41":0,"m42":0,"m43":0,"m44":1,"is2D":true,"isIdentity":true}
2. {"a":1,"b":0,"c":0,"d":1,"e":10,"f":10,"m11":1,"m12":0,"m13":0,"m14":0,"m21":0,"m22":1,"m23":0,"m24":0,"m31":0,"m32":0,"m33":1,"m34":0,"m41":10,"m42":10,"m43":0,"m44":1,"is2D":true,"isIdentity":false}
3. {"a":0.9848077297210693,"b":0.1736481785774231,"c":-0.1736481785774231,"d":0.9848077297210693,"e":8.11159610748291,"f":11.584559440612793,"m11":0.9848077297210693,"m12":0.1736481785774231,"m13":0,"m14":0,"m21":-0.1736481785774231,"m22":0.9848077297210693,"m23":0,"m24":0,"m31":0,"m32":0,"m33":1,"m34":0,"m41":8.11159610748291,"m42":11.584559440612793,"m43":0,"m44":1,"is2D":true,"isIdentity":false}
4. {"a":0.19696155190467834,"b":0.03472963720560074,"c":-0.03472963720560074,"d":0.19696155190467834,"e":0,"f":0,"m11":0.19696155190467834,"m12":0.03472963720560074,"m13":0,"m14":0,"m21":-0.03472963720560074,"m22":0.19696155190467834,"m23":0,"m24":0,"m31":0,"m32":0,"m33":1,"m34":0,"m41":0,"m42":0,"m43":0,"m44":1,"is2D":true,"isIdentity":false}
5. Exception: SyntaxError
6. Exception: SyntaxError

View file

@ -40,5 +40,29 @@
// 10. Creating a DOMMatrix with fromFloat64Array wrong amount
testPart(() => DOMMatrix.fromFloat64Array(new Float64Array([10, 20, 30, 40])));
// 11. Creating a DOMMatrix with CSS transform string
testPart(() => new DOMMatrix('translate(10px, 10px)'));
// 12. Creating a DOMMatrix with CSS transform string
testPart(() => new DOMMatrix('scale(10) translate(10px, 10px)'));
// 13. Creating a DOMMatrix with CSS transform string
testPart(() => new DOMMatrix('scale(2)'));
// 14. Creating a DOMMatrix with CSS transform string
testPart(() => new DOMMatrix('rotate(20deg) translate(10px, 10px)'));
// 15. Creating a DOMMatrix with CSS transform string with error
testPart(() => new DOMMatrix('translate(2)'));
// 16. Creating a DOMMatrix with CSS transform string with error
testPart(() => new DOMMatrix('rotate(20)'));
// 17. Creating a DOMMatrix with CSS transform string with error
testPart(() => new DOMMatrix('rotate(20px)'));
// 18. Creating a DOMMatrix with CSS transform string with error
testPart(() => new DOMMatrix('matrix(1.0, 2.0deg, 3.0, 4.0, 5.0, 6.0)'));
});
</script>

View file

@ -0,0 +1,32 @@
<script src="../include.js"></script>
<script>
test(() => {
let testCounter = 1;
function testPart(part) {
try {
println(`${testCounter}. ${JSON.stringify(part())}`);
} catch (e) {
println(`${testCounter}. Exception: ${e.name}`);
}
testCounter++;
}
// 1. DOMMatrix set matrix value empty
testPart(() => new DOMMatrix().setMatrixValue(''));
// 2. DOMMatrix set matrix value
testPart(() => new DOMMatrix().setMatrixValue('translate(10px, 10px)'));
// 3. DOMMatrix set matrix value
testPart(() => new DOMMatrix().setMatrixValue('rotate(10deg) translate(10px, 10px)'));
// 4. DOMMatrix set matrix value
testPart(() => new DOMMatrix().setMatrixValue('scale(0.2) rotate(10deg)'));
// 5. DOMMatrix set matrix value wrong
testPart(() => new DOMMatrix().setMatrixValue('scale(2deg)'));
// 6. DOMMatrix set matrix value wrong
testPart(() => new DOMMatrix().setMatrixValue('translate(40%)'));
});
</script>

View file

@ -357,7 +357,7 @@ RefPtr<StyleValue const> ResolvedCSSStyleDeclaration::style_value_for_property(L
VERIFY(layout_node.paintable());
auto const& paintable_box = verify_cast<Painting::PaintableBox const>(*layout_node.paintable());
for (auto transformation : transformations) {
transform = transform * transformation.to_matrix(paintable_box);
transform = transform * transformation.to_matrix(paintable_box).release_value();
}
// https://drafts.csswg.org/css-transforms-1/#2d-matrix

View file

@ -405,17 +405,15 @@ Optional<CSS::JustifySelf> StyleProperties::justify_self() const
return value_id_to_justify_self(value->to_identifier());
}
Vector<CSS::Transformation> StyleProperties::transformations() const
Vector<CSS::Transformation> StyleProperties::transformations_for_style_value(StyleValue const& value)
{
auto value = property(CSS::PropertyID::Transform);
if (value->is_identifier() && value->to_identifier() == CSS::ValueID::None)
if (value.is_identifier() && value.to_identifier() == CSS::ValueID::None)
return {};
if (!value->is_value_list())
if (!value.is_value_list())
return {};
auto& list = value->as_value_list();
auto& list = value.as_value_list();
Vector<CSS::Transformation> transformations;
@ -456,6 +454,11 @@ Vector<CSS::Transformation> StyleProperties::transformations() const
return transformations;
}
Vector<CSS::Transformation> StyleProperties::transformations() const
{
return transformations_for_style_value(property(CSS::PropertyID::Transform));
}
static Optional<LengthPercentage> length_percentage_for_style_value(StyleValue const& value)
{
if (value.is_length())

View file

@ -117,6 +117,7 @@ public:
CSS::PositionStyleValue const& object_position() const;
Optional<CSS::TableLayout> table_layout() const;
static Vector<CSS::Transformation> transformations_for_style_value(StyleValue const& value);
Vector<CSS::Transformation> transformations() const;
CSS::TransformOrigin transform_origin() const;

View file

@ -6,7 +6,6 @@
*/
#include "Transformation.h"
#include <LibWeb/Layout/Node.h>
#include <LibWeb/Painting/PaintableBox.h>
namespace Web::CSS {
@ -17,62 +16,74 @@ Transformation::Transformation(TransformFunction function, Vector<TransformValue
{
}
Gfx::FloatMatrix4x4 Transformation::to_matrix(Painting::PaintableBox const& paintable_box) const
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) -> float {
auto value = [&](size_t index, CSSPixels const& reference_length = 0) -> ErrorOr<float> {
return m_values[index].visit(
[&](CSS::LengthPercentage const& value) -> double {
return value.resolved(paintable_box.layout_node(), reference_length).to_px(paintable_box.layout_node()).to_float();
[&](CSS::LengthPercentage const& value) -> ErrorOr<float> {
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())
return value.length().absolute_length_to_px().to_float();
return Error::from_string_literal("Transform contains non absolute units");
},
[&](CSS::AngleOrCalculated const& value) {
return AK::to_radians(value.resolved(paintable_box.layout_node()).to_degrees());
[&](CSS::AngleOrCalculated const& value) -> ErrorOr<float> {
if (paintable_box.has_value())
return value.resolved(paintable_box->layout_node()).to_radians();
if (!value.is_calculated())
return value.value().to_radians();
return Error::from_string_literal("Transform contains non absolute units");
},
[](double value) {
[](double value) -> ErrorOr<float> {
return value;
});
};
auto reference_box = paintable_box.absolute_rect();
auto width = reference_box.width();
auto height = reference_box.height();
CSSPixels width = 1;
CSSPixels height = 1;
if (paintable_box.has_value()) {
auto reference_box = paintable_box->absolute_rect();
width = reference_box.width();
height = reference_box.height();
}
switch (m_function) {
case CSS::TransformFunction::Matrix:
if (count == 6)
return Gfx::FloatMatrix4x4(value(0), value(2), 0, value(4),
value(1), value(3), 0, value(5),
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(value(0), value(4), value(8), value(12),
value(1), value(5), value(9), value(13),
value(2), value(6), value(10), value(14),
value(3), value(7), value(11), value(15));
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, value(0, width),
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, value(0, width),
0, 1, 0, value(1, height),
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, value(0, width),
0, 1, 0, value(1, height),
0, 0, 1, value(2),
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);
break;
case CSS::TransformFunction::TranslateX:
if (count == 1)
return Gfx::FloatMatrix4x4(1, 0, 0, value(0, width),
return Gfx::FloatMatrix4x4(1, 0, 0, TRY(value(0, width)),
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
@ -80,7 +91,7 @@ Gfx::FloatMatrix4x4 Transformation::to_matrix(Painting::PaintableBox const& pain
case CSS::TransformFunction::TranslateY:
if (count == 1)
return Gfx::FloatMatrix4x4(1, 0, 0, 0,
0, 1, 0, value(0, height),
0, 1, 0, TRY(value(0, height)),
0, 0, 1, 0,
0, 0, 0, 1);
break;
@ -88,24 +99,24 @@ Gfx::FloatMatrix4x4 Transformation::to_matrix(Painting::PaintableBox const& pain
if (count == 1)
return Gfx::FloatMatrix4x4(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, value(0),
0, 0, 1, TRY(value(0)),
0, 0, 0, 1);
break;
case CSS::TransformFunction::Scale:
if (count == 1)
return Gfx::FloatMatrix4x4(value(0), 0, 0, 0,
0, value(0), 0, 0,
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(value(0), 0, 0, 0,
0, value(1), 0, 0,
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::ScaleX:
if (count == 1)
return Gfx::FloatMatrix4x4(value(0), 0, 0, 0,
return Gfx::FloatMatrix4x4(TRY(value(0)), 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
@ -113,38 +124,38 @@ Gfx::FloatMatrix4x4 Transformation::to_matrix(Painting::PaintableBox const& pain
case CSS::TransformFunction::ScaleY:
if (count == 1)
return Gfx::FloatMatrix4x4(1, 0, 0, 0,
0, value(0), 0, 0,
0, TRY(value(0)), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
break;
case CSS::TransformFunction::RotateX:
if (count == 1)
return Gfx::rotation_matrix({ 1.0f, 0.0f, 0.0f }, value(0));
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 }, value(0));
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 }, value(0));
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(value(0)), 0, 0,
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(value(0)), 0, 0,
tanf(value(1)), 1, 0, 0,
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(value(0)), 0, 0,
return Gfx::FloatMatrix4x4(1, tanf(TRY(value(0))), 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
@ -152,7 +163,7 @@ Gfx::FloatMatrix4x4 Transformation::to_matrix(Painting::PaintableBox const& pain
case CSS::TransformFunction::SkewY:
if (count == 1)
return Gfx::FloatMatrix4x4(1, 0, 0, 0,
tanf(value(0)), 1, 0, 0,
tanf(TRY(value(0))), 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
break;

View file

@ -20,7 +20,10 @@ using TransformValue = Variant<AngleOrCalculated, LengthPercentage, double>;
class Transformation {
public:
Transformation(TransformFunction function, Vector<TransformValue>&& values);
Gfx::FloatMatrix4x4 to_matrix(Painting::PaintableBox const&) const;
TransformFunction function() const { return m_function; }
ErrorOr<Gfx::FloatMatrix4x4> to_matrix(Optional<Painting::PaintableBox const&>) const;
private:
TransformFunction m_function;

View file

@ -8,6 +8,7 @@
#include <LibJS/Runtime/TypedArray.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Geometry/DOMMatrix.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/WebIDL/Buffers.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
@ -15,20 +16,63 @@ namespace Web::Geometry {
JS_DEFINE_ALLOCATOR(DOMMatrix);
// https://drafts.fxtf.org/geometry/#dom-dommatrix-dommatrix
WebIDL::ExceptionOr<JS::NonnullGCPtr<DOMMatrix>> DOMMatrix::construct_impl(JS::Realm& realm, Optional<Variant<String, Vector<double>>> const& init)
{
auto& vm = realm.vm();
// https://drafts.fxtf.org/geometry/#dom-dommatrix-dommatrix
if (init.has_value()) {
// -> Otherwise
// Throw a TypeError exception.
// The only condition where this can be met is with a sequence type which doesn't have exactly 6 or 16 elements.
if (auto* double_sequence = init.value().get_pointer<Vector<double>>(); double_sequence && (double_sequence->size() != 6 && double_sequence->size() != 16))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, TRY_OR_THROW_OOM(vm, String::formatted("Sequence must contain exactly 6 or 16 elements, got {} element(s)", double_sequence->size())) };
// -> If init is omitted
if (!init.has_value()) {
// Return the result of invoking create a 2d matrix of type DOMMatrixReadOnly or DOMMatrix as appropriate, with the sequence [1, 0, 0, 1, 0, 0].
return realm.heap().allocate<DOMMatrix>(realm, realm, 1, 0, 0, 1, 0, 0);
}
return realm.heap().allocate<DOMMatrix>(realm, realm, init);
auto const& init_value = init.value();
// -> If init is a DOMString
if (init_value.has<String>()) {
// 1. If current global object is not a Window object, then throw a TypeError exception.
if (!is<HTML::Window>(realm.global_object()))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "This can only be used in a Window context"_string };
// 2. Parse init into an abstract matrix, and let matrix and 2dTransform be the result. If the result is failure, then throw a "SyntaxError" DOMException.
auto result = TRY(parse_dom_matrix_init_string(realm, init_value.get<String>()));
auto* elements = result.matrix.elements();
// If 2dTransform is true
if (result.is_2d_transform) {
// Return the result of invoking create a 2d matrix of type DOMMatrixReadOnly or DOMMatrix as appropriate, with a sequence of numbers, the values being the elements m11, m12, m21, m22, m41 and m42 of matrix.
return realm.heap().allocate<DOMMatrix>(realm, realm, elements[0][0], elements[1][0], elements[0][1], elements[1][1], elements[0][3], elements[1][3]);
}
// Otherwise, return the result of invoking create a 3d matrix of type DOMMatrixReadOnly or DOMMatrix as appropriate, with a sequence of numbers, the values being the 16 elements of matrix.
return realm.heap().allocate<DOMMatrix>(realm, realm,
elements[0][0], elements[1][0], elements[2][0], elements[3][0],
elements[0][1], elements[1][1], elements[2][1], elements[3][1],
elements[0][2], elements[1][2], elements[2][2], elements[3][2],
elements[0][3], elements[1][3], elements[2][3], elements[3][3]);
}
auto const& double_sequence = init_value.get<Vector<double>>();
// -> If init is a sequence with 6 elements
if (double_sequence.size() == 6) {
// Return the result of invoking create a 2d matrix of type DOMMatrixReadOnly or DOMMatrix as appropriate, with the sequence init.
return realm.heap().allocate<DOMMatrix>(realm, realm, double_sequence[0], double_sequence[1], double_sequence[2], double_sequence[3], double_sequence[4], double_sequence[5]);
}
// -> If init is a sequence with 16 elements
if (double_sequence.size() == 16) {
// Return the result of invoking create a 3d matrix of type DOMMatrixReadOnly or DOMMatrix as appropriate, with the sequence init.
return realm.heap().allocate<DOMMatrix>(realm, realm,
double_sequence[0], double_sequence[1], double_sequence[2], double_sequence[3],
double_sequence[4], double_sequence[5], double_sequence[6], double_sequence[7],
double_sequence[8], double_sequence[9], double_sequence[10], double_sequence[11],
double_sequence[12], double_sequence[13], double_sequence[14], double_sequence[15]);
}
// -> Otherwise, throw a TypeError exception.
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, TRY_OR_THROW_OOM(vm, String::formatted("Sequence must contain exactly 6 or 16 elements, got {} element(s)", double_sequence.size())) };
}
// https://drafts.fxtf.org/geometry/#create-a-dommatrix-from-the-2d-dictionary
@ -84,11 +128,6 @@ DOMMatrix::DOMMatrix(JS::Realm& realm, double m11, double m12, double m13, doubl
{
}
DOMMatrix::DOMMatrix(JS::Realm& realm, Optional<Variant<String, Vector<double>>> const& init)
: DOMMatrixReadOnly(realm, init)
{
}
DOMMatrix::DOMMatrix(JS::Realm& realm, DOMMatrixReadOnly const& read_only_matrix)
: DOMMatrixReadOnly(realm, read_only_matrix)
{
@ -540,4 +579,20 @@ JS::NonnullGCPtr<DOMMatrix> DOMMatrix::invert_self()
return *this;
}
// https://drafts.fxtf.org/geometry/#dom-dommatrix-setmatrixvalue
WebIDL::ExceptionOr<JS::NonnullGCPtr<DOMMatrix>> DOMMatrix::set_matrix_value(String const& transform_list)
{
// 1. Parse transformList into an abstract matrix, and let matrix and 2dTransform be the result. If the result is failure, then throw a "SyntaxError" DOMException.
auto result = TRY(parse_dom_matrix_init_string(realm(), transform_list));
// 2. Set is 2D to the value of 2dTransform.
m_is_2d = result.is_2d_transform;
// 3. Set m11 element through m44 element to the element values of matrix in column-major order.
m_matrix = result.matrix;
// 4. Return the current matrix.
return JS::NonnullGCPtr<DOMMatrix>(*this);
}
}

View file

@ -65,10 +65,11 @@ public:
JS::NonnullGCPtr<DOMMatrix> skew_y_self(double sy = 0);
JS::NonnullGCPtr<DOMMatrix> invert_self();
WebIDL::ExceptionOr<JS::NonnullGCPtr<DOMMatrix>> set_matrix_value(String const& transform_list);
private:
DOMMatrix(JS::Realm&, double m11, double m12, double m21, double m22, double m41, double m42);
DOMMatrix(JS::Realm&, double m11, double m12, double m13, double m14, double m21, double m22, double m23, double m24, double m31, double m32, double m33, double m34, double m41, double m42, double m43, double m44);
DOMMatrix(JS::Realm&, Optional<Variant<String, Vector<double>>> const& init);
DOMMatrix(JS::Realm&, DOMMatrixReadOnly const& read_only_matrix);
virtual void initialize(JS::Realm&) override;

View file

@ -47,5 +47,5 @@ interface DOMMatrix : DOMMatrixReadOnly {
DOMMatrix skewYSelf(optional unrestricted double sy = 0);
DOMMatrix invertSelf();
// FIXME: [Exposed=Window] DOMMatrix setMatrixValue(DOMString transformList);
[Exposed=Window] DOMMatrix setMatrixValue(DOMString transformList);
};

View file

@ -6,29 +6,75 @@
*/
#include <LibJS/Runtime/TypedArray.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/StyleProperties.h>
#include <LibWeb/CSS/StyleValues/ShorthandStyleValue.h>
#include <LibWeb/Geometry/DOMMatrix.h>
#include <LibWeb/Geometry/DOMMatrixReadOnly.h>
#include <LibWeb/Geometry/DOMPoint.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/WebIDL/Buffers.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
namespace Web::Geometry {
// https://drafts.fxtf.org/geometry/#dom-dommatrixreadonly-dommatrixreadonly
WebIDL::ExceptionOr<JS::NonnullGCPtr<DOMMatrixReadOnly>> DOMMatrixReadOnly::construct_impl(JS::Realm& realm, Optional<Variant<String, Vector<double>>> const& init)
{
auto& vm = realm.vm();
// https://drafts.fxtf.org/geometry/#dom-dommatrixreadonly-dommatrixreadonly
if (init.has_value()) {
// -> Otherwise
// Throw a TypeError exception.
// The only condition where this can be met is with a sequence type which doesn't have exactly 6 or 16 elements.
if (auto* double_sequence = init.value().get_pointer<Vector<double>>(); double_sequence && (double_sequence->size() != 6 && double_sequence->size() != 16))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, TRY_OR_THROW_OOM(vm, String::formatted("Sequence must contain exactly 6 or 16 elements, got {} element(s)", double_sequence->size())) };
// -> If init is omitted
if (!init.has_value()) {
// Return the result of invoking create a 2d matrix of type DOMMatrixReadOnly or DOMMatrix as appropriate, with the sequence [1, 0, 0, 1, 0, 0].
return realm.heap().allocate<DOMMatrixReadOnly>(realm, realm, 1, 0, 0, 1, 0, 0);
}
return realm.heap().allocate<DOMMatrixReadOnly>(realm, realm, init);
auto const& init_value = init.value();
// -> If init is a DOMString
if (init_value.has<String>()) {
// 1. If current global object is not a Window object, then throw a TypeError exception.
if (!is<HTML::Window>(realm.global_object()))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "This can only be used in a Window context"_string };
// 2. Parse init into an abstract matrix, and let matrix and 2dTransform be the result. If the result is failure, then throw a "SyntaxError" DOMException.
auto result = TRY(parse_dom_matrix_init_string(realm, init_value.get<String>()));
auto* elements = result.matrix.elements();
// If 2dTransform is true
if (result.is_2d_transform) {
// Return the result of invoking create a 2d matrix of type DOMMatrixReadOnly or DOMMatrix as appropriate, with a sequence of numbers, the values being the elements m11, m12, m21, m22, m41 and m42 of matrix.
return realm.heap().allocate<DOMMatrixReadOnly>(realm, realm, elements[0][0], elements[1][0], elements[0][1], elements[1][1], elements[0][3], elements[1][3]);
}
// Otherwise, return the result of invoking create a 3d matrix of type DOMMatrixReadOnly or DOMMatrix as appropriate, with a sequence of numbers, the values being the 16 elements of matrix.
return realm.heap().allocate<DOMMatrixReadOnly>(realm, realm,
elements[0][0], elements[1][0], elements[2][0], elements[3][0],
elements[0][1], elements[1][1], elements[2][1], elements[3][1],
elements[0][2], elements[1][2], elements[2][2], elements[3][2],
elements[0][3], elements[1][3], elements[2][3], elements[3][3]);
}
auto const& double_sequence = init_value.get<Vector<double>>();
// -> If init is a sequence with 6 elements
if (double_sequence.size() == 6) {
// Return the result of invoking create a 2d matrix of type DOMMatrixReadOnly or DOMMatrix as appropriate, with the sequence init.
return realm.heap().allocate<DOMMatrixReadOnly>(realm, realm, double_sequence[0], double_sequence[1], double_sequence[2], double_sequence[3], double_sequence[4], double_sequence[5]);
}
// -> If init is a sequence with 16 elements
if (double_sequence.size() == 16) {
// Return the result of invoking create a 3d matrix of type DOMMatrixReadOnly or DOMMatrix as appropriate, with the sequence init.
return realm.heap().allocate<DOMMatrixReadOnly>(realm, realm,
double_sequence[0], double_sequence[1], double_sequence[2], double_sequence[3],
double_sequence[4], double_sequence[5], double_sequence[6], double_sequence[7],
double_sequence[8], double_sequence[9], double_sequence[10], double_sequence[11],
double_sequence[12], double_sequence[13], double_sequence[14], double_sequence[15]);
}
// -> Otherwise, throw a TypeError exception.
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, TRY_OR_THROW_OOM(vm, String::formatted("Sequence must contain exactly 6 or 16 elements, got {} element(s)", double_sequence.size())) };
}
// https://drafts.fxtf.org/geometry/#create-a-dommatrixreadonly-from-the-2d-dictionary
@ -59,11 +105,11 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<DOMMatrixReadOnly>> DOMMatrixReadOnly::crea
// 2. If the is2D dictionary member of other is true.
if (init.is2d.has_value() && init.is2d.value()) {
// Return the result of invoking create a 2d matrix of type DOMMatrixReadOnly or DOMMatrix as appropriate, with a sequence of numbers, the values being the 6 elements m11, m12, m21, m22, m41 and m42 of other in the given order.
return realm.heap().allocate<DOMMatrix>(realm, realm, init.m11.value(), init.m12.value(), init.m21.value(), init.m22.value(), init.m41.value(), init.m42.value());
return realm.heap().allocate<DOMMatrixReadOnly>(realm, realm, init.m11.value(), init.m12.value(), init.m21.value(), init.m22.value(), init.m41.value(), init.m42.value());
}
// Otherwise, Return the result of invoking create a 3d matrix of type DOMMatrixReadOnly or DOMMatrix as appropriate, with a sequence of numbers, the values being the 16 elements m11, m12, m13, ..., m44 of other in the given order.
return realm.heap().allocate<DOMMatrix>(realm, realm, init.m11.value(), init.m12.value(), init.m13, init.m14,
return realm.heap().allocate<DOMMatrixReadOnly>(realm, realm, init.m11.value(), init.m12.value(), init.m13, init.m14,
init.m21.value(), init.m22.value(), init.m23, init.m24,
init.m31, init.m32, init.m33, init.m34,
init.m41.value(), init.m42.value(), init.m43, init.m44);
@ -81,46 +127,6 @@ DOMMatrixReadOnly::DOMMatrixReadOnly(JS::Realm& realm, double m11, double m12, d
initialize_from_create_3d_matrix(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44);
}
DOMMatrixReadOnly::DOMMatrixReadOnly(JS::Realm& realm, Optional<Variant<String, Vector<double>>> const& init)
: Bindings::PlatformObject(realm)
{
// https://drafts.fxtf.org/geometry/#dom-dommatrixreadonly-dommatrixreadonly
// -> If init is omitted
if (!init.has_value()) {
// Return the result of invoking create a 2d matrix of type DOMMatrixReadOnly or DOMMatrix as appropriate, with the sequence [1, 0, 0, 1, 0, 0].
initialize_from_create_2d_matrix(1, 0, 0, 1, 0, 0);
return;
}
auto const& init_value = init.value();
// -> If init is a DOMString
if (init_value.has<String>()) {
dbgln("FIXME: Implement initializing DOMMatrix(ReadOnly) from DOMString: '{}'", init_value.get<String>());
// NOTE: This will result in an identity matrix for now.
return;
}
auto const& double_sequence = init_value.get<Vector<double>>();
// -> If init is a sequence with 6 elements
if (double_sequence.size() == 6) {
// Return the result of invoking create a 2d matrix of type DOMMatrixReadOnly or DOMMatrix as appropriate, with the sequence init.
initialize_from_create_2d_matrix(double_sequence[0], double_sequence[1], double_sequence[2], double_sequence[3], double_sequence[4], double_sequence[5]);
return;
}
// -> If init is a sequence with 16 elements
// NOTE: The "otherwise" case should be handled in construct_impl, leaving the only other possible condition here to be 16 elements.
VERIFY(double_sequence.size() == 16);
// Return the result of invoking create a 3d matrix of type DOMMatrixReadOnly or DOMMatrix as appropriate, with the sequence init.
initialize_from_create_3d_matrix(
double_sequence[0], double_sequence[1], double_sequence[2], double_sequence[3],
double_sequence[4], double_sequence[5], double_sequence[6], double_sequence[7],
double_sequence[8], double_sequence[9], double_sequence[10], double_sequence[11],
double_sequence[12], double_sequence[13], double_sequence[14], double_sequence[15]);
}
DOMMatrixReadOnly::DOMMatrixReadOnly(JS::Realm& realm, DOMMatrixReadOnly const& other)
: Bindings::PlatformObject(realm)
, m_matrix(other.m_matrix)
@ -220,11 +226,11 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<DOMMatrixReadOnly>> DOMMatrixReadOnly::from
// If array32 has 6 elements, return the result of invoking create a 2d matrix of type DOMMatrixReadOnly or DOMMatrix as appropriate, with a sequence of numbers taking the values from array32 in the provided order.
if (elements.size() == 6)
return realm.heap().allocate<DOMMatrix>(realm, realm, elements.at(0), elements.at(1), elements.at(2), elements.at(3), elements.at(4), elements.at(5));
return realm.heap().allocate<DOMMatrixReadOnly>(realm, realm, elements.at(0), elements.at(1), elements.at(2), elements.at(3), elements.at(4), elements.at(5));
// If array32 has 16 elements, return the result of invoking create a 3d matrix of type DOMMatrixReadOnly or DOMMatrix as appropriate, with a sequence of numbers taking the values from array32 in the provided order.
if (elements.size() == 16)
return realm.heap().allocate<DOMMatrix>(realm, realm, elements.at(0), elements.at(1), elements.at(2), elements.at(3),
return realm.heap().allocate<DOMMatrixReadOnly>(realm, realm, elements.at(0), elements.at(1), elements.at(2), elements.at(3),
elements.at(4), elements.at(5), elements.at(6), elements.at(7),
elements.at(8), elements.at(9), elements.at(10), elements.at(11),
elements.at(12), elements.at(13), elements.at(14), elements.at(15));
@ -245,11 +251,11 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<DOMMatrixReadOnly>> DOMMatrixReadOnly::from
// If array64 has 6 elements, return the result of invoking create a 2d matrix of type DOMMatrixReadOnly or DOMMatrix as appropriate, with a sequence of numbers taking the values from array64 in the provided order.
if (elements.size() == 6)
return realm.heap().allocate<DOMMatrix>(realm, realm, elements.at(0), elements.at(1), elements.at(2), elements.at(3), elements.at(4), elements.at(5));
return realm.heap().allocate<DOMMatrixReadOnly>(realm, realm, elements.at(0), elements.at(1), elements.at(2), elements.at(3), elements.at(4), elements.at(5));
// If array64 has 16 elements, return the result of invoking create a 3d matrix of type DOMMatrixReadOnly or DOMMatrix as appropriate, with a sequence of numbers taking the values from array64 in the provided order.
if (elements.size() == 16)
return realm.heap().allocate<DOMMatrix>(realm, realm, elements.at(0), elements.at(1), elements.at(2), elements.at(3),
return realm.heap().allocate<DOMMatrixReadOnly>(realm, realm, elements.at(0), elements.at(1), elements.at(2), elements.at(3),
elements.at(4), elements.at(5), elements.at(6), elements.at(7),
elements.at(8), elements.at(9), elements.at(10), elements.at(11),
elements.at(12), elements.at(13), elements.at(14), elements.at(15));
@ -781,4 +787,64 @@ WebIDL::ExceptionOr<void> validate_and_fixup_dom_matrix_init(DOMMatrixInit& init
return {};
}
// https://drafts.fxtf.org/geometry/#parse-a-string-into-an-abstract-matrix
WebIDL::ExceptionOr<ParsedMatrix> parse_dom_matrix_init_string(JS::Realm& realm, StringView transform_list)
{
// 1. If transformList is the empty string, set it to the string "matrix(1, 0, 0, 1, 0, 0)".
if (transform_list.is_empty())
transform_list = "matrix(1, 0, 0, 1, 0, 0)"sv;
// 2. Parse transformList into parsedValue given the grammar for the CSS transform property.
// The result will be a <transform-list>, the keyword none, or failure.
// If parsedValue is failure, or any <transform-function> has <length> values without absolute length units, or any keyword other than none is used, then return failure. [CSS3-SYNTAX] [CSS3-TRANSFORMS]
auto parsing_context = CSS::Parser::ParsingContext { realm };
auto transform_style_value = parse_css_value(parsing_context, transform_list, CSS::PropertyID::Transform);
if (!transform_style_value)
return WebIDL::SyntaxError::create(realm, "Failed to parse CSS transform string."_fly_string);
auto parsed_value = CSS::StyleProperties::transformations_for_style_value(*transform_style_value);
// 3. If parsedValue is none, set parsedValue to a <transform-list> containing a single identity matrix.
// NOTE: parsed_value is empty on none so for loop in 6 won't modify matrix
auto matrix = Gfx::FloatMatrix4x4::identity();
// 4. Let 2dTransform track the 2D/3D dimension status of parsedValue.
// -> If parsedValue consists of any three-dimensional transform functions, set 2dTransform to false.
// -> Otherwise, set 2dTransform to true.
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)
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({});
if (transform_matrix.is_error())
return WebIDL::SyntaxError::create(realm, "Failed to parse CSS transform string."_fly_string);
matrix = matrix * transform_matrix.value();
}
// 7. Return matrix and 2dTransform.
auto* elements = matrix.elements();
Gfx::DoubleMatrix4x4 double_matrix {
static_cast<double>(elements[0][0]), static_cast<double>(elements[0][1]), static_cast<double>(elements[0][2]), static_cast<double>(elements[0][3]),
static_cast<double>(elements[1][0]), static_cast<double>(elements[1][1]), static_cast<double>(elements[1][2]), static_cast<double>(elements[1][3]),
static_cast<double>(elements[2][0]), static_cast<double>(elements[2][1]), static_cast<double>(elements[2][2]), static_cast<double>(elements[2][3]),
static_cast<double>(elements[3][0]), static_cast<double>(elements[3][1]), static_cast<double>(elements[3][2]), static_cast<float>(elements[3][3])
};
return ParsedMatrix { double_matrix, is_2d_transform };
}
}

View file

@ -112,7 +112,6 @@ public:
protected:
DOMMatrixReadOnly(JS::Realm&, double m11, double m12, double m21, double m22, double m41, double m42);
DOMMatrixReadOnly(JS::Realm&, double m11, double m12, double m13, double m14, double m21, double m22, double m23, double m24, double m31, double m32, double m33, double m34, double m41, double m42, double m43, double m44);
DOMMatrixReadOnly(JS::Realm&, Optional<Variant<String, Vector<double>>> const& init);
DOMMatrixReadOnly(JS::Realm&, DOMMatrixReadOnly const& other);
// NOTE: The matrix used in the spec is column-major (https://drafts.fxtf.org/geometry/#4x4-abstract-matrix) but Gfx::Matrix4x4 is row-major so we need to transpose the values.
@ -130,4 +129,11 @@ private:
WebIDL::ExceptionOr<void> validate_and_fixup_dom_matrix_2d_init(DOMMatrix2DInit& init);
WebIDL::ExceptionOr<void> validate_and_fixup_dom_matrix_init(DOMMatrixInit& init);
struct ParsedMatrix {
Gfx::DoubleMatrix4x4 matrix;
bool is_2d_transform;
};
WebIDL::ExceptionOr<ParsedMatrix> parse_dom_matrix_init_string(JS::Realm& realm, StringView transform_list);
}

View file

@ -300,7 +300,7 @@ Gfx::FloatMatrix4x4 StackingContext::combine_transformations(Vector<CSS::Transfo
auto matrix = Gfx::FloatMatrix4x4::identity();
if (paintable().is_paintable_box()) {
for (auto const& transform : transformations)
matrix = matrix * transform.to_matrix(paintable_box());
matrix = matrix * transform.to_matrix(paintable_box()).release_value();
return matrix;
}