diff --git a/Userland/Libraries/LibWeb/CSS/ComputedValues.h b/Userland/Libraries/LibWeb/CSS/ComputedValues.h index 8c80073e287..f82ef035212 100644 --- a/Userland/Libraries/LibWeb/CSS/ComputedValues.h +++ b/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 const& fill() const { return m_inherited.fill; } + CSS::FillRule fill_rule() const { return m_inherited.fill_rule; } Optional 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 fill; + CSS::FillRule fill_rule { InitialValues::fill_rule() }; Optional 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; } diff --git a/Userland/Libraries/LibWeb/CSS/Enums.json b/Userland/Libraries/LibWeb/CSS/Enums.json index f47fb00bae5..7a3ee894405 100644 --- a/Userland/Libraries/LibWeb/CSS/Enums.json +++ b/Userland/Libraries/LibWeb/CSS/Enums.json @@ -132,6 +132,10 @@ "zoom-in", "zoom-out" ], + "fill-rule": [ + "nonzero", + "evenodd" + ], "flex-direction": [ "row", "row-reverse", diff --git a/Userland/Libraries/LibWeb/CSS/Identifiers.json b/Userland/Libraries/LibWeb/CSS/Identifiers.json index 285a4be8310..06c50f2eda4 100644 --- a/Userland/Libraries/LibWeb/CSS/Identifiers.json +++ b/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", diff --git a/Userland/Libraries/LibWeb/CSS/Properties.json b/Userland/Libraries/LibWeb/CSS/Properties.json index 655fbb1dfa9..327ea574135 100644 --- a/Userland/Libraries/LibWeb/CSS/Properties.json +++ b/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", diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp index baba7375e7e..3b76443dcb0 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp @@ -309,6 +309,12 @@ float StyleProperties::stop_opacity() const return resolve_opacity_value(*value); } +Optional StyleProperties::fill_rule() const +{ + auto value = property(CSS::PropertyID::FillRule); + return value_id_to_fill_rule(value->to_identifier()); +} + Optional StyleProperties::flex_direction() const { auto value = property(CSS::PropertyID::FlexDirection); diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.h b/Userland/Libraries/LibWeb/CSS/StyleProperties.h index f1d6925c907..b25ce78e630 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleProperties.h +++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.h @@ -107,6 +107,7 @@ public: float stop_opacity() const; float fill_opacity() const; float stroke_opacity() const; + Optional fill_rule() const; Gfx::Font const& computed_font() const { diff --git a/Userland/Libraries/LibWeb/Layout/Node.cpp b/Userland/Libraries/LibWeb/Layout/Node.cpp index 4f70f82ee0c..5b5f6d69825 100644 --- a/Userland/Libraries/LibWeb/Layout/Node.cpp +++ b/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()); diff --git a/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.cpp b/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.cpp index 5f915f6c16e..bc8dd68b796 100644 --- a/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.cpp @@ -42,6 +42,18 @@ Optional 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()); diff --git a/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp b/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp index 43a790e6cc5..9af4b164982 100644 --- a/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp +++ b/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()) diff --git a/Userland/Libraries/LibWeb/SVG/AttributeParser.h b/Userland/Libraries/LibWeb/SVG/AttributeParser.h index 2fee747f693..45476a403b0 100644 --- a/Userland/Libraries/LibWeb/SVG/AttributeParser.h +++ b/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; diff --git a/Userland/Libraries/LibWeb/SVG/SVGContext.h b/Userland/Libraries/LibWeb/SVG/SVGContext.h index 27c8c902e93..2dfd307ede5 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGContext.h +++ b/Userland/Libraries/LibWeb/SVG/SVGContext.h @@ -9,6 +9,7 @@ #include #include #include +#include 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 }; diff --git a/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.cpp b/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.cpp index 50195e1d3f1..d3dd21b4f6f 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.cpp +++ b/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 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 SVGGraphicsElement::fill_color() const { if (!layout_node()) diff --git a/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.h b/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.h index c0d1fd9b624..8757a13bbc6 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.h +++ b/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.h @@ -27,7 +27,7 @@ public: virtual void parse_attribute(DeprecatedFlyString const& name, DeprecatedString const& value) override; Optional fill_color() const; - Gfx::Painter::WindingRule fill_rule() const; + Optional fill_rule() const; Optional stroke_color() const; Optional stroke_width() const; Optional fill_opacity() const;