1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 14:17:36 +00:00

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/.
This commit is contained in:
MacDue 2023-06-11 16:43:46 +01:00 committed by Andreas Kling
parent ead56e88db
commit 377ff0ac26
13 changed files with 74 additions and 3 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -309,6 +309,12 @@ float StyleProperties::stop_opacity() const
return resolve_opacity_value(*value); 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 Optional<CSS::FlexDirection> StyleProperties::flex_direction() const
{ {
auto value = property(CSS::PropertyID::FlexDirection); auto value = property(CSS::PropertyID::FlexDirection);

View file

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

View file

@ -705,6 +705,9 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style)
else if (stroke_width->is_percentage()) else if (stroke_width->is_percentage())
computed_values.set_stroke_width(CSS::LengthPercentage { stroke_width->as_percentage().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_fill_opacity(computed_style.fill_opacity());
computed_values.set_stroke_opacity(computed_style.stroke_opacity()); computed_values.set_stroke_opacity(computed_style.stroke_opacity());
computed_values.set_stop_opacity(computed_style.stop_opacity()); computed_values.set_stop_opacity(computed_style.stop_opacity());

View file

@ -42,6 +42,18 @@ Optional<HitTestResult> SVGGeometryPaintable::hit_test(CSSPixelPoint position, H
return result; 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 void SVGGeometryPaintable::paint(PaintContext& context, PaintPhase phase) const
{ {
if (!is_visible()) 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 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()) { if (auto paint_style = geometry_element.fill_paint_style(paint_context); paint_style.has_value()) {
painter.fill_path( painter.fill_path(
closed_path(), closed_path(),
*paint_style, *paint_style,
fill_opacity, 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) { } 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( painter.fill_path(
closed_path(), closed_path(),
fill_color, fill_color,
Gfx::Painter::WindingRule::EvenOdd); winding_rule);
} }
auto stroke_opacity = geometry_element.stroke_opacity().value_or(svg_context.stroke_opacity()); auto stroke_opacity = geometry_element.stroke_opacity().value_or(svg_context.stroke_opacity());

View file

@ -32,6 +32,8 @@ void SVGGraphicsPaintable::before_children_paint(PaintContext& context, PaintPha
auto& graphics_element = layout_box().dom_node(); 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()) if (auto fill_color = graphics_element.fill_color(); fill_color.has_value())
context.svg_context().set_fill_color(*fill_color); context.svg_context().set_fill_color(*fill_color);
if (auto stroke_color = graphics_element.stroke_color(); stroke_color.has_value()) if (auto stroke_color = graphics_element.stroke_color(); stroke_color.has_value())

View file

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

View file

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

View file

@ -129,6 +129,9 @@ void SVGGraphicsElement::apply_presentational_hints(CSS::StyleProperties& style)
} else if (name.equals_ignoring_ascii_case("stroke-width"sv)) { } 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()) 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()); 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)) { } 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()) 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()); 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 Optional<Gfx::Color> SVGGraphicsElement::fill_color() const
{ {
if (!layout_node()) if (!layout_node())

View file

@ -27,7 +27,7 @@ public:
virtual void parse_attribute(DeprecatedFlyString const& name, DeprecatedString const& value) override; virtual void parse_attribute(DeprecatedFlyString const& name, DeprecatedString const& value) override;
Optional<Gfx::Color> fill_color() const; Optional<Gfx::Color> fill_color() const;
Gfx::Painter::WindingRule fill_rule() 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;