Browse Source

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).
MacDue 1 năm trước cách đây
mục cha
commit
b10f58a1fe

+ 26 - 0
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 <html> at (0,0) content-size 800x600 [BFC] children: not-inline
+    BlockContainer <body> at (8,8) content-size 784x150 children: inline
+      frag 0 from SVGSVGBox start: 0, length: 0, rect: [8,8 300x150] baseline: 150
+      SVGSVGBox <svg> at (8,8) content-size 300x150 [SVG] children: inline
+        TextNode <#text>
+        SVGSVGBox <svg> at (18,8) content-size 300x150 [SVG] children: inline
+          TextNode <#text>
+          SVGGeometryBox <rect> at (27.5,17.5) content-size 101x101 children: not-inline
+          TextNode <#text>
+        TextNode <#text>
+        SVGSVGBox <svg> at (208,23) content-size 300x150 [SVG] children: inline
+          TextNode <#text>
+          SVGGeometryBox <rect> 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<HTML>) [0,0 800x600]
+    PaintableWithLines (BlockContainer<BODY>) [8,8 784x150]
+      SVGSVGPaintable (SVGSVGBox<svg>) [8,8 300x150]
+        SVGSVGPaintable (SVGSVGBox<svg>) [18,8 300x150]
+          SVGPathPaintable (SVGGeometryBox<rect>) [27.5,17.5 101x101]
+        SVGSVGPaintable (SVGSVGBox<svg>) [208,23 300x150]
+          SVGPathPaintable (SVGGeometryBox<rect>) [217.5,32.5 101x101]

+ 11 - 0
Tests/LibWeb/Layout/input/svg/svg-inside-svg-with-xy.html

@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+  xmlns:xlink="http://www.w3.org/1999/xlink">
+  <svg x="10">
+    <rect x="10" y="10" height="100" width="100"
+        style="stroke:#ff0000; fill: #0000ff"/>
+  </svg>
+  <svg x="200" y="15">
+    <rect x="10" y="10" height="100" width="100"
+        style="stroke:#009900; fill: #00cc00"/>
+  </svg>
+</svg>

+ 2 - 0
Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt

@@ -166,4 +166,6 @@ white-space: normal
 width: auto
 width: auto
 word-spacing: normal
 word-spacing: normal
 word-wrap: normal
 word-wrap: normal
+x: 0px
+y: 0px
 z-index: auto
 z-index: auto

+ 8 - 0
Userland/Libraries/LibWeb/CSS/ComputedValues.h

@@ -161,6 +161,8 @@ public:
     static CSS::TableLayout table_layout() { return CSS::TableLayout::Auto; }
     static CSS::TableLayout table_layout() { return CSS::TableLayout::Auto; }
     static QuotesData quotes() { return QuotesData { .type = QuotesData::Type::Auto }; }
     static QuotesData quotes() { return QuotesData { .type = QuotesData::Type::Auto }; }
     static CSS::TransformBox transform_box() { return CSS::TransformBox::ViewBox; }
     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::MaskType mask_type() { return CSS::MaskType::Luminance; }
     static CSS::MathShift math_shift() { return CSS::MathShift::Normal; }
     static CSS::MathShift math_shift() { return CSS::MathShift::Normal; }
@@ -392,6 +394,8 @@ public:
     CSS::TextAnchor text_anchor() const { return m_inherited.text_anchor; }
     CSS::TextAnchor text_anchor() const { return m_inherited.text_anchor; }
     Optional<MaskReference> const& mask() const { return m_noninherited.mask; }
     Optional<MaskReference> const& mask() const { return m_noninherited.mask; }
     CSS::MaskType mask_type() const { return m_noninherited.mask_type; }
     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<CSS::Transformation> const& transformations() const { return m_noninherited.transformations; }
     Vector<CSS::Transformation> const& transformations() const { return m_noninherited.transformations; }
     CSS::TransformBox const& transform_box() const { return m_noninherited.transform_box; }
     CSS::TransformBox const& transform_box() const { return m_noninherited.transform_box; }
@@ -545,6 +549,8 @@ protected:
 
 
         Optional<MaskReference> mask;
         Optional<MaskReference> mask;
         CSS::MaskType mask_type { InitialValues::mask_type() };
         CSS::MaskType mask_type { InitialValues::mask_type() };
+        LengthPercentage x { InitialValues::x() };
+        LengthPercentage y { InitialValues::x() };
     } m_noninherited;
     } m_noninherited;
 };
 };
 
 
@@ -667,6 +673,8 @@ public:
     void set_outline_width(CSS::Length value) { m_noninherited.outline_width = value; }
     void set_outline_width(CSS::Length value) { m_noninherited.outline_width = value; }
     void set_mask(MaskReference value) { m_noninherited.mask = value; }
     void set_mask(MaskReference value) { m_noninherited.mask = value; }
     void set_mask_type(CSS::MaskType value) { m_noninherited.mask_type = 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_shift(CSS::MathShift value) { m_inherited.math_shift = value; }
     void set_math_style(CSS::MathStyle value) { m_inherited.math_style = value; }
     void set_math_style(CSS::MathStyle value) { m_inherited.math_style = value; }

+ 26 - 0
Userland/Libraries/LibWeb/CSS/Properties.json

@@ -2196,6 +2196,32 @@
       "normal"
       "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": {
   "z-index": {
     "affects-layout": false,
     "affects-layout": false,
     "affects-stacking-context": true,
     "affects-stacking-context": true,

+ 4 - 0
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_template_areas(computed_style.grid_template_areas());
     computed_values.set_grid_auto_flow(computed_style.grid_auto_flow());
     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);
     auto fill = computed_style.property(CSS::PropertyID::Fill);
     if (fill->has_color())
     if (fill->has_color())
         computed_values.set_fill(fill->to_color(*this));
         computed_values.set_fill(fill->to_color(*this));

+ 3 - 1
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);
                 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_width = resolve_dimension(descendant, descendant.computed_values().width(), viewport_width);
             auto nested_viewport_height = resolve_dimension(descendant, descendant.computed_values().height(), viewport_height);
             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_width(nested_viewport_width);
             nested_viewport_state.set_content_height(nested_viewport_height);
             nested_viewport_state.set_content_height(nested_viewport_height);
             nested_context.run(static_cast<Box const&>(descendant), layout_mode, available_space);
             nested_context.run(static_cast<Box const&>(descendant), layout_mode, available_space);

+ 11 - 1
Userland/Libraries/LibWeb/SVG/SVGSVGElement.cpp

@@ -47,9 +47,19 @@ JS::GCPtr<Layout::Node> SVGSVGElement::create_layout_node(NonnullRefPtr<CSS::Sty
 void SVGSVGElement::apply_presentational_hints(CSS::StyleProperties& style) const
 void SVGSVGElement::apply_presentational_hints(CSS::StyleProperties& style) const
 {
 {
     Base::apply_presentational_hints(style);
     Base::apply_presentational_hints(style);
+    auto parsing_context = CSS::Parser::ParsingContext { document(), CSS::Parser::ParsingContext::Mode::SVGPresentationAttribute };
+
+    auto x_attribute = attribute(SVG::AttributeNames::x);
+    if (auto x_value = parse_css_value(parsing_context, x_attribute.value_or(String {}), CSS::PropertyID::X)) {
+        style.set_property(CSS::PropertyID::X, x_value.release_nonnull());
+    }
+
+    auto y_attribute = attribute(SVG::AttributeNames::y);
+    if (auto y_value = parse_css_value(parsing_context, y_attribute.value_or(String {}), CSS::PropertyID::Y)) {
+        style.set_property(CSS::PropertyID::Y, y_value.release_nonnull());
+    }
 
 
     auto width_attribute = attribute(SVG::AttributeNames::width);
     auto width_attribute = attribute(SVG::AttributeNames::width);
-    auto parsing_context = CSS::Parser::ParsingContext { document(), CSS::Parser::ParsingContext::Mode::SVGPresentationAttribute };
     if (auto width_value = parse_css_value(parsing_context, width_attribute.value_or(String {}), CSS::PropertyID::Width)) {
     if (auto width_value = parse_css_value(parsing_context, width_attribute.value_or(String {}), CSS::PropertyID::Width)) {
         style.set_property(CSS::PropertyID::Width, width_value.release_nonnull());
         style.set_property(CSS::PropertyID::Width, width_value.release_nonnull());
     } else if (width_attribute == "") {
     } else if (width_attribute == "") {