diff --git a/Tests/LibWeb/Screenshot/css-filter-drop-shadow.html b/Tests/LibWeb/Screenshot/css-filter-drop-shadow.html
index 84a285d8eb7..ef380851ac9 100644
--- a/Tests/LibWeb/Screenshot/css-filter-drop-shadow.html
+++ b/Tests/LibWeb/Screenshot/css-filter-drop-shadow.html
@@ -20,3 +20,4 @@
+
diff --git a/Tests/LibWeb/Screenshot/css-filter.html b/Tests/LibWeb/Screenshot/css-filter.html
new file mode 100644
index 00000000000..4344a29075b
--- /dev/null
+++ b/Tests/LibWeb/Screenshot/css-filter.html
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/LibWeb/Screenshot/images/css-filter-drop-shadow-ref.png b/Tests/LibWeb/Screenshot/images/css-filter-drop-shadow-ref.png
index 991ddf9c9cb..c73f22b9f35 100644
Binary files a/Tests/LibWeb/Screenshot/images/css-filter-drop-shadow-ref.png and b/Tests/LibWeb/Screenshot/images/css-filter-drop-shadow-ref.png differ
diff --git a/Tests/LibWeb/Screenshot/images/css-filter-ref.png b/Tests/LibWeb/Screenshot/images/css-filter-ref.png
new file mode 100644
index 00000000000..c83791fd0ab
Binary files /dev/null and b/Tests/LibWeb/Screenshot/images/css-filter-ref.png differ
diff --git a/Tests/LibWeb/Screenshot/reference/css-filter-ref.html b/Tests/LibWeb/Screenshot/reference/css-filter-ref.html
new file mode 100644
index 00000000000..7c27bfd3733
--- /dev/null
+++ b/Tests/LibWeb/Screenshot/reference/css-filter-ref.html
@@ -0,0 +1,10 @@
+
+
diff --git a/Tests/LibWeb/Text/expected/css/calc-coverage.txt b/Tests/LibWeb/Text/expected/css/calc-coverage.txt
index eb71f2c3a3f..7917a439704 100644
--- a/Tests/LibWeb/Text/expected/css/calc-coverage.txt
+++ b/Tests/LibWeb/Text/expected/css/calc-coverage.txt
@@ -4,10 +4,10 @@ animation-duration: 'calc(2s)' -> 'calc(2s)'
animation-duration: 'calc(2s * var(--n))' -> '4s'
animation-iteration-count: 'calc(2)' -> 'calc(2)'
animation-iteration-count: 'calc(2 * var(--n))' -> '4'
-backdrop-filter: 'grayscale(calc(2%))' -> 'none'
-backdrop-filter: 'grayscale(calc(2% * var(--n)))' -> 'none'
-backdrop-filter: 'grayscale(calc(0.02))' -> 'none'
-backdrop-filter: 'grayscale(calc(0.02 * var(--n)))' -> 'none'
+backdrop-filter: 'grayscale(calc(2%))' -> 'grayscale(calc(2%))'
+backdrop-filter: 'grayscale(calc(2% * var(--n)))' -> 'grayscale(calc(2% * 2))'
+backdrop-filter: 'grayscale(calc(0.02))' -> 'grayscale(calc(0.02))'
+backdrop-filter: 'grayscale(calc(0.02 * var(--n)))' -> 'grayscale(calc(0.02 * 2))'
background-position-x: 'calc(2px)' -> 'left calc(2px)'
background-position-x: 'calc(2px * var(--n))' -> 'left calc(2px * 2)'
background-position-y: 'calc(2%)' -> 'top calc(2%)'
@@ -56,10 +56,10 @@ cy: 'calc(2%)' -> 'calc(2%)'
cy: 'calc(2% * var(--n))' -> '4%'
fill-opacity: 'calc(2)' -> 'calc(2)'
fill-opacity: 'calc(2 * var(--n))' -> '4'
-filter: 'grayscale(calc(2%))' -> 'none'
-filter: 'grayscale(calc(2% * var(--n)))' -> 'none'
-filter: 'grayscale(calc(0.02))' -> 'none'
-filter: 'grayscale(calc(0.02 * var(--n)))' -> 'none'
+filter: 'grayscale(calc(2%))' -> 'grayscale(calc(2%))'
+filter: 'grayscale(calc(2% * var(--n)))' -> 'grayscale(calc(2% * 2))'
+filter: 'grayscale(calc(0.02))' -> 'grayscale(calc(0.02))'
+filter: 'grayscale(calc(0.02 * var(--n)))' -> 'grayscale(calc(0.02 * 2))'
flex-basis: 'calc(2px)' -> 'calc(2px)'
flex-basis: 'calc(2px * var(--n))' -> 'calc(2px * 2)'
flex-grow: 'calc(2)' -> 'calc(2)'
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
index 8af7a1069b8..89c4d543f5f 100644
--- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
+++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
@@ -2246,6 +2246,29 @@ Optional Parser::parse_number(TokenStream& t
return {};
}
+Optional Parser::parse_number_percentage(TokenStream& tokens)
+{
+ auto transaction = tokens.begin_transaction();
+ auto& token = tokens.consume_a_token();
+
+ if (token.is(Token::Type::Number)) {
+ transaction.commit();
+ return token.token().number();
+ }
+
+ if (token.is(Token::Type::Percentage)) {
+ transaction.commit();
+ return Percentage(token.token().percentage());
+ }
+
+ if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_number_percentage()) {
+ transaction.commit();
+ return calc.release_nonnull();
+ }
+
+ return {};
+}
+
Optional Parser::parse_resolution(TokenStream& tokens)
{
auto transaction = tokens.begin_transaction();
@@ -5395,14 +5418,6 @@ RefPtr Parser::parse_filter_value_list_value(TokenStream(filter);
};
- auto parse_number_percentage = [&](auto& token) -> Optional {
- if (token.is(Token::Type::Percentage))
- return NumberPercentage(Percentage(token.token().percentage()));
- if (token.is(Token::Type::Number))
- return NumberPercentage(Number(Number::Type::Number, token.token().number_value()));
- return {};
- };
-
auto parse_filter_function_name = [&](auto name) -> Optional {
if (name.equals_ignoring_ascii_case("blur"sv))
return FilterToken::Blur;
@@ -5446,8 +5461,7 @@ RefPtr Parser::parse_filter_value_list_value(TokenStreamvalue() });
+ return if_no_more_tokens_return(FilterOperation::Blur { blur_radius.value() });
} else if (filter_token == FilterToken::DropShadow) {
if (!tokens.has_next_token())
return {};
@@ -5481,29 +5495,24 @@ RefPtr Parser::parse_filter_value_list_value(TokenStreamto_color({});
- // FIXME: Support calculated offsets and radius
- return if_no_more_tokens_return(FilterOperation::DropShadow { x_offset->value(), y_offset->value(), maybe_radius.map([](auto& it) { return it.value(); }), color });
+ return if_no_more_tokens_return(FilterOperation::DropShadow { x_offset.value(), y_offset.value(), maybe_radius, color });
} else if (filter_token == FilterToken::HueRotate) {
// hue-rotate( [ | ]? )
if (!tokens.has_next_token())
return FilterOperation::HueRotate {};
- auto& token = tokens.consume_a_token();
- if (token.is(Token::Type::Number)) {
+
+ if (tokens.next_token().is(Token::Type::Number)) {
// hue-rotate(0)
- auto number = token.token().number();
+ auto number = tokens.consume_a_token().token().number();
if (number.is_integer() && number.integer_value() == 0)
return if_no_more_tokens_return(FilterOperation::HueRotate { FilterOperation::HueRotate::Zero {} });
return {};
}
- if (!token.is(Token::Type::Dimension))
- return {};
- auto angle_value = token.token().dimension_value();
- auto angle_unit_name = token.token().dimension_unit();
- auto angle_unit = Angle::unit_from_name(angle_unit_name);
- if (!angle_unit.has_value())
- return {};
- Angle angle { angle_value, angle_unit.release_value() };
- return if_no_more_tokens_return(FilterOperation::HueRotate { angle });
+
+ if (auto angle = parse_angle(tokens); angle.has_value())
+ return if_no_more_tokens_return(FilterOperation::HueRotate { angle.value() });
+
+ return {};
} else {
// Simple filters:
// brightness( ? )
@@ -5515,10 +5524,8 @@ RefPtr Parser::parse_filter_value_list_value(TokenStream? )
if (!tokens.has_next_token())
return FilterOperation::Color { filter_token_to_operation(filter_token) };
- auto amount = parse_number_percentage(tokens.consume_a_token());
- if (!amount.has_value())
- return {};
- return if_no_more_tokens_return(FilterOperation::Color { filter_token_to_operation(filter_token), *amount });
+ auto amount = parse_number_percentage(tokens);
+ return if_no_more_tokens_return(FilterOperation::Color { filter_token_to_operation(filter_token), amount });
}
};
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h
index df5a608f3b2..9316341c507 100644
--- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h
+++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h
@@ -202,6 +202,7 @@ private:
Optional parse_length(TokenStream&);
Optional parse_length_percentage(TokenStream&);
Optional parse_number(TokenStream&);
+ Optional parse_number_percentage(TokenStream&);
Optional parse_resolution(TokenStream&);
Optional parse_time(TokenStream&);
Optional parse_time_percentage(TokenStream&);
diff --git a/Userland/Libraries/LibWeb/CSS/StyleValues/FilterValueListStyleValue.cpp b/Userland/Libraries/LibWeb/CSS/StyleValues/FilterValueListStyleValue.cpp
index e6fd46453a8..89704f3c84a 100644
--- a/Userland/Libraries/LibWeb/CSS/StyleValues/FilterValueListStyleValue.cpp
+++ b/Userland/Libraries/LibWeb/CSS/StyleValues/FilterValueListStyleValue.cpp
@@ -15,30 +15,42 @@ namespace Web::CSS {
float FilterOperation::Blur::resolved_radius(Layout::Node const& node) const
{
- // Default value when omitted is 0px.
- auto sigma = 0;
if (radius.has_value())
- sigma = radius->to_px(node).to_int();
- return sigma;
+ return radius->resolved(Length::ResolutionContext::for_layout_node(node)).to_px(node).to_float();
+
+ // Default value when omitted is 0px.
+ return 0;
}
-float FilterOperation::HueRotate::angle_degrees() const
+float FilterOperation::HueRotate::angle_degrees(Layout::Node const& node) const
{
// Default value when omitted is 0deg.
if (!angle.has_value())
return 0.0f;
- return angle->visit([&](Angle const& a) { return a.to_degrees(); }, [&](auto) { return 0.0; });
+ return angle->visit([&](AngleOrCalculated const& a) { return a.resolved(node).to_degrees(); }, [&](Zero) { return 0.0; });
}
float FilterOperation::Color::resolved_amount() const
{
- if (amount.has_value()) {
- if (amount->is_percentage())
- return amount->percentage().as_fraction();
+ // Default value when omitted is 1.
+ if (!amount.has_value())
+ return 1;
+
+ if (amount->is_number())
return amount->number().value();
+
+ if (amount->is_percentage())
+ return amount->percentage().as_fraction();
+
+ if (amount->is_calculated()) {
+ if (amount->calculated()->resolves_to_number())
+ return amount->calculated()->resolve_number().value();
+
+ if (amount->calculated()->resolves_to_percentage())
+ return amount->calculated()->resolve_percentage()->as_fraction();
}
- // All color filters (brightness, sepia, etc) have a default amount of 1.
- return 1.0f;
+
+ VERIFY_NOT_REACHED();
}
String FilterValueListStyleValue::to_string() const
diff --git a/Userland/Libraries/LibWeb/CSS/StyleValues/FilterValueListStyleValue.h b/Userland/Libraries/LibWeb/CSS/StyleValues/FilterValueListStyleValue.h
index 4fd66a801ea..4690ba805f1 100644
--- a/Userland/Libraries/LibWeb/CSS/StyleValues/FilterValueListStyleValue.h
+++ b/Userland/Libraries/LibWeb/CSS/StyleValues/FilterValueListStyleValue.h
@@ -10,6 +10,7 @@
#pragma once
#include
+#include
#include
#include
#include
@@ -19,16 +20,16 @@ namespace Web::CSS {
namespace FilterOperation {
struct Blur {
- Optional radius {};
+ Optional radius;
float resolved_radius(Layout::Node const&) const;
bool operator==(Blur const&) const = default;
};
struct DropShadow {
- Length offset_x;
- Length offset_y;
- Optional radius {};
- Optional color {};
+ LengthOrCalculated offset_x;
+ LengthOrCalculated offset_y;
+ Optional radius;
+ Optional color;
bool operator==(DropShadow const&) const = default;
};
@@ -36,9 +37,9 @@ struct HueRotate {
struct Zero {
bool operator==(Zero const&) const = default;
};
- using AngleOrZero = Variant;
- Optional angle {};
- float angle_degrees() const;
+ using AngleOrZero = Variant;
+ Optional angle;
+ float angle_degrees(Layout::Node const&) const;
bool operator==(HueRotate const&) const = default;
};
diff --git a/Userland/Libraries/LibWeb/Layout/Node.cpp b/Userland/Libraries/LibWeb/Layout/Node.cpp
index 49372e5711b..4d4df0068f6 100644
--- a/Userland/Libraries/LibWeb/Layout/Node.cpp
+++ b/Userland/Libraries/LibWeb/Layout/Node.cpp
@@ -523,12 +523,13 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style)
.radius = blur.resolved_radius(*this) });
},
[&](CSS::FilterOperation::DropShadow const& drop_shadow) {
+ auto context = CSS::Length::ResolutionContext::for_layout_node(*this);
// The default value for omitted values is missing length values set to 0
// and the missing used color is taken from the color property.
resolved_filter.filters.append(CSS::ResolvedFilter::DropShadow {
- .offset_x = drop_shadow.offset_x.to_px(*this).to_double(),
- .offset_y = drop_shadow.offset_y.to_px(*this).to_double(),
- .radius = drop_shadow.radius.has_value() ? drop_shadow.radius->to_px(*this).to_double() : 0.0,
+ .offset_x = drop_shadow.offset_x.resolved(context).to_px(*this).to_double(),
+ .offset_y = drop_shadow.offset_y.resolved(context).to_px(*this).to_double(),
+ .radius = drop_shadow.radius.has_value() ? drop_shadow.radius->resolved(context).to_px(*this).to_double() : 0.0,
.color = drop_shadow.color.has_value() ? *drop_shadow.color : this->computed_values().color() });
},
[&](CSS::FilterOperation::Color const& color_operation) {
@@ -537,7 +538,7 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style)
.amount = color_operation.resolved_amount() });
},
[&](CSS::FilterOperation::HueRotate const& hue_rotate) {
- resolved_filter.filters.append(CSS::ResolvedFilter::HueRotate { .angle_degrees = hue_rotate.angle_degrees() });
+ resolved_filter.filters.append(CSS::ResolvedFilter::HueRotate { .angle_degrees = hue_rotate.angle_degrees(*this) });
});
}
return resolved_filter;