diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index a17ee91fbe78..ef6912274e59 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -748,6 +748,7 @@ set(SOURCES SVG/SVGPathElement.cpp SVG/SVGCircleElement.cpp SVG/SVGEllipseElement.cpp + SVG/SVGFilterElement.cpp SVG/SVGForeignObjectElement.cpp SVG/SVGLength.cpp SVG/SVGLineElement.cpp diff --git a/Libraries/LibWeb/DOM/ElementFactory.cpp b/Libraries/LibWeb/DOM/ElementFactory.cpp index 457381fa1da1..e7ed43172f97 100644 --- a/Libraries/LibWeb/DOM/ElementFactory.cpp +++ b/Libraries/LibWeb/DOM/ElementFactory.cpp @@ -91,6 +91,7 @@ #include #include #include +#include #include #include #include @@ -460,6 +461,8 @@ static GC::Ref create_svg_element(JS::Realm& realm, Document& d return realm.create(document, move(qualified_name)); if (local_name == SVG::TagNames::ellipse) return realm.create(document, move(qualified_name)); + if (local_name == SVG::TagNames::filter) + return realm.create(document, move(qualified_name)); if (local_name.equals_ignoring_ascii_case(SVG::TagNames::foreignObject)) return realm.create(document, move(qualified_name)); if (local_name == SVG::TagNames::line) diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index f4a29992385c..e322d35d7001 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -804,6 +804,7 @@ class SVGDefsElement; class SVGDescElement; class SVGElement; class SVGEllipseElement; +class SVGFilterElement; class SVGForeignObjectElement; class SVGGeometryElement; class SVGGraphicsElement; diff --git a/Libraries/LibWeb/SVG/SVGFilterElement.cpp b/Libraries/LibWeb/SVG/SVGFilterElement.cpp new file mode 100644 index 000000000000..7ef6dbf2d563 --- /dev/null +++ b/Libraries/LibWeb/SVG/SVGFilterElement.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2025, Jelle Raaijmakers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace Web::SVG { + +GC_DEFINE_ALLOCATOR(SVGFilterElement); + +SVGFilterElement::SVGFilterElement(DOM::Document& document, DOM::QualifiedName qualified_name) + : SVGElement(document, qualified_name) +{ +} + +void SVGFilterElement::initialize(JS::Realm& realm) +{ + Base::initialize(realm); + WEB_SET_PROTOTYPE_FOR_INTERFACE(SVGFilterElement); +} + +void SVGFilterElement::apply_presentational_hints(GC::Ref cascaded_properties) const +{ + Base::apply_presentational_hints(cascaded_properties); + auto parsing_context = CSS::Parser::ParsingParams { document(), CSS::Parser::ParsingMode::SVGPresentationAttribute }; + + auto x_attribute = attribute(AttributeNames::x); + if (auto x_value = parse_css_value(parsing_context, x_attribute.value_or(String {}), CSS::PropertyID::X)) + cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::X, x_value.release_nonnull()); + + auto y_attribute = attribute(AttributeNames::y); + if (auto y_value = parse_css_value(parsing_context, y_attribute.value_or(String {}), CSS::PropertyID::Y)) + cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Y, y_value.release_nonnull()); + + auto width_attribute = attribute(AttributeNames::width); + if (auto width_value = parse_css_value(parsing_context, width_attribute.value_or(String {}), CSS::PropertyID::Width)) + cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Width, width_value.release_nonnull()); + + auto height_attribute = attribute(AttributeNames::height); + if (auto height_value = parse_css_value(parsing_context, height_attribute.value_or(String {}), CSS::PropertyID::Height)) + cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Height, height_value.release_nonnull()); +} + +bool SVGFilterElement::is_presentational_hint(FlyString const& name) const +{ + if (Base::is_presentational_hint(name)) + return true; + + return name.is_one_of(AttributeNames::x, AttributeNames::y, AttributeNames::width, AttributeNames::height); +} + +void SVGFilterElement::attribute_changed(FlyString const& name, Optional const& old_value, Optional const& value, Optional const& namespace_) +{ + Base::attribute_changed(name, old_value, value, namespace_); + + if (name == AttributeNames::filterUnits) + m_filter_units = AttributeParser::parse_units(value.value_or({})); + else if (name == AttributeNames::primitiveUnits) + m_primitive_units = AttributeParser::parse_units(value.value_or({})); +} + +// https://drafts.fxtf.org/filter-effects/#element-attrdef-filter-filterunits +GC::Ref SVGFilterElement::filter_units() const +{ + return SVGAnimatedEnumeration::create(realm(), to_underlying(m_filter_units.value_or(SVGUnits::ObjectBoundingBox))); +} + +// https://drafts.fxtf.org/filter-effects/#element-attrdef-filter-primitiveunits +GC::Ref SVGFilterElement::primitive_units() const +{ + return SVGAnimatedEnumeration::create(realm(), to_underlying(m_primitive_units.value_or(SVGUnits::UserSpaceOnUse))); +} + +// https://drafts.fxtf.org/filter-effects/#element-attrdef-filter-x +GC::Ref SVGFilterElement::x() const +{ + return svg_animated_length_for_property(CSS::PropertyID::X); +} + +// https://drafts.fxtf.org/filter-effects/#element-attrdef-filter-y +GC::Ref SVGFilterElement::y() const +{ + return svg_animated_length_for_property(CSS::PropertyID::Y); +} + +// https://drafts.fxtf.org/filter-effects/#element-attrdef-filter-width +GC::Ref SVGFilterElement::width() const +{ + return svg_animated_length_for_property(CSS::PropertyID::Width); +} + +// https://drafts.fxtf.org/filter-effects/#element-attrdef-filter-height +GC::Ref SVGFilterElement::height() const +{ + return svg_animated_length_for_property(CSS::PropertyID::Height); +} + +} diff --git a/Libraries/LibWeb/SVG/SVGFilterElement.h b/Libraries/LibWeb/SVG/SVGFilterElement.h new file mode 100644 index 000000000000..dd17c805daee --- /dev/null +++ b/Libraries/LibWeb/SVG/SVGFilterElement.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025, Jelle Raaijmakers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Web::SVG { + +// https://drafts.fxtf.org/filter-effects/#elementdef-filter +class SVGFilterElement final + : public SVGElement + , public SVGURIReferenceMixin { + WEB_PLATFORM_OBJECT(SVGFilterElement, SVGElement); + GC_DECLARE_ALLOCATOR(SVGFilterElement); + +public: + virtual ~SVGFilterElement() override = default; + + // ^DOM::Element + virtual void apply_presentational_hints(GC::Ref) const override; + virtual bool is_presentational_hint(AK::FlyString const&) const override; + + virtual void attribute_changed(FlyString const& name, Optional const& old_value, Optional const& value, Optional const& namespace_) override; + + GC::Ref filter_units() const; + GC::Ref primitive_units() const; + GC::Ref x() const; + GC::Ref y() const; + GC::Ref width() const; + GC::Ref height() const; + +private: + SVGFilterElement(DOM::Document&, DOM::QualifiedName); + + virtual void initialize(JS::Realm&) override; + + Optional m_filter_units {}; + Optional m_primitive_units {}; +}; + +} diff --git a/Libraries/LibWeb/SVG/SVGFilterElement.idl b/Libraries/LibWeb/SVG/SVGFilterElement.idl new file mode 100644 index 000000000000..9e82519664d3 --- /dev/null +++ b/Libraries/LibWeb/SVG/SVGFilterElement.idl @@ -0,0 +1,16 @@ +#import +#import +#import + +// https://drafts.fxtf.org/filter-effects/#InterfaceSVGFilterElement +[Exposed=Window] +interface SVGFilterElement : SVGElement { + readonly attribute SVGAnimatedEnumeration filterUnits; + readonly attribute SVGAnimatedEnumeration primitiveUnits; + readonly attribute SVGAnimatedLength x; + readonly attribute SVGAnimatedLength y; + readonly attribute SVGAnimatedLength width; + readonly attribute SVGAnimatedLength height; +}; + +SVGFilterElement includes SVGURIReference; diff --git a/Libraries/LibWeb/SVG/TagNames.h b/Libraries/LibWeb/SVG/TagNames.h index b14882e15bb9..6472daa80098 100644 --- a/Libraries/LibWeb/SVG/TagNames.h +++ b/Libraries/LibWeb/SVG/TagNames.h @@ -17,6 +17,7 @@ namespace Web::SVG::TagNames { __ENUMERATE_SVG_TAG(defs) \ __ENUMERATE_SVG_TAG(desc) \ __ENUMERATE_SVG_TAG(ellipse) \ + __ENUMERATE_SVG_TAG(filter) \ __ENUMERATE_SVG_TAG(foreignObject) \ __ENUMERATE_SVG_TAG(g) \ __ENUMERATE_SVG_TAG(image) \ diff --git a/Libraries/LibWeb/idl_files.cmake b/Libraries/LibWeb/idl_files.cmake index f6230c1610e9..6cc31f28aa79 100644 --- a/Libraries/LibWeb/idl_files.cmake +++ b/Libraries/LibWeb/idl_files.cmake @@ -319,6 +319,7 @@ libweb_js_bindings(SVG/SVGGraphicsElement) libweb_js_bindings(SVG/SVGImageElement) libweb_js_bindings(SVG/SVGCircleElement) libweb_js_bindings(SVG/SVGEllipseElement) +libweb_js_bindings(SVG/SVGFilterElement) libweb_js_bindings(SVG/SVGForeignObjectElement) libweb_js_bindings(SVG/SVGLength) libweb_js_bindings(SVG/SVGLineElement) diff --git a/Meta/gn/secondary/Userland/Libraries/LibWeb/idl_files.gni b/Meta/gn/secondary/Userland/Libraries/LibWeb/idl_files.gni index 02077dead105..8e98b735e3a0 100644 --- a/Meta/gn/secondary/Userland/Libraries/LibWeb/idl_files.gni +++ b/Meta/gn/secondary/Userland/Libraries/LibWeb/idl_files.gni @@ -312,6 +312,7 @@ standard_idl_files = [ "//Userland/Libraries/LibWeb/SVG/SVGDescElement.idl", "//Userland/Libraries/LibWeb/SVG/SVGElement.idl", "//Userland/Libraries/LibWeb/SVG/SVGEllipseElement.idl", + "//Userland/Libraries/LibWeb/SVG/SVGFilterElement.idl", "//Userland/Libraries/LibWeb/SVG/SVGForeignObjectElement.idl", "//Userland/Libraries/LibWeb/SVG/SVGGElement.idl", "//Userland/Libraries/LibWeb/SVG/SVGGeometryElement.idl", diff --git a/Tests/LibWeb/Text/expected/all-window-properties.txt b/Tests/LibWeb/Text/expected/all-window-properties.txt index 83cca30c30a3..6f9784f60dc8 100644 --- a/Tests/LibWeb/Text/expected/all-window-properties.txt +++ b/Tests/LibWeb/Text/expected/all-window-properties.txt @@ -321,6 +321,7 @@ SVGDefsElement SVGDescElement SVGElement SVGEllipseElement +SVGFilterElement SVGForeignObjectElement SVGGElement SVGGeometryElement