diff --git a/Tests/LibWeb/Ref/reference/images/svg-clip-rule-ref.png b/Tests/LibWeb/Ref/reference/images/svg-clip-rule-ref.png
new file mode 100644
index 00000000000..356d7890265
Binary files /dev/null and b/Tests/LibWeb/Ref/reference/images/svg-clip-rule-ref.png differ
diff --git a/Tests/LibWeb/Ref/reference/svg-clip-rule-ref.html b/Tests/LibWeb/Ref/reference/svg-clip-rule-ref.html
new file mode 100644
index 00000000000..7a74e11956a
--- /dev/null
+++ b/Tests/LibWeb/Ref/reference/svg-clip-rule-ref.html
@@ -0,0 +1,9 @@
+
+
diff --git a/Tests/LibWeb/Ref/svg-clip-rule.html b/Tests/LibWeb/Ref/svg-clip-rule.html
new file mode 100644
index 00000000000..8c51024a4fd
--- /dev/null
+++ b/Tests/LibWeb/Ref/svg-clip-rule.html
@@ -0,0 +1,18 @@
+
diff --git a/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt b/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt
index ebd6eac5720..d0a23a150e4 100644
--- a/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt
+++ b/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt
@@ -48,6 +48,7 @@ caption-side: top
clear: none
clip: auto
clip-path: none
+clip-rule: nonzero
color: rgb(0, 0, 0)
column-count: auto
column-gap: auto
@@ -84,7 +85,7 @@ grid-row-start: auto
grid-template-areas:
grid-template-columns:
grid-template-rows:
-height: 1462px
+height: 1479px
image-rendering: auto
inline-size: auto
inset-block-end: auto
diff --git a/Userland/Libraries/LibWeb/CSS/ComputedValues.h b/Userland/Libraries/LibWeb/CSS/ComputedValues.h
index 8beb4f8f02c..5989040f1a6 100644
--- a/Userland/Libraries/LibWeb/CSS/ComputedValues.h
+++ b/Userland/Libraries/LibWeb/CSS/ComputedValues.h
@@ -27,6 +27,8 @@
namespace Web::CSS {
+using ClipRule = FillRule;
+
struct FlexBasisContent { };
using FlexBasis = Variant;
@@ -134,6 +136,7 @@ public:
static float opacity() { return 1.0f; }
static float fill_opacity() { return 1.0f; }
static CSS::FillRule fill_rule() { return CSS::FillRule::Nonzero; }
+ static CSS::ClipRule clip_rule() { return CSS::ClipRule::Nonzero; }
static float stroke_opacity() { return 1.0f; }
static float stop_opacity() { return 1.0f; }
static CSS::TextAnchor text_anchor() { return CSS::TextAnchor::Start; }
@@ -430,6 +433,7 @@ public:
Optional const& mask() const { return m_noninherited.mask; }
CSS::MaskType mask_type() const { return m_noninherited.mask_type; }
Optional const& clip_path() const { return m_noninherited.clip_path; }
+ CSS::ClipRule clip_rule() const { return m_inherited.clip_rule; }
LengthPercentage const& cx() const { return m_noninherited.cx; }
LengthPercentage const& cy() const { return m_noninherited.cy; }
@@ -505,6 +509,7 @@ protected:
float stroke_opacity { InitialValues::stroke_opacity() };
LengthPercentage stroke_width { Length::make_px(1) };
CSS::TextAnchor text_anchor { InitialValues::text_anchor() };
+ CSS::ClipRule clip_rule { InitialValues::clip_rule() };
Vector text_shadow;
@@ -731,6 +736,7 @@ public:
void set_mask(MaskReference value) { m_noninherited.mask = value; }
void set_mask_type(CSS::MaskType value) { m_noninherited.mask_type = value; }
void set_clip_path(ClipPathReference value) { m_noninherited.clip_path = value; }
+ void set_clip_rule(CSS::ClipRule value) { m_inherited.clip_rule = value; }
void set_cx(LengthPercentage cx) { m_noninherited.cx = cx; }
void set_cy(LengthPercentage cy) { m_noninherited.cy = cy; }
diff --git a/Userland/Libraries/LibWeb/CSS/Properties.json b/Userland/Libraries/LibWeb/CSS/Properties.json
index d7e165c53d7..640d44dab30 100644
--- a/Userland/Libraries/LibWeb/CSS/Properties.json
+++ b/Userland/Libraries/LibWeb/CSS/Properties.json
@@ -716,6 +716,13 @@
],
"initial": "none"
},
+ "clip-rule": {
+ "affects-layout": false,
+ "animation-type": "discrete",
+ "inherited": true,
+ "initial": "nonzero",
+ "valid-types": [ "fill-rule" ]
+ },
"color": {
"affects-layout": false,
"animation-type": "by-computed-value",
diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp
index 80698604909..d4f61e74fb3 100644
--- a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp
+++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp
@@ -336,6 +336,12 @@ Optional StyleProperties::fill_rule() const
return value_id_to_fill_rule(value->to_identifier());
}
+Optional StyleProperties::clip_rule() const
+{
+ auto value = property(CSS::PropertyID::ClipRule);
+ 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 86478853681..6782dfd46ab 100644
--- a/Userland/Libraries/LibWeb/CSS/StyleProperties.h
+++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.h
@@ -148,6 +148,7 @@ public:
float fill_opacity() const;
float stroke_opacity() const;
Optional fill_rule() const;
+ Optional clip_rule() const;
Gfx::Font const& first_available_computed_font() const { return m_font_list->first(); }
diff --git a/Userland/Libraries/LibWeb/Layout/Node.cpp b/Userland/Libraries/LibWeb/Layout/Node.cpp
index 3a5f4a4ec9d..e9cef051b60 100644
--- a/Userland/Libraries/LibWeb/Layout/Node.cpp
+++ b/Userland/Libraries/LibWeb/Layout/Node.cpp
@@ -804,6 +804,9 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style)
if (auto clip_path = computed_style.property(CSS::PropertyID::ClipPath); clip_path->is_url())
computed_values.set_clip_path(clip_path->as_url().url());
+ if (auto clip_rule = computed_style.clip_rule(); clip_rule.has_value())
+ computed_values.set_clip_rule(*clip_rule);
+
if (auto fill_rule = computed_style.fill_rule(); fill_rule.has_value())
computed_values.set_fill_rule(*fill_rule);
diff --git a/Userland/Libraries/LibWeb/Painting/SVGPathPaintable.cpp b/Userland/Libraries/LibWeb/Painting/SVGPathPaintable.cpp
index 12a61c9e6a1..22597d63bcd 100644
--- a/Userland/Libraries/LibWeb/Painting/SVGPathPaintable.cpp
+++ b/Userland/Libraries/LibWeb/Painting/SVGPathPaintable.cpp
@@ -101,8 +101,7 @@ void SVGPathPaintable::paint(PaintContext& context, PaintPhase phase) const
context.recording_painter().fill_path({
.path = closed_path(),
.color = Color::Black,
- // FIXME: Support clip-rule.
- .winding_rule = Gfx::Painter::WindingRule::Nonzero,
+ .winding_rule = to_gfx_winding_rule(graphics_element.clip_rule().value_or(SVG::ClipRule::Nonzero)),
.translation = offset,
});
return;
diff --git a/Userland/Libraries/LibWeb/SVG/AttributeParser.h b/Userland/Libraries/LibWeb/SVG/AttributeParser.h
index bc506863e68..7fe794fe2ac 100644
--- a/Userland/Libraries/LibWeb/SVG/AttributeParser.h
+++ b/Userland/Libraries/LibWeb/SVG/AttributeParser.h
@@ -137,6 +137,8 @@ enum class FillRule {
Evenodd
};
+using ClipRule = FillRule;
+
enum class TextAnchor {
Start,
Middle,
diff --git a/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.cpp b/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.cpp
index 01693ede0ae..50a3f3856ab 100644
--- a/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.cpp
+++ b/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.cpp
@@ -158,6 +158,7 @@ void SVGGraphicsElement::apply_presentational_hints(CSS::StyleProperties& style)
NamedPropertyID(CSS::PropertyID::Mask),
NamedPropertyID(CSS::PropertyID::MaskType),
NamedPropertyID(CSS::PropertyID::ClipPath),
+ NamedPropertyID(CSS::PropertyID::ClipRule),
};
CSS::Parser::ParsingContext parsing_context { document(), CSS::Parser::ParsingContext::Mode::SVGPresentationAttribute };
@@ -172,11 +173,9 @@ void SVGGraphicsElement::apply_presentational_hints(CSS::StyleProperties& style)
});
}
-Optional SVGGraphicsElement::fill_rule() const
+static FillRule to_svg_fill_rule(CSS::FillRule fill_rule)
{
- if (!layout_node())
- return {};
- switch (layout_node()->computed_values().fill_rule()) {
+ switch (fill_rule) {
case CSS::FillRule::Nonzero:
return FillRule::Nonzero;
case CSS::FillRule::Evenodd:
@@ -186,6 +185,20 @@ Optional SVGGraphicsElement::fill_rule() const
}
}
+Optional SVGGraphicsElement::fill_rule() const
+{
+ if (!layout_node())
+ return {};
+ return to_svg_fill_rule(layout_node()->computed_values().fill_rule());
+}
+
+Optional SVGGraphicsElement::clip_rule() const
+{
+ if (!layout_node())
+ return {};
+ return to_svg_fill_rule(layout_node()->computed_values().clip_rule());
+}
+
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 cf6f3f772b9..9448abd26a4 100644
--- a/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.h
+++ b/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.h
@@ -35,11 +35,12 @@ public:
virtual void attribute_changed(FlyString const& name, Optional const& value) override;
Optional fill_color() const;
- Optional fill_rule() const;
Optional stroke_color() const;
Optional stroke_width() const;
Optional fill_opacity() const;
Optional stroke_opacity() const;
+ Optional fill_rule() const;
+ Optional clip_rule() const;
float visible_stroke_width() const
{