ladybird/Userland/Libraries/LibWeb/SVG/SVGLinearGradientElement.cpp
MacDue aa3464466e LibWeb: Implement SVGLinearGradientElement (<linearGradient>)
This represents the SVG <linearGradient>. The actual gradient is
converted to a Gfx::PaintStyle for use in SVG fills... There is a little
guesswork in the implementation, but it seems to match Chrome/Firefox.

Note: Still not hooked up to actual painting in this commit.
2023-04-28 09:42:28 +02:00

173 lines
7.5 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/SVG/AttributeNames.h>
#include <LibWeb/SVG/AttributeParser.h>
#include <LibWeb/SVG/SVGLinearGradientElement.h>
#include <LibWeb/SVG/SVGStopElement.h>
namespace Web::SVG {
SVGLinearGradientElement::SVGLinearGradientElement(DOM::Document& document, DOM::QualifiedName qualified_name)
: SVGGradientElement(document, qualified_name)
{
}
JS::ThrowCompletionOr<void> SVGLinearGradientElement::initialize(JS::Realm& realm)
{
MUST_OR_THROW_OOM(Base::initialize(realm));
set_prototype(&Bindings::ensure_web_prototype<Bindings::SVGLinearGradientElementPrototype>(realm, "SVGLinearGradientElement"));
return {};
}
void SVGLinearGradientElement::parse_attribute(DeprecatedFlyString const& name, DeprecatedString const& value)
{
SVGGradientElement::parse_attribute(name, value);
// FIXME: Should allow for `<number-percentage> | <length>` for x1, x2, y1, y2
if (name == SVG::AttributeNames::x1) {
m_x1 = AttributeParser::parse_number_percentage(value);
m_paint_style = nullptr;
} else if (name == SVG::AttributeNames::y1) {
m_y1 = AttributeParser::parse_number_percentage(value);
m_paint_style = nullptr;
} else if (name == SVG::AttributeNames::x2) {
m_x2 = AttributeParser::parse_number_percentage(value);
m_paint_style = nullptr;
} else if (name == SVG::AttributeNames::y2) {
m_y2 = AttributeParser::parse_number_percentage(value);
m_paint_style = nullptr;
}
}
// https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementX1Attribute
NumberPercentage SVGLinearGradientElement::start_x() const
{
if (m_x1.has_value())
return *m_x1;
if (auto href = linear_gradient_xlink_href())
return href->start_x();
// If the attribute is not specified, the effect is as if a value of '0%' were specified.
return NumberPercentage::create_percentage(0);
}
// https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementY1Attribute
NumberPercentage SVGLinearGradientElement::start_y() const
{
if (m_y1.has_value())
return *m_y1;
if (auto href = linear_gradient_xlink_href())
return href->start_x();
// If the attribute is not specified, the effect is as if a value of '0%' were specified.
return NumberPercentage::create_percentage(0);
}
// https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementX2Attribute
NumberPercentage SVGLinearGradientElement::end_x() const
{
if (m_x2.has_value())
return *m_x2;
if (auto href = linear_gradient_xlink_href())
return href->start_x();
// If the attribute is not specified, the effect is as if a value of '100%' were specified.
return NumberPercentage::create_percentage(100);
}
// https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementY2Attribute
NumberPercentage SVGLinearGradientElement::end_y() const
{
if (m_y2.has_value())
return *m_y2;
if (auto href = linear_gradient_xlink_href())
return href->start_x();
// If the attribute is not specified, the effect is as if a value of '0%' were specified.
return NumberPercentage::create_percentage(0);
}
Optional<Gfx::PaintStyle const&> SVGLinearGradientElement::to_gfx_paint_style(SVGPaintContext const& paint_context) const
{
auto units = gradient_units();
// FIXME: Resolve percentages properly
Gfx::FloatPoint start_point {};
Gfx::FloatPoint end_point {};
// https://svgwg.org/svg2-draft/pservers.html#LinearGradientElementGradientUnitsAttribute
if (units == GradientUnits::ObjectBoundingBox) {
// If gradientUnits="objectBoundingBox", the user coordinate system for attributes x1, y1, x2 and y2
// is established using the bounding box of the element to which the gradient is applied (see Object bounding
// box units) and then applying the transform specified by attribute gradientTransform. Percentages represent
// values relative to the bounding box for the object.
// Note: For gradientUnits="objectBoundingBox" both "100%" and "1" are treated the same.
start_point = paint_context.path_bounding_box.location() + Gfx::FloatPoint { start_x().value() * paint_context.path_bounding_box.width(), start_y().value() * paint_context.path_bounding_box.height() };
end_point = paint_context.path_bounding_box.location() + Gfx::FloatPoint { end_x().value() * paint_context.path_bounding_box.width(), end_y().value() * paint_context.path_bounding_box.height() };
} else {
// GradientUnits::UserSpaceOnUse
// If gradientUnits="userSpaceOnUse", x1, y1, x2, and y2 represent values in the coordinate system
// that results from taking the current user coordinate system in place at the time when the gradient element
// is referenced (i.e., the user coordinate system for the element referencing the gradient element via a
// fill or stroke property) and then applying the transform specified by attribute gradientTransform.
// Percentages represent values relative to the current SVG viewport.
start_point = Gfx::FloatPoint {
start_x().resolve_relative_to(paint_context.viewport.width()),
start_y().resolve_relative_to(paint_context.viewport.height()),
};
end_point = Gfx::FloatPoint {
end_x().resolve_relative_to(paint_context.viewport.width()),
end_y().resolve_relative_to(paint_context.viewport.height()),
};
}
if (!m_paint_style) {
m_paint_style = Gfx::SVGLinearGradientPaintStyle::create(start_point, end_point)
.release_value_but_fixme_should_propagate_errors();
// FIXME: Update this if DOM changes?
for_each_color_stop([&](auto& stop) {
m_paint_style->add_color_stop(stop.stop_offset().value(), stop.stop_color()).release_value_but_fixme_should_propagate_errors();
});
} else {
m_paint_style->set_start_point(start_point);
m_paint_style->set_end_point(end_point);
}
auto gradient_affine_transform = gradient_transform().value_or(Gfx::AffineTransform {});
if (units == GradientUnits::ObjectBoundingBox) {
// Adjust transform to take place in the coordinate system defined by the bounding box:
gradient_affine_transform = Gfx::AffineTransform {}
.translate(paint_context.path_bounding_box.location())
.scale(paint_context.path_bounding_box.width(), paint_context.path_bounding_box.height())
.multiply(gradient_affine_transform)
.scale(1 / paint_context.path_bounding_box.width(), 1 / paint_context.path_bounding_box.height())
.translate(-paint_context.path_bounding_box.location());
}
m_paint_style->set_gradient_transform(Gfx::AffineTransform { paint_context.transform }.multiply(gradient_affine_transform));
return *m_paint_style;
}
JS::NonnullGCPtr<SVGAnimatedLength> SVGLinearGradientElement::x1() const
{
TODO();
}
JS::NonnullGCPtr<SVGAnimatedLength> SVGLinearGradientElement::y1() const
{
TODO();
}
JS::NonnullGCPtr<SVGAnimatedLength> SVGLinearGradientElement::x2() const
{
TODO();
}
JS::NonnullGCPtr<SVGAnimatedLength> SVGLinearGradientElement::y2() const
{
TODO();
}
}