mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-28 11:49:44 +00:00
LibWeb/SVG: Implement resolution for a subset of SVG filters
This commit is contained in:
parent
5d85959f5f
commit
635adc8aa7
Notes:
github-actions[bot]
2025-07-09 17:08:27 +00:00
Author: https://github.com/ananas-dev
Commit: 635adc8aa7
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5028
Reviewed-by: https://github.com/gmta ✅
10 changed files with 161 additions and 17 deletions
|
@ -50,17 +50,20 @@ Filter Filter::compose(Filter const& outer, Filter const& inner)
|
||||||
return Filter(Impl::create(filter));
|
return Filter(Impl::create(filter));
|
||||||
}
|
}
|
||||||
|
|
||||||
Filter Filter::blend(Filter const& background, Filter const& foreground, Gfx::CompositingAndBlendingOperator mode)
|
Filter Filter::blend(Optional<Filter const&> background, Optional<Filter const&> foreground, Gfx::CompositingAndBlendingOperator mode)
|
||||||
{
|
{
|
||||||
auto filter = SkImageFilters::Blend(to_skia_blender(mode), background.m_impl->filter, foreground.m_impl->filter);
|
sk_sp<SkImageFilter> background_skia = background.has_value() ? background->m_impl->filter : nullptr;
|
||||||
|
sk_sp<SkImageFilter> foreground_skia = foreground.has_value() ? foreground->m_impl->filter : nullptr;
|
||||||
|
|
||||||
|
auto filter = SkImageFilters::Blend(to_skia_blender(mode), background_skia, foreground_skia);
|
||||||
return Filter(Impl::create(filter));
|
return Filter(Impl::create(filter));
|
||||||
}
|
}
|
||||||
|
|
||||||
Filter Filter::blur(float radius, Optional<Filter const&> input)
|
Filter Filter::blur(float radius_x, float radius_y, Optional<Filter const&> input)
|
||||||
{
|
{
|
||||||
sk_sp<SkImageFilter> input_skia = input.has_value() ? input->m_impl->filter : nullptr;
|
sk_sp<SkImageFilter> input_skia = input.has_value() ? input->m_impl->filter : nullptr;
|
||||||
|
|
||||||
auto filter = SkImageFilters::Blur(radius, radius, input_skia);
|
auto filter = SkImageFilters::Blur(radius_x, radius_y, input_skia);
|
||||||
return Filter(Impl::create(filter));
|
return Filter(Impl::create(filter));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,10 +32,10 @@ public:
|
||||||
~Filter();
|
~Filter();
|
||||||
|
|
||||||
static Filter compose(Filter const& outer, Filter const& inner);
|
static Filter compose(Filter const& outer, Filter const& inner);
|
||||||
static Filter blend(Filter const& background, Filter const& foreground, CompositingAndBlendingOperator mode);
|
static Filter blend(Optional<Filter const&> background, Optional<Filter const&> foreground, CompositingAndBlendingOperator mode);
|
||||||
static Filter flood(Gfx::Color color, float opacity);
|
static Filter flood(Gfx::Color color, float opacity);
|
||||||
static Filter drop_shadow(float offset_x, float offset_y, float radius, Gfx::Color color, Optional<Filter const&> input = {});
|
static Filter drop_shadow(float offset_x, float offset_y, float radius, Gfx::Color color, Optional<Filter const&> input = {});
|
||||||
static Filter blur(float radius, Optional<Filter const&> input = {});
|
static Filter blur(float radius_x, float radius_y, Optional<Filter const&> input = {});
|
||||||
static Filter color(ColorFilterType type, float amount, Optional<Filter const&> input = {});
|
static Filter color(ColorFilterType type, float amount, Optional<Filter const&> input = {});
|
||||||
static Filter color_matrix(float matrix[20], Optional<Filter const&> input = {});
|
static Filter color_matrix(float matrix[20], Optional<Filter const&> input = {});
|
||||||
static Filter saturate(float value, Optional<Filter const&> input = {});
|
static Filter saturate(float value, Optional<Filter const&> input = {});
|
||||||
|
|
|
@ -28,7 +28,7 @@ public:
|
||||||
bool has_filters() const { return m_filter_value_list; }
|
bool has_filters() const { return m_filter_value_list; }
|
||||||
bool is_none() const { return !has_filters(); }
|
bool is_none() const { return !has_filters(); }
|
||||||
|
|
||||||
ReadonlySpan<FilterFunction> filters() const
|
ReadonlySpan<FilterValue> filters() const
|
||||||
{
|
{
|
||||||
VERIFY(has_filters());
|
VERIFY(has_filters());
|
||||||
return m_filter_value_list->filter_value_list().span();
|
return m_filter_value_list->filter_value_list().span();
|
||||||
|
|
|
@ -5049,11 +5049,11 @@ RefPtr<CSSStyleValue const> Parser::parse_filter_value_list_value(TokenStream<Co
|
||||||
return {};
|
return {};
|
||||||
};
|
};
|
||||||
|
|
||||||
auto parse_filter_function = [&](auto filter_token, auto const& function_values) -> Optional<FilterFunction> {
|
auto parse_filter_function = [&](auto filter_token, auto const& function_values) -> Optional<FilterValue> {
|
||||||
TokenStream tokens { function_values };
|
TokenStream tokens { function_values };
|
||||||
tokens.discard_whitespace();
|
tokens.discard_whitespace();
|
||||||
|
|
||||||
auto if_no_more_tokens_return = [&](auto filter) -> Optional<FilterFunction> {
|
auto if_no_more_tokens_return = [&](auto filter) -> Optional<FilterValue> {
|
||||||
tokens.discard_whitespace();
|
tokens.discard_whitespace();
|
||||||
if (tokens.has_next_token())
|
if (tokens.has_next_token())
|
||||||
return {};
|
return {};
|
||||||
|
@ -5149,12 +5149,19 @@ RefPtr<CSSStyleValue const> Parser::parse_filter_value_list_value(TokenStream<Co
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Vector<FilterFunction> filter_value_list {};
|
Vector<FilterValue> filter_value_list {};
|
||||||
|
|
||||||
while (tokens.has_next_token()) {
|
while (tokens.has_next_token()) {
|
||||||
tokens.discard_whitespace();
|
tokens.discard_whitespace();
|
||||||
if (!tokens.has_next_token())
|
if (!tokens.has_next_token())
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
auto url_function = parse_url_function(tokens);
|
||||||
|
if (url_function.has_value()) {
|
||||||
|
filter_value_list.append(*url_function);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
auto& token = tokens.consume_a_token();
|
auto& token = tokens.consume_a_token();
|
||||||
if (!token.is_function())
|
if (!token.is_function())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
|
@ -98,6 +98,9 @@ String FilterValueListStyleValue::to_string(SerializationMode) const
|
||||||
}());
|
}());
|
||||||
|
|
||||||
builder.append(color.amount.to_string());
|
builder.append(color.amount.to_string());
|
||||||
|
},
|
||||||
|
[&](CSS::URL const& url) {
|
||||||
|
builder.append(url.to_string());
|
||||||
});
|
});
|
||||||
builder.append(')');
|
builder.append(')');
|
||||||
first = false;
|
first = false;
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include <LibWeb/CSS/Length.h>
|
#include <LibWeb/CSS/Length.h>
|
||||||
#include <LibWeb/CSS/Number.h>
|
#include <LibWeb/CSS/Number.h>
|
||||||
#include <LibWeb/CSS/PercentageOr.h>
|
#include <LibWeb/CSS/PercentageOr.h>
|
||||||
|
#include <LibWeb/CSS/URL.h>
|
||||||
|
|
||||||
namespace Web::CSS {
|
namespace Web::CSS {
|
||||||
|
|
||||||
|
@ -53,18 +54,18 @@ struct Color {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
using FilterFunction = Variant<FilterOperation::Blur, FilterOperation::DropShadow, FilterOperation::HueRotate, FilterOperation::Color>;
|
using FilterValue = Variant<FilterOperation::Blur, FilterOperation::DropShadow, FilterOperation::HueRotate, FilterOperation::Color, URL>;
|
||||||
|
|
||||||
class FilterValueListStyleValue final : public StyleValueWithDefaultOperators<FilterValueListStyleValue> {
|
class FilterValueListStyleValue final : public StyleValueWithDefaultOperators<FilterValueListStyleValue> {
|
||||||
public:
|
public:
|
||||||
static ValueComparingNonnullRefPtr<FilterValueListStyleValue const> create(
|
static ValueComparingNonnullRefPtr<FilterValueListStyleValue const> create(
|
||||||
Vector<FilterFunction> filter_value_list)
|
Vector<FilterValue> filter_value_list)
|
||||||
{
|
{
|
||||||
VERIFY(filter_value_list.size() >= 1);
|
VERIFY(filter_value_list.size() >= 1);
|
||||||
return adopt_ref(*new (nothrow) FilterValueListStyleValue(move(filter_value_list)));
|
return adopt_ref(*new (nothrow) FilterValueListStyleValue(move(filter_value_list)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector<FilterFunction> const& filter_value_list() const { return m_filter_value_list; }
|
Vector<FilterValue> const& filter_value_list() const { return m_filter_value_list; }
|
||||||
|
|
||||||
virtual String to_string(SerializationMode) const override;
|
virtual String to_string(SerializationMode) const override;
|
||||||
|
|
||||||
|
@ -73,14 +74,14 @@ public:
|
||||||
bool properties_equal(FilterValueListStyleValue const& other) const { return m_filter_value_list == other.m_filter_value_list; }
|
bool properties_equal(FilterValueListStyleValue const& other) const { return m_filter_value_list == other.m_filter_value_list; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FilterValueListStyleValue(Vector<FilterFunction> filter_value_list)
|
FilterValueListStyleValue(Vector<FilterValue> filter_value_list)
|
||||||
: StyleValueWithDefaultOperators(Type::FilterValueList)
|
: StyleValueWithDefaultOperators(Type::FilterValueList)
|
||||||
, m_filter_value_list(move(filter_value_list))
|
, m_filter_value_list(move(filter_value_list))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: No support for SVG filters yet
|
// FIXME: No support for SVG filters yet
|
||||||
Vector<FilterFunction> m_filter_value_list;
|
Vector<FilterValue> m_filter_value_list;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1049,7 +1049,7 @@ void CanvasRenderingContext2D::set_filter(String filter)
|
||||||
item.visit(
|
item.visit(
|
||||||
[&](CSS::FilterOperation::Blur const& blur_filter) {
|
[&](CSS::FilterOperation::Blur const& blur_filter) {
|
||||||
float radius = blur_filter.resolved_radius(*layout_node);
|
float radius = blur_filter.resolved_radius(*layout_node);
|
||||||
auto new_filter = Gfx::Filter::blur(radius);
|
auto new_filter = Gfx::Filter::blur(radius, radius);
|
||||||
|
|
||||||
drawing_state().filter = drawing_state().filter.has_value()
|
drawing_state().filter = drawing_state().filter.has_value()
|
||||||
? Gfx::Filter::compose(new_filter, *drawing_state().filter)
|
? Gfx::Filter::compose(new_filter, *drawing_state().filter)
|
||||||
|
@ -1093,6 +1093,11 @@ void CanvasRenderingContext2D::set_filter(String filter)
|
||||||
drawing_state().filter = drawing_state().filter.has_value()
|
drawing_state().filter = drawing_state().filter.has_value()
|
||||||
? Gfx::Filter::compose(new_filter, *drawing_state().filter)
|
? Gfx::Filter::compose(new_filter, *drawing_state().filter)
|
||||||
: new_filter;
|
: new_filter;
|
||||||
|
},
|
||||||
|
[&](CSS::URL const& url) {
|
||||||
|
(void)url;
|
||||||
|
// FIXME: Resolve the SVG filter
|
||||||
|
dbgln("FIXME: SVG filters are not implemented for Canvas2D");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
#include <LibWeb/Layout/TextNode.h>
|
#include <LibWeb/Layout/TextNode.h>
|
||||||
#include <LibWeb/Layout/Viewport.h>
|
#include <LibWeb/Layout/Viewport.h>
|
||||||
#include <LibWeb/Page/Page.h>
|
#include <LibWeb/Page/Page.h>
|
||||||
|
#include <LibWeb/SVG/SVGFilterElement.h>
|
||||||
#include <LibWeb/SVG/SVGForeignObjectElement.h>
|
#include <LibWeb/SVG/SVGForeignObjectElement.h>
|
||||||
|
|
||||||
namespace Web::Layout {
|
namespace Web::Layout {
|
||||||
|
@ -591,7 +592,8 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style)
|
||||||
for (auto const& filter : computed_filter.filters()) {
|
for (auto const& filter : computed_filter.filters()) {
|
||||||
filter.visit(
|
filter.visit(
|
||||||
[&](CSS::FilterOperation::Blur const& blur) {
|
[&](CSS::FilterOperation::Blur const& blur) {
|
||||||
auto new_filter = Gfx::Filter::blur(blur.resolved_radius(*this));
|
auto resolved_radius = blur.resolved_radius(*this);
|
||||||
|
auto new_filter = Gfx::Filter::blur(resolved_radius, resolved_radius);
|
||||||
|
|
||||||
resolved_filter = resolved_filter.has_value()
|
resolved_filter = resolved_filter.has_value()
|
||||||
? Gfx::Filter::compose(new_filter, *resolved_filter)
|
? Gfx::Filter::compose(new_filter, *resolved_filter)
|
||||||
|
@ -627,6 +629,39 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style)
|
||||||
resolved_filter = resolved_filter.has_value()
|
resolved_filter = resolved_filter.has_value()
|
||||||
? Gfx::Filter::compose(new_filter, *resolved_filter)
|
? Gfx::Filter::compose(new_filter, *resolved_filter)
|
||||||
: new_filter;
|
: new_filter;
|
||||||
|
},
|
||||||
|
[&](CSS::URL const& css_url) {
|
||||||
|
// FIXME: This is not the right place to resolve SVG filters. Some filter primitives
|
||||||
|
// wont work if the filter is referenced before its defined because some parameters
|
||||||
|
// are passed by CSS property. Ideally they should be resolved in another pass or
|
||||||
|
// lazily.
|
||||||
|
|
||||||
|
auto& url_string = css_url.url();
|
||||||
|
|
||||||
|
if (url_string.is_empty() || !url_string.starts_with('#'))
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto fragment_or_error = url_string.substring_from_byte_offset(1);
|
||||||
|
|
||||||
|
if (fragment_or_error.is_error())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// FIXME: Support urls that are not only composed of a fragment.
|
||||||
|
auto maybe_filter = document().get_element_by_id(fragment_or_error.value());
|
||||||
|
|
||||||
|
if (!maybe_filter)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (auto* filter_element = as_if<SVG::SVGFilterElement>(*maybe_filter)) {
|
||||||
|
Optional<Gfx::Filter> new_filter = filter_element->gfx_filter();
|
||||||
|
|
||||||
|
if (!new_filter.has_value())
|
||||||
|
return;
|
||||||
|
|
||||||
|
resolved_filter = resolved_filter.has_value()
|
||||||
|
? Gfx::Filter::compose(*new_filter, *resolved_filter)
|
||||||
|
: new_filter;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return resolved_filter;
|
return resolved_filter;
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
|
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
|
||||||
|
* Copyright (c) 2025, Lucien Fiorini <lucienfiorini@gmail.com>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <LibWeb/Bindings/SVGFilterElementPrototype.h>
|
#include <LibWeb/Bindings/SVGFilterElementPrototype.h>
|
||||||
#include <LibWeb/CSS/Parser/Parser.h>
|
#include <LibWeb/CSS/Parser/Parser.h>
|
||||||
|
#include <LibWeb/SVG/SVGFEBlendElement.h>
|
||||||
|
#include <LibWeb/SVG/SVGFEFloodElement.h>
|
||||||
|
#include <LibWeb/SVG/SVGFEGaussianBlurElement.h>
|
||||||
#include <LibWeb/SVG/SVGFilterElement.h>
|
#include <LibWeb/SVG/SVGFilterElement.h>
|
||||||
|
|
||||||
namespace Web::SVG {
|
namespace Web::SVG {
|
||||||
|
@ -23,6 +27,12 @@ void SVGFilterElement::initialize(JS::Realm& realm)
|
||||||
Base::initialize(realm);
|
Base::initialize(realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SVGFilterElement::visit_edges(Cell::Visitor& visitor)
|
||||||
|
{
|
||||||
|
Base::visit_edges(visitor);
|
||||||
|
SVGURIReferenceMixin::visit_edges(visitor);
|
||||||
|
}
|
||||||
|
|
||||||
void SVGFilterElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
|
void SVGFilterElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
|
||||||
{
|
{
|
||||||
Base::apply_presentational_hints(cascaded_properties);
|
Base::apply_presentational_hints(cascaded_properties);
|
||||||
|
@ -63,6 +73,82 @@ void SVGFilterElement::attribute_changed(FlyString const& name, Optional<String>
|
||||||
m_primitive_units = AttributeParser::parse_units(value.value_or({}));
|
m_primitive_units = AttributeParser::parse_units(value.value_or({}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Gfx::Filter SVGFilterElement::gfx_filter()
|
||||||
|
{
|
||||||
|
HashMap<String, Gfx::Filter> result_map;
|
||||||
|
Optional<Gfx::Filter> root_filter;
|
||||||
|
|
||||||
|
// https://www.w3.org/TR/filter-effects-1/#element-attrdef-filter-primitive-in
|
||||||
|
auto resolve_input_filter = [&](String const& name) -> Optional<Gfx::Filter> {
|
||||||
|
// TODO: Add missing ones.
|
||||||
|
if (name == "SourceGraphic"sv)
|
||||||
|
return {};
|
||||||
|
if (name == "SourceAlpha"sv) {
|
||||||
|
float matrix[20] = {
|
||||||
|
0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 1, 0
|
||||||
|
};
|
||||||
|
return Gfx::Filter::color_matrix(matrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto filter_from_map = result_map.get(name).copy();
|
||||||
|
|
||||||
|
if (filter_from_map.has_value())
|
||||||
|
return filter_from_map;
|
||||||
|
|
||||||
|
return root_filter;
|
||||||
|
};
|
||||||
|
|
||||||
|
for_each_child([&](auto& node) {
|
||||||
|
if (is<SVGFEFloodElement>(node)) {
|
||||||
|
auto& flood_primitive = static_cast<SVGFEFloodElement&>(node);
|
||||||
|
|
||||||
|
root_filter = Gfx::Filter::flood(flood_primitive.flood_color(), flood_primitive.flood_opacity());
|
||||||
|
|
||||||
|
auto result = flood_primitive.result()->base_val();
|
||||||
|
if (!result.is_empty()) {
|
||||||
|
result_map.set(result, *root_filter);
|
||||||
|
}
|
||||||
|
} else if (is<SVGFEBlendElement>(node)) {
|
||||||
|
auto& blend_primitive = static_cast<SVGFEBlendElement&>(node);
|
||||||
|
|
||||||
|
auto foreground = resolve_input_filter(blend_primitive.in1()->base_val());
|
||||||
|
|
||||||
|
auto background = resolve_input_filter(blend_primitive.in2()->base_val());
|
||||||
|
|
||||||
|
// FIXME: Actually resolve the blend mode
|
||||||
|
auto blend_mode = Gfx::CompositingAndBlendingOperator::Normal;
|
||||||
|
|
||||||
|
root_filter = Gfx::Filter::blend(background, foreground, blend_mode);
|
||||||
|
|
||||||
|
auto result = blend_primitive.result()->base_val();
|
||||||
|
if (!result.is_empty()) {
|
||||||
|
result_map.set(result, *root_filter);
|
||||||
|
}
|
||||||
|
} else if (is<SVGFEGaussianBlurElement>(node)) {
|
||||||
|
auto& blur_primitive = static_cast<SVGFEGaussianBlurElement&>(node);
|
||||||
|
|
||||||
|
auto input = resolve_input_filter(blur_primitive.in1()->base_val());
|
||||||
|
|
||||||
|
auto radius_x = blur_primitive.std_deviation_x()->base_val();
|
||||||
|
auto radius_y = blur_primitive.std_deviation_y()->base_val();
|
||||||
|
|
||||||
|
root_filter = Gfx::Filter::blur(radius_x, radius_y, input);
|
||||||
|
|
||||||
|
auto result = blur_primitive.result()->base_val();
|
||||||
|
if (!result.is_empty()) {
|
||||||
|
result_map.set(result, *root_filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return IterationDecision::Continue;
|
||||||
|
});
|
||||||
|
|
||||||
|
return *root_filter;
|
||||||
|
}
|
||||||
|
|
||||||
// https://drafts.fxtf.org/filter-effects/#element-attrdef-filter-filterunits
|
// https://drafts.fxtf.org/filter-effects/#element-attrdef-filter-filterunits
|
||||||
GC::Ref<SVGAnimatedEnumeration> SVGFilterElement::filter_units() const
|
GC::Ref<SVGAnimatedEnumeration> SVGFilterElement::filter_units() const
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <LibGfx/Filter.h>
|
||||||
#include <LibWeb/SVG/AttributeParser.h>
|
#include <LibWeb/SVG/AttributeParser.h>
|
||||||
#include <LibWeb/SVG/SVGAnimatedEnumeration.h>
|
#include <LibWeb/SVG/SVGAnimatedEnumeration.h>
|
||||||
#include <LibWeb/SVG/SVGAnimatedLength.h>
|
#include <LibWeb/SVG/SVGAnimatedLength.h>
|
||||||
|
@ -30,6 +31,8 @@ public:
|
||||||
|
|
||||||
virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_) override;
|
virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_) override;
|
||||||
|
|
||||||
|
Gfx::Filter gfx_filter();
|
||||||
|
|
||||||
GC::Ref<SVGAnimatedEnumeration> filter_units() const;
|
GC::Ref<SVGAnimatedEnumeration> filter_units() const;
|
||||||
GC::Ref<SVGAnimatedEnumeration> primitive_units() const;
|
GC::Ref<SVGAnimatedEnumeration> primitive_units() const;
|
||||||
GC::Ref<SVGAnimatedLength> x() const;
|
GC::Ref<SVGAnimatedLength> x() const;
|
||||||
|
@ -41,6 +44,7 @@ private:
|
||||||
SVGFilterElement(DOM::Document&, DOM::QualifiedName);
|
SVGFilterElement(DOM::Document&, DOM::QualifiedName);
|
||||||
|
|
||||||
virtual void initialize(JS::Realm&) override;
|
virtual void initialize(JS::Realm&) override;
|
||||||
|
virtual void visit_edges(Cell::Visitor&) override;
|
||||||
|
|
||||||
Optional<SVGUnits> m_filter_units {};
|
Optional<SVGUnits> m_filter_units {};
|
||||||
Optional<SVGUnits> m_primitive_units {};
|
Optional<SVGUnits> m_primitive_units {};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue