diff --git a/Userland/Libraries/LibWeb/CSS/ComputedValues.h b/Userland/Libraries/LibWeb/CSS/ComputedValues.h index 5cba459aef..1098a2264b 100644 --- a/Userland/Libraries/LibWeb/CSS/ComputedValues.h +++ b/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(); } + bool is_url() const { return m_value.has(); } + Color as_color() const { return m_value.get(); } + AK::URL const& as_url() const { return m_value.get(); } + +private: + Variant m_value; +}; + struct BackgroundLayerData { RefPtr 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 const& fill() const { return m_inherited.fill; } - Optional const& stroke() const { return m_inherited.stroke; } + Optional const& fill() const { return m_inherited.fill; } + Optional const& stroke() const { return m_inherited.stroke; } Optional 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 fill; - Optional stroke; + Optional fill; + Optional stroke; Optional 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> 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; } }; diff --git a/Userland/Libraries/LibWeb/Layout/Node.cpp b/Userland/Libraries/LibWeb/Layout/Node.cpp index 9d4b6a89c9..7b4c21d771 100644 --- a/Userland/Libraries/LibWeb/Layout/Node.cpp +++ b/Userland/Libraries/LibWeb/Layout/Node.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -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()) diff --git a/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.cpp b/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.cpp index 77b0c2ad29..fa1fefc59c 100644 --- a/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.cpp +++ b/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(geometry_element).get_path().copy_transformed(paint_transform); + auto const& original_path = const_cast(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(); + auto copy = path; + copy.close(); + return copy; + }; - // Fills are computed as though all paths are closed (https://svgwg.org/svg2-draft/painting.html#FillProperties) + // 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() }; + }(); + + 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, + 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(), 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); } } diff --git a/Userland/Libraries/LibWeb/SVG/SVGContext.h b/Userland/Libraries/LibWeb/SVG/SVGContext.h index 6c184cbdc9..04885a2209 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGContext.h +++ b/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(); } diff --git a/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.cpp b/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.cpp index 37adc16d33..62eab723c7 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.cpp +++ b/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.cpp @@ -8,8 +8,10 @@ #include #include +#include #include #include +#include #include #include @@ -40,6 +42,23 @@ void SVGGraphicsElement::parse_attribute(DeprecatedFlyString const& name, Deprec } } +Optional 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(*maybe_gradient)) { + auto& gradient = verify_cast(*maybe_gradient); + return gradient.to_gfx_paint_style(paint_context); + } + return {}; +} + Gfx::AffineTransform transform_from_transform_list(ReadonlySpan transform_list) { Gfx::AffineTransform affine_transform; @@ -111,8 +130,10 @@ Optional 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 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 SVGGraphicsElement::stroke_width() const diff --git a/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.h b/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.h index 97dc46d2ef..3bcb380ec7 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.h +++ b/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.h @@ -7,10 +7,12 @@ #pragma once +#include #include #include #include #include +#include #include namespace Web::SVG { @@ -37,6 +39,8 @@ public: Gfx::AffineTransform get_transform() const; + Optional fill_paint_style(SVGPaintContext const&) const; + protected: SVGGraphicsElement(DOM::Document&, DOM::QualifiedName);