mirror of
https://github.com/RGBCube/serenity
synced 2025-07-24 13:27:35 +00:00
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.
This commit is contained in:
parent
aa3464466e
commit
afd355c135
6 changed files with 97 additions and 17 deletions
|
@ -90,6 +90,27 @@ enum class BackgroundSize {
|
||||||
LengthPercentage,
|
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 {
|
struct BackgroundLayerData {
|
||||||
RefPtr<CSS::AbstractImageStyleValue const> background_image { nullptr };
|
RefPtr<CSS::AbstractImageStyleValue const> background_image { nullptr };
|
||||||
CSS::BackgroundAttachment attachment { CSS::BackgroundAttachment::Scroll };
|
CSS::BackgroundAttachment attachment { CSS::BackgroundAttachment::Scroll };
|
||||||
|
@ -257,8 +278,8 @@ public:
|
||||||
|
|
||||||
CSS::ListStyleType list_style_type() const { return m_inherited.list_style_type; }
|
CSS::ListStyleType list_style_type() const { return m_inherited.list_style_type; }
|
||||||
|
|
||||||
Optional<Color> const& fill() const { return m_inherited.fill; }
|
Optional<SVGPaint> const& fill() const { return m_inherited.fill; }
|
||||||
Optional<Color> const& stroke() const { return m_inherited.stroke; }
|
Optional<SVGPaint> const& stroke() const { return m_inherited.stroke; }
|
||||||
Optional<LengthPercentage> const& stroke_width() const { return m_inherited.stroke_width; }
|
Optional<LengthPercentage> const& stroke_width() const { return m_inherited.stroke_width; }
|
||||||
Color stop_color() const { return m_noninherited.stop_color; }
|
Color stop_color() const { return m_noninherited.stop_color; }
|
||||||
|
|
||||||
|
@ -293,8 +314,8 @@ protected:
|
||||||
CSS::ListStyleType list_style_type { InitialValues::list_style_type() };
|
CSS::ListStyleType list_style_type { InitialValues::list_style_type() };
|
||||||
CSS::Visibility visibility { InitialValues::visibility() };
|
CSS::Visibility visibility { InitialValues::visibility() };
|
||||||
|
|
||||||
Optional<Color> fill;
|
Optional<SVGPaint> fill;
|
||||||
Optional<Color> stroke;
|
Optional<SVGPaint> stroke;
|
||||||
Optional<LengthPercentage> stroke_width;
|
Optional<LengthPercentage> stroke_width;
|
||||||
} m_inherited;
|
} m_inherited;
|
||||||
|
|
||||||
|
@ -446,8 +467,8 @@ public:
|
||||||
void set_border_collapse(CSS::BorderCollapse const& border_collapse) { m_noninherited.border_collapse = border_collapse; }
|
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_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_fill(SVGPaint value) { m_inherited.fill = value; }
|
||||||
void set_stroke(Color value) { m_inherited.stroke = value; }
|
void set_stroke(SVGPaint value) { m_inherited.stroke = value; }
|
||||||
void set_stroke_width(LengthPercentage value) { m_inherited.stroke_width = value; }
|
void set_stroke_width(LengthPercentage value) { m_inherited.stroke_width = value; }
|
||||||
void set_stop_color(Color value) { m_noninherited.stop_color = value; }
|
void set_stop_color(Color value) { m_noninherited.stop_color = value; }
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <LibWeb/CSS/StyleValues/BorderRadiusStyleValue.h>
|
#include <LibWeb/CSS/StyleValues/BorderRadiusStyleValue.h>
|
||||||
#include <LibWeb/CSS/StyleValues/EdgeStyleValue.h>
|
#include <LibWeb/CSS/StyleValues/EdgeStyleValue.h>
|
||||||
#include <LibWeb/CSS/StyleValues/StyleValueList.h>
|
#include <LibWeb/CSS/StyleValues/StyleValueList.h>
|
||||||
|
#include <LibWeb/CSS/StyleValues/URLStyleValue.h>
|
||||||
#include <LibWeb/DOM/Document.h>
|
#include <LibWeb/DOM/Document.h>
|
||||||
#include <LibWeb/Dump.h>
|
#include <LibWeb/Dump.h>
|
||||||
#include <LibWeb/HTML/BrowsingContext.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_row_start(computed_style.grid_row_start());
|
||||||
computed_values.set_grid_template_areas(computed_style.grid_template_areas());
|
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));
|
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())
|
if (auto stroke = computed_style.property(CSS::PropertyID::Stroke); stroke->has_color())
|
||||||
computed_values.set_stroke(stroke->to_color(*this));
|
computed_values.set_stroke(stroke->to_color(*this));
|
||||||
if (auto stop_color = computed_style.property(CSS::PropertyID::StopColor); stop_color->has_color())
|
if (auto stop_color = computed_style.property(CSS::PropertyID::StopColor); stop_color->has_color())
|
||||||
|
|
|
@ -73,18 +73,42 @@ void SVGGeometryPaintable::paint(PaintContext& context, PaintPhase phase) const
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto paint_transform = Gfx::AffineTransform {}.scale(css_scale, css_scale).multiply(*transform);
|
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
|
// 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.
|
// 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
|
// Copy the path and close it for filling, but use the previous path for stroke
|
||||||
auto closed_path = path;
|
auto copy = path;
|
||||||
closed_path.close();
|
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<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(
|
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,
|
fill_color,
|
||||||
Gfx::Painter::WindingRule::EvenOdd);
|
Gfx::Painter::WindingRule::EvenOdd);
|
||||||
}
|
}
|
||||||
|
@ -94,7 +118,7 @@ void SVGGeometryPaintable::paint(PaintContext& context, PaintPhase phase) const
|
||||||
path,
|
path,
|
||||||
stroke_color,
|
stroke_color,
|
||||||
// Note: This is assuming .x_scale() == .y_scale() (which it does currently).
|
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ public:
|
||||||
void set_stroke_width(float width) { state().stroke_width = width; }
|
void set_stroke_width(float width) { state().stroke_width = width; }
|
||||||
|
|
||||||
CSSPixelPoint svg_element_position() const { return m_svg_element_bounds.top_left(); }
|
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 save() { m_states.append(m_states.last()); }
|
||||||
void restore() { m_states.take_last(); }
|
void restore() { m_states.take_last(); }
|
||||||
|
|
|
@ -8,8 +8,10 @@
|
||||||
|
|
||||||
#include <LibWeb/Bindings/Intrinsics.h>
|
#include <LibWeb/Bindings/Intrinsics.h>
|
||||||
#include <LibWeb/CSS/Parser/Parser.h>
|
#include <LibWeb/CSS/Parser/Parser.h>
|
||||||
|
#include <LibWeb/DOM/Document.h>
|
||||||
#include <LibWeb/Layout/Node.h>
|
#include <LibWeb/Layout/Node.h>
|
||||||
#include <LibWeb/SVG/AttributeParser.h>
|
#include <LibWeb/SVG/AttributeParser.h>
|
||||||
|
#include <LibWeb/SVG/SVGGradientElement.h>
|
||||||
#include <LibWeb/SVG/SVGGraphicsElement.h>
|
#include <LibWeb/SVG/SVGGraphicsElement.h>
|
||||||
#include <LibWeb/SVG/SVGSVGElement.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 transform_from_transform_list(ReadonlySpan<Transform> transform_list)
|
||||||
{
|
{
|
||||||
Gfx::AffineTransform affine_transform;
|
Gfx::AffineTransform affine_transform;
|
||||||
|
@ -111,8 +130,10 @@ Optional<Gfx::Color> SVGGraphicsElement::fill_color() const
|
||||||
return {};
|
return {};
|
||||||
// FIXME: In the working-draft spec, `fill` is intended to be a shorthand, with `fill-color`
|
// 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.
|
// 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 layout_node()->computed_values().fill().map([&](auto& paint) -> Gfx::Color {
|
||||||
return color.with_alpha(m_fill_opacity.value_or(1) * 255);
|
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 {};
|
return {};
|
||||||
// FIXME: In the working-draft spec, `stroke` is intended to be a shorthand, with `stroke-color`
|
// 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.
|
// 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
|
Optional<float> SVGGraphicsElement::stroke_width() const
|
||||||
|
|
|
@ -7,10 +7,12 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <LibGfx/PaintStyle.h>
|
||||||
#include <LibGfx/Path.h>
|
#include <LibGfx/Path.h>
|
||||||
#include <LibWeb/DOM/Node.h>
|
#include <LibWeb/DOM/Node.h>
|
||||||
#include <LibWeb/SVG/AttributeParser.h>
|
#include <LibWeb/SVG/AttributeParser.h>
|
||||||
#include <LibWeb/SVG/SVGElement.h>
|
#include <LibWeb/SVG/SVGElement.h>
|
||||||
|
#include <LibWeb/SVG/SVGGradientElement.h>
|
||||||
#include <LibWeb/SVG/TagNames.h>
|
#include <LibWeb/SVG/TagNames.h>
|
||||||
|
|
||||||
namespace Web::SVG {
|
namespace Web::SVG {
|
||||||
|
@ -37,6 +39,8 @@ public:
|
||||||
|
|
||||||
Gfx::AffineTransform get_transform() const;
|
Gfx::AffineTransform get_transform() const;
|
||||||
|
|
||||||
|
Optional<Gfx::PaintStyle const&> fill_paint_style(SVGPaintContext const&) const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
SVGGraphicsElement(DOM::Document&, DOM::QualifiedName);
|
SVGGraphicsElement(DOM::Document&, DOM::QualifiedName);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue