From 74b655d035f1f78da12345abda0b1bb9bd652aec Mon Sep 17 00:00:00 2001 From: MacDue Date: Sun, 3 Mar 2024 20:47:10 +0000 Subject: [PATCH] LibWeb: Update SVG `` element to use geometry properties With this the `` element now correctly parses percentage sizes, and resolves them relative to the viewport. The rest of the geometry elements are still left TODO. --- .../expected/svg/objectBoundingBox-mask.txt | 2 +- .../svg/svg-circle-percentage-attr.txt | 13 ++++ .../input/svg/svg-circle-percentage-attr.html | 6 ++ .../Libraries/LibWeb/SVG/SVGCircleElement.cpp | 71 +++++++++++-------- .../Libraries/LibWeb/SVG/SVGCircleElement.h | 6 +- Userland/Libraries/LibWeb/SVG/SVGViewport.h | 8 +++ 6 files changed, 71 insertions(+), 35 deletions(-) create mode 100644 Tests/LibWeb/Layout/expected/svg/svg-circle-percentage-attr.txt create mode 100644 Tests/LibWeb/Layout/input/svg/svg-circle-percentage-attr.html diff --git a/Tests/LibWeb/Layout/expected/svg/objectBoundingBox-mask.txt b/Tests/LibWeb/Layout/expected/svg/objectBoundingBox-mask.txt index bfd8d7a9d06..2104cc1d2c2 100644 --- a/Tests/LibWeb/Layout/expected/svg/objectBoundingBox-mask.txt +++ b/Tests/LibWeb/Layout/expected/svg/objectBoundingBox-mask.txt @@ -8,7 +8,7 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline TextNode <#text> SVGGeometryBox at (8,8) content-size 1x1 children: not-inline TextNode <#text> - SVGGeometryBox at (8.09375,8.09375) content-size 0.796875x0.796875 children: not-inline + SVGGeometryBox at (8.09375,8.09375) content-size 0.8125x0.8125 children: not-inline TextNode <#text> TextNode <#text> SVGGeometryBox at (8,8) content-size 200x200 children: not-inline diff --git a/Tests/LibWeb/Layout/expected/svg/svg-circle-percentage-attr.txt b/Tests/LibWeb/Layout/expected/svg/svg-circle-percentage-attr.txt new file mode 100644 index 00000000000..a360bdc3bc8 --- /dev/null +++ b/Tests/LibWeb/Layout/expected/svg/svg-circle-percentage-attr.txt @@ -0,0 +1,13 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer at (0,0) content-size 800x366 [BFC] children: not-inline + BlockContainer at (8,8) content-size 784x350 children: inline + frag 0 from SVGSVGBox start: 0, length: 0, rect: [8,8 784x350] baseline: 350 + SVGSVGBox at (8,8) content-size 784x350 [SVG] children: not-inline + SVGGeometryBox at (250,28) content-size 300x300 children: not-inline + TextNode <#text> + +ViewportPaintable (Viewport<#document>) [0,0 800x600] + PaintableWithLines (BlockContainer) [0,0 800x366] + PaintableWithLines (BlockContainer) [8,8 784x350] + SVGSVGPaintable (SVGSVGBox) [8,8 784x350] + SVGPathPaintable (SVGGeometryBox) [250,28 300x300] diff --git a/Tests/LibWeb/Layout/input/svg/svg-circle-percentage-attr.html b/Tests/LibWeb/Layout/input/svg/svg-circle-percentage-attr.html new file mode 100644 index 00000000000..60275b13a8e --- /dev/null +++ b/Tests/LibWeb/Layout/input/svg/svg-circle-percentage-attr.html @@ -0,0 +1,6 @@ + diff --git a/Userland/Libraries/LibWeb/SVG/SVGCircleElement.cpp b/Userland/Libraries/LibWeb/SVG/SVGCircleElement.cpp index cecc1a52e75..0f2d750238b 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGCircleElement.cpp +++ b/Userland/Libraries/LibWeb/SVG/SVGCircleElement.cpp @@ -5,9 +5,12 @@ */ #include +#include +#include #include #include #include +#include namespace Web::SVG { @@ -24,34 +27,38 @@ void SVGCircleElement::initialize(JS::Realm& realm) set_prototype(&Bindings::ensure_web_prototype(realm, "SVGCircleElement"_fly_string)); } -void SVGCircleElement::attribute_changed(FlyString const& name, Optional const& value) +void SVGCircleElement::apply_presentational_hints(CSS::StyleProperties& style) const { - SVGGeometryElement::attribute_changed(name, value); + Base::apply_presentational_hints(style); + auto parsing_context = CSS::Parser::ParsingContext { document(), CSS::Parser::ParsingContext::Mode::SVGPresentationAttribute }; - if (name == SVG::AttributeNames::cx) { - m_center_x = AttributeParser::parse_coordinate(value.value_or(String {})); - m_path.clear(); - } else if (name == SVG::AttributeNames::cy) { - m_center_y = AttributeParser::parse_coordinate(value.value_or(String {})); - m_path.clear(); - } else if (name == SVG::AttributeNames::r) { - m_radius = AttributeParser::parse_positive_length(value.value_or(String {})); - m_path.clear(); - } + auto cx_attribute = attribute(SVG::AttributeNames::cx); + if (auto cx_value = parse_css_value(parsing_context, cx_attribute.value_or(String {}), CSS::PropertyID::Cx)) + style.set_property(CSS::PropertyID::Cx, cx_value.release_nonnull()); + + auto cy_attribute = attribute(SVG::AttributeNames::cy); + if (auto cy_value = parse_css_value(parsing_context, cy_attribute.value_or(String {}), CSS::PropertyID::Cy)) + style.set_property(CSS::PropertyID::Cy, cy_value.release_nonnull()); + + auto r_attribute = attribute(SVG::AttributeNames::r); + if (auto r_value = parse_css_value(parsing_context, r_attribute.value_or(String {}), CSS::PropertyID::R)) + style.set_property(CSS::PropertyID::R, r_value.release_nonnull()); } Gfx::Path SVGCircleElement::get_path(CSSPixelSize viewport_size) { - float cx = m_center_x.value_or(0); - float cy = m_center_y.value_or(0); - float r = m_radius.value_or(0); - - Gfx::Path path; + auto* node = layout_node(); + auto cx = float(node->computed_values().cx().to_px(*node, viewport_size.width())); + auto cy = float(node->computed_values().cy().to_px(*node, viewport_size.height())); + // Percentages refer to the normalized diagonal of the current SVG viewport + // (see Units: https://svgwg.org/svg2-draft/coords.html#Units) + auto r = float(node->computed_values().r().to_px(*node, normalized_diagonal_length(viewport_size))); // A zero radius disables rendering. if (r == 0) return {}; + Gfx::Path path; bool large_arc = false; bool sweep = true; @@ -76,31 +83,37 @@ Gfx::Path SVGCircleElement::get_path(CSSPixelSize viewport_size) // https://www.w3.org/TR/SVG11/shapes.html#CircleElementCXAttribute JS::NonnullGCPtr SVGCircleElement::cx() const { - // FIXME: Populate the unit type when it is parsed (0 here is "unknown"). // FIXME: Create a proper animated value when animations are supported. - auto base_length = SVGLength::create(realm(), 0, m_center_x.value_or(0)); - auto anim_length = SVGLength::create(realm(), 0, m_center_x.value_or(0)); - return SVGAnimatedLength::create(realm(), move(base_length), move(anim_length)); + auto make_length = [&] { + if (auto cx = computed_css_values()->length_percentage(CSS::PropertyID::Cx); cx.has_value()) + return SVGLength::from_length_percentage(realm(), *cx); + return SVGLength::create(realm(), 0, 0.0f); + }; + return SVGAnimatedLength::create(realm(), make_length(), make_length()); } // https://www.w3.org/TR/SVG11/shapes.html#CircleElementCYAttribute JS::NonnullGCPtr SVGCircleElement::cy() const { - // FIXME: Populate the unit type when it is parsed (0 here is "unknown"). // FIXME: Create a proper animated value when animations are supported. - auto base_length = SVGLength::create(realm(), 0, m_center_y.value_or(0)); - auto anim_length = SVGLength::create(realm(), 0, m_center_y.value_or(0)); - return SVGAnimatedLength::create(realm(), move(base_length), move(anim_length)); + auto make_length = [&] { + if (auto cy = computed_css_values()->length_percentage(CSS::PropertyID::Cy); cy.has_value()) + return SVGLength::from_length_percentage(realm(), *cy); + return SVGLength::create(realm(), 0, 0.0f); + }; + return SVGAnimatedLength::create(realm(), make_length(), make_length()); } // https://www.w3.org/TR/SVG11/shapes.html#CircleElementRAttribute JS::NonnullGCPtr SVGCircleElement::r() const { - // FIXME: Populate the unit type when it is parsed (0 here is "unknown"). // FIXME: Create a proper animated value when animations are supported. - auto base_length = SVGLength::create(realm(), 0, m_radius.value_or(0)); - auto anim_length = SVGLength::create(realm(), 0, m_radius.value_or(0)); - return SVGAnimatedLength::create(realm(), move(base_length), move(anim_length)); + auto make_length = [&] { + if (auto r = computed_css_values()->length_percentage(CSS::PropertyID::R); r.has_value()) + return SVGLength::from_length_percentage(realm(), *r); + return SVGLength::create(realm(), 0, 0.0f); + }; + return SVGAnimatedLength::create(realm(), make_length(), make_length()); } } diff --git a/Userland/Libraries/LibWeb/SVG/SVGCircleElement.h b/Userland/Libraries/LibWeb/SVG/SVGCircleElement.h index 9887153ad0c..d8c3e0af9b7 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGCircleElement.h +++ b/Userland/Libraries/LibWeb/SVG/SVGCircleElement.h @@ -18,7 +18,7 @@ class SVGCircleElement final : public SVGGeometryElement { public: virtual ~SVGCircleElement() override = default; - virtual void attribute_changed(FlyString const& name, Optional const& value) override; + virtual void apply_presentational_hints(CSS::StyleProperties&) const override; virtual Gfx::Path get_path(CSSPixelSize viewport_size) override; @@ -30,10 +30,6 @@ private: SVGCircleElement(DOM::Document&, DOM::QualifiedName); virtual void initialize(JS::Realm&) override; - - Optional m_center_x; - Optional m_center_y; - Optional m_radius; }; } diff --git a/Userland/Libraries/LibWeb/SVG/SVGViewport.h b/Userland/Libraries/LibWeb/SVG/SVGViewport.h index 5ae4aa5837f..c41f1a09f07 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGViewport.h +++ b/Userland/Libraries/LibWeb/SVG/SVGViewport.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include @@ -18,4 +19,11 @@ public: virtual ~SVGViewport() = default; }; +inline CSSPixels normalized_diagonal_length(CSSPixelSize viewport_size) +{ + if (viewport_size.width() == viewport_size.height()) + return viewport_size.width(); + return sqrt((viewport_size.width() * viewport_size.width()) + (viewport_size.height() * viewport_size.height())) / CSSPixels::nearest_value_for(AK::Sqrt2); +} + }