Prechádzať zdrojové kódy

LibWeb: Implement SVGGradientElement

This is the base class for all SVG gradient types. This supports:

- The `gradientUnits` attribute
- The `gradientTransform` attribute
- And following `xlink:hrefs` for inheriting <stops>/attributes
MacDue 2 rokov pred
rodič
commit
71938550fa

+ 1 - 0
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

+ 13 - 0
Userland/Libraries/LibWeb/SVG/AttributeParser.cpp

@@ -494,6 +494,19 @@ Optional<PreserveAspectRatio> AttributeParser::parse_preserve_aspect_ratio(Strin
     return PreserveAspectRatio { *align, *meet_or_slice };
 }
 
+// https://svgwg.org/svg2-draft/pservers.html#LinearGradientElementGradientUnitsAttribute
+Optional<GradientUnits> 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<Vector<Transform>> AttributeParser::parse_transform()
 {

+ 6 - 0
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<PathInstruction> parse_path_data(StringView input);
     static Optional<Vector<Transform>> parse_transform(StringView input);
     static Optional<PreserveAspectRatio> parse_preserve_aspect_ratio(StringView input);
+    static Optional<GradientUnits> parse_gradient_units(StringView input);
 
 private:
     AttributeParser(StringView source);

+ 78 - 0
Userland/Libraries/LibWeb/SVG/SVGGradientElement.cpp

@@ -0,0 +1,78 @@
+/*
+ * 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/SVGGradientElement.h>
+#include <LibWeb/SVG/SVGGraphicsElement.h>
+
+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<Gfx::AffineTransform> 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 const> SVGGradientElement::xlink_href() const
+{
+    // FIXME: This entire function is an ad-hoc hack!
+    // It can only resolve #<ids> 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<SVGGradientElement>(*element))
+            return {};
+        return &verify_cast<SVGGradientElement>(*element);
+    }
+    return {};
+}
+
+JS::ThrowCompletionOr<void> SVGGradientElement::initialize(JS::Realm& realm)
+{
+    MUST_OR_THROW_OOM(Base::initialize(realm));
+    set_prototype(&Bindings::ensure_web_prototype<Bindings::SVGGradientElementPrototype>(realm, "SVGGradientElement"));
+    return {};
+}
+
+}

+ 59 - 0
Userland/Libraries/LibWeb/SVG/SVGGradientElement.h

@@ -0,0 +1,59 @@
+/*
+ * 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/SVG/AttributeParser.h>
+#include <LibWeb/SVG/SVGElement.h>
+#include <LibWeb/SVG/SVGStopElement.h>
+
+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<Gfx::PaintStyle const&> to_gfx_paint_style(SVGPaintContext const&) const = 0;
+
+    GradientUnits gradient_units() const;
+
+    Optional<Gfx::AffineTransform> gradient_transform() const;
+
+protected:
+    SVGGradientElement(DOM::Document&, DOM::QualifiedName);
+
+    virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override;
+
+    JS::GCPtr<SVGGradientElement const> xlink_href() const;
+
+    template<VoidFunction<SVGStopElement> Callback>
+    void for_each_color_stop(Callback const& callback) const
+    {
+        for_each_child_of_type<SVG::SVGStopElement>([&](auto& stop) {
+            callback(stop);
+        });
+        if (auto href = xlink_href())
+            href->for_each_color_stop(callback);
+    }
+
+private:
+    Optional<GradientUnits> m_gradient_units = {};
+    Optional<Gfx::AffineTransform> m_gradient_transform = {};
+};
+
+}

+ 17 - 0
Userland/Libraries/LibWeb/SVG/SVGGradientElement.idl

@@ -0,0 +1,17 @@
+#import <SVG/SVGElement.idl>
+
+[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;

+ 1 - 0
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)