diff --git a/Libraries/LibGfx/Filter.cpp b/Libraries/LibGfx/Filter.cpp index d01c320efad..a3c6ba9fe1f 100644 --- a/Libraries/LibGfx/Filter.cpp +++ b/Libraries/LibGfx/Filter.cpp @@ -224,4 +224,10 @@ Filter Filter::hue_rotate(float angle_degrees, Optional input) return Filter(Impl::create(SkImageFilters::ColorFilter(color_filter, input_skia))); } +Filter Filter::offset(float dx, float dy, Optional input) +{ + sk_sp input_skia = input.has_value() ? input->m_impl->filter : nullptr; + return Filter(Impl::create(SkImageFilters::Offset(dx, dy, input_skia))); +} + } diff --git a/Libraries/LibGfx/Filter.h b/Libraries/LibGfx/Filter.h index 8c2be5ce07d..c072f4a5570 100644 --- a/Libraries/LibGfx/Filter.h +++ b/Libraries/LibGfx/Filter.h @@ -40,6 +40,7 @@ public: static Filter color_matrix(float matrix[20], Optional input = {}); static Filter saturate(float value, Optional input = {}); static Filter hue_rotate(float angle_degrees, Optional input = {}); + static Filter offset(float dx, float dy, Optional input = {}); FilterImpl const& impl() const; diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 12ada8f0f8e..d2f51fbc1f9 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -850,6 +850,7 @@ set(SOURCES SVG/SVGFEBlendElement.cpp SVG/SVGFEFloodElement.cpp SVG/SVGFEGaussianBlurElement.cpp + SVG/SVGFEOffsetElement.cpp SVG/SVGFilterElement.cpp SVG/SVGForeignObjectElement.cpp SVG/SVGGElement.cpp diff --git a/Libraries/LibWeb/DOM/ElementFactory.cpp b/Libraries/LibWeb/DOM/ElementFactory.cpp index 54c2b79c924..9203f7f1051 100644 --- a/Libraries/LibWeb/DOM/ElementFactory.cpp +++ b/Libraries/LibWeb/DOM/ElementFactory.cpp @@ -94,6 +94,7 @@ #include #include #include +#include #include #include #include @@ -471,6 +472,8 @@ static GC::Ref create_svg_element(JS::Realm& realm, Document& d return realm.create(document, move(qualified_name)); if (local_name == SVG::TagNames::feGaussianBlur) return realm.create(document, move(qualified_name)); + if (local_name == SVG::TagNames::feOffset) + 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)) diff --git a/Libraries/LibWeb/SVG/SVGFEOffsetElement.cpp b/Libraries/LibWeb/SVG/SVGFEOffsetElement.cpp new file mode 100644 index 00000000000..528eaf0745b --- /dev/null +++ b/Libraries/LibWeb/SVG/SVGFEOffsetElement.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025, Jelle Raaijmakers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Web::SVG { + +GC_DEFINE_ALLOCATOR(SVGFEOffsetElement); + +SVGFEOffsetElement::SVGFEOffsetElement(DOM::Document& document, DOM::QualifiedName qualified_name) + : SVGElement(document, qualified_name) +{ +} + +void SVGFEOffsetElement::initialize(JS::Realm& realm) +{ + WEB_SET_PROTOTYPE_FOR_INTERFACE(SVGFEOffsetElement); + Base::initialize(realm); +} + +void SVGFEOffsetElement::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + SVGFilterPrimitiveStandardAttributes::visit_edges(visitor); + visitor.visit(m_in1); + visitor.visit(m_dx); + visitor.visit(m_dy); +} + +// https://www.w3.org/TR/filter-effects-1/#dom-svgfeoffsetelement-in1 +GC::Ref SVGFEOffsetElement::in1() +{ + if (!m_in1) + m_in1 = SVGAnimatedString::create(realm(), *this, AttributeNames::in); + + return *m_in1; +} + +// https://www.w3.org/TR/filter-effects-1/#dom-svgfeoffsetelement-dx +GC::Ref SVGFEOffsetElement::dx() +{ + if (!m_dx) { + m_dx = SVGAnimatedNumber::create(realm(), *this, AttributeNames::dx, 0.f, + SVGAnimatedNumber::SupportsSecondValue::Yes, SVGAnimatedNumber::ValueRepresented::First); + } + return *m_dx; +} + +// https://www.w3.org/TR/filter-effects-1/#dom-svgfeoffsetelement-dy +GC::Ref SVGFEOffsetElement::dy() +{ + if (!m_dy) { + m_dy = SVGAnimatedNumber::create(realm(), *this, AttributeNames::dy, 0.f, + SVGAnimatedNumber::SupportsSecondValue::Yes, SVGAnimatedNumber::ValueRepresented::Second); + } + return *m_dy; +} + +} diff --git a/Libraries/LibWeb/SVG/SVGFEOffsetElement.h b/Libraries/LibWeb/SVG/SVGFEOffsetElement.h new file mode 100644 index 00000000000..1cbe3f257cc --- /dev/null +++ b/Libraries/LibWeb/SVG/SVGFEOffsetElement.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025, Jelle Raaijmakers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace Web::SVG { + +// https://www.w3.org/TR/filter-effects-1/#svgfeoffsetelement +class SVGFEOffsetElement final + : public SVGElement + , public SVGFilterPrimitiveStandardAttributes { + WEB_PLATFORM_OBJECT(SVGFEOffsetElement, SVGElement); + GC_DECLARE_ALLOCATOR(SVGFEOffsetElement); + +public: + virtual ~SVGFEOffsetElement() override = default; + + GC::Ref in1(); + GC::Ref dx(); + GC::Ref dy(); + +private: + SVGFEOffsetElement(DOM::Document&, DOM::QualifiedName); + + virtual void initialize(JS::Realm&) override; + virtual void visit_edges(Cell::Visitor&) override; + + GC::Ptr m_in1; + GC::Ptr m_dx; + GC::Ptr m_dy; +}; + +} diff --git a/Libraries/LibWeb/SVG/SVGFEOffsetElement.idl b/Libraries/LibWeb/SVG/SVGFEOffsetElement.idl new file mode 100644 index 00000000000..364a651a9fc --- /dev/null +++ b/Libraries/LibWeb/SVG/SVGFEOffsetElement.idl @@ -0,0 +1,14 @@ +#import +#import +#import +#import + +// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEOffsetElement +[Exposed=Window] +interface SVGFEOffsetElement : SVGElement { + readonly attribute SVGAnimatedString in1; + readonly attribute SVGAnimatedNumber dx; + readonly attribute SVGAnimatedNumber dy; +}; + +SVGFEOffsetElement includes SVGFilterPrimitiveStandardAttributes; diff --git a/Libraries/LibWeb/SVG/SVGFilterElement.cpp b/Libraries/LibWeb/SVG/SVGFilterElement.cpp index d4b14a60022..6974c4ca86b 100644 --- a/Libraries/LibWeb/SVG/SVGFilterElement.cpp +++ b/Libraries/LibWeb/SVG/SVGFilterElement.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include namespace Web::SVG { @@ -129,6 +130,14 @@ Optional SVGFilterElement::gfx_filter() root_filter = Gfx::Filter::blur(radius_x, radius_y, input); update_result_map(*blur_primitive); + } else if (auto* offset_primitive = as_if(node)) { + auto input = resolve_input_filter(offset_primitive->in1()->base_val()); + + auto dx = offset_primitive->dx()->base_val(); + auto dy = offset_primitive->dy()->base_val(); + + root_filter = Gfx::Filter::offset(dx, dy, input); + update_result_map(*offset_primitive); } else { dbgln("SVGFilterElement::gfx_filter(): Unknown or unsupported filter element '{}'", node.debug_description()); } diff --git a/Libraries/LibWeb/SVG/TagNames.h b/Libraries/LibWeb/SVG/TagNames.h index e75415dddbc..55577869f38 100644 --- a/Libraries/LibWeb/SVG/TagNames.h +++ b/Libraries/LibWeb/SVG/TagNames.h @@ -20,6 +20,7 @@ namespace Web::SVG::TagNames { __ENUMERATE_SVG_TAG(feBlend) \ __ENUMERATE_SVG_TAG(feFlood) \ __ENUMERATE_SVG_TAG(feGaussianBlur) \ + __ENUMERATE_SVG_TAG(feOffset) \ __ENUMERATE_SVG_TAG(filter) \ __ENUMERATE_SVG_TAG(foreignObject) \ __ENUMERATE_SVG_TAG(g) \ diff --git a/Libraries/LibWeb/idl_files.cmake b/Libraries/LibWeb/idl_files.cmake index 8ed64d053b1..1e6ee32ed49 100644 --- a/Libraries/LibWeb/idl_files.cmake +++ b/Libraries/LibWeb/idl_files.cmake @@ -347,6 +347,7 @@ libweb_js_bindings(SVG/SVGEllipseElement) libweb_js_bindings(SVG/SVGFEBlendElement) libweb_js_bindings(SVG/SVGFEFloodElement) libweb_js_bindings(SVG/SVGFEGaussianBlurElement) +libweb_js_bindings(SVG/SVGFEOffsetElement) libweb_js_bindings(SVG/SVGFilterElement) libweb_js_bindings(SVG/SVGForeignObjectElement) libweb_js_bindings(SVG/SVGLength) diff --git a/Tests/LibWeb/Text/expected/all-window-properties.txt b/Tests/LibWeb/Text/expected/all-window-properties.txt index 80a03263a4b..5dd2db43d90 100644 --- a/Tests/LibWeb/Text/expected/all-window-properties.txt +++ b/Tests/LibWeb/Text/expected/all-window-properties.txt @@ -344,6 +344,7 @@ SVGEllipseElement SVGFEBlendElement SVGFEFloodElement SVGFEGaussianBlurElement +SVGFEOffsetElement SVGFilterElement SVGForeignObjectElement SVGGElement