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;