From b10f58a1fe2a5302aff61801bcb44c4a69fa003a Mon Sep 17 00:00:00 2001 From: MacDue Date: Sun, 28 Jan 2024 17:48:59 +0000 Subject: [PATCH] LibWeb: Support `x` and `y` attributes on nested SVGs This allows positioning a child SVG relative to its parent SVG. Note: These have been implemented as CSS properties as in SVG 2, these are geometry properties that can be used in CSS (see https://www.w3.org/TR/SVG/geometry.html), but there is not much browser support for this. It is nicer to implement than the ad-hoc SVG attribute parsing though, so I feel it may make sense to port the rest of the attributes specified here (which should fix some issues with viewport relative sizes). --- .../expected/svg/svg-inside-svg-with-xy.txt | 26 +++++++++++++++++++ .../input/svg/svg-inside-svg-with-xy.html | 11 ++++++++ .../css/getComputedStyle-print-all.txt | 2 ++ .../Libraries/LibWeb/CSS/ComputedValues.h | 8 ++++++ Userland/Libraries/LibWeb/CSS/Properties.json | 26 +++++++++++++++++++ Userland/Libraries/LibWeb/Layout/Node.cpp | 4 +++ .../LibWeb/Layout/SVGFormattingContext.cpp | 4 ++- .../Libraries/LibWeb/SVG/SVGSVGElement.cpp | 12 ++++++++- 8 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 Tests/LibWeb/Layout/expected/svg/svg-inside-svg-with-xy.txt create mode 100644 Tests/LibWeb/Layout/input/svg/svg-inside-svg-with-xy.html diff --git a/Tests/LibWeb/Layout/expected/svg/svg-inside-svg-with-xy.txt b/Tests/LibWeb/Layout/expected/svg/svg-inside-svg-with-xy.txt new file mode 100644 index 0000000000..5c1e4f8740 --- /dev/null +++ b/Tests/LibWeb/Layout/expected/svg/svg-inside-svg-with-xy.txt @@ -0,0 +1,26 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer at (0,0) content-size 800x600 [BFC] children: not-inline + BlockContainer at (8,8) content-size 784x150 children: inline + frag 0 from SVGSVGBox start: 0, length: 0, rect: [8,8 300x150] baseline: 150 + SVGSVGBox at (8,8) content-size 300x150 [SVG] children: inline + TextNode <#text> + SVGSVGBox at (18,8) content-size 300x150 [SVG] children: inline + TextNode <#text> + SVGGeometryBox at (27.5,17.5) content-size 101x101 children: not-inline + TextNode <#text> + TextNode <#text> + SVGSVGBox at (208,23) content-size 300x150 [SVG] children: inline + TextNode <#text> + SVGGeometryBox at (217.5,32.5) content-size 101x101 children: not-inline + TextNode <#text> + TextNode <#text> + TextNode <#text> + +ViewportPaintable (Viewport<#document>) [0,0 800x600] + PaintableWithLines (BlockContainer) [0,0 800x600] + PaintableWithLines (BlockContainer) [8,8 784x150] + SVGSVGPaintable (SVGSVGBox) [8,8 300x150] + SVGSVGPaintable (SVGSVGBox) [18,8 300x150] + SVGPathPaintable (SVGGeometryBox) [27.5,17.5 101x101] + SVGSVGPaintable (SVGSVGBox) [208,23 300x150] + SVGPathPaintable (SVGGeometryBox) [217.5,32.5 101x101] diff --git a/Tests/LibWeb/Layout/input/svg/svg-inside-svg-with-xy.html b/Tests/LibWeb/Layout/input/svg/svg-inside-svg-with-xy.html new file mode 100644 index 0000000000..8282162cb7 --- /dev/null +++ b/Tests/LibWeb/Layout/input/svg/svg-inside-svg-with-xy.html @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt b/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt index e56b55f896..1c3fd4bd9d 100644 --- a/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt +++ b/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt @@ -166,4 +166,6 @@ white-space: normal width: auto word-spacing: normal word-wrap: normal +x: 0px +y: 0px z-index: auto diff --git a/Userland/Libraries/LibWeb/CSS/ComputedValues.h b/Userland/Libraries/LibWeb/CSS/ComputedValues.h index 29fadd3734..a3b1447c54 100644 --- a/Userland/Libraries/LibWeb/CSS/ComputedValues.h +++ b/Userland/Libraries/LibWeb/CSS/ComputedValues.h @@ -161,6 +161,8 @@ public: static CSS::TableLayout table_layout() { return CSS::TableLayout::Auto; } static QuotesData quotes() { return QuotesData { .type = QuotesData::Type::Auto }; } static CSS::TransformBox transform_box() { return CSS::TransformBox::ViewBox; } + static LengthPercentage x() { return CSS::Length::make_px(0); } + static LengthPercentage y() { return CSS::Length::make_px(0); } static CSS::MaskType mask_type() { return CSS::MaskType::Luminance; } static CSS::MathShift math_shift() { return CSS::MathShift::Normal; } @@ -392,6 +394,8 @@ public: CSS::TextAnchor text_anchor() const { return m_inherited.text_anchor; } Optional const& mask() const { return m_noninherited.mask; } CSS::MaskType mask_type() const { return m_noninherited.mask_type; } + LengthPercentage const& x() const { return m_noninherited.x; } + LengthPercentage const& y() const { return m_noninherited.y; } Vector const& transformations() const { return m_noninherited.transformations; } CSS::TransformBox const& transform_box() const { return m_noninherited.transform_box; } @@ -545,6 +549,8 @@ protected: Optional mask; CSS::MaskType mask_type { InitialValues::mask_type() }; + LengthPercentage x { InitialValues::x() }; + LengthPercentage y { InitialValues::x() }; } m_noninherited; }; @@ -667,6 +673,8 @@ public: void set_outline_width(CSS::Length value) { m_noninherited.outline_width = value; } void set_mask(MaskReference value) { m_noninherited.mask = value; } void set_mask_type(CSS::MaskType value) { m_noninherited.mask_type = value; } + void set_x(LengthPercentage x) { m_noninherited.x = x; } + void set_y(LengthPercentage y) { m_noninherited.y = y; } void set_math_shift(CSS::MathShift value) { m_inherited.math_shift = value; } void set_math_style(CSS::MathStyle value) { m_inherited.math_style = value; } diff --git a/Userland/Libraries/LibWeb/CSS/Properties.json b/Userland/Libraries/LibWeb/CSS/Properties.json index 5238e672bd..b4f3bc6ad1 100644 --- a/Userland/Libraries/LibWeb/CSS/Properties.json +++ b/Userland/Libraries/LibWeb/CSS/Properties.json @@ -2196,6 +2196,32 @@ "normal" ] }, + "x": { + "__comment": "This is an SVG 2 geometry property, see: https://www.w3.org/TR/SVG/geometry.html#X.", + "inherited": false, + "initial": "0", + "valid-types": [ + "length [-∞,∞]", + "percentage [-∞,∞]" + ], + "percentages-resolve-to": "length", + "quirks": [ + "unitless-length" + ] + }, + "y": { + "__comment": "This is an SVG 2 geometry property, see: https://www.w3.org/TR/SVG/geometry.html#Y.", + "inherited": false, + "initial": "0", + "valid-types": [ + "length [-∞,∞]", + "percentage [-∞,∞]" + ], + "percentages-resolve-to": "length", + "quirks": [ + "unitless-length" + ] + }, "z-index": { "affects-layout": false, "affects-stacking-context": true, diff --git a/Userland/Libraries/LibWeb/Layout/Node.cpp b/Userland/Libraries/LibWeb/Layout/Node.cpp index 524178da4d..6cf09be56c 100644 --- a/Userland/Libraries/LibWeb/Layout/Node.cpp +++ b/Userland/Libraries/LibWeb/Layout/Node.cpp @@ -751,6 +751,10 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style) computed_values.set_grid_template_areas(computed_style.grid_template_areas()); computed_values.set_grid_auto_flow(computed_style.grid_auto_flow()); + if (auto x_value = computed_style.length_percentage(CSS::PropertyID::X); x_value.has_value()) + computed_values.set_x(*x_value); + if (auto y_value = computed_style.length_percentage(CSS::PropertyID::Y); y_value.has_value()) + computed_values.set_y(*y_value); auto fill = computed_style.property(CSS::PropertyID::Fill); if (fill->has_color()) computed_values.set_fill(fill->to_color(*this)); diff --git a/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp index ebce36ed4b..fe28c588dc 100644 --- a/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp @@ -285,9 +285,11 @@ void SVGFormattingContext::run(Box const& box, LayoutMode layout_mode, Available return size.to_px(node, reference_value); }; - // FIXME: Support the x/y attributes to calculate the offset. + auto nested_viewport_x = descendant.computed_values().x().to_px(descendant, viewport_width); + auto nested_viewport_y = descendant.computed_values().y().to_px(descendant, viewport_height); auto nested_viewport_width = resolve_dimension(descendant, descendant.computed_values().width(), viewport_width); auto nested_viewport_height = resolve_dimension(descendant, descendant.computed_values().height(), viewport_height); + nested_viewport_state.set_content_offset({ nested_viewport_x, nested_viewport_y }); nested_viewport_state.set_content_width(nested_viewport_width); nested_viewport_state.set_content_height(nested_viewport_height); nested_context.run(static_cast(descendant), layout_mode, available_space); diff --git a/Userland/Libraries/LibWeb/SVG/SVGSVGElement.cpp b/Userland/Libraries/LibWeb/SVG/SVGSVGElement.cpp index 71989e382f..35a76c2ab1 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGSVGElement.cpp +++ b/Userland/Libraries/LibWeb/SVG/SVGSVGElement.cpp @@ -47,9 +47,19 @@ JS::GCPtr SVGSVGElement::create_layout_node(NonnullRefPtr