From 809c5b0b038d0f94fd13ba40a03b08c3be55ee07 Mon Sep 17 00:00:00 2001 From: MacDue Date: Sun, 17 Dec 2023 18:35:21 +0000 Subject: [PATCH] LibWeb: Add initial support for SVG `` This patch adds basic support for the SVG ``, 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. --- Userland/Libraries/LibWeb/CMakeLists.txt | 2 + .../Libraries/LibWeb/DOM/ElementFactory.cpp | 3 ++ .../LibWeb/Layout/SVGFormattingContext.cpp | 15 +++++++ .../LibWeb/Layout/SVGTextPathBox.cpp | 22 ++++++++++ .../Libraries/LibWeb/Layout/SVGTextPathBox.h | 30 ++++++++++++++ .../LibWeb/SVG/SVGTextPathElement.cpp | 41 +++++++++++++++++++ .../Libraries/LibWeb/SVG/SVGTextPathElement.h | 30 ++++++++++++++ .../LibWeb/SVG/SVGTextPathElement.idl | 23 +++++++++++ .../Libraries/LibWeb/SVG/SVGURIReference.idl | 3 ++ Userland/Libraries/LibWeb/SVG/TagNames.h | 1 + Userland/Libraries/LibWeb/idl_files.cmake | 1 + 11 files changed, 171 insertions(+) create mode 100644 Userland/Libraries/LibWeb/Layout/SVGTextPathBox.cpp create mode 100644 Userland/Libraries/LibWeb/Layout/SVGTextPathBox.h create mode 100644 Userland/Libraries/LibWeb/SVG/SVGTextPathElement.cpp create mode 100644 Userland/Libraries/LibWeb/SVG/SVGTextPathElement.h create mode 100644 Userland/Libraries/LibWeb/SVG/SVGTextPathElement.idl create mode 100644 Userland/Libraries/LibWeb/SVG/SVGURIReference.idl diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 6edaf343ec..c091c60e37 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -458,6 +458,7 @@ set(SOURCES Layout/SVGGraphicsBox.cpp Layout/SVGSVGBox.cpp Layout/SVGTextBox.cpp + Layout/SVGTextPathBox.cpp Layout/TableFormattingContext.cpp Layout/TableGrid.cpp Layout/TableWrapper.cpp @@ -584,6 +585,7 @@ set(SOURCES SVG/SVGSymbolElement.cpp SVG/SVGTextContentElement.cpp SVG/SVGTextElement.cpp + SVG/SVGTextPathElement.cpp SVG/SVGTextPositioningElement.cpp SVG/SVGTitleElement.cpp SVG/SVGTSpanElement.cpp diff --git a/Userland/Libraries/LibWeb/DOM/ElementFactory.cpp b/Userland/Libraries/LibWeb/DOM/ElementFactory.cpp index e069f785d4..b1b3505734 100644 --- a/Userland/Libraries/LibWeb/DOM/ElementFactory.cpp +++ b/Userland/Libraries/LibWeb/DOM/ElementFactory.cpp @@ -105,6 +105,7 @@ #include #include #include +#include #include #include #include @@ -468,6 +469,8 @@ static JS::GCPtr create_svg_element(JS::Realm& realm, Document& return realm.heap().allocate(realm, document, move(qualified_name)); if (local_name == SVG::TagNames::text) return realm.heap().allocate(realm, document, move(qualified_name)); + if (local_name == SVG::TagNames::textPath) + return realm.heap().allocate(realm, document, move(qualified_name)); if (local_name == SVG::TagNames::title) return realm.heap().allocate(realm, document, move(qualified_name)); if (local_name == SVG::TagNames::tspan) diff --git a/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp index 2fa232811a..74cd4ee329 100644 --- a/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp @@ -9,11 +9,14 @@ #include #include +#include +#include #include #include #include #include #include +#include #include #include #include @@ -253,6 +256,18 @@ void SVGFormattingContext::run(Box const& box, LayoutMode layout_mode, Available path.move_to(text_offset); path.text(text_utf8, font); + } else if (is(descendant)) { + auto& text_path_element = static_cast(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(*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(); diff --git a/Userland/Libraries/LibWeb/Layout/SVGTextPathBox.cpp b/Userland/Libraries/LibWeb/Layout/SVGTextPathBox.cpp new file mode 100644 index 0000000000..27a096d16a --- /dev/null +++ b/Userland/Libraries/LibWeb/Layout/SVGTextPathBox.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023, MacDue + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Web::Layout { + +SVGTextPathBox::SVGTextPathBox(DOM::Document& document, SVG::SVGTextPathElement& element, NonnullRefPtr properties) + : SVGGraphicsBox(document, element, properties) +{ +} + +JS::GCPtr SVGTextPathBox::create_paintable() const +{ + return Painting::SVGPathPaintable::create(*this); +} + +} diff --git a/Userland/Libraries/LibWeb/Layout/SVGTextPathBox.h b/Userland/Libraries/LibWeb/Layout/SVGTextPathBox.h new file mode 100644 index 0000000000..b7097d19d8 --- /dev/null +++ b/Userland/Libraries/LibWeb/Layout/SVGTextPathBox.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023, MacDue + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::Layout { + +class SVGTextPathBox final : public SVGGraphicsBox { + JS_CELL(SVGTextPathBox, SVGGraphicsBox); + +public: + SVGTextPathBox(DOM::Document&, SVG::SVGTextPathElement&, NonnullRefPtr); + virtual ~SVGTextPathBox() override = default; + + SVG::SVGTextPathElement& dom_node() { return static_cast(SVGGraphicsBox::dom_node()); } + SVG::SVGTextPathElement const& dom_node() const { return static_cast(SVGGraphicsBox::dom_node()); } + + virtual JS::GCPtr create_paintable() const override; + +private: + CSSPixelPoint viewbox_origin() const; +}; + +} diff --git a/Userland/Libraries/LibWeb/SVG/SVGTextPathElement.cpp b/Userland/Libraries/LibWeb/SVG/SVGTextPathElement.cpp new file mode 100644 index 0000000000..3686fb8eb5 --- /dev/null +++ b/Userland/Libraries/LibWeb/SVG/SVGTextPathElement.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023, MacDue + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace Web::SVG { + +JS_DEFINE_ALLOCATOR(SVGTextPathElement); + +SVGTextPathElement::SVGTextPathElement(DOM::Document& document, DOM::QualifiedName qualified_name) + : SVGTextContentElement(document, move(qualified_name)) +{ +} + +JS::GCPtr 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(url); +} + +void SVGTextPathElement::initialize(JS::Realm& realm) +{ + Base::initialize(realm); + set_prototype(&Bindings::ensure_web_prototype(realm, "SVGTextPathElement"_fly_string)); +} + +JS::GCPtr SVGTextPathElement::create_layout_node(NonnullRefPtr style) +{ + return heap().allocate_without_realm(document(), *this, move(style)); +} + +}; diff --git a/Userland/Libraries/LibWeb/SVG/SVGTextPathElement.h b/Userland/Libraries/LibWeb/SVG/SVGTextPathElement.h new file mode 100644 index 0000000000..e635034f91 --- /dev/null +++ b/Userland/Libraries/LibWeb/SVG/SVGTextPathElement.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023, MacDue + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +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 create_layout_node(NonnullRefPtr) override; + + JS::GCPtr path_or_shape() const; + +protected: + SVGTextPathElement(DOM::Document&, DOM::QualifiedName); + + virtual void initialize(JS::Realm&) override; +}; + +} diff --git a/Userland/Libraries/LibWeb/SVG/SVGTextPathElement.idl b/Userland/Libraries/LibWeb/SVG/SVGTextPathElement.idl new file mode 100644 index 0000000000..d286fa2abe --- /dev/null +++ b/Userland/Libraries/LibWeb/SVG/SVGTextPathElement.idl @@ -0,0 +1,23 @@ +#import +#import + +// 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; diff --git a/Userland/Libraries/LibWeb/SVG/SVGURIReference.idl b/Userland/Libraries/LibWeb/SVG/SVGURIReference.idl new file mode 100644 index 0000000000..8046b91382 --- /dev/null +++ b/Userland/Libraries/LibWeb/SVG/SVGURIReference.idl @@ -0,0 +1,3 @@ +interface mixin SVGURIReference { + // FIXME: [SameObject] readonly attribute SVGAnimatedString href; +}; diff --git a/Userland/Libraries/LibWeb/SVG/TagNames.h b/Userland/Libraries/LibWeb/SVG/TagNames.h index 8a6a21c22c..65c66e9dfd 100644 --- a/Userland/Libraries/LibWeb/SVG/TagNames.h +++ b/Userland/Libraries/LibWeb/SVG/TagNames.h @@ -22,6 +22,7 @@ namespace Web::SVG::TagNames { __ENUMERATE_SVG_TAG(rect) \ __ENUMERATE_SVG_TAG(svg) \ __ENUMERATE_SVG_TAG(text) \ + __ENUMERATE_SVG_TAG(textPath) \ __ENUMERATE_SVG_TAG(tspan) #define ENUMERATE_SVG_TAGS \ diff --git a/Userland/Libraries/LibWeb/idl_files.cmake b/Userland/Libraries/LibWeb/idl_files.cmake index 1cf11d88ef..c19f950135 100644 --- a/Userland/Libraries/LibWeb/idl_files.cmake +++ b/Userland/Libraries/LibWeb/idl_files.cmake @@ -253,6 +253,7 @@ libweb_js_bindings(SVG/SVGStyleElement) libweb_js_bindings(SVG/SVGSymbolElement) libweb_js_bindings(SVG/SVGTextContentElement) libweb_js_bindings(SVG/SVGTextElement) +libweb_js_bindings(SVG/SVGTextPathElement) libweb_js_bindings(SVG/SVGTextPositioningElement) libweb_js_bindings(SVG/SVGTitleElement) libweb_js_bindings(SVG/SVGTSpanElement)