From 0d8d7ae94e14b98ca29ab9f25694a94a4b4f6c5b Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Thu, 8 Jun 2023 21:32:33 +0300 Subject: [PATCH] LibWeb: Implement painting for svg text The implementation of painting for SVG text follows the same pattern as the implementation of painting for SVG geometries. However, instead of reusing the existing PaintableWithLines to draw text, a new class called SVGTextPaintable is introduced. because everything that is painted inside an SVG is expected to inherit from SVGGraphicsPaintable. Therefore reusing the text painting from regular text nodes would require significant refactoring. --- Userland/Libraries/LibWeb/CMakeLists.txt | 2 + .../LibWeb/Layout/SVGFormattingContext.cpp | 5 ++ .../Libraries/LibWeb/Layout/SVGTextBox.cpp | 43 ++++++++++++++ Userland/Libraries/LibWeb/Layout/SVGTextBox.h | 33 +++++++++++ .../LibWeb/Painting/SVGTextPaintable.cpp | 56 +++++++++++++++++++ .../LibWeb/Painting/SVGTextPaintable.h | 33 +++++++++++ .../Libraries/LibWeb/SVG/AttributeNames.h | 2 + .../LibWeb/SVG/SVGTextContentElement.cpp | 30 ++++++++++ .../LibWeb/SVG/SVGTextContentElement.h | 12 ++++ 9 files changed, 216 insertions(+) create mode 100644 Userland/Libraries/LibWeb/Layout/SVGTextBox.cpp create mode 100644 Userland/Libraries/LibWeb/Layout/SVGTextBox.h create mode 100644 Userland/Libraries/LibWeb/Painting/SVGTextPaintable.cpp create mode 100644 Userland/Libraries/LibWeb/Painting/SVGTextPaintable.h diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 0e2e201042..d9180f3a7e 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -422,6 +422,7 @@ set(SOURCES Layout/SVGGeometryBox.cpp Layout/SVGGraphicsBox.cpp Layout/SVGSVGBox.cpp + Layout/SVGTextBox.cpp Layout/TableFormattingContext.cpp Layout/TableWrapper.cpp Layout/TextNode.cpp @@ -465,6 +466,7 @@ set(SOURCES Painting/SVGGraphicsPaintable.cpp Painting/SVGPaintable.cpp Painting/SVGSVGPaintable.cpp + Painting/SVGTextPaintable.cpp Painting/ShadowPainting.cpp Painting/StackingContext.cpp Painting/TextPaintable.cpp diff --git a/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp index 7bce7d03bd..fd7c9bc8bd 100644 --- a/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -187,6 +188,10 @@ void SVGFormattingContext::run(Box const& box, LayoutMode layout_mode, Available } else if (is(descendant)) { SVGFormattingContext nested_context(m_state, descendant, this); nested_context.run(descendant, layout_mode, available_space); + } else if (is(descendant)) { + auto const& svg_text_box = static_cast(descendant); + // NOTE: This hack creates a layout state to ensure the existence of a paintable box node in LayoutState::commit(), even when none of the values from UsedValues impact the SVG text. + m_state.get_mutable(svg_text_box); } return IterationDecision::Continue; diff --git a/Userland/Libraries/LibWeb/Layout/SVGTextBox.cpp b/Userland/Libraries/LibWeb/Layout/SVGTextBox.cpp new file mode 100644 index 0000000000..f4005f5f88 --- /dev/null +++ b/Userland/Libraries/LibWeb/Layout/SVGTextBox.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace Web::Layout { + +SVGTextBox::SVGTextBox(DOM::Document& document, SVG::SVGTextContentElement& element, NonnullRefPtr properties) + : SVGGraphicsBox(document, element, properties) +{ +} + +CSSPixelPoint SVGTextBox::viewbox_origin() const +{ + auto* svg_box = dom_node().first_ancestor_of_type(); + if (!svg_box || !svg_box->view_box().has_value()) + return { 0, 0 }; + return { svg_box->view_box().value().min_x, svg_box->view_box().value().min_y }; +} + +Optional SVGTextBox::layout_transform() const +{ + auto& geometry_element = dom_node(); + auto transform = geometry_element.get_transform(); + auto* svg_box = geometry_element.first_ancestor_of_type(); + auto origin = viewbox_origin().to_type().to_type(); + Gfx::FloatPoint paint_offset = {}; + if (svg_box && svg_box->view_box().has_value()) + paint_offset = svg_box->paintable_box()->absolute_rect().location().to_type().to_type(); + return Gfx::AffineTransform {}.translate(paint_offset).translate(-origin).multiply(transform); +} + +JS::GCPtr SVGTextBox::create_paintable() const +{ + return Painting::SVGTextPaintable::create(*this); +} + +} diff --git a/Userland/Libraries/LibWeb/Layout/SVGTextBox.h b/Userland/Libraries/LibWeb/Layout/SVGTextBox.h new file mode 100644 index 0000000000..f47c5cc917 --- /dev/null +++ b/Userland/Libraries/LibWeb/Layout/SVGTextBox.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace Web::Layout { + +class SVGTextBox final : public SVGGraphicsBox { + JS_CELL(SVGTextBox, SVGGraphicsBox); + +public: + SVGTextBox(DOM::Document&, SVG::SVGTextContentElement&, NonnullRefPtr); + virtual ~SVGTextBox() override = default; + + SVG::SVGTextContentElement& dom_node() { return static_cast(SVGGraphicsBox::dom_node()); } + SVG::SVGTextContentElement const& dom_node() const { return static_cast(SVGGraphicsBox::dom_node()); } + + Optional layout_transform() const; + + virtual JS::GCPtr create_paintable() const override; + +private: + CSSPixelPoint viewbox_origin() const; +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/SVGTextPaintable.cpp b/Userland/Libraries/LibWeb/Painting/SVGTextPaintable.cpp new file mode 100644 index 0000000000..32fd1fcd14 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/SVGTextPaintable.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Web::Painting { + +JS::NonnullGCPtr SVGTextPaintable::create(Layout::SVGTextBox const& layout_box) +{ + return layout_box.heap().allocate_without_realm(layout_box); +} + +SVGTextPaintable::SVGTextPaintable(Layout::SVGTextBox const& layout_box) + : SVGGraphicsPaintable(layout_box) +{ +} + +Optional SVGTextPaintable::hit_test(CSSPixelPoint position, HitTestType type) const +{ + (void)position; + (void)type; + return {}; +} + +void SVGTextPaintable::paint(PaintContext& context, PaintPhase phase) const +{ + if (!is_visible()) + return; + + SVGGraphicsPaintable::paint(context, phase); + + if (phase != PaintPhase::Foreground) + return; + + auto& painter = context.painter(); + + Gfx::PainterStateSaver save_painter { painter }; + auto& svg_context = context.svg_context(); + auto svg_context_offset = context.floored_device_point(svg_context.svg_element_position()).to_type(); + painter.translate(svg_context_offset); + + auto const& dom_node = layout_box().dom_node(); + + auto child_text_content = dom_node.child_text_content(); + + auto transform = layout_box().layout_transform(); + auto& scaled_font = layout_node().scaled_font(context); + auto text_offset = context.floored_device_point(dom_node.get_offset().transformed(*transform).to_type()); + painter.draw_text_run(text_offset.to_type(), Utf8View { child_text_content }, scaled_font, layout_node().computed_values().fill()->as_color()); +} + +} diff --git a/Userland/Libraries/LibWeb/Painting/SVGTextPaintable.h b/Userland/Libraries/LibWeb/Painting/SVGTextPaintable.h new file mode 100644 index 0000000000..c6910e48df --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/SVGTextPaintable.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::Painting { + +class SVGTextPaintable final : public SVGGraphicsPaintable { + JS_CELL(SVGTextPaintable, SVGGraphicsPaintable); + +public: + static JS::NonnullGCPtr create(Layout::SVGTextBox const&); + + virtual Optional hit_test(CSSPixelPoint, HitTestType) const override; + + virtual void paint(PaintContext&, PaintPhase) const override; + + Layout::SVGTextBox const& layout_box() const + { + return static_cast(layout_node()); + } + +protected: + SVGTextPaintable(Layout::SVGTextBox const&); +}; + +} diff --git a/Userland/Libraries/LibWeb/SVG/AttributeNames.h b/Userland/Libraries/LibWeb/SVG/AttributeNames.h index 7594e138b3..f223a8d68a 100644 --- a/Userland/Libraries/LibWeb/SVG/AttributeNames.h +++ b/Userland/Libraries/LibWeb/SVG/AttributeNames.h @@ -22,6 +22,8 @@ namespace Web::SVG::AttributeNames { E(contentStyleType) \ E(cx) \ E(cy) \ + E(dx) \ + E(dy) \ E(diffuseConstant) \ E(edgeMode) \ E(filterUnits) \ diff --git a/Userland/Libraries/LibWeb/SVG/SVGTextContentElement.cpp b/Userland/Libraries/LibWeb/SVG/SVGTextContentElement.cpp index df66953ebc..ee99c04f5e 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGTextContentElement.cpp +++ b/Userland/Libraries/LibWeb/SVG/SVGTextContentElement.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2022, Andreas Kling + * Copyright (c) 2023, Aliaksandr Kalenik * * SPDX-License-Identifier: BSD-2-Clause */ @@ -8,6 +9,10 @@ #include #include #include +#include +#include +#include +#include #include namespace Web::SVG { @@ -25,6 +30,26 @@ JS::ThrowCompletionOr SVGTextContentElement::initialize(JS::Realm& realm) return {}; } +void SVGTextContentElement::parse_attribute(DeprecatedFlyString const& name, DeprecatedString const& value) +{ + SVGGraphicsElement::parse_attribute(name, value); + + if (name == SVG::AttributeNames::x) { + m_x = AttributeParser::parse_coordinate(value).value(); + } else if (name == SVG::AttributeNames::y) { + m_y = AttributeParser::parse_coordinate(value).value(); + } else if (name == SVG::AttributeNames::dx) { + m_dx = AttributeParser::parse_coordinate(value).value(); + } else if (name == SVG::AttributeNames::dy) { + m_dy = AttributeParser::parse_coordinate(value).value(); + } +} + +JS::GCPtr SVGTextContentElement::create_layout_node(NonnullRefPtr style) +{ + return heap().allocate_without_realm(document(), *this, move(style)); +} + // https://svgwg.org/svg2-draft/text.html#__svg__SVGTextContentElement__getNumberOfChars WebIDL::ExceptionOr SVGTextContentElement::get_number_of_chars() const { @@ -32,4 +57,9 @@ WebIDL::ExceptionOr SVGTextContentElement::get_number_of_chars() const return static_cast(chars.size()); } +Gfx::FloatPoint SVGTextContentElement::get_offset() const +{ + return { m_x + m_dx, m_y + m_dy }; +} + } diff --git a/Userland/Libraries/LibWeb/SVG/SVGTextContentElement.h b/Userland/Libraries/LibWeb/SVG/SVGTextContentElement.h index 1f68e6d7f5..e7fe940f7c 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGTextContentElement.h +++ b/Userland/Libraries/LibWeb/SVG/SVGTextContentElement.h @@ -16,12 +16,24 @@ class SVGTextContentElement : public SVGGraphicsElement { WEB_PLATFORM_OBJECT(SVGTextContentElement, SVGGraphicsElement); public: + virtual JS::GCPtr create_layout_node(NonnullRefPtr) override; + + virtual void parse_attribute(DeprecatedFlyString const& name, DeprecatedString const& value) override; + WebIDL::ExceptionOr get_number_of_chars() const; + Gfx::FloatPoint get_offset() const; + protected: SVGTextContentElement(DOM::Document&, DOM::QualifiedName); virtual JS::ThrowCompletionOr initialize(JS::Realm&) override; + +private: + float m_x { 0 }; + float m_y { 0 }; + float m_dx { 0 }; + float m_dy { 0 }; }; }