ソースを参照

LibWeb: Resolve and paint SVG gradient fills

This bit is mostly ad-hoc for now. This simply turns fill: url(#grad1)
into document().get_element_by_id('grad1') then resolves the gradient.
This seems to do the trick for most use cases, but this is not
attempting to follow the spec yet to keep things simple.
MacDue 2 年 前
コミット
afd355c135

+ 27 - 6
Userland/Libraries/LibWeb/CSS/ComputedValues.h

@@ -90,6 +90,27 @@ enum class BackgroundSize {
     LengthPercentage,
 };
 
+// https://svgwg.org/svg2-draft/painting.html#SpecifyingPaint
+class SVGPaint {
+public:
+    SVGPaint(Color color)
+        : m_value(color)
+    {
+    }
+    SVGPaint(AK::URL const& url)
+        : m_value(url)
+    {
+    }
+
+    bool is_color() const { return m_value.has<Color>(); }
+    bool is_url() const { return m_value.has<AK::URL>(); }
+    Color as_color() const { return m_value.get<Color>(); }
+    AK::URL const& as_url() const { return m_value.get<AK::URL>(); }
+
+private:
+    Variant<AK::URL, Color> m_value;
+};
+
 struct BackgroundLayerData {
     RefPtr<CSS::AbstractImageStyleValue const> background_image { nullptr };
     CSS::BackgroundAttachment attachment { CSS::BackgroundAttachment::Scroll };
@@ -257,8 +278,8 @@ public:
 
     CSS::ListStyleType list_style_type() const { return m_inherited.list_style_type; }
 
-    Optional<Color> const& fill() const { return m_inherited.fill; }
-    Optional<Color> const& stroke() const { return m_inherited.stroke; }
+    Optional<SVGPaint> const& fill() const { return m_inherited.fill; }
+    Optional<SVGPaint> const& stroke() const { return m_inherited.stroke; }
     Optional<LengthPercentage> const& stroke_width() const { return m_inherited.stroke_width; }
     Color stop_color() const { return m_noninherited.stop_color; }
 
@@ -293,8 +314,8 @@ protected:
         CSS::ListStyleType list_style_type { InitialValues::list_style_type() };
         CSS::Visibility visibility { InitialValues::visibility() };
 
-        Optional<Color> fill;
-        Optional<Color> stroke;
+        Optional<SVGPaint> fill;
+        Optional<SVGPaint> stroke;
         Optional<LengthPercentage> stroke_width;
     } m_inherited;
 
@@ -446,8 +467,8 @@ public:
     void set_border_collapse(CSS::BorderCollapse const& border_collapse) { m_noninherited.border_collapse = border_collapse; }
     void set_grid_template_areas(Vector<Vector<String>> const& grid_template_areas) { m_noninherited.grid_template_areas = grid_template_areas; }
 
-    void set_fill(Color value) { m_inherited.fill = value; }
-    void set_stroke(Color value) { m_inherited.stroke = value; }
+    void set_fill(SVGPaint value) { m_inherited.fill = value; }
+    void set_stroke(SVGPaint value) { m_inherited.stroke = value; }
     void set_stroke_width(LengthPercentage value) { m_inherited.stroke_width = value; }
     void set_stop_color(Color value) { m_noninherited.stop_color = value; }
 };

+ 6 - 1
Userland/Libraries/LibWeb/Layout/Node.cpp

@@ -11,6 +11,7 @@
 #include <LibWeb/CSS/StyleValues/BorderRadiusStyleValue.h>
 #include <LibWeb/CSS/StyleValues/EdgeStyleValue.h>
 #include <LibWeb/CSS/StyleValues/StyleValueList.h>
+#include <LibWeb/CSS/StyleValues/URLStyleValue.h>
 #include <LibWeb/DOM/Document.h>
 #include <LibWeb/Dump.h>
 #include <LibWeb/HTML/BrowsingContext.h>
@@ -646,8 +647,12 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style)
     computed_values.set_grid_row_start(computed_style.grid_row_start());
     computed_values.set_grid_template_areas(computed_style.grid_template_areas());
 
-    if (auto fill = computed_style.property(CSS::PropertyID::Fill); fill->has_color())
+    auto fill = computed_style.property(CSS::PropertyID::Fill);
+    if (fill->has_color())
         computed_values.set_fill(fill->to_color(*this));
+    else if (fill->is_url())
+        computed_values.set_fill(fill->as_url().url());
+    // TODO: Allow url()s for strokes
     if (auto stroke = computed_style.property(CSS::PropertyID::Stroke); stroke->has_color())
         computed_values.set_stroke(stroke->to_color(*this));
     if (auto stop_color = computed_style.property(CSS::PropertyID::StopColor); stop_color->has_color())

+ 32 - 8
Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.cpp

@@ -73,18 +73,42 @@ void SVGGeometryPaintable::paint(PaintContext& context, PaintPhase phase) const
         return;
 
     auto paint_transform = Gfx::AffineTransform {}.scale(css_scale, css_scale).multiply(*transform);
-    Gfx::Path path = const_cast<SVG::SVGGeometryElement&>(geometry_element).get_path().copy_transformed(paint_transform);
+    auto const& original_path = const_cast<SVG::SVGGeometryElement&>(geometry_element).get_path();
+    Gfx::Path path = original_path.copy_transformed(paint_transform);
 
-    if (auto fill_color = geometry_element.fill_color().value_or(svg_context.fill_color()); fill_color.alpha() > 0) {
+    // Fills are computed as though all paths are closed (https://svgwg.org/svg2-draft/painting.html#FillProperties)
+    auto closed_path = [&] {
         // We need to fill the path before applying the stroke, however the filled
         // path must be closed, whereas the stroke path may not necessary be closed.
         // Copy the path and close it for filling, but use the previous path for stroke
-        auto closed_path = path;
-        closed_path.close();
-
-        // Fills are computed as though all paths are closed (https://svgwg.org/svg2-draft/painting.html#FillProperties)
+        auto copy = path;
+        copy.close();
+        return copy;
+    };
+
+    // Note: This is assuming .x_scale() == .y_scale() (which it does currently).
+    auto viewbox_scale = paint_transform.x_scale();
+
+    auto svg_viewport = [&] {
+        if (maybe_view_box.has_value())
+            return Gfx::FloatRect { maybe_view_box->min_x, maybe_view_box->min_y, maybe_view_box->width, maybe_view_box->height };
+        return Gfx::FloatRect { { 0, 0 }, svg_context.svg_element_size().to_type<float>() };
+    }();
+
+    SVG::SVGPaintContext paint_context {
+        .viewport = svg_viewport,
+        .path_bounding_box = original_path.bounding_box(),
+        .transform = paint_transform
+    };
+
+    if (auto paint_style = geometry_element.fill_paint_style(paint_context); paint_style.has_value()) {
+        painter.fill_path(
+            closed_path(),
+            *paint_style,
+            Gfx::Painter::WindingRule::EvenOdd);
+    } else if (auto fill_color = geometry_element.fill_color().value_or(svg_context.fill_color()); fill_color.alpha() > 0) {
         painter.fill_path(
-            closed_path,
+            closed_path(),
             fill_color,
             Gfx::Painter::WindingRule::EvenOdd);
     }
@@ -94,7 +118,7 @@ void SVGGeometryPaintable::paint(PaintContext& context, PaintPhase phase) const
             path,
             stroke_color,
             // Note: This is assuming .x_scale() == .y_scale() (which it does currently).
-            geometry_element.stroke_width().value_or(svg_context.stroke_width()) * paint_transform.x_scale());
+            geometry_element.stroke_width().value_or(svg_context.stroke_width()) * viewbox_scale);
     }
 }
 

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

@@ -29,6 +29,7 @@ public:
     void set_stroke_width(float width) { state().stroke_width = width; }
 
     CSSPixelPoint svg_element_position() const { return m_svg_element_bounds.top_left(); }
+    CSSPixelSize svg_element_size() const { return m_svg_element_bounds.size(); }
 
     void save() { m_states.append(m_states.last()); }
     void restore() { m_states.take_last(); }

+ 28 - 3
Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.cpp

@@ -8,8 +8,10 @@
 
 #include <LibWeb/Bindings/Intrinsics.h>
 #include <LibWeb/CSS/Parser/Parser.h>
+#include <LibWeb/DOM/Document.h>
 #include <LibWeb/Layout/Node.h>
 #include <LibWeb/SVG/AttributeParser.h>
+#include <LibWeb/SVG/SVGGradientElement.h>
 #include <LibWeb/SVG/SVGGraphicsElement.h>
 #include <LibWeb/SVG/SVGSVGElement.h>
 
@@ -40,6 +42,23 @@ void SVGGraphicsElement::parse_attribute(DeprecatedFlyString const& name, Deprec
     }
 }
 
+Optional<Gfx::PaintStyle const&> SVGGraphicsElement::fill_paint_style(SVGPaintContext const& paint_context) const
+{
+    // FIXME: This entire function is an ad-hoc hack:
+    if (!layout_node())
+        return {};
+    auto& fill = layout_node()->computed_values().fill();
+    if (!fill.has_value() || !fill->is_url())
+        return {};
+    auto& url = fill->as_url();
+    auto maybe_gradient = document().get_element_by_id(url.fragment());
+    if (is<SVG::SVGGradientElement>(*maybe_gradient)) {
+        auto& gradient = verify_cast<SVG::SVGGradientElement>(*maybe_gradient);
+        return gradient.to_gfx_paint_style(paint_context);
+    }
+    return {};
+}
+
 Gfx::AffineTransform transform_from_transform_list(ReadonlySpan<Transform> transform_list)
 {
     Gfx::AffineTransform affine_transform;
@@ -111,8 +130,10 @@ Optional<Gfx::Color> SVGGraphicsElement::fill_color() const
         return {};
     // FIXME: In the working-draft spec, `fill` is intended to be a shorthand, with `fill-color`
     //        being what we actually want to use. But that's not final or widely supported yet.
-    return layout_node()->computed_values().fill().map([&](Gfx::Color color) {
-        return color.with_alpha(m_fill_opacity.value_or(1) * 255);
+    return layout_node()->computed_values().fill().map([&](auto& paint) -> Gfx::Color {
+        if (!paint.is_color())
+            return Color::Black;
+        return paint.as_color().with_alpha(m_fill_opacity.value_or(1) * 255);
     });
 }
 
@@ -122,7 +143,11 @@ Optional<Gfx::Color> SVGGraphicsElement::stroke_color() const
         return {};
     // FIXME: In the working-draft spec, `stroke` is intended to be a shorthand, with `stroke-color`
     //        being what we actually want to use. But that's not final or widely supported yet.
-    return layout_node()->computed_values().stroke();
+    return layout_node()->computed_values().stroke().map([](auto& paint) -> Gfx::Color {
+        if (!paint.is_color())
+            return Color::Black;
+        return paint.as_color();
+    });
 }
 
 Optional<float> SVGGraphicsElement::stroke_width() const

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

@@ -7,10 +7,12 @@
 
 #pragma once
 
+#include <LibGfx/PaintStyle.h>
 #include <LibGfx/Path.h>
 #include <LibWeb/DOM/Node.h>
 #include <LibWeb/SVG/AttributeParser.h>
 #include <LibWeb/SVG/SVGElement.h>
+#include <LibWeb/SVG/SVGGradientElement.h>
 #include <LibWeb/SVG/TagNames.h>
 
 namespace Web::SVG {
@@ -37,6 +39,8 @@ public:
 
     Gfx::AffineTransform get_transform() const;
 
+    Optional<Gfx::PaintStyle const&> fill_paint_style(SVGPaintContext const&) const;
+
 protected:
     SVGGraphicsElement(DOM::Document&, DOM::QualifiedName);