diff --git a/Tests/LibWeb/Text/expected/geometry/dommatrix-create.txt b/Tests/LibWeb/Text/expected/geometry/dommatrix-create.txt index bd434f0af8..517e542d47 100644 --- a/Tests/LibWeb/Text/expected/geometry/dommatrix-create.txt +++ b/Tests/LibWeb/Text/expected/geometry/dommatrix-create.txt @@ -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 diff --git a/Tests/LibWeb/Text/expected/geometry/dommatrix-matrix-value.txt b/Tests/LibWeb/Text/expected/geometry/dommatrix-matrix-value.txt new file mode 100644 index 0000000000..b5c727ec0d --- /dev/null +++ b/Tests/LibWeb/Text/expected/geometry/dommatrix-matrix-value.txt @@ -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 diff --git a/Tests/LibWeb/Text/input/geometry/dommatrix-create.html b/Tests/LibWeb/Text/input/geometry/dommatrix-create.html index 12d3cd11bf..2ea5513b1b 100644 --- a/Tests/LibWeb/Text/input/geometry/dommatrix-create.html +++ b/Tests/LibWeb/Text/input/geometry/dommatrix-create.html @@ -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)')); }); diff --git a/Tests/LibWeb/Text/input/geometry/dommatrix-matrix-value.html b/Tests/LibWeb/Text/input/geometry/dommatrix-matrix-value.html new file mode 100644 index 0000000000..45b5105a6f --- /dev/null +++ b/Tests/LibWeb/Text/input/geometry/dommatrix-matrix-value.html @@ -0,0 +1,32 @@ + + diff --git a/Userland/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp b/Userland/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp index 0b2f71d216..c363c77365 100644 --- a/Userland/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp +++ b/Userland/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp @@ -357,7 +357,7 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_property(L VERIFY(layout_node.paintable()); auto const& paintable_box = verify_cast(*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 diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp index 8ce1989e5c..9a6fef9cf7 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp @@ -405,17 +405,15 @@ Optional StyleProperties::justify_self() const return value_id_to_justify_self(value->to_identifier()); } -Vector StyleProperties::transformations() const +Vector 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 transformations; @@ -456,6 +454,11 @@ Vector StyleProperties::transformations() const return transformations; } +Vector StyleProperties::transformations() const +{ + return transformations_for_style_value(property(CSS::PropertyID::Transform)); +} + static Optional length_percentage_for_style_value(StyleValue const& value) { if (value.is_length()) diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.h b/Userland/Libraries/LibWeb/CSS/StyleProperties.h index 88cb18d4ac..6109722352 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleProperties.h +++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.h @@ -117,6 +117,7 @@ public: CSS::PositionStyleValue const& object_position() const; Optional table_layout() const; + static Vector transformations_for_style_value(StyleValue const& value); Vector transformations() const; CSS::TransformOrigin transform_origin() const; diff --git a/Userland/Libraries/LibWeb/CSS/Transformation.cpp b/Userland/Libraries/LibWeb/CSS/Transformation.cpp index 1921f9db82..b76cda62b4 100644 --- a/Userland/Libraries/LibWeb/CSS/Transformation.cpp +++ b/Userland/Libraries/LibWeb/CSS/Transformation.cpp @@ -6,7 +6,6 @@ */ #include "Transformation.h" -#include #include namespace Web::CSS { @@ -17,62 +16,74 @@ Transformation::Transformation(TransformFunction function, Vector Transformation::to_matrix(Optional 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 { 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 { + 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 { + 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 { 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; diff --git a/Userland/Libraries/LibWeb/CSS/Transformation.h b/Userland/Libraries/LibWeb/CSS/Transformation.h index 096392046b..56bed71e75 100644 --- a/Userland/Libraries/LibWeb/CSS/Transformation.h +++ b/Userland/Libraries/LibWeb/CSS/Transformation.h @@ -20,7 +20,10 @@ using TransformValue = Variant; class Transformation { public: Transformation(TransformFunction function, Vector&& values); - Gfx::FloatMatrix4x4 to_matrix(Painting::PaintableBox const&) const; + + TransformFunction function() const { return m_function; } + + ErrorOr to_matrix(Optional) const; private: TransformFunction m_function; diff --git a/Userland/Libraries/LibWeb/Geometry/DOMMatrix.cpp b/Userland/Libraries/LibWeb/Geometry/DOMMatrix.cpp index 8d1ef71710..c1c4c599e3 100644 --- a/Userland/Libraries/LibWeb/Geometry/DOMMatrix.cpp +++ b/Userland/Libraries/LibWeb/Geometry/DOMMatrix.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -15,20 +16,63 @@ namespace Web::Geometry { JS_DEFINE_ALLOCATOR(DOMMatrix); +// https://drafts.fxtf.org/geometry/#dom-dommatrix-dommatrix WebIDL::ExceptionOr> DOMMatrix::construct_impl(JS::Realm& realm, Optional>> 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>(); 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(realm, realm, 1, 0, 0, 1, 0, 0); } - return realm.heap().allocate(realm, realm, init); + auto const& init_value = init.value(); + + // -> If init is a DOMString + if (init_value.has()) { + // 1. If current global object is not a Window object, then throw a TypeError exception. + if (!is(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())); + 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(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(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>(); + + // -> 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(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(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>> 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::invert_self() return *this; } +// https://drafts.fxtf.org/geometry/#dom-dommatrix-setmatrixvalue +WebIDL::ExceptionOr> 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(*this); +} + } diff --git a/Userland/Libraries/LibWeb/Geometry/DOMMatrix.h b/Userland/Libraries/LibWeb/Geometry/DOMMatrix.h index 834bcdb095..b6ba1ecd04 100644 --- a/Userland/Libraries/LibWeb/Geometry/DOMMatrix.h +++ b/Userland/Libraries/LibWeb/Geometry/DOMMatrix.h @@ -65,10 +65,11 @@ public: JS::NonnullGCPtr skew_y_self(double sy = 0); JS::NonnullGCPtr invert_self(); + WebIDL::ExceptionOr> 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>> const& init); DOMMatrix(JS::Realm&, DOMMatrixReadOnly const& read_only_matrix); virtual void initialize(JS::Realm&) override; diff --git a/Userland/Libraries/LibWeb/Geometry/DOMMatrix.idl b/Userland/Libraries/LibWeb/Geometry/DOMMatrix.idl index 8a181f21d8..6bd54fa7f9 100644 --- a/Userland/Libraries/LibWeb/Geometry/DOMMatrix.idl +++ b/Userland/Libraries/LibWeb/Geometry/DOMMatrix.idl @@ -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); }; diff --git a/Userland/Libraries/LibWeb/Geometry/DOMMatrixReadOnly.cpp b/Userland/Libraries/LibWeb/Geometry/DOMMatrixReadOnly.cpp index ebc3333453..6dcc89c1e8 100644 --- a/Userland/Libraries/LibWeb/Geometry/DOMMatrixReadOnly.cpp +++ b/Userland/Libraries/LibWeb/Geometry/DOMMatrixReadOnly.cpp @@ -6,29 +6,75 @@ */ #include -#include +#include +#include +#include #include #include #include +#include #include #include namespace Web::Geometry { +// https://drafts.fxtf.org/geometry/#dom-dommatrixreadonly-dommatrixreadonly WebIDL::ExceptionOr> DOMMatrixReadOnly::construct_impl(JS::Realm& realm, Optional>> 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>(); 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(realm, realm, 1, 0, 0, 1, 0, 0); } - return realm.heap().allocate(realm, realm, init); + auto const& init_value = init.value(); + + // -> If init is a DOMString + if (init_value.has()) { + // 1. If current global object is not a Window object, then throw a TypeError exception. + if (!is(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())); + 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(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(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>(); + + // -> 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(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(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> 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(realm, realm, init.m11.value(), init.m12.value(), init.m21.value(), init.m22.value(), init.m41.value(), init.m42.value()); + return realm.heap().allocate(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(realm, realm, init.m11.value(), init.m12.value(), init.m13, init.m14, + return realm.heap().allocate(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>> 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()) { - dbgln("FIXME: Implement initializing DOMMatrix(ReadOnly) from DOMString: '{}'", init_value.get()); - // NOTE: This will result in an identity matrix for now. - return; - } - - auto const& double_sequence = init_value.get>(); - - // -> 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> 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(realm, realm, elements.at(0), elements.at(1), elements.at(2), elements.at(3), elements.at(4), elements.at(5)); + return realm.heap().allocate(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(realm, realm, elements.at(0), elements.at(1), elements.at(2), elements.at(3), + return realm.heap().allocate(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> 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(realm, realm, elements.at(0), elements.at(1), elements.at(2), elements.at(3), elements.at(4), elements.at(5)); + return realm.heap().allocate(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(realm, realm, elements.at(0), elements.at(1), elements.at(2), elements.at(3), + return realm.heap().allocate(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 validate_and_fixup_dom_matrix_init(DOMMatrixInit& init return {}; } +// https://drafts.fxtf.org/geometry/#parse-a-string-into-an-abstract-matrix +WebIDL::ExceptionOr 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 , the keyword none, or failure. + // If parsedValue is failure, or any has 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 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 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(elements[0][0]), static_cast(elements[0][1]), static_cast(elements[0][2]), static_cast(elements[0][3]), + static_cast(elements[1][0]), static_cast(elements[1][1]), static_cast(elements[1][2]), static_cast(elements[1][3]), + static_cast(elements[2][0]), static_cast(elements[2][1]), static_cast(elements[2][2]), static_cast(elements[2][3]), + static_cast(elements[3][0]), static_cast(elements[3][1]), static_cast(elements[3][2]), static_cast(elements[3][3]) + }; + return ParsedMatrix { double_matrix, is_2d_transform }; +} + } diff --git a/Userland/Libraries/LibWeb/Geometry/DOMMatrixReadOnly.h b/Userland/Libraries/LibWeb/Geometry/DOMMatrixReadOnly.h index 9b9f49f135..ad5d6092a9 100644 --- a/Userland/Libraries/LibWeb/Geometry/DOMMatrixReadOnly.h +++ b/Userland/Libraries/LibWeb/Geometry/DOMMatrixReadOnly.h @@ -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>> 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 validate_and_fixup_dom_matrix_2d_init(DOMMatrix2DInit& init); WebIDL::ExceptionOr validate_and_fixup_dom_matrix_init(DOMMatrixInit& init); +struct ParsedMatrix { + Gfx::DoubleMatrix4x4 matrix; + bool is_2d_transform; +}; + +WebIDL::ExceptionOr parse_dom_matrix_init_string(JS::Realm& realm, StringView transform_list); + } diff --git a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp index 56ef8b1c19..6a72456332 100644 --- a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp +++ b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp @@ -300,7 +300,7 @@ Gfx::FloatMatrix4x4 StackingContext::combine_transformations(Vector