diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index efeda19328..22bd9d8c32 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -495,6 +495,7 @@ set(SOURCES SVG/SVGGElement.cpp SVG/SVGGeometryElement.cpp SVG/SVGGraphicsElement.cpp + SVG/SVGGradientElement.cpp SVG/SVGPathElement.cpp SVG/SVGCircleElement.cpp SVG/SVGEllipseElement.cpp diff --git a/Userland/Libraries/LibWeb/SVG/AttributeParser.cpp b/Userland/Libraries/LibWeb/SVG/AttributeParser.cpp index f4cc77a016..826e4a9d51 100644 --- a/Userland/Libraries/LibWeb/SVG/AttributeParser.cpp +++ b/Userland/Libraries/LibWeb/SVG/AttributeParser.cpp @@ -494,6 +494,19 @@ Optional AttributeParser::parse_preserve_aspect_ratio(Strin return PreserveAspectRatio { *align, *meet_or_slice }; } +// https://svgwg.org/svg2-draft/pservers.html#LinearGradientElementGradientUnitsAttribute +Optional AttributeParser::parse_gradient_units(StringView input) +{ + GenericLexer lexer { input }; + lexer.ignore_while(whitespace); + auto gradient_units_string = lexer.consume_until(whitespace); + if (gradient_units_string == "userSpaceOnUse"sv) + return GradientUnits::UserSpaceOnUse; + if (gradient_units_string == "objectBoundingBox"sv) + return GradientUnits::ObjectBoundingBox; + return {}; +} + // https://drafts.csswg.org/css-transforms/#svg-syntax Optional> AttributeParser::parse_transform() { diff --git a/Userland/Libraries/LibWeb/SVG/AttributeParser.h b/Userland/Libraries/LibWeb/SVG/AttributeParser.h index 7ddcedc573..1e7578cdb2 100644 --- a/Userland/Libraries/LibWeb/SVG/AttributeParser.h +++ b/Userland/Libraries/LibWeb/SVG/AttributeParser.h @@ -88,6 +88,11 @@ struct PreserveAspectRatio { MeetOrSlice meet_or_slice { MeetOrSlice::Meet }; }; +enum class GradientUnits { + ObjectBoundingBox, + UserSpaceOnUse +}; + class NumberPercentage { public: NumberPercentage(float value, bool is_percentage) @@ -127,6 +132,7 @@ public: static Vector parse_path_data(StringView input); static Optional> parse_transform(StringView input); static Optional parse_preserve_aspect_ratio(StringView input); + static Optional parse_gradient_units(StringView input); private: AttributeParser(StringView source); diff --git a/Userland/Libraries/LibWeb/SVG/SVGGradientElement.cpp b/Userland/Libraries/LibWeb/SVG/SVGGradientElement.cpp new file mode 100644 index 0000000000..1fcbf500b4 --- /dev/null +++ b/Userland/Libraries/LibWeb/SVG/SVGGradientElement.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023, MacDue + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +namespace Web::SVG { + +SVGGradientElement::SVGGradientElement(DOM::Document& document, DOM::QualifiedName qualified_name) + : SVGElement(document, move(qualified_name)) +{ +} + +void SVGGradientElement::parse_attribute(DeprecatedFlyString const& name, DeprecatedString const& value) +{ + SVGElement::parse_attribute(name, value); + if (name == AttributeNames::gradientUnits) { + m_gradient_units = AttributeParser::parse_gradient_units(value); + } else if (name == AttributeNames::gradientTransform) { + if (auto transform_list = AttributeParser::parse_transform(value); transform_list.has_value()) { + m_gradient_transform = transform_from_transform_list(*transform_list); + } else { + m_gradient_transform = {}; + } + } +} + +GradientUnits SVGGradientElement::gradient_units() const +{ + if (m_gradient_units.has_value()) + return *m_gradient_units; + if (auto href = xlink_href()) + return href->gradient_units(); + return GradientUnits::ObjectBoundingBox; +} + +Optional SVGGradientElement::gradient_transform() const +{ + if (m_gradient_transform.has_value()) + return m_gradient_transform; + if (auto href = xlink_href()) + return href->gradient_transform(); + return {}; +} + +JS::GCPtr SVGGradientElement::xlink_href() const +{ + // FIXME: This entire function is an ad-hoc hack! + // It can only resolve # in the same document. + if (auto href = get_attribute("href"); !href.is_empty()) { + auto url = document().parse_url(href); + auto id = url.fragment(); + if (id.is_empty()) + return {}; + auto element = document().get_element_by_id(id); + if (!element) + return {}; + if (!is(*element)) + return {}; + return &verify_cast(*element); + } + return {}; +} + +JS::ThrowCompletionOr SVGGradientElement::initialize(JS::Realm& realm) +{ + MUST_OR_THROW_OOM(Base::initialize(realm)); + set_prototype(&Bindings::ensure_web_prototype(realm, "SVGGradientElement")); + return {}; +} + +} diff --git a/Userland/Libraries/LibWeb/SVG/SVGGradientElement.h b/Userland/Libraries/LibWeb/SVG/SVGGradientElement.h new file mode 100644 index 0000000000..380a1be774 --- /dev/null +++ b/Userland/Libraries/LibWeb/SVG/SVGGradientElement.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023, MacDue + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Web::SVG { + +struct SVGPaintContext { + Gfx::FloatRect viewport; + Gfx::FloatRect path_bounding_box; + Gfx::AffineTransform transform; +}; + +class SVGGradientElement : public SVGElement { + WEB_PLATFORM_OBJECT(SVGGradientElement, SVGElement); + +public: + virtual ~SVGGradientElement() override = default; + + virtual void parse_attribute(DeprecatedFlyString const& name, DeprecatedString const& value) override; + + virtual Optional to_gfx_paint_style(SVGPaintContext const&) const = 0; + + GradientUnits gradient_units() const; + + Optional gradient_transform() const; + +protected: + SVGGradientElement(DOM::Document&, DOM::QualifiedName); + + virtual JS::ThrowCompletionOr initialize(JS::Realm&) override; + + JS::GCPtr xlink_href() const; + + template Callback> + void for_each_color_stop(Callback const& callback) const + { + for_each_child_of_type([&](auto& stop) { + callback(stop); + }); + if (auto href = xlink_href()) + href->for_each_color_stop(callback); + } + +private: + Optional m_gradient_units = {}; + Optional m_gradient_transform = {}; +}; + +} diff --git a/Userland/Libraries/LibWeb/SVG/SVGGradientElement.idl b/Userland/Libraries/LibWeb/SVG/SVGGradientElement.idl new file mode 100644 index 0000000000..007aa9cd69 --- /dev/null +++ b/Userland/Libraries/LibWeb/SVG/SVGGradientElement.idl @@ -0,0 +1,17 @@ +#import + +[Exposed=Window] +interface SVGGradientElement : SVGElement { + + // Spread Method Types + const unsigned short SVG_SPREADMETHOD_UNKNOWN = 0; + const unsigned short SVG_SPREADMETHOD_PAD = 1; + const unsigned short SVG_SPREADMETHOD_REFLECT = 2; + const unsigned short SVG_SPREADMETHOD_REPEAT = 3; + + // FIXME: [SameObject] readonly attribute SVGAnimatedEnumeration gradientUnits; + // FIXME: [SameObject] readonly attribute SVGAnimatedTransformList gradientTransform; + // FIXME: [SameObject] readonly attribute SVGAnimatedEnumeration spreadMethod; +}; + +// FIXME: SVGGradientElement includes SVGURIReference; diff --git a/Userland/Libraries/LibWeb/idl_files.cmake b/Userland/Libraries/LibWeb/idl_files.cmake index cb73115393..11a8a0bc68 100644 --- a/Userland/Libraries/LibWeb/idl_files.cmake +++ b/Userland/Libraries/LibWeb/idl_files.cmake @@ -196,6 +196,7 @@ libweb_js_bindings(SVG/SVGClipPathElement) libweb_js_bindings(SVG/SVGDefsElement) libweb_js_bindings(SVG/SVGElement) libweb_js_bindings(SVG/SVGGeometryElement) +libweb_js_bindings(SVG/SVGGradientElement) libweb_js_bindings(SVG/SVGGraphicsElement) libweb_js_bindings(SVG/SVGCircleElement) libweb_js_bindings(SVG/SVGEllipseElement)