Browse Source

LibWeb: Add initial support for SVG `<textPath>`

This patch adds basic support for the SVG `<textPath>`, so it supports
placing text along a path, but none of the extra attributes for
controlling the layout of the text. This is enough to correctly display
the MDN example.
MacDue 1 năm trước cách đây
mục cha
commit
809c5b0b03

+ 2 - 0
Userland/Libraries/LibWeb/CMakeLists.txt

@@ -458,6 +458,7 @@ set(SOURCES
     Layout/SVGGraphicsBox.cpp
     Layout/SVGGraphicsBox.cpp
     Layout/SVGSVGBox.cpp
     Layout/SVGSVGBox.cpp
     Layout/SVGTextBox.cpp
     Layout/SVGTextBox.cpp
+    Layout/SVGTextPathBox.cpp
     Layout/TableFormattingContext.cpp
     Layout/TableFormattingContext.cpp
     Layout/TableGrid.cpp
     Layout/TableGrid.cpp
     Layout/TableWrapper.cpp
     Layout/TableWrapper.cpp
@@ -584,6 +585,7 @@ set(SOURCES
     SVG/SVGSymbolElement.cpp
     SVG/SVGSymbolElement.cpp
     SVG/SVGTextContentElement.cpp
     SVG/SVGTextContentElement.cpp
     SVG/SVGTextElement.cpp
     SVG/SVGTextElement.cpp
+    SVG/SVGTextPathElement.cpp
     SVG/SVGTextPositioningElement.cpp
     SVG/SVGTextPositioningElement.cpp
     SVG/SVGTitleElement.cpp
     SVG/SVGTitleElement.cpp
     SVG/SVGTSpanElement.cpp
     SVG/SVGTSpanElement.cpp

+ 3 - 0
Userland/Libraries/LibWeb/DOM/ElementFactory.cpp

@@ -105,6 +105,7 @@
 #include <LibWeb/SVG/SVGSymbolElement.h>
 #include <LibWeb/SVG/SVGSymbolElement.h>
 #include <LibWeb/SVG/SVGTSpanElement.h>
 #include <LibWeb/SVG/SVGTSpanElement.h>
 #include <LibWeb/SVG/SVGTextElement.h>
 #include <LibWeb/SVG/SVGTextElement.h>
+#include <LibWeb/SVG/SVGTextPathElement.h>
 #include <LibWeb/SVG/SVGTitleElement.h>
 #include <LibWeb/SVG/SVGTitleElement.h>
 #include <LibWeb/SVG/SVGUseElement.h>
 #include <LibWeb/SVG/SVGUseElement.h>
 #include <LibWeb/SVG/TagNames.h>
 #include <LibWeb/SVG/TagNames.h>
@@ -468,6 +469,8 @@ static JS::GCPtr<SVG::SVGElement> create_svg_element(JS::Realm& realm, Document&
         return realm.heap().allocate<SVG::SVGSymbolElement>(realm, document, move(qualified_name));
         return realm.heap().allocate<SVG::SVGSymbolElement>(realm, document, move(qualified_name));
     if (local_name == SVG::TagNames::text)
     if (local_name == SVG::TagNames::text)
         return realm.heap().allocate<SVG::SVGTextElement>(realm, document, move(qualified_name));
         return realm.heap().allocate<SVG::SVGTextElement>(realm, document, move(qualified_name));
+    if (local_name == SVG::TagNames::textPath)
+        return realm.heap().allocate<SVG::SVGTextPathElement>(realm, document, move(qualified_name));
     if (local_name == SVG::TagNames::title)
     if (local_name == SVG::TagNames::title)
         return realm.heap().allocate<SVG::SVGTitleElement>(realm, document, move(qualified_name));
         return realm.heap().allocate<SVG::SVGTitleElement>(realm, document, move(qualified_name));
     if (local_name == SVG::TagNames::tspan)
     if (local_name == SVG::TagNames::tspan)

+ 15 - 0
Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp

@@ -9,11 +9,14 @@
 
 
 #include <AK/Debug.h>
 #include <AK/Debug.h>
 #include <LibGfx/BoundingBox.h>
 #include <LibGfx/BoundingBox.h>
+#include <LibGfx/Font/ScaledFont.h>
+#include <LibGfx/TextLayout.h>
 #include <LibWeb/Layout/BlockFormattingContext.h>
 #include <LibWeb/Layout/BlockFormattingContext.h>
 #include <LibWeb/Layout/SVGFormattingContext.h>
 #include <LibWeb/Layout/SVGFormattingContext.h>
 #include <LibWeb/Layout/SVGGeometryBox.h>
 #include <LibWeb/Layout/SVGGeometryBox.h>
 #include <LibWeb/Layout/SVGSVGBox.h>
 #include <LibWeb/Layout/SVGSVGBox.h>
 #include <LibWeb/Layout/SVGTextBox.h>
 #include <LibWeb/Layout/SVGTextBox.h>
+#include <LibWeb/Layout/SVGTextPathBox.h>
 #include <LibWeb/SVG/SVGForeignObjectElement.h>
 #include <LibWeb/SVG/SVGForeignObjectElement.h>
 #include <LibWeb/SVG/SVGGElement.h>
 #include <LibWeb/SVG/SVGGElement.h>
 #include <LibWeb/SVG/SVGMaskElement.h>
 #include <LibWeb/SVG/SVGMaskElement.h>
@@ -253,6 +256,18 @@ void SVGFormattingContext::run(Box const& box, LayoutMode layout_mode, Available
 
 
                 path.move_to(text_offset);
                 path.move_to(text_offset);
                 path.text(text_utf8, font);
                 path.text(text_utf8, font);
+            } else if (is<SVGTextPathBox>(descendant)) {
+                auto& text_path_element = static_cast<SVG::SVGTextPathElement&>(dom_node);
+                auto path_or_shape = text_path_element.path_or_shape();
+                if (!path_or_shape)
+                    return IterationDecision::Continue;
+
+                auto& font = graphics_box.first_available_font();
+                auto text_contents = text_path_element.text_contents();
+                Utf8View text_utf8 { text_contents };
+
+                auto shape_path = const_cast<SVG::SVGGeometryElement&>(*path_or_shape).get_path();
+                path = shape_path.place_text_along(text_utf8, font);
             }
             }
 
 
             auto path_bounding_box = to_css_pixels_transform.map(path.bounding_box()).to_type<CSSPixels>();
             auto path_bounding_box = to_css_pixels_transform.map(path.bounding_box()).to_type<CSSPixels>();

+ 22 - 0
Userland/Libraries/LibWeb/Layout/SVGTextPathBox.cpp

@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2023, MacDue <macdue@dueutil.tech>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibWeb/Layout/SVGTextPathBox.h>
+#include <LibWeb/Painting/SVGPathPaintable.h>
+
+namespace Web::Layout {
+
+SVGTextPathBox::SVGTextPathBox(DOM::Document& document, SVG::SVGTextPathElement& element, NonnullRefPtr<CSS::StyleProperties> properties)
+    : SVGGraphicsBox(document, element, properties)
+{
+}
+
+JS::GCPtr<Painting::Paintable> SVGTextPathBox::create_paintable() const
+{
+    return Painting::SVGPathPaintable::create(*this);
+}
+
+}

+ 30 - 0
Userland/Libraries/LibWeb/Layout/SVGTextPathBox.h

@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2023, MacDue <macdue@dueutil.tech>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibWeb/Layout/SVGGraphicsBox.h>
+#include <LibWeb/SVG/SVGTextPathElement.h>
+
+namespace Web::Layout {
+
+class SVGTextPathBox final : public SVGGraphicsBox {
+    JS_CELL(SVGTextPathBox, SVGGraphicsBox);
+
+public:
+    SVGTextPathBox(DOM::Document&, SVG::SVGTextPathElement&, NonnullRefPtr<CSS::StyleProperties>);
+    virtual ~SVGTextPathBox() override = default;
+
+    SVG::SVGTextPathElement& dom_node() { return static_cast<SVG::SVGTextPathElement&>(SVGGraphicsBox::dom_node()); }
+    SVG::SVGTextPathElement const& dom_node() const { return static_cast<SVG::SVGTextPathElement const&>(SVGGraphicsBox::dom_node()); }
+
+    virtual JS::GCPtr<Painting::Paintable> create_paintable() const override;
+
+private:
+    CSSPixelPoint viewbox_origin() const;
+};
+
+}

+ 41 - 0
Userland/Libraries/LibWeb/SVG/SVGTextPathElement.cpp

@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2023, MacDue <macdue@dueutil.tech>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/URL.h>
+#include <LibWeb/Layout/SVGTextPathBox.h>
+#include <LibWeb/SVG/AttributeNames.h>
+#include <LibWeb/SVG/SVGTextPathElement.h>
+
+namespace Web::SVG {
+
+JS_DEFINE_ALLOCATOR(SVGTextPathElement);
+
+SVGTextPathElement::SVGTextPathElement(DOM::Document& document, DOM::QualifiedName qualified_name)
+    : SVGTextContentElement(document, move(qualified_name))
+{
+}
+
+JS::GCPtr<SVGGeometryElement const> SVGTextPathElement::path_or_shape() const
+{
+    auto href = get_attribute(AttributeNames::href);
+    if (!href.has_value())
+        return {};
+    auto url = document().url().complete_url(*href);
+    return try_resolve_url_to<SVGGeometryElement const>(url);
+}
+
+void SVGTextPathElement::initialize(JS::Realm& realm)
+{
+    Base::initialize(realm);
+    set_prototype(&Bindings::ensure_web_prototype<Bindings::SVGTextPathElementPrototype>(realm, "SVGTextPathElement"_fly_string));
+}
+
+JS::GCPtr<Layout::Node> SVGTextPathElement::create_layout_node(NonnullRefPtr<CSS::StyleProperties> style)
+{
+    return heap().allocate_without_realm<Layout::SVGTextPathBox>(document(), *this, move(style));
+}
+
+};

+ 30 - 0
Userland/Libraries/LibWeb/SVG/SVGTextPathElement.h

@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2023, MacDue <macdue@dueutil.tech>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibWeb/SVG/SVGGeometryElement.h>
+#include <LibWeb/SVG/SVGTextContentElement.h>
+
+namespace Web::SVG {
+
+// https://svgwg.org/svg2-draft/text.html#TextPathElement
+class SVGTextPathElement : public SVGTextContentElement {
+    WEB_PLATFORM_OBJECT(SVGTextPathElement, SVGTextContentElement);
+    JS_DECLARE_ALLOCATOR(SVGTextPathElement);
+
+public:
+    virtual JS::GCPtr<Layout::Node> create_layout_node(NonnullRefPtr<CSS::StyleProperties>) override;
+
+    JS::GCPtr<SVGGeometryElement const> path_or_shape() const;
+
+protected:
+    SVGTextPathElement(DOM::Document&, DOM::QualifiedName);
+
+    virtual void initialize(JS::Realm&) override;
+};
+
+}

+ 23 - 0
Userland/Libraries/LibWeb/SVG/SVGTextPathElement.idl

@@ -0,0 +1,23 @@
+#import <SVG/SVGTextContentElement.idl>
+#import <SVG/SVGURIReference.idl>
+
+// https://svgwg.org/svg2-draft/text.html#InterfaceSVGTextPathElement
+[Exposed=Window]
+interface SVGTextPathElement : SVGTextContentElement {
+
+  // textPath Method Types
+  const unsigned short TEXTPATH_METHODTYPE_UNKNOWN = 0;
+  const unsigned short TEXTPATH_METHODTYPE_ALIGN = 1;
+  const unsigned short TEXTPATH_METHODTYPE_STRETCH = 2;
+
+  // textPath Spacing Types
+  const unsigned short TEXTPATH_SPACINGTYPE_UNKNOWN = 0;
+  const unsigned short TEXTPATH_SPACINGTYPE_AUTO = 1;
+  const unsigned short TEXTPATH_SPACINGTYPE_EXACT = 2;
+
+  // FIXME: [SameObject] readonly attribute SVGAnimatedLength startOffset;
+  // FIXME: [SameObject] readonly attribute SVGAnimatedEnumeration method;
+  // FIXME: [SameObject] readonly attribute SVGAnimatedEnumeration spacing;
+};
+
+SVGTextPathElement includes SVGURIReference;

+ 3 - 0
Userland/Libraries/LibWeb/SVG/SVGURIReference.idl

@@ -0,0 +1,3 @@
+interface mixin SVGURIReference {
+  // FIXME: [SameObject] readonly attribute SVGAnimatedString href;
+};

+ 1 - 0
Userland/Libraries/LibWeb/SVG/TagNames.h

@@ -22,6 +22,7 @@ namespace Web::SVG::TagNames {
     __ENUMERATE_SVG_TAG(rect)       \
     __ENUMERATE_SVG_TAG(rect)       \
     __ENUMERATE_SVG_TAG(svg)        \
     __ENUMERATE_SVG_TAG(svg)        \
     __ENUMERATE_SVG_TAG(text)       \
     __ENUMERATE_SVG_TAG(text)       \
+    __ENUMERATE_SVG_TAG(textPath)   \
     __ENUMERATE_SVG_TAG(tspan)
     __ENUMERATE_SVG_TAG(tspan)
 
 
 #define ENUMERATE_SVG_TAGS              \
 #define ENUMERATE_SVG_TAGS              \

+ 1 - 0
Userland/Libraries/LibWeb/idl_files.cmake

@@ -253,6 +253,7 @@ libweb_js_bindings(SVG/SVGStyleElement)
 libweb_js_bindings(SVG/SVGSymbolElement)
 libweb_js_bindings(SVG/SVGSymbolElement)
 libweb_js_bindings(SVG/SVGTextContentElement)
 libweb_js_bindings(SVG/SVGTextContentElement)
 libweb_js_bindings(SVG/SVGTextElement)
 libweb_js_bindings(SVG/SVGTextElement)
+libweb_js_bindings(SVG/SVGTextPathElement)
 libweb_js_bindings(SVG/SVGTextPositioningElement)
 libweb_js_bindings(SVG/SVGTextPositioningElement)
 libweb_js_bindings(SVG/SVGTitleElement)
 libweb_js_bindings(SVG/SVGTitleElement)
 libweb_js_bindings(SVG/SVGTSpanElement)
 libweb_js_bindings(SVG/SVGTSpanElement)