LibWeb: Implement the SVG clip-rule attribute

This controls the fill rule used when rasterizing `<clipPath>` elements.
This commit is contained in:
MacDue 2024-05-12 20:19:43 +01:00 committed by Sam Atkins
commit 6c9069fa5d
Notes: sideshowbarker 2024-07-16 22:34:39 +09:00
13 changed files with 74 additions and 8 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,004 B

View file

@ -0,0 +1,9 @@
<style>
* {
margin: 0;
}
body {
background-color: white;
}
</style>
<img src="./images/svg-clip-rule-ref.png">

View file

@ -0,0 +1,18 @@
<svg
width="100"
viewBox="0 0 100 90"
xmlns="http://www.w3.org/2000/svg"
version="1.1">
<defs>
<link rel="match" href="reference/svg-clip-rule-ref.html" />
<path d="M50,0 21,90 98,35 2,35 79,90z" id="star" />
</defs>
<clipPath id="emptyStar">
<use href="#star" clip-rule="evenodd" />
</clipPath>
<rect clip-path="url(#emptyStar)" width="50" height="90" fill="blue" />
<clipPath id="filledStar">
<use href="#star" clip-rule="nonzero" />
</clipPath>
<rect clip-path="url(#filledStar)" width="50" height="90" x="50" fill="red" />
</svg>

After

Width:  |  Height:  |  Size: 574 B

View file

@ -48,6 +48,7 @@ caption-side: top
clear: none clear: none
clip: auto clip: auto
clip-path: none clip-path: none
clip-rule: nonzero
color: rgb(0, 0, 0) color: rgb(0, 0, 0)
column-count: auto column-count: auto
column-gap: auto column-gap: auto
@ -84,7 +85,7 @@ grid-row-start: auto
grid-template-areas: grid-template-areas:
grid-template-columns: grid-template-columns:
grid-template-rows: grid-template-rows:
height: 1462px height: 1479px
image-rendering: auto image-rendering: auto
inline-size: auto inline-size: auto
inset-block-end: auto inset-block-end: auto

View file

@ -27,6 +27,8 @@
namespace Web::CSS { namespace Web::CSS {
using ClipRule = FillRule;
struct FlexBasisContent { }; struct FlexBasisContent { };
using FlexBasis = Variant<FlexBasisContent, Size>; using FlexBasis = Variant<FlexBasisContent, Size>;
@ -134,6 +136,7 @@ public:
static float opacity() { return 1.0f; } static float opacity() { return 1.0f; }
static float fill_opacity() { return 1.0f; } static float fill_opacity() { return 1.0f; }
static CSS::FillRule fill_rule() { return CSS::FillRule::Nonzero; } 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 stroke_opacity() { return 1.0f; }
static float stop_opacity() { return 1.0f; } static float stop_opacity() { return 1.0f; }
static CSS::TextAnchor text_anchor() { return CSS::TextAnchor::Start; } static CSS::TextAnchor text_anchor() { return CSS::TextAnchor::Start; }
@ -430,6 +433,7 @@ public:
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; }
Optional<ClipPathReference> const& clip_path() const { return m_noninherited.clip_path; } Optional<ClipPathReference> 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& cx() const { return m_noninherited.cx; }
LengthPercentage const& cy() const { return m_noninherited.cy; } LengthPercentage const& cy() const { return m_noninherited.cy; }
@ -505,6 +509,7 @@ protected:
float stroke_opacity { InitialValues::stroke_opacity() }; float stroke_opacity { InitialValues::stroke_opacity() };
LengthPercentage stroke_width { Length::make_px(1) }; LengthPercentage stroke_width { Length::make_px(1) };
CSS::TextAnchor text_anchor { InitialValues::text_anchor() }; CSS::TextAnchor text_anchor { InitialValues::text_anchor() };
CSS::ClipRule clip_rule { InitialValues::clip_rule() };
Vector<ShadowData> text_shadow; Vector<ShadowData> text_shadow;
@ -731,6 +736,7 @@ public:
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_clip_path(ClipPathReference value) { m_noninherited.clip_path = 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_cx(LengthPercentage cx) { m_noninherited.cx = cx; }
void set_cy(LengthPercentage cy) { m_noninherited.cy = cy; } void set_cy(LengthPercentage cy) { m_noninherited.cy = cy; }

View file

@ -716,6 +716,13 @@
], ],
"initial": "none" "initial": "none"
}, },
"clip-rule": {
"affects-layout": false,
"animation-type": "discrete",
"inherited": true,
"initial": "nonzero",
"valid-types": [ "fill-rule" ]
},
"color": { "color": {
"affects-layout": false, "affects-layout": false,
"animation-type": "by-computed-value", "animation-type": "by-computed-value",

View file

@ -336,6 +336,12 @@ Optional<CSS::FillRule> StyleProperties::fill_rule() const
return value_id_to_fill_rule(value->to_identifier()); return value_id_to_fill_rule(value->to_identifier());
} }
Optional<CSS::ClipRule> StyleProperties::clip_rule() const
{
auto value = property(CSS::PropertyID::ClipRule);
return value_id_to_fill_rule(value->to_identifier());
}
Optional<CSS::FlexDirection> StyleProperties::flex_direction() const Optional<CSS::FlexDirection> StyleProperties::flex_direction() const
{ {
auto value = property(CSS::PropertyID::FlexDirection); auto value = property(CSS::PropertyID::FlexDirection);

View file

@ -148,6 +148,7 @@ public:
float fill_opacity() const; float fill_opacity() const;
float stroke_opacity() const; float stroke_opacity() const;
Optional<CSS::FillRule> fill_rule() const; Optional<CSS::FillRule> fill_rule() const;
Optional<CSS::ClipRule> clip_rule() const;
Gfx::Font const& first_available_computed_font() const { return m_font_list->first(); } Gfx::Font const& first_available_computed_font() const { return m_font_list->first(); }

View file

@ -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()) if (auto clip_path = computed_style.property(CSS::PropertyID::ClipPath); clip_path->is_url())
computed_values.set_clip_path(clip_path->as_url().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()) if (auto fill_rule = computed_style.fill_rule(); fill_rule.has_value())
computed_values.set_fill_rule(*fill_rule); computed_values.set_fill_rule(*fill_rule);

View file

@ -101,8 +101,7 @@ void SVGPathPaintable::paint(PaintContext& context, PaintPhase phase) const
context.recording_painter().fill_path({ context.recording_painter().fill_path({
.path = closed_path(), .path = closed_path(),
.color = Color::Black, .color = Color::Black,
// FIXME: Support clip-rule. .winding_rule = to_gfx_winding_rule(graphics_element.clip_rule().value_or(SVG::ClipRule::Nonzero)),
.winding_rule = Gfx::Painter::WindingRule::Nonzero,
.translation = offset, .translation = offset,
}); });
return; return;

View file

@ -137,6 +137,8 @@ enum class FillRule {
Evenodd Evenodd
}; };
using ClipRule = FillRule;
enum class TextAnchor { enum class TextAnchor {
Start, Start,
Middle, Middle,

View file

@ -158,6 +158,7 @@ void SVGGraphicsElement::apply_presentational_hints(CSS::StyleProperties& style)
NamedPropertyID(CSS::PropertyID::Mask), NamedPropertyID(CSS::PropertyID::Mask),
NamedPropertyID(CSS::PropertyID::MaskType), NamedPropertyID(CSS::PropertyID::MaskType),
NamedPropertyID(CSS::PropertyID::ClipPath), NamedPropertyID(CSS::PropertyID::ClipPath),
NamedPropertyID(CSS::PropertyID::ClipRule),
}; };
CSS::Parser::ParsingContext parsing_context { document(), CSS::Parser::ParsingContext::Mode::SVGPresentationAttribute }; CSS::Parser::ParsingContext parsing_context { document(), CSS::Parser::ParsingContext::Mode::SVGPresentationAttribute };
@ -172,11 +173,9 @@ void SVGGraphicsElement::apply_presentational_hints(CSS::StyleProperties& style)
}); });
} }
Optional<FillRule> SVGGraphicsElement::fill_rule() const static FillRule to_svg_fill_rule(CSS::FillRule fill_rule)
{ {
if (!layout_node()) switch (fill_rule) {
return {};
switch (layout_node()->computed_values().fill_rule()) {
case CSS::FillRule::Nonzero: case CSS::FillRule::Nonzero:
return FillRule::Nonzero; return FillRule::Nonzero;
case CSS::FillRule::Evenodd: case CSS::FillRule::Evenodd:
@ -186,6 +185,20 @@ Optional<FillRule> SVGGraphicsElement::fill_rule() const
} }
} }
Optional<FillRule> SVGGraphicsElement::fill_rule() const
{
if (!layout_node())
return {};
return to_svg_fill_rule(layout_node()->computed_values().fill_rule());
}
Optional<ClipRule> SVGGraphicsElement::clip_rule() const
{
if (!layout_node())
return {};
return to_svg_fill_rule(layout_node()->computed_values().clip_rule());
}
Optional<Gfx::Color> SVGGraphicsElement::fill_color() const Optional<Gfx::Color> SVGGraphicsElement::fill_color() const
{ {
if (!layout_node()) if (!layout_node())

View file

@ -35,11 +35,12 @@ public:
virtual void attribute_changed(FlyString const& name, Optional<String> const& value) override; virtual void attribute_changed(FlyString const& name, Optional<String> const& value) override;
Optional<Gfx::Color> fill_color() const; Optional<Gfx::Color> fill_color() const;
Optional<FillRule> fill_rule() const;
Optional<Gfx::Color> stroke_color() const; Optional<Gfx::Color> stroke_color() const;
Optional<float> stroke_width() const; Optional<float> stroke_width() const;
Optional<float> fill_opacity() const; Optional<float> fill_opacity() const;
Optional<float> stroke_opacity() const; Optional<float> stroke_opacity() const;
Optional<FillRule> fill_rule() const;
Optional<ClipRule> clip_rule() const;
float visible_stroke_width() const float visible_stroke_width() const
{ {