Bläddra i källkod

LibWeb: Implement SVG `fill-rule` attribute

Previously, we did an evenodd fill for everything which while for most
SVGs works, it is not correct default (it should be nonzero), and broke
some SVGs. This fixes a few of the icons on https://shopify.com/.
MacDue 2 år sedan
förälder
incheckning
377ff0ac26

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

@@ -72,6 +72,7 @@ public:
     static int order() { return 0; }
     static float opacity() { return 1.0f; }
     static float fill_opacity() { return 1.0f; }
+    static CSS::FillRule fill_rule() { return CSS::FillRule::Nonzero; }
     static float stroke_opacity() { return 1.0f; }
     static float stop_opacity() { return 1.0f; }
     static CSS::Length border_radius() { return Length::make_px(0); }
@@ -303,6 +304,7 @@ public:
     CSS::ListStylePosition list_style_position() const { return m_inherited.list_style_position; }
 
     Optional<SVGPaint> const& fill() const { return m_inherited.fill; }
+    CSS::FillRule fill_rule() const { return m_inherited.fill_rule; }
     Optional<SVGPaint> const& stroke() const { return m_inherited.stroke; }
     float fill_opacity() const { return m_inherited.fill_opacity; }
     float stroke_opacity() const { return m_inherited.stroke_opacity; }
@@ -346,6 +348,7 @@ protected:
         CSS::Visibility visibility { InitialValues::visibility() };
 
         Optional<SVGPaint> fill;
+        CSS::FillRule fill_rule { InitialValues::fill_rule() };
         Optional<SVGPaint> stroke;
         float fill_opacity { InitialValues::fill_opacity() };
         float stroke_opacity { InitialValues::stroke_opacity() };
@@ -515,6 +518,7 @@ public:
 
     void set_fill(SVGPaint value) { m_inherited.fill = value; }
     void set_stroke(SVGPaint value) { m_inherited.stroke = value; }
+    void set_fill_rule(CSS::FillRule value) { m_inherited.fill_rule = value; }
     void set_fill_opacity(float value) { m_inherited.fill_opacity = value; }
     void set_stroke_opacity(float value) { m_inherited.stroke_opacity = value; }
     void set_stroke_width(LengthPercentage value) { m_inherited.stroke_width = value; }

+ 4 - 0
Userland/Libraries/LibWeb/CSS/Enums.json

@@ -132,6 +132,10 @@
         "zoom-in",
         "zoom-out"
     ],
+    "fill-rule": [
+        "nonzero",
+        "evenodd"
+    ],
     "flex-direction": [
         "row",
         "row-reverse",

+ 2 - 0
Userland/Libraries/LibWeb/CSS/Identifiers.json

@@ -120,6 +120,7 @@
   "ease-out",
   "enabled",
   "end",
+  "evenodd",
   "ew-resize",
   "expanded",
   "extra-condensed",
@@ -199,6 +200,7 @@
   "no-preference",
   "no-repeat",
   "none",
+  "nonzero",
   "normal",
   "not-allowed",
   "nowrap",

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

@@ -755,6 +755,15 @@
       "percentage [-∞,∞]"
     ]
   },
+  "fill-rule": {
+    "affects-layout": false,
+    "inherited": true,
+    "initial": "nonzero",
+    "valid-identifiers": [
+      "nonzero",
+      "evenodd"
+    ]
+  },
   "flex": {
     "inherited": false,
     "initial": "0 1 auto",

+ 6 - 0
Userland/Libraries/LibWeb/CSS/StyleProperties.cpp

@@ -309,6 +309,12 @@ float StyleProperties::stop_opacity() const
     return resolve_opacity_value(*value);
 }
 
+Optional<CSS::FillRule> StyleProperties::fill_rule() const
+{
+    auto value = property(CSS::PropertyID::FillRule);
+    return value_id_to_fill_rule(value->to_identifier());
+}
+
 Optional<CSS::FlexDirection> StyleProperties::flex_direction() const
 {
     auto value = property(CSS::PropertyID::FlexDirection);

+ 1 - 0
Userland/Libraries/LibWeb/CSS/StyleProperties.h

@@ -107,6 +107,7 @@ public:
     float stop_opacity() const;
     float fill_opacity() const;
     float stroke_opacity() const;
+    Optional<CSS::FillRule> fill_rule() const;
 
     Gfx::Font const& computed_font() const
     {

+ 3 - 0
Userland/Libraries/LibWeb/Layout/Node.cpp

@@ -705,6 +705,9 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style)
     else if (stroke_width->is_percentage())
         computed_values.set_stroke_width(CSS::LengthPercentage { stroke_width->as_percentage().percentage() });
 
+    if (auto fill_rule = computed_style.fill_rule(); fill_rule.has_value())
+        computed_values.set_fill_rule(*fill_rule);
+
     computed_values.set_fill_opacity(computed_style.fill_opacity());
     computed_values.set_stroke_opacity(computed_style.stroke_opacity());
     computed_values.set_stop_opacity(computed_style.stop_opacity());

+ 16 - 2
Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.cpp

@@ -42,6 +42,18 @@ Optional<HitTestResult> SVGGeometryPaintable::hit_test(CSSPixelPoint position, H
     return result;
 }
 
+static Gfx::Painter::WindingRule to_gfx_winding_rule(SVG::FillRule fill_rule)
+{
+    switch (fill_rule) {
+    case SVG::FillRule::Nonzero:
+        return Gfx::Painter::WindingRule::Nonzero;
+    case SVG::FillRule::Evenodd:
+        return Gfx::Painter::WindingRule::EvenOdd;
+    default:
+        VERIFY_NOT_REACHED();
+    }
+}
+
 void SVGGeometryPaintable::paint(PaintContext& context, PaintPhase phase) const
 {
     if (!is_visible())
@@ -100,17 +112,19 @@ void SVGGeometryPaintable::paint(PaintContext& context, PaintPhase phase) const
     };
 
     auto fill_opacity = geometry_element.fill_opacity().value_or(svg_context.fill_opacity());
+    auto winding_rule = to_gfx_winding_rule(geometry_element.fill_rule().value_or(svg_context.fill_rule()));
+
     if (auto paint_style = geometry_element.fill_paint_style(paint_context); paint_style.has_value()) {
         painter.fill_path(
             closed_path(),
             *paint_style,
             fill_opacity,
-            Gfx::Painter::WindingRule::EvenOdd);
+            winding_rule);
     } else if (auto fill_color = geometry_element.fill_color().value_or(svg_context.fill_color()).with_opacity(fill_opacity); fill_color.alpha() > 0) {
         painter.fill_path(
             closed_path(),
             fill_color,
-            Gfx::Painter::WindingRule::EvenOdd);
+            winding_rule);
     }
 
     auto stroke_opacity = geometry_element.stroke_opacity().value_or(svg_context.stroke_opacity());

+ 2 - 0
Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp

@@ -32,6 +32,8 @@ void SVGGraphicsPaintable::before_children_paint(PaintContext& context, PaintPha
 
     auto& graphics_element = layout_box().dom_node();
 
+    if (auto fill_rule = graphics_element.fill_rule(); fill_rule.has_value())
+        context.svg_context().set_fill_rule(*fill_rule);
     if (auto fill_color = graphics_element.fill_color(); fill_color.has_value())
         context.svg_context().set_fill_color(*fill_color);
     if (auto stroke_color = graphics_element.stroke_color(); stroke_color.has_value())

+ 5 - 0
Userland/Libraries/LibWeb/SVG/AttributeParser.h

@@ -120,6 +120,11 @@ private:
     bool m_is_percentage { false };
 };
 
+enum class FillRule {
+    Nonzero,
+    Evenodd
+};
+
 class AttributeParser final {
 public:
     ~AttributeParser() = default;

+ 4 - 0
Userland/Libraries/LibWeb/SVG/SVGContext.h

@@ -9,6 +9,7 @@
 #include <AK/Vector.h>
 #include <LibGfx/Color.h>
 #include <LibGfx/Rect.h>
+#include <LibWeb/SVG/AttributeParser.h>
 
 namespace Web {
 
@@ -20,12 +21,14 @@ public:
         m_states.append(State());
     }
 
+    SVG::FillRule fill_rule() const { return state().fill_rule; }
     Gfx::Color fill_color() const { return state().fill_color; }
     Gfx::Color stroke_color() const { return state().stroke_color; }
     float stroke_width() const { return state().stroke_width; }
     float fill_opacity() const { return state().fill_opacity; }
     float stroke_opacity() const { return state().stroke_opacity; }
 
+    void set_fill_rule(SVG::FillRule fill_rule) { state().fill_rule = fill_rule; }
     void set_fill_color(Gfx::Color color) { state().fill_color = color; }
     void set_stroke_color(Gfx::Color color) { state().stroke_color = color; }
     void set_stroke_width(float width) { state().stroke_width = width; }
@@ -40,6 +43,7 @@ public:
 
 private:
     struct State {
+        SVG::FillRule fill_rule { SVG::FillRule::Nonzero };
         Gfx::Color fill_color { Gfx::Color::Transparent };
         Gfx::Color stroke_color { Gfx::Color::Transparent };
         float stroke_width { 1.0f };

+ 17 - 0
Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.cpp

@@ -129,6 +129,9 @@ void SVGGraphicsElement::apply_presentational_hints(CSS::StyleProperties& style)
         } else if (name.equals_ignoring_ascii_case("stroke-width"sv)) {
             if (auto stroke_width_value = parse_css_value(parsing_context, value, CSS::PropertyID::StrokeWidth).release_value_but_fixme_should_propagate_errors())
                 style.set_property(CSS::PropertyID::StrokeWidth, stroke_width_value.release_nonnull());
+        } else if (name.equals_ignoring_ascii_case("fill-rule"sv)) {
+            if (auto fill_rule_value = parse_css_value(parsing_context, value, CSS::PropertyID::FillRule).release_value_but_fixme_should_propagate_errors())
+                style.set_property(CSS::PropertyID::FillRule, fill_rule_value.release_nonnull());
         } else if (name.equals_ignoring_ascii_case("fill-opacity"sv)) {
             if (auto fill_opacity_value = parse_css_value(parsing_context, value, CSS::PropertyID::FillOpacity).release_value_but_fixme_should_propagate_errors())
                 style.set_property(CSS::PropertyID::FillOpacity, fill_opacity_value.release_nonnull());
@@ -139,6 +142,20 @@ void SVGGraphicsElement::apply_presentational_hints(CSS::StyleProperties& style)
     });
 }
 
+Optional<FillRule> SVGGraphicsElement::fill_rule() const
+{
+    if (!layout_node())
+        return {};
+    switch (layout_node()->computed_values().fill_rule()) {
+    case CSS::FillRule::Nonzero:
+        return FillRule::Nonzero;
+    case CSS::FillRule::Evenodd:
+        return FillRule::Evenodd;
+    default:
+        VERIFY_NOT_REACHED();
+    }
+}
+
 Optional<Gfx::Color> SVGGraphicsElement::fill_color() const
 {
     if (!layout_node())

+ 1 - 1
Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.h

@@ -27,7 +27,7 @@ public:
     virtual void parse_attribute(DeprecatedFlyString const& name, DeprecatedString const& value) override;
 
     Optional<Gfx::Color> fill_color() const;
-    Gfx::Painter::WindingRule fill_rule() const;
+    Optional<FillRule> fill_rule() const;
     Optional<Gfx::Color> stroke_color() const;
     Optional<float> stroke_width() const;
     Optional<float> fill_opacity() const;