LibWeb: Move common SVGViewport code into an SVGFitToViewBox class

Special handling for SVGClipPathElement and SVGMaskElement, which use a
a ViewBox and PreserveAspectRatio value internally, has been moved to
`SVGFormattingContext`.
This commit is contained in:
Tim Ledbetter 2025-08-27 22:17:33 +01:00 committed by Jelle Raaijmakers
commit ea41aba6c7
Notes: github-actions[bot] 2025-08-28 13:44:09 +00:00
16 changed files with 144 additions and 152 deletions

View file

@ -878,6 +878,7 @@ set(SOURCES
SVG/SVGFEMergeNodeElement.cpp
SVG/SVGFEOffsetElement.cpp
SVG/SVGFilterElement.cpp
SVG/SVGFitToViewBox.cpp
SVG/SVGForeignObjectElement.cpp
SVG/SVGGElement.cpp
SVG/SVGGeometryElement.cpp

View file

@ -1052,6 +1052,7 @@ class SVGFEBlendElement;
class SVGFEFloodElement;
class SVGFEGaussianBlurElement;
class SVGFilterElement;
class SVGFitToViewBox;
class SVGForeignObjectElement;
class SVGGeometryElement;
class SVGGraphicsElement;

View file

@ -177,7 +177,6 @@ void SVGFormattingContext::run(AvailableSpace const& available_space)
// NOTE: SVG doesn't have a "formatting context" in the spec, but this is the most
// obvious way to drive SVG layout in our engine at the moment.
auto const& svg_viewport = as<SVG::SVGViewport>(*context_box().dom_node());
auto& svg_box_state = m_state.get_mutable(context_box());
if (!this->context_box().root().document().is_decoded_svg()) {
@ -197,38 +196,48 @@ void SVGFormattingContext::run(AvailableSpace const& available_space)
svg_box_state.set_has_definite_width(true);
svg_box_state.set_has_definite_height(true);
auto viewbox = svg_viewport.view_box();
auto* dom_node = context_box().dom_node();
VERIFY(dom_node);
auto& svg_graphics_element = as<SVG::SVGGraphicsElement>(*dom_node);
auto active_view_box = svg_graphics_element.active_view_box();
// https://svgwg.org/svg2-draft/coords.html#ViewBoxAttribute
if (viewbox.has_value()) {
if (viewbox->width < 0 || viewbox->height < 0) {
if (active_view_box.has_value()) {
if (active_view_box->width < 0 || active_view_box->height < 0) {
// A negative value for <width> or <height> is an error and invalidates the viewBox attribute.
viewbox = {};
} else if (viewbox->width == 0 || viewbox->height == 0) {
active_view_box = {};
} else if (active_view_box->width == 0 || active_view_box->height == 0) {
// A value of zero disables rendering of the element.
return;
}
}
m_current_viewbox_transform = m_parent_viewbox_transform;
if (viewbox.has_value()) {
if (active_view_box.has_value()) {
// FIXME: This should allow just one of width or height to be specified.
// E.g. We should be able to layout <svg width="100%"> where height is unspecified/auto.
if (!svg_box_state.has_definite_width() || !svg_box_state.has_definite_height()) {
dbgln_if(LIBWEB_CSS_DEBUG, "FIXME: Attempting to layout indefinitely sized SVG with a viewbox -- this likely won't work!");
}
auto scale_width = svg_box_state.has_definite_width() ? svg_box_state.content_width() / viewbox->width : 1;
auto scale_height = svg_box_state.has_definite_height() ? svg_box_state.content_height() / viewbox->height : 1;
auto scale_width = svg_box_state.has_definite_width() ? svg_box_state.content_width() / active_view_box->width : 1;
auto scale_height = svg_box_state.has_definite_height() ? svg_box_state.content_height() / active_view_box->height : 1;
// The initial value for preserveAspectRatio is xMidYMid meet.
auto preserve_aspect_ratio = svg_viewport.preserve_aspect_ratio().value_or(SVG::PreserveAspectRatio {});
auto viewbox_offset_and_scale = scale_and_align_viewbox_content(preserve_aspect_ratio, *viewbox, { scale_width, scale_height }, svg_box_state);
SVG::PreserveAspectRatio preserve_aspect_ratio {};
if (auto* view_box = as_if<SVG::SVGFitToViewBox>(*dom_node)) {
preserve_aspect_ratio = view_box->preserve_aspect_ratio().value_or(SVG::PreserveAspectRatio {});
} else if (is<SVG::SVGMaskElement>(*dom_node) || is<SVG::SVGClipPathElement>(*dom_node)) {
// This allows mask and clipPath elements to be scaled in the x and y directions independently to match the target size.
preserve_aspect_ratio = SVG::PreserveAspectRatio { SVG::PreserveAspectRatio::Align::None, {} };
}
auto viewbox_offset_and_scale = scale_and_align_viewbox_content(preserve_aspect_ratio, *active_view_box, { scale_width, scale_height }, svg_box_state);
CSSPixelPoint offset = viewbox_offset_and_scale.offset;
m_current_viewbox_transform = Gfx::AffineTransform { m_current_viewbox_transform }.multiply(Gfx::AffineTransform {}
.translate(offset.to_type<float>())
.scale(viewbox_offset_and_scale.scale_factor_x, viewbox_offset_and_scale.scale_factor_y)
.translate({ -viewbox->min_x, -viewbox->min_y }));
.translate({ -active_view_box->min_x, -active_view_box->min_y }));
}
if (svg_box_state.has_definite_width() && svg_box_state.has_definite_height()) {
@ -246,8 +255,8 @@ void SVGFormattingContext::run(AvailableSpace const& available_space)
}
auto viewport_width = [&] {
if (viewbox.has_value())
return CSSPixels::nearest_value_for(viewbox->width);
if (active_view_box.has_value())
return CSSPixels::nearest_value_for(active_view_box->width);
if (svg_box_state.has_definite_width())
return svg_box_state.content_width();
dbgln_if(LIBWEB_CSS_DEBUG, "FIXME: Failed to resolve width of SVG viewport!");
@ -255,8 +264,8 @@ void SVGFormattingContext::run(AvailableSpace const& available_space)
}();
auto viewport_height = [&] {
if (viewbox.has_value())
return CSSPixels::nearest_value_for(viewbox->height);
if (active_view_box.has_value())
return CSSPixels::nearest_value_for(active_view_box->height);
if (svg_box_state.has_definite_height())
return svg_box_state.content_height();
dbgln_if(LIBWEB_CSS_DEBUG, "FIXME: Failed to resolve height of SVG viewport!");
@ -275,7 +284,7 @@ void SVGFormattingContext::run(AvailableSpace const& available_space)
void SVGFormattingContext::layout_svg_element(Box const& child)
{
if (is<SVG::SVGViewport>(child.dom_node())) {
if (is<SVG::SVGFitToViewBox>(child.dom_node())) {
layout_nested_viewport(child);
} else if (is<SVG::SVGForeignObjectElement>(child.dom_node()) && is<BlockContainer>(child)) {
Layout::BlockFormattingContext bfc(m_state, m_layout_mode, static_cast<BlockContainer const&>(child), this);

View file

@ -12,7 +12,6 @@
#include <LibWeb/SVG/AttributeNames.h>
#include <LibWeb/SVG/AttributeParser.h>
#include <LibWeb/SVG/SVGCircleElement.h>
#include <LibWeb/SVG/SVGViewport.h>
namespace Web::SVG {
@ -58,6 +57,13 @@ void SVGCircleElement::apply_presentational_hints(GC::Ref<CSS::CascadedPropertie
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::R, r_value.release_nonnull());
}
static 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<float>);
}
Gfx::Path SVGCircleElement::get_path(CSSPixelSize viewport_size)
{
auto node = layout_node();

View file

@ -9,19 +9,17 @@
#include <LibWeb/SVG/AttributeParser.h>
#include <LibWeb/SVG/SVGElement.h>
#include <LibWeb/SVG/SVGGraphicsElement.h>
#include <LibWeb/SVG/SVGViewport.h>
namespace Web::SVG {
class SVGClipPathElement final : public SVGGraphicsElement
, public SVGViewport {
class SVGClipPathElement final : public SVGGraphicsElement {
WEB_PLATFORM_OBJECT(SVGClipPathElement, SVGGraphicsElement);
GC_DECLARE_ALLOCATOR(SVGClipPathElement);
public:
virtual ~SVGClipPathElement();
virtual Optional<ViewBox> view_box() const override
virtual Optional<ViewBox> active_view_box() const override
{
// Same trick as SVGMaskElement.
if (clip_path_units() == MaskContentUnits::ObjectBoundingBox)
@ -29,11 +27,6 @@ public:
return {};
}
virtual Optional<PreserveAspectRatio> preserve_aspect_ratio() const override
{
return PreserveAspectRatio { PreserveAspectRatio::Align::None, {} };
}
virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_) override;
ClipPathUnits clip_path_units() const

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2025, Tim Ledbetter <tim.ledbetter@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/Realm.h>
#include <LibWeb/DOM/Element.h>
#include <LibWeb/SVG/AttributeNames.h>
#include <LibWeb/SVG/SVGAnimatedRect.h>
#include <LibWeb/SVG/SVGFitToViewBox.h>
namespace Web::SVG {
void SVGFitToViewBox::initialize(JS::Realm& realm)
{
m_view_box_for_bindings = realm.create<SVGAnimatedRect>(realm);
}
void SVGFitToViewBox::visit_edges(JS::Cell::Visitor& visitor)
{
visitor.visit(m_view_box_for_bindings);
}
void SVGFitToViewBox::attribute_changed(DOM::Element&, FlyString const& name, Optional<String> const& value)
{
if (name.equals_ignoring_ascii_case(SVG::AttributeNames::viewBox)) {
if (!value.has_value()) {
m_view_box_for_bindings->set_nulled(true);
} else {
m_view_box = try_parse_view_box(value.value_or(String {}));
m_view_box_for_bindings->set_nulled(!m_view_box.has_value());
if (m_view_box.has_value()) {
m_view_box_for_bindings->set_base_val(Gfx::DoubleRect { m_view_box->min_x, m_view_box->min_y, m_view_box->width, m_view_box->height });
m_view_box_for_bindings->set_anim_val(Gfx::DoubleRect { m_view_box->min_x, m_view_box->min_y, m_view_box->width, m_view_box->height });
}
}
} else if (name.equals_ignoring_ascii_case(SVG::AttributeNames::preserveAspectRatio)) {
m_preserve_aspect_ratio = AttributeParser::parse_preserve_aspect_ratio(value.value_or(String {}));
}
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2025, Tim Ledbetter <tim.ledbetter@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Heap/Cell.h>
#include <LibWeb/SVG/AttributeParser.h>
#include <LibWeb/SVG/SVGAnimatedString.h>
#include <LibWeb/SVG/ViewBox.h>
namespace Web::SVG {
class SVGFitToViewBox {
public:
virtual ~SVGFitToViewBox() = default;
GC::Ref<SVGAnimatedRect> view_box_for_bindings() const { return *m_view_box_for_bindings; }
Optional<ViewBox> view_box() const { return m_view_box; }
Optional<PreserveAspectRatio> preserve_aspect_ratio() const { return m_preserve_aspect_ratio; }
protected:
void initialize(JS::Realm&);
void visit_edges(JS::Cell::Visitor&);
void attribute_changed(DOM::Element& element, FlyString const& name, Optional<String> const& value);
private:
Optional<ViewBox> m_view_box;
GC::Ptr<SVGAnimatedRect> m_view_box_for_bindings;
Optional<PreserveAspectRatio> m_preserve_aspect_ratio;
};
}

View file

@ -14,6 +14,7 @@
#include <LibWeb/SVG/AttributeParser.h>
#include <LibWeb/SVG/SVGAnimatedTransformList.h>
#include <LibWeb/SVG/SVGElement.h>
#include <LibWeb/SVG/SVGFitToViewBox.h>
#include <LibWeb/SVG/SVGGradientElement.h>
#include <LibWeb/SVG/TagNames.h>
#include <LibWeb/SVG/ViewBox.h>
@ -47,6 +48,13 @@ public:
Optional<FillRule> fill_rule() const;
Optional<ClipRule> clip_rule() const;
virtual Optional<ViewBox> active_view_box() const
{
if (auto* svg_fit_to_view_box = as_if<SVGFitToViewBox>(*this))
return svg_fit_to_view_box->view_box();
return {};
}
float visible_stroke_width() const
{
if (auto color = stroke_color(); color.has_value() && color->alpha() > 0)

View file

@ -8,12 +8,10 @@
#include <LibWeb/SVG/AttributeParser.h>
#include <LibWeb/SVG/SVGGraphicsElement.h>
#include <LibWeb/SVG/SVGViewport.h>
namespace Web::SVG {
class SVGMaskElement final : public SVGGraphicsElement
, public SVGViewport {
class SVGMaskElement final : public SVGGraphicsElement {
WEB_PLATFORM_OBJECT(SVGMaskElement, SVGGraphicsElement);
GC_DECLARE_ALLOCATOR(SVGMaskElement);
@ -21,7 +19,7 @@ class SVGMaskElement final : public SVGGraphicsElement
public:
virtual ~SVGMaskElement() override;
virtual Optional<ViewBox> view_box() const override
virtual Optional<ViewBox> active_view_box() const override
{
// maskContentUnits = objectBoundingBox acts like the mask is sized to the bounding box
// of the target element, with a viewBox of "0 0 1 1".
@ -30,12 +28,6 @@ public:
return {};
}
virtual Optional<PreserveAspectRatio> preserve_aspect_ratio() const override
{
// preserveAspectRatio = none (allow mask to be scaled in both x and y to match target size)
return PreserveAspectRatio { PreserveAspectRatio::Align::None, {} };
}
virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_) override;
virtual GC::Ptr<Layout::Node> create_layout_node(GC::Ref<CSS::ComputedProperties>) override;

View file

@ -34,14 +34,14 @@ void SVGSVGElement::initialize(JS::Realm& realm)
{
WEB_SET_PROTOTYPE_FOR_INTERFACE(SVGSVGElement);
Base::initialize(realm);
m_view_box_for_bindings = realm.create<SVGAnimatedRect>(realm);
SVGFitToViewBox::initialize(realm);
}
void SVGSVGElement::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
SVGFitToViewBox::visit_edges(visitor);
visitor.visit(m_active_view_element);
visitor.visit(m_view_box_for_bindings);
}
GC::Ptr<Layout::Node> SVGSVGElement::create_layout_node(GC::Ref<CSS::ComputedProperties> style)
@ -120,21 +120,8 @@ void SVGSVGElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties>
void SVGSVGElement::attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_)
{
Base::attribute_changed(name, old_value, value, namespace_);
SVGFitToViewBox::attribute_changed(*this, name, value);
if (name.equals_ignoring_ascii_case(SVG::AttributeNames::viewBox)) {
if (!value.has_value()) {
m_view_box_for_bindings->set_nulled(true);
} else {
m_view_box = try_parse_view_box(value.value_or(String {}));
m_view_box_for_bindings->set_nulled(!m_view_box.has_value());
if (m_view_box.has_value()) {
m_view_box_for_bindings->set_base_val(Gfx::DoubleRect { m_view_box->min_x, m_view_box->min_y, m_view_box->width, m_view_box->height });
m_view_box_for_bindings->set_anim_val(Gfx::DoubleRect { m_view_box->min_x, m_view_box->min_y, m_view_box->width, m_view_box->height });
}
}
}
if (name.equals_ignoring_ascii_case(SVG::AttributeNames::preserveAspectRatio))
m_preserve_aspect_ratio = AttributeParser::parse_preserve_aspect_ratio(value.value_or(String {}));
if (name.equals_ignoring_ascii_case(SVG::AttributeNames::width) || name.equals_ignoring_ascii_case(SVG::AttributeNames::height))
update_fallback_view_box_for_svg_as_image();
}
@ -188,13 +175,13 @@ void SVGSVGElement::set_fallback_view_box_for_svg_as_image(Optional<ViewBox> vie
m_fallback_view_box_for_svg_as_image = view_box;
}
Optional<ViewBox> SVGSVGElement::view_box() const
Optional<ViewBox> SVGSVGElement::active_view_box() const
{
if (m_active_view_element && m_active_view_element->view_box().has_value())
return m_active_view_element->view_box().value();
if (m_view_box.has_value())
return m_view_box;
if (auto view_box = SVGFitToViewBox::view_box(); view_box.has_value())
return view_box;
// NOTE: If the parent is a document, we're an <svg> element used as an image.
if (parent() && parent()->is_document() && m_fallback_view_box_for_svg_as_image.has_value())

View file

@ -11,17 +11,17 @@
#include <LibWeb/Geometry/DOMPoint.h>
#include <LibWeb/SVG/AttributeParser.h>
#include <LibWeb/SVG/SVGAnimatedLength.h>
#include <LibWeb/SVG/SVGFitToViewBox.h>
#include <LibWeb/SVG/SVGGraphicsElement.h>
#include <LibWeb/SVG/SVGLength.h>
#include <LibWeb/SVG/SVGTransform.h>
#include <LibWeb/SVG/SVGViewport.h>
#include <LibWeb/SVG/ViewBox.h>
#include <LibWeb/WebIDL/Types.h>
namespace Web::SVG {
class SVGSVGElement final : public SVGGraphicsElement
, public SVGViewport {
, public SVGFitToViewBox {
WEB_PLATFORM_OBJECT(SVGSVGElement, SVGGraphicsElement);
GC_DECLARE_ALLOCATOR(SVGSVGElement);
@ -34,16 +34,12 @@ public:
virtual bool requires_svg_container() const override { return false; }
virtual bool is_svg_container() const override { return true; }
virtual Optional<ViewBox> view_box() const override;
virtual Optional<ViewBox> active_view_box() const override;
void set_active_view_element(GC::Ptr<SVGViewElement> view_element) { m_active_view_element = view_element; }
virtual Optional<PreserveAspectRatio> preserve_aspect_ratio() const override { return m_preserve_aspect_ratio; }
void set_fallback_view_box_for_svg_as_image(Optional<ViewBox>);
GC::Ref<SVGAnimatedRect> view_box_for_bindings() { return *m_view_box_for_bindings; }
GC::Ref<SVGAnimatedLength> x() const;
GC::Ref<SVGAnimatedLength> y() const;
GC::Ref<SVGAnimatedLength> width() const;
@ -107,13 +103,8 @@ private:
void update_fallback_view_box_for_svg_as_image();
Optional<ViewBox> m_view_box;
Optional<PreserveAspectRatio> m_preserve_aspect_ratio;
Optional<ViewBox> m_fallback_view_box_for_svg_as_image;
GC::Ptr<SVGAnimatedRect> m_view_box_for_bindings;
GC::Ptr<SVGViewElement> m_active_view_element;
};

View file

@ -30,13 +30,13 @@ void SVGSymbolElement::initialize(JS::Realm& realm)
{
WEB_SET_PROTOTYPE_FOR_INTERFACE(SVGSymbolElement);
Base::initialize(realm);
m_view_box_for_bindings = realm.create<SVGAnimatedRect>(realm);
SVGFitToViewBox::initialize(realm);
}
void SVGSymbolElement::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_view_box_for_bindings);
SVGFitToViewBox::visit_edges(visitor);
}
bool SVGSymbolElement::is_presentational_hint(FlyString const& name) const
@ -66,15 +66,7 @@ void SVGSymbolElement::apply_presentational_hints(GC::Ref<CSS::CascadedPropertie
void SVGSymbolElement::attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_)
{
Base::attribute_changed(name, old_value, value, namespace_);
if (name.equals_ignoring_ascii_case(SVG::AttributeNames::viewBox)) {
m_view_box = try_parse_view_box(value.value_or(String {}));
m_view_box_for_bindings->set_nulled(!m_view_box.has_value());
if (m_view_box.has_value()) {
m_view_box_for_bindings->set_base_val(Gfx::DoubleRect { m_view_box->min_x, m_view_box->min_y, m_view_box->width, m_view_box->height });
m_view_box_for_bindings->set_anim_val(Gfx::DoubleRect { m_view_box->min_x, m_view_box->min_y, m_view_box->width, m_view_box->height });
}
}
SVGFitToViewBox::attribute_changed(*this, name, value);
}
bool SVGSymbolElement::is_direct_child_of_use_shadow_tree() const

View file

@ -6,13 +6,13 @@
#pragma once
#include <LibWeb/SVG/SVGFitToViewBox.h>
#include <LibWeb/SVG/SVGGraphicsElement.h>
#include <LibWeb/SVG/SVGViewport.h>
namespace Web::SVG {
class SVGSymbolElement final : public SVGGraphicsElement
, public SVGViewport {
, public SVGFitToViewBox {
WEB_PLATFORM_OBJECT(SVGSymbolElement, SVGGraphicsElement);
GC_DECLARE_ALLOCATOR(SVGSymbolElement);
@ -22,15 +22,6 @@ public:
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
virtual Optional<ViewBox> view_box() const override { return m_view_box; }
virtual Optional<PreserveAspectRatio> preserve_aspect_ratio() const override
{
// FIXME: Support the `preserveAspectRatio` attribute on <symbol>.
return {};
}
GC::Ref<SVGAnimatedRect> view_box_for_bindings() { return *m_view_box_for_bindings; }
private:
SVGSymbolElement(DOM::Document&, DOM::QualifiedName);
@ -42,10 +33,6 @@ private:
bool is_direct_child_of_use_shadow_tree() const;
virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_) override;
Optional<ViewBox> m_view_box;
GC::Ptr<SVGAnimatedRect> m_view_box_for_bindings;
};
}

View file

@ -24,13 +24,13 @@ void SVGViewElement::initialize(JS::Realm& realm)
{
WEB_SET_PROTOTYPE_FOR_INTERFACE(SVGViewElement);
Base::initialize(realm);
m_view_box_for_bindings = realm.create<SVGAnimatedRect>(realm);
SVGFitToViewBox::initialize(realm);
}
void SVGViewElement::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_view_box_for_bindings);
SVGFitToViewBox::visit_edges(visitor);
}
bool SVGViewElement::is_presentational_hint(FlyString const& name) const
@ -52,21 +52,7 @@ void SVGViewElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties>
void SVGViewElement::attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_)
{
Base::attribute_changed(name, old_value, value, namespace_);
if (name.equals_ignoring_ascii_case(SVG::AttributeNames::viewBox)) {
if (!value.has_value()) {
m_view_box_for_bindings->set_nulled(true);
} else {
m_view_box = try_parse_view_box(value.value_or(String {}));
m_view_box_for_bindings->set_nulled(!m_view_box.has_value());
if (m_view_box.has_value()) {
m_view_box_for_bindings->set_base_val(Gfx::DoubleRect { m_view_box->min_x, m_view_box->min_y, m_view_box->width, m_view_box->height });
m_view_box_for_bindings->set_anim_val(Gfx::DoubleRect { m_view_box->min_x, m_view_box->min_y, m_view_box->width, m_view_box->height });
}
}
}
if (name.equals_ignoring_ascii_case(SVG::AttributeNames::preserveAspectRatio))
m_preserve_aspect_ratio = AttributeParser::parse_preserve_aspect_ratio(value.value_or(String {}));
SVGFitToViewBox::attribute_changed(*this, name, value);
}
}

View file

@ -6,14 +6,13 @@
#pragma once
#include <LibWeb/SVG/SVGFitToViewBox.h>
#include <LibWeb/SVG/SVGGraphicsElement.h>
#include <LibWeb/SVG/SVGViewport.h>
#include <LibWeb/SVG/ViewBox.h>
namespace Web::SVG {
class SVGViewElement final : public SVGGraphicsElement
, public SVGViewport {
, public SVGFitToViewBox {
WEB_PLATFORM_OBJECT(SVGViewElement, SVGGraphicsElement);
GC_DECLARE_ALLOCATOR(SVGViewElement);
@ -21,11 +20,6 @@ public:
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
virtual Optional<ViewBox> view_box() const override { return m_view_box; }
virtual Optional<PreserveAspectRatio> preserve_aspect_ratio() const override { return m_preserve_aspect_ratio; }
GC::Ref<SVGAnimatedRect> view_box_for_bindings() { return *m_view_box_for_bindings; }
private:
SVGViewElement(DOM::Document&, DOM::QualifiedName);
@ -35,10 +29,6 @@ private:
virtual bool is_svg_view_element() const override { return true; }
virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_) override;
Optional<ViewBox> m_view_box;
Optional<PreserveAspectRatio> m_preserve_aspect_ratio;
GC::Ptr<SVGAnimatedRect> m_view_box_for_bindings;
};
}

View file

@ -1,29 +0,0 @@
/*
* Copyright (c) 2024, MacDue <macdue@dueutil.tech>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Math.h>
#include <LibWeb/SVG/AttributeParser.h>
#include <LibWeb/SVG/ViewBox.h>
namespace Web::SVG {
class SVGViewport {
public:
virtual Optional<ViewBox> view_box() const = 0;
virtual Optional<PreserveAspectRatio> preserve_aspect_ratio() const = 0;
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<float>);
}
}