ladybird/Userland/Libraries/LibWeb/SVG/SVGGradientElement.h
Jelle Raaijmakers 1b82cb43c2 LibWeb+LibGfx: Fix SVG userSpaceOnUse gradient coordinate transformation
We were transforming coordinates for SVG gradients in a pretty
convoluted way: an inverse, unscaled transformation matrix was set up in
order to work around some (old?) technical limitations.

Rework this so the coordinate transformation no longer needs to be
inversed. This fixes gradients with "userSpaceOnUse" for its
gradientUnits attribute, which might cause coordinates to lie outside of
the bounding box of the gradient.

Two tests have updated reference screenshots with minor pixel updates;
this is probably the result of floating point precision improvements by
not inversing the matrix.

One test (svg-text-effects) has a bigger change: the gradient stops seem
to have moved along the text. This does seem to match other browsers
slightly better, so I'm moving forward with this ref update.
2024-10-28 22:53:17 +01:00

102 lines
3.4 KiB
C++

/*
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/IterationDecision.h>
#include <LibGfx/PaintStyle.h>
#include <LibWeb/Painting/PaintStyle.h>
#include <LibWeb/SVG/AttributeParser.h>
#include <LibWeb/SVG/SVGElement.h>
#include <LibWeb/SVG/SVGStopElement.h>
#include <LibWeb/SVG/SVGURIReference.h>
namespace Web::SVG {
struct SVGPaintContext {
Gfx::FloatRect viewport;
Gfx::FloatRect path_bounding_box;
};
inline Painting::SVGGradientPaintStyle::SpreadMethod to_painting_spread_method(SpreadMethod spread_method)
{
switch (spread_method) {
case SpreadMethod::Pad:
return Painting::SVGGradientPaintStyle::SpreadMethod::Pad;
case SpreadMethod::Reflect:
return Painting::SVGGradientPaintStyle::SpreadMethod::Reflect;
case SpreadMethod::Repeat:
return Painting::SVGGradientPaintStyle::SpreadMethod::Repeat;
default:
VERIFY_NOT_REACHED();
}
}
class SVGGradientElement
: public SVGElement
, public SVGURIReferenceMixin<SupportsXLinkHref::Yes> {
WEB_PLATFORM_OBJECT(SVGGradientElement, SVGElement);
public:
virtual ~SVGGradientElement() override = default;
virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value) override;
virtual Optional<Painting::PaintStyle> to_gfx_paint_style(SVGPaintContext const&) const = 0;
GradientUnits gradient_units() const;
SpreadMethod spread_method() const;
Optional<Gfx::AffineTransform> gradient_transform() const;
protected:
SVGGradientElement(DOM::Document&, DOM::QualifiedName);
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
JS::GCPtr<SVGGradientElement const> linked_gradient(HashTable<SVGGradientElement const*>& seen_gradients) const;
Gfx::AffineTransform gradient_paint_transform(SVGPaintContext const&) const;
template<VoidFunction<SVGStopElement> Callback>
void for_each_color_stop(Callback const& callback) const
{
HashTable<SVGGradientElement const*> seen_gradients;
return for_each_color_stop_impl(callback, seen_gradients);
}
void add_color_stops(Painting::SVGGradientPaintStyle&) const;
private:
template<VoidFunction<SVGStopElement> Callback>
void for_each_color_stop_impl(Callback const& callback, HashTable<SVGGradientElement const*>& seen_gradients) const
{
bool color_stops_found = false;
for_each_child_of_type<SVG::SVGStopElement>([&](auto& stop) {
color_stops_found = true;
callback(stop);
return IterationDecision::Continue;
});
if (!color_stops_found) {
if (auto gradient = linked_gradient(seen_gradients))
gradient->for_each_color_stop_impl(callback, seen_gradients);
}
}
GradientUnits gradient_units_impl(HashTable<SVGGradientElement const*>& seen_gradients) const;
SpreadMethod spread_method_impl(HashTable<SVGGradientElement const*>& seen_gradients) const;
Optional<Gfx::AffineTransform> gradient_transform_impl(HashTable<SVGGradientElement const*>& seen_gradients) const;
// https://svgwg.org/svg2-draft/pservers.html#LinearGradientAttributes
Optional<GradientUnits> m_gradient_units = {};
Optional<SpreadMethod> m_spread_method = {};
Optional<Gfx::AffineTransform> m_gradient_transform = {};
};
}