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 }; }; }