diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 42d47c9f0c..a27d131ff5 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -510,6 +510,7 @@ set(SOURCES SVG/SVGPolygonElement.cpp SVG/SVGPolylineElement.cpp SVG/SVGRectElement.cpp + SVG/SVGRadialGradientElement.cpp SVG/SVGSVGElement.cpp SVG/SVGStopElement.cpp SVG/SVGTextContentElement.cpp diff --git a/Userland/Libraries/LibWeb/DOM/ElementFactory.cpp b/Userland/Libraries/LibWeb/DOM/ElementFactory.cpp index b2d943046e..97a1dc49ca 100644 --- a/Userland/Libraries/LibWeb/DOM/ElementFactory.cpp +++ b/Userland/Libraries/LibWeb/DOM/ElementFactory.cpp @@ -92,6 +92,7 @@ #include #include #include +#include #include #include #include @@ -441,6 +442,8 @@ static WebIDL::ExceptionOr> create_svg_element(JS::Re return MUST_OR_THROW_OOM(realm.heap().allocate(realm, document, move(qualified_name))); if (local_name == SVG::TagNames::polyline) return MUST_OR_THROW_OOM(realm.heap().allocate(realm, document, move(qualified_name))); + if (local_name == SVG::TagNames::radialGradient) + return MUST_OR_THROW_OOM(realm.heap().allocate(realm, document, move(qualified_name))); if (local_name == SVG::TagNames::rect) return MUST_OR_THROW_OOM(realm.heap().allocate(realm, document, move(qualified_name))); if (local_name == SVG::TagNames::g) diff --git a/Userland/Libraries/LibWeb/SVG/SVGRadialGradientElement.cpp b/Userland/Libraries/LibWeb/SVG/SVGRadialGradientElement.cpp new file mode 100644 index 0000000000..a3ab138710 --- /dev/null +++ b/Userland/Libraries/LibWeb/SVG/SVGRadialGradientElement.cpp @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2023, MacDue + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Web::SVG { + +SVGRadialGradientElement::SVGRadialGradientElement(DOM::Document& document, DOM::QualifiedName qualified_name) + : SVGGradientElement(document, qualified_name) +{ +} + +JS::ThrowCompletionOr SVGRadialGradientElement::initialize(JS::Realm& realm) +{ + MUST_OR_THROW_OOM(Base::initialize(realm)); + set_prototype(&Bindings::ensure_web_prototype(realm, "SVGRadialGradientElement")); + + return {}; +} + +void SVGRadialGradientElement::parse_attribute(DeprecatedFlyString const& name, DeprecatedString const& value) +{ + SVGGradientElement::parse_attribute(name, value); + + // FIXME: These are or in the spec, but all examples seem to allow percentages + // and unitless values. + if (name == SVG::AttributeNames::cx) { + m_cx = AttributeParser::parse_number_percentage(value); + m_paint_style = nullptr; + } else if (name == SVG::AttributeNames::cy) { + m_cy = AttributeParser::parse_number_percentage(value); + m_paint_style = nullptr; + } else if (name == SVG::AttributeNames::fx) { + m_fx = AttributeParser::parse_number_percentage(value); + m_paint_style = nullptr; + } else if (name == SVG::AttributeNames::fy) { + m_fy = AttributeParser::parse_number_percentage(value); + m_paint_style = nullptr; + } else if (name == SVG::AttributeNames::fr) { + m_fr = AttributeParser::parse_number_percentage(value); + m_paint_style = nullptr; + } else if (name == SVG::AttributeNames::r) { + m_r = AttributeParser::parse_number_percentage(value); + m_paint_style = nullptr; + } +} + +// https://svgwg.org/svg2-draft/pservers.html#RadialGradientElementFXAttribute +NumberPercentage SVGRadialGradientElement::start_circle_x() const +{ + if (m_fx.has_value()) + return *m_fx; + // If the element references an element that specifies a value for 'fx', then the value of 'fx' is + // inherited from the referenced element. + if (auto href = radial_gradient_xlink_href()) + return href->start_circle_x(); + // If attribute ‘fx’ is not specified, ‘fx’ will coincide with the presentational value of ‘cx’ for + // the element whether the value for 'cx' was inherited or not. + return end_circle_x(); +} + +// https://svgwg.org/svg2-draft/pservers.html#RadialGradientElementFYAttribute +NumberPercentage SVGRadialGradientElement::start_circle_y() const +{ + if (m_fy.has_value()) + return *m_fy; + // If the element references an element that specifies a value for 'fy', then the value of 'fy' is + // inherited from the referenced element. + if (auto href = radial_gradient_xlink_href()) + return href->start_circle_y(); + // If attribute ‘fy’ is not specified, ‘fy’ will coincide with the presentational value of ‘cy’ for + // the element whether the value for 'cy' was inherited or not. + return end_circle_y(); +} + +// https://svgwg.org/svg2-draft/pservers.html#RadialGradientElementFRAttribute +NumberPercentage SVGRadialGradientElement::start_circle_radius() const +{ + // Note: A negative value is an error. + if (m_fr.has_value() && m_fr->value() >= 0) + return *m_fr; + // if the element references an element that specifies a value for 'fr', then the value of + // 'fr' is inherited from the referenced element. + if (auto href = radial_gradient_xlink_href()) + return href->start_circle_radius(); + // If the attribute is not specified, the effect is as if a value of '0%' were specified. + return NumberPercentage::create_percentage(0); +} + +// https://svgwg.org/svg2-draft/pservers.html#RadialGradientElementCXAttribute +NumberPercentage SVGRadialGradientElement::end_circle_x() const +{ + if (m_cx.has_value()) + return *m_cx; + if (auto href = radial_gradient_xlink_href()) + return href->end_circle_x(); + return NumberPercentage::create_percentage(50); +} + +// https://svgwg.org/svg2-draft/pservers.html#RadialGradientElementCYAttribute +NumberPercentage SVGRadialGradientElement::end_circle_y() const +{ + if (m_cy.has_value()) + return *m_cy; + if (auto href = radial_gradient_xlink_href()) + return href->end_circle_y(); + return NumberPercentage::create_percentage(50); +} + +// https://svgwg.org/svg2-draft/pservers.html#RadialGradientElementRAttribute +NumberPercentage SVGRadialGradientElement::end_circle_radius() const +{ + // Note: A negative value is an error. + if (m_r.has_value() && m_r->value() >= 0) + return *m_r; + if (auto href = radial_gradient_xlink_href()) + return href->end_circle_radius(); + return NumberPercentage::create_percentage(50); +} + +Optional SVGRadialGradientElement::to_gfx_paint_style(SVGPaintContext const& paint_context) const +{ + auto units = gradient_units(); + Gfx::FloatPoint start_center; + float start_radius = 0.0f; + Gfx::FloatPoint end_center; + float end_radius = 0.0f; + + if (units == GradientUnits::ObjectBoundingBox) { + // If gradientUnits="objectBoundingBox", the user coordinate system for attributes ‘cx’, ‘cy’, ‘r’, ‘fx’, ‘fy’, and ‘fr’ + // 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. + start_center = Gfx::FloatPoint { + start_circle_x().value(), start_circle_y().value() + }; + start_radius = start_circle_radius().value(); + end_center = paint_context.path_bounding_box.location() + Gfx::FloatPoint { end_circle_x().value(), end_circle_y().value() }; + end_radius = end_circle_radius().value(); + } else { + // GradientUnits::UserSpaceOnUse + // If gradientUnits="userSpaceOnUse", ‘cx’, ‘cy’, ‘r’, ‘fx’, ‘fy’, and ‘fr’ 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. + // Note: The start/end centers will be in relative units here. + // They will be resolved at paint time using the gradient paint transform. + start_center = Gfx::FloatPoint { + start_circle_x().resolve_relative_to(paint_context.viewport.width()), + start_circle_y().resolve_relative_to(paint_context.viewport.height()), + }; + // FIXME: Where in the spec does it say what axis the radius is relative to? + start_radius = start_circle_radius().resolve_relative_to(paint_context.viewport.width()); + end_center = Gfx::FloatPoint { + end_circle_x().resolve_relative_to(paint_context.viewport.width()), + end_circle_y().resolve_relative_to(paint_context.viewport.height()), + }; + end_radius = end_circle_radius().resolve_relative_to(paint_context.viewport.width()); + } + + if (!m_paint_style) { + m_paint_style = Gfx::SVGRadialGradientPaintStyle::create(start_center, start_radius, end_center, end_radius) + .release_value_but_fixme_should_propagate_errors(); + // FIXME: Update stops in DOM changes: + add_color_stops(*m_paint_style); + } else { + m_paint_style->set_start_center(start_center); + m_paint_style->set_start_radius(start_radius); + m_paint_style->set_end_center(end_center); + m_paint_style->set_end_radius(end_radius); + } + m_paint_style->set_gradient_transform(gradient_paint_transform(paint_context)); + return *m_paint_style; +} + +JS::NonnullGCPtr SVGRadialGradientElement::cx() const +{ + TODO(); +} + +JS::NonnullGCPtr SVGRadialGradientElement::cy() const +{ + TODO(); +} + +JS::NonnullGCPtr SVGRadialGradientElement::fx() const +{ + TODO(); +} + +JS::NonnullGCPtr SVGRadialGradientElement::fy() const +{ + TODO(); +} + +JS::NonnullGCPtr SVGRadialGradientElement::fr() const +{ + TODO(); +} + +JS::NonnullGCPtr SVGRadialGradientElement::r() const +{ + TODO(); +} + +} diff --git a/Userland/Libraries/LibWeb/SVG/SVGRadialGradientElement.h b/Userland/Libraries/LibWeb/SVG/SVGRadialGradientElement.h new file mode 100644 index 0000000000..da0dfe888e --- /dev/null +++ b/Userland/Libraries/LibWeb/SVG/SVGRadialGradientElement.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023, MacDue + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace Web::SVG { + +class SVGRadialGradientElement : public SVGGradientElement { + WEB_PLATFORM_OBJECT(SVGRadialGradientElement, SVGGradientElement); + +public: + virtual ~SVGRadialGradientElement() override = default; + + virtual void parse_attribute(DeprecatedFlyString const& name, DeprecatedString const& value) override; + + virtual Optional to_gfx_paint_style(SVGPaintContext const&) const override; + + JS::NonnullGCPtr cx() const; + JS::NonnullGCPtr cy() const; + JS::NonnullGCPtr fx() const; + JS::NonnullGCPtr fy() const; + JS::NonnullGCPtr fr() const; + JS::NonnullGCPtr r() const; + +protected: + SVGRadialGradientElement(DOM::Document&, DOM::QualifiedName); + + virtual JS::ThrowCompletionOr initialize(JS::Realm&) override; + +private: + JS::GCPtr radial_gradient_xlink_href() const + { + if (auto href = xlink_href(); href && is(*href)) + return &verify_cast(*href); + return {}; + } + + NumberPercentage start_circle_x() const; + NumberPercentage start_circle_y() const; + NumberPercentage start_circle_radius() const; + NumberPercentage end_circle_x() const; + NumberPercentage end_circle_y() const; + NumberPercentage end_circle_radius() const; + + Optional m_cx; + Optional m_cy; + Optional m_fx; + Optional m_fy; + Optional m_fr; + Optional m_r; + + mutable RefPtr m_paint_style; +}; + +} diff --git a/Userland/Libraries/LibWeb/SVG/SVGRadialGradientElement.idl b/Userland/Libraries/LibWeb/SVG/SVGRadialGradientElement.idl new file mode 100644 index 0000000000..10ffc8d8d4 --- /dev/null +++ b/Userland/Libraries/LibWeb/SVG/SVGRadialGradientElement.idl @@ -0,0 +1,11 @@ +#import + +[Exposed=Window] +interface SVGRadialGradientElement : SVGGradientElement { + [SameObject] readonly attribute SVGAnimatedLength cx; + [SameObject] readonly attribute SVGAnimatedLength cy; + [SameObject] readonly attribute SVGAnimatedLength r; + [SameObject] readonly attribute SVGAnimatedLength fx; + [SameObject] readonly attribute SVGAnimatedLength fy; + [SameObject] readonly attribute SVGAnimatedLength fr; +}; diff --git a/Userland/Libraries/LibWeb/idl_files.cmake b/Userland/Libraries/LibWeb/idl_files.cmake index ac0653a92e..756124b256 100644 --- a/Userland/Libraries/LibWeb/idl_files.cmake +++ b/Userland/Libraries/LibWeb/idl_files.cmake @@ -207,6 +207,7 @@ libweb_js_bindings(SVG/SVGLinearGradientElement) libweb_js_bindings(SVG/SVGPathElement) libweb_js_bindings(SVG/SVGPolygonElement) libweb_js_bindings(SVG/SVGPolylineElement) +libweb_js_bindings(SVG/SVGRadialGradientElement) libweb_js_bindings(SVG/SVGRectElement) libweb_js_bindings(SVG/SVGSVGElement) libweb_js_bindings(SVG/SVGStopElement)