From 31853c13d3c7e4fbb5712bf76bdfbf0e1d3dde9a Mon Sep 17 00:00:00 2001 From: Gingeh <39150378+Gingeh@users.noreply.github.com> Date: Wed, 19 Feb 2025 21:02:12 +1100 Subject: [PATCH] LibWeb: Implement css gradient-interpolation-method --- Libraries/LibWeb/CSS/Interpolation.cpp | 6 +- .../LibWeb/CSS/Parser/GradientParsing.cpp | 141 ++++++++++++++++-- Libraries/LibWeb/CSS/Parser/Parser.h | 1 + Libraries/LibWeb/CSS/Parser/ValueParsing.cpp | 10 +- .../CSS/ResolvedCSSStyleDeclaration.cpp | 20 +-- .../CSS/StyleValues/AbstractImageStyleValue.h | 107 +++++++++++++ Libraries/LibWeb/CSS/StyleValues/CSSColor.h | 2 +- .../LibWeb/CSS/StyleValues/CSSColorValue.cpp | 3 +- .../LibWeb/CSS/StyleValues/CSSColorValue.h | 12 +- Libraries/LibWeb/CSS/StyleValues/CSSHSL.h | 10 +- Libraries/LibWeb/CSS/StyleValues/CSSHWB.h | 2 +- Libraries/LibWeb/CSS/StyleValues/CSSLCHLike.h | 2 +- Libraries/LibWeb/CSS/StyleValues/CSSLabLike.h | 2 +- .../LibWeb/CSS/StyleValues/CSSLightDark.h | 2 +- Libraries/LibWeb/CSS/StyleValues/CSSRGB.h | 10 +- .../StyleValues/ConicGradientStyleValue.cpp | 9 +- .../CSS/StyleValues/ConicGradientStyleValue.h | 20 ++- .../StyleValues/LinearGradientStyleValue.cpp | 37 +++-- .../StyleValues/LinearGradientStyleValue.h | 23 ++- .../StyleValues/RadialGradientStyleValue.cpp | 29 +++- .../StyleValues/RadialGradientStyleValue.h | 19 ++- Libraries/LibWeb/HTML/HTMLBodyElement.cpp | 4 +- Libraries/LibWeb/HTML/HTMLFontElement.cpp | 2 +- Libraries/LibWeb/HTML/HTMLHRElement.cpp | 2 +- Libraries/LibWeb/HTML/HTMLMarqueeElement.cpp | 2 +- .../LibWeb/HTML/HTMLTableCellElement.cpp | 2 +- Libraries/LibWeb/HTML/HTMLTableElement.cpp | 6 +- Libraries/LibWeb/HTML/HTMLTableRowElement.cpp | 2 +- .../LibWeb/HTML/HTMLTableSectionElement.cpp | 2 +- .../LibWeb/Painting/DisplayListPlayerSkia.cpp | 72 +++++++-- Libraries/LibWeb/Painting/GradientData.h | 4 + .../LibWeb/Painting/GradientPainting.cpp | 6 +- .../gradient-interpolation-method-ref.html | 10 ++ .../gradient-interpolation-method-ref.png | Bin 0 -> 4913 bytes .../input/gradient-interpolation-method.html | 19 +++ 35 files changed, 499 insertions(+), 101 deletions(-) create mode 100644 Tests/LibWeb/Screenshot/expected/gradient-interpolation-method-ref.html create mode 100644 Tests/LibWeb/Screenshot/images/gradient-interpolation-method-ref.png create mode 100644 Tests/LibWeb/Screenshot/input/gradient-interpolation-method.html diff --git a/Libraries/LibWeb/CSS/Interpolation.cpp b/Libraries/LibWeb/CSS/Interpolation.cpp index 2bddd359ec3..de84c8345d8 100644 --- a/Libraries/LibWeb/CSS/Interpolation.cpp +++ b/Libraries/LibWeb/CSS/Interpolation.cpp @@ -465,7 +465,7 @@ NonnullRefPtr interpolate_box_shadow(DOM::Element& element, values.ensure_capacity(other.size()); for (size_t i = values.size(); i < other.size(); i++) { values.unchecked_append(ShadowStyleValue::create( - CSSColorValue::create_from_color(Color::Transparent), + CSSColorValue::create_from_color(Color::Transparent, ColorSyntax::Legacy), LengthStyleValue::create(Length::make_px(0)), LengthStyleValue::create(Length::make_px(0)), LengthStyleValue::create(Length::make_px(0)), @@ -488,7 +488,7 @@ NonnullRefPtr interpolate_box_shadow(DOM::Element& element, auto const& from_shadow = from_shadows[i]->as_shadow(); auto const& to_shadow = to_shadows[i]->as_shadow(); auto result_shadow = ShadowStyleValue::create( - CSSColorValue::create_from_color(interpolate_color(from_shadow.color()->to_color({}), to_shadow.color()->to_color({}), delta)), + CSSColorValue::create_from_color(interpolate_color(from_shadow.color()->to_color({}), to_shadow.color()->to_color({}), delta), ColorSyntax::Modern), interpolate_value(element, calculation_context, from_shadow.offset_x(), to_shadow.offset_x(), delta), interpolate_value(element, calculation_context, from_shadow.offset_y(), to_shadow.offset_y(), delta), interpolate_value(element, calculation_context, from_shadow.blur_radius(), to_shadow.blur_radius(), delta), @@ -584,7 +584,7 @@ NonnullRefPtr interpolate_value(DOM::Element& element, Calc Optional layout_node; if (auto node = element.layout_node()) layout_node = *node; - return CSSColorValue::create_from_color(interpolate_color(from.to_color(layout_node), to.to_color(layout_node), delta)); + return CSSColorValue::create_from_color(interpolate_color(from.to_color(layout_node), to.to_color(layout_node), delta), ColorSyntax::Modern); } case CSSStyleValue::Type::Integer: return IntegerStyleValue::create(interpolate_raw(from.as_integer().integer(), to.as_integer().integer(), delta)); diff --git a/Libraries/LibWeb/CSS/Parser/GradientParsing.cpp b/Libraries/LibWeb/CSS/Parser/GradientParsing.cpp index e55436044e8..884973311e4 100644 --- a/Libraries/LibWeb/CSS/Parser/GradientParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/GradientParsing.cpp @@ -128,6 +128,101 @@ Optional> Parser::parse_angular_color_stop_l [&](auto& it) { return parse_angle_percentage(it); }); } +Optional Parser::parse_interpolation_method(TokenStream& tokens) +{ + // = in [ | ? ] + + auto transaction = tokens.begin_transaction(); + + if (!tokens.consume_a_token().is_ident("in"sv)) + return {}; + + tokens.discard_whitespace(); + auto first_value = tokens.consume_a_token(); + if (!first_value.is(Token::Type::Ident)) + return {}; + + auto color_space_name = first_value.token().ident(); + GradientSpace color_space; + bool polar_space = false; + + if (color_space_name.equals_ignoring_ascii_case("srgb"sv)) { + color_space = GradientSpace::sRGB; + } else if (color_space_name.equals_ignoring_ascii_case("srgb-linear"sv)) { + color_space = GradientSpace::sRGBLinear; + } else if (color_space_name.equals_ignoring_ascii_case("display-p3"sv)) { + color_space = GradientSpace::DisplayP3; + } else if (color_space_name.equals_ignoring_ascii_case("a98-rgb"sv)) { + color_space = GradientSpace::A98RGB; + } else if (color_space_name.equals_ignoring_ascii_case("prophoto-rgb"sv)) { + color_space = GradientSpace::ProPhotoRGB; + } else if (color_space_name.equals_ignoring_ascii_case("rec2020"sv)) { + color_space = GradientSpace::Rec2020; + } else if (color_space_name.equals_ignoring_ascii_case("lab"sv)) { + color_space = GradientSpace::Lab; + } else if (color_space_name.equals_ignoring_ascii_case("oklab"sv)) { + color_space = GradientSpace::OKLab; + } else if (color_space_name.equals_ignoring_ascii_case("xyz-d50"sv)) { + color_space = GradientSpace::XYZD50; + } else if (color_space_name.equals_ignoring_ascii_case("xyz-d65"sv) + || color_space_name.equals_ignoring_ascii_case("xyz"sv)) { + color_space = GradientSpace::XYZD65; + } else { + polar_space = true; + if (color_space_name.equals_ignoring_ascii_case("hsl"sv)) { + color_space = GradientSpace::HSL; + } else if (color_space_name.equals_ignoring_ascii_case("hwb"sv)) { + color_space = GradientSpace::HWB; + } else if (color_space_name.equals_ignoring_ascii_case("lch"sv)) { + color_space = GradientSpace::LCH; + } else if (color_space_name.equals_ignoring_ascii_case("oklch"sv)) { + color_space = GradientSpace::OKLCH; + } else { + return {}; + } + } + + Optional hue_method; + if (polar_space) { + [&]() { + auto hue_transaction = transaction.create_child(); + + tokens.discard_whitespace(); + auto second_value = tokens.consume_a_token(); + if (!second_value.is(Token::Type::Ident)) + return; + + auto hue_method_name = second_value.token().ident(); + if (hue_method_name.equals_ignoring_ascii_case("shorter"sv)) { + hue_method = HueMethod::Shorter; + } else if (hue_method_name.equals_ignoring_ascii_case("longer"sv)) { + hue_method = HueMethod::Longer; + } else if (hue_method_name.equals_ignoring_ascii_case("increasing"sv)) { + hue_method = HueMethod::Increasing; + } else if (hue_method_name.equals_ignoring_ascii_case("decreasing"sv)) { + hue_method = HueMethod::Decreasing; + } else { + return; + } + + tokens.discard_whitespace(); + if (!tokens.consume_a_token().is_ident("hue"sv)) + return; + + hue_transaction.commit(); + }(); + } + + transaction.commit(); + + InterpolationMethod interpolation_method; + interpolation_method.color_space = color_space; + if (hue_method.has_value()) + interpolation_method.hue_method = hue_method.value(); + + return interpolation_method; +} + RefPtr Parser::parse_linear_gradient_function(TokenStream& outer_tokens) { using GradientType = LinearGradientStyleValue::GradientType; @@ -156,7 +251,7 @@ RefPtr Parser::parse_linear_gradient_function(TokenStr if (!function_name.equals_ignoring_ascii_case("linear-gradient"sv)) return nullptr; - // linear-gradient() = linear-gradient([ | to ]?, ) + // = [ [ | to ] || ]? , TokenStream tokens { component_value.function().value }; tokens.discard_whitespace(); @@ -189,6 +284,9 @@ RefPtr Parser::parse_linear_gradient_function(TokenStr return token.token().ident().equals_ignoring_ascii_case("to"sv); }; + auto maybe_interpolation_method = parse_interpolation_method(tokens); + tokens.discard_whitespace(); + auto const& first_param = tokens.next_token(); if (first_param.is(Token::Type::Dimension)) { // @@ -222,11 +320,12 @@ RefPtr Parser::parse_linear_gradient_function(TokenStr tokens.discard_whitespace(); Optional side_b; if (tokens.has_next_token() && tokens.next_token().is(Token::Type::Ident)) - side_b = to_side(tokens.consume_a_token().token().ident()); + side_b = to_side(tokens.next_token().token().ident()); if (side_a.has_value() && !side_b.has_value()) { gradient_direction = *side_a; } else if (side_a.has_value() && side_b.has_value()) { + tokens.discard_a_token(); // Convert two sides to a corner if (to_underlying(*side_b) < to_underlying(*side_a)) swap(side_a, side_b); @@ -247,11 +346,16 @@ RefPtr Parser::parse_linear_gradient_function(TokenStr has_direction_param = false; } + if (!maybe_interpolation_method.has_value()) { + tokens.discard_whitespace(); + maybe_interpolation_method = parse_interpolation_method(tokens); + } + tokens.discard_whitespace(); if (!tokens.has_next_token()) return nullptr; - if (has_direction_param && !tokens.consume_a_token().is(Token::Type::Comma)) + if ((has_direction_param || maybe_interpolation_method.has_value()) && !tokens.consume_a_token().is(Token::Type::Comma)) return nullptr; auto color_stops = parse_linear_color_stop_list(tokens); @@ -259,7 +363,7 @@ RefPtr Parser::parse_linear_gradient_function(TokenStr return nullptr; transaction.commit(); - return LinearGradientStyleValue::create(gradient_direction, move(*color_stops), gradient_type, repeating_gradient); + return LinearGradientStyleValue::create(gradient_direction, move(*color_stops), gradient_type, repeating_gradient, maybe_interpolation_method); } RefPtr Parser::parse_conic_gradient_function(TokenStream& outer_tokens) @@ -290,9 +394,9 @@ RefPtr Parser::parse_conic_gradient_function(TokenStrea Angle from_angle(0, Angle::Type::Deg); RefPtr at_position; + Optional maybe_interpolation_method; - // conic-gradient( [ [ from ]? [ at ]? ] || - // , ) + // conic-gradient( [ [ [ from ]? [ at ]? ] || ]? , ) NonnullRawPtr token = tokens.next_token(); bool got_from_angle = false; bool got_color_interpolation_method = false; @@ -335,12 +439,15 @@ RefPtr Parser::parse_conic_gradient_function(TokenStrea return nullptr; at_position = position; got_at_position = true; - } else if (consume_identifier("in"sv)) { + } else if (token->token().ident().equals_ignoring_ascii_case("in"sv)) { // if (got_color_interpolation_method) return nullptr; - dbgln("FIXME: Parse color interpolation method for conic-gradient()"); got_color_interpolation_method = true; + + maybe_interpolation_method = parse_interpolation_method(tokens); + if (!maybe_interpolation_method.has_value()) + return nullptr; } else { break; } @@ -364,7 +471,7 @@ RefPtr Parser::parse_conic_gradient_function(TokenStrea at_position = PositionStyleValue::create_center(); transaction.commit(); - return ConicGradientStyleValue::create(from_angle, at_position.release_nonnull(), move(*color_stops), repeating_gradient); + return ConicGradientStyleValue::create(from_angle, at_position.release_nonnull(), move(*color_stops), repeating_gradient, maybe_interpolation_method); } RefPtr Parser::parse_radial_gradient_function(TokenStream& outer_tokens) @@ -405,7 +512,8 @@ RefPtr Parser::parse_radial_gradient_function(TokenStr return value; }; - // radial-gradient( [ || ]? [ at ]? , ) + // = [ [ [ || ]? [ at ]? ] || ]? , + // FIXME: Maybe rename ending-shape things to radial-shape Size size = Extent::FarthestCorner; EndingShape ending_shape = EndingShape::Circle; @@ -469,6 +577,9 @@ RefPtr Parser::parse_radial_gradient_function(TokenStr return {}; }; + auto maybe_interpolation_method = parse_interpolation_method(tokens); + tokens.discard_whitespace(); + { // [ || ]? auto maybe_ending_shape = parse_ending_shape(); @@ -506,6 +617,14 @@ RefPtr Parser::parse_radial_gradient_function(TokenStr } tokens.discard_whitespace(); + if (!maybe_interpolation_method.has_value()) { + maybe_interpolation_method = parse_interpolation_method(tokens); + tokens.discard_whitespace(); + } + + if (maybe_interpolation_method.has_value()) + expect_comma = true; + if (!tokens.has_next_token()) return nullptr; if (expect_comma && !tokens.consume_a_token().is(Token::Type::Comma)) @@ -520,7 +639,7 @@ RefPtr Parser::parse_radial_gradient_function(TokenStr at_position = PositionStyleValue::create_center(); transaction.commit(); - return RadialGradientStyleValue::create(ending_shape, size, at_position.release_nonnull(), move(*color_stops), repeating_gradient); + return RadialGradientStyleValue::create(ending_shape, size, at_position.release_nonnull(), move(*color_stops), repeating_gradient, maybe_interpolation_method); } } diff --git a/Libraries/LibWeb/CSS/Parser/Parser.h b/Libraries/LibWeb/CSS/Parser/Parser.h index e51027eced9..1e406748f59 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Libraries/LibWeb/CSS/Parser/Parser.h @@ -281,6 +281,7 @@ private: Optional> parse_color_stop_list(TokenStream& tokens, auto parse_position); Optional> parse_linear_color_stop_list(TokenStream&); Optional> parse_angular_color_stop_list(TokenStream&); + Optional parse_interpolation_method(TokenStream&); RefPtr parse_linear_gradient_function(TokenStream&); RefPtr parse_conic_gradient_function(TokenStream&); diff --git a/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp b/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp index 599220220a4..58f481d9bc7 100644 --- a/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp @@ -1198,7 +1198,7 @@ RefPtr Parser::parse_rgb_color_value(TokenStream& alpha = NumberStyleValue::create(1); transaction.commit(); - return CSSRGB::create(red.release_nonnull(), green.release_nonnull(), blue.release_nonnull(), alpha.release_nonnull()); + return CSSRGB::create(red.release_nonnull(), green.release_nonnull(), blue.release_nonnull(), alpha.release_nonnull(), legacy_syntax ? ColorSyntax::Legacy : ColorSyntax::Modern); } // https://www.w3.org/TR/css-color-4/#funcdef-hsl @@ -1310,7 +1310,7 @@ RefPtr Parser::parse_hsl_color_value(TokenStream& alpha = NumberStyleValue::create(1); transaction.commit(); - return CSSHSL::create(h.release_nonnull(), s.release_nonnull(), l.release_nonnull(), alpha.release_nonnull()); + return CSSHSL::create(h.release_nonnull(), s.release_nonnull(), l.release_nonnull(), alpha.release_nonnull(), legacy_syntax ? ColorSyntax::Legacy : ColorSyntax::Modern); } // https://www.w3.org/TR/css-color-4/#funcdef-hwb @@ -1687,7 +1687,7 @@ RefPtr Parser::parse_color_value(TokenStream& tok auto color = Color::from_string(ident); if (color.has_value()) { transaction.commit(); - return CSSColorValue::create_from_color(color.release_value(), ident); + return CSSColorValue::create_from_color(color.release_value(), ColorSyntax::Legacy, ident); } // Otherwise, fall through to the hashless-hex-color case } @@ -1696,7 +1696,7 @@ RefPtr Parser::parse_color_value(TokenStream& tok auto color = Color::from_string(MUST(String::formatted("#{}", component_value.token().hash_value()))); if (color.has_value()) { transaction.commit(); - return CSSColorValue::create_from_color(color.release_value()); + return CSSColorValue::create_from_color(color.release_value(), ColorSyntax::Legacy); } return {}; } @@ -1782,7 +1782,7 @@ RefPtr Parser::parse_color_value(TokenStream& tok auto color = Color::from_string(MUST(String::formatted("#{}", serialization))); if (color.has_value()) { transaction.commit(); - return CSSColorValue::create_from_color(color.release_value()); + return CSSColorValue::create_from_color(color.release_value(), ColorSyntax::Legacy); } } } diff --git a/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp b/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp index 9e9fb51bea5..5d8d2d314d4 100644 --- a/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp +++ b/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp @@ -147,7 +147,7 @@ static RefPtr style_value_for_shadow(Vector con auto make_shadow_style_value = [](ShadowData const& shadow) { return ShadowStyleValue::create( - CSSColorValue::create_from_color(shadow.color), + CSSColorValue::create_from_color(shadow.color, ColorSyntax::Modern), style_value_for_length_percentage(shadow.offset_x), style_value_for_length_percentage(shadow.offset_y), style_value_for_length_percentage(shadow.blur_radius), @@ -209,23 +209,23 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_propert // -> A resolved value special case property like color defined in another specification // The resolved value is the used value. case PropertyID::BackgroundColor: - return CSSColorValue::create_from_color(layout_node.computed_values().background_color()); + return CSSColorValue::create_from_color(layout_node.computed_values().background_color(), ColorSyntax::Modern); case PropertyID::BorderBottomColor: - return CSSColorValue::create_from_color(layout_node.computed_values().border_bottom().color); + return CSSColorValue::create_from_color(layout_node.computed_values().border_bottom().color, ColorSyntax::Modern); case PropertyID::BorderLeftColor: - return CSSColorValue::create_from_color(layout_node.computed_values().border_left().color); + return CSSColorValue::create_from_color(layout_node.computed_values().border_left().color, ColorSyntax::Modern); case PropertyID::BorderRightColor: - return CSSColorValue::create_from_color(layout_node.computed_values().border_right().color); + return CSSColorValue::create_from_color(layout_node.computed_values().border_right().color, ColorSyntax::Modern); case PropertyID::BorderTopColor: - return CSSColorValue::create_from_color(layout_node.computed_values().border_top().color); + return CSSColorValue::create_from_color(layout_node.computed_values().border_top().color, ColorSyntax::Modern); case PropertyID::BoxShadow: return style_value_for_shadow(layout_node.computed_values().box_shadow()); case PropertyID::Color: - return CSSColorValue::create_from_color(layout_node.computed_values().color()); + return CSSColorValue::create_from_color(layout_node.computed_values().color(), ColorSyntax::Modern); case PropertyID::OutlineColor: - return CSSColorValue::create_from_color(layout_node.computed_values().outline_color()); + return CSSColorValue::create_from_color(layout_node.computed_values().outline_color(), ColorSyntax::Modern); case PropertyID::TextDecorationColor: - return CSSColorValue::create_from_color(layout_node.computed_values().text_decoration_color()); + return CSSColorValue::create_from_color(layout_node.computed_values().text_decoration_color(), ColorSyntax::Modern); // NOTE: text-shadow isn't listed, but is computed the same as box-shadow. case PropertyID::TextShadow: return style_value_for_shadow(layout_node.computed_values().text_shadow()); @@ -456,7 +456,7 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_propert // The resolved value is the computed value. // NOTE: This is handled inside the `default` case. case PropertyID::WebkitTextFillColor: - return CSSColorValue::create_from_color(layout_node.computed_values().webkit_text_fill_color()); + return CSSColorValue::create_from_color(layout_node.computed_values().webkit_text_fill_color(), ColorSyntax::Modern); case PropertyID::Invalid: return CSSKeywordValue::create(Keyword::Invalid); case PropertyID::Custom: diff --git a/Libraries/LibWeb/CSS/StyleValues/AbstractImageStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/AbstractImageStyleValue.h index 9c078e92ad4..a530da24e17 100644 --- a/Libraries/LibWeb/CSS/StyleValues/AbstractImageStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/AbstractImageStyleValue.h @@ -13,6 +13,7 @@ #include #include #include +#include namespace Web::CSS { @@ -48,6 +49,112 @@ enum class GradientRepeating { No }; +enum class GradientSpace : u8 { + sRGB, + sRGBLinear, + DisplayP3, + A98RGB, + ProPhotoRGB, + Rec2020, + Lab, + OKLab, + XYZD50, + XYZD65, + HSL, + HWB, + LCH, + OKLCH, +}; + +enum class HueMethod : u8 { + Shorter, + Longer, + Increasing, + Decreasing, +}; + +struct InterpolationMethod { + GradientSpace color_space; + HueMethod hue_method = HueMethod::Shorter; + + String to_string() const + { + StringBuilder builder; + + switch (color_space) { + case GradientSpace::OKLab: + builder.append("in oklab"sv); + break; + case GradientSpace::sRGB: + builder.append("in srgb"sv); + break; + case GradientSpace::sRGBLinear: + builder.append("in srgb-linear"sv); + break; + case GradientSpace::DisplayP3: + builder.append("in display-p3"sv); + break; + case GradientSpace::A98RGB: + builder.append("in a98-rgb"sv); + break; + case GradientSpace::ProPhotoRGB: + builder.append("in prophoto-rgb"sv); + break; + case GradientSpace::Rec2020: + builder.append("in rec2020"sv); + break; + case GradientSpace::Lab: + builder.append("in lab"sv); + break; + case GradientSpace::XYZD50: + builder.append("in xyz-d50"sv); + break; + case GradientSpace::XYZD65: + builder.append("in xyz-d65"sv); + break; + case GradientSpace::HSL: + builder.append("in hsl"sv); + break; + case GradientSpace::HWB: + builder.append("in hwb"sv); + break; + case GradientSpace::LCH: + builder.append("in lch"sv); + break; + case GradientSpace::OKLCH: + builder.append("in oklch"sv); + break; + } + + switch (hue_method) { + case HueMethod::Shorter: + // "shorter" is the default value and isn't serialized + break; + case HueMethod::Longer: + builder.append(" longer hue"sv); + break; + case HueMethod::Increasing: + builder.append(" increasing hue"sv); + break; + case HueMethod::Decreasing: + builder.append(" decreasing hue"sv); + break; + } + + return MUST(builder.to_string()); + } + + static GradientSpace default_color_space(ColorSyntax color_syntax) + { + if (color_syntax == ColorSyntax::Legacy) + return GradientSpace::sRGB; + + return GradientSpace::OKLab; + } + + bool operator==(InterpolationMethod const&) const = default; +}; + template struct ColorStopListElement { using PositionType = TPosition; diff --git a/Libraries/LibWeb/CSS/StyleValues/CSSColor.h b/Libraries/LibWeb/CSS/StyleValues/CSSColor.h index 671f632b57c..e2b1d090468 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CSSColor.h +++ b/Libraries/LibWeb/CSS/StyleValues/CSSColor.h @@ -25,7 +25,7 @@ public: private: CSSColor(ColorType color_type, ValueComparingNonnullRefPtr c1, ValueComparingNonnullRefPtr c2, ValueComparingNonnullRefPtr c3, ValueComparingNonnullRefPtr alpha) - : CSSColorValue(color_type) + : CSSColorValue(color_type, ColorSyntax::Modern) , m_properties { .channels = { move(c1), move(c2), move(c3) }, .alpha = move(alpha) } { } diff --git a/Libraries/LibWeb/CSS/StyleValues/CSSColorValue.cpp b/Libraries/LibWeb/CSS/StyleValues/CSSColorValue.cpp index a86da4c2998..c6ea9da5cc9 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CSSColorValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/CSSColorValue.cpp @@ -18,13 +18,14 @@ namespace Web::CSS { -ValueComparingNonnullRefPtr CSSColorValue::create_from_color(Color color, Optional name) +ValueComparingNonnullRefPtr CSSColorValue::create_from_color(Color color, ColorSyntax color_syntax, Optional name) { return CSSRGB::create( NumberStyleValue::create(color.red()), NumberStyleValue::create(color.green()), NumberStyleValue::create(color.blue()), NumberStyleValue::create(color.alpha() / 255.0), + color_syntax, name); } diff --git a/Libraries/LibWeb/CSS/StyleValues/CSSColorValue.h b/Libraries/LibWeb/CSS/StyleValues/CSSColorValue.h index 39ddf3e2df4..38af42e20b4 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CSSColorValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/CSSColorValue.h @@ -15,10 +15,15 @@ namespace Web::CSS { +enum class ColorSyntax : u8 { + Legacy, + Modern, +}; + // https://drafts.css-houdini.org/css-typed-om-1/#csscolorvalue class CSSColorValue : public CSSStyleValue { public: - static ValueComparingNonnullRefPtr create_from_color(Color color, Optional name = {}); + static ValueComparingNonnullRefPtr create_from_color(Color color, ColorSyntax color_syntax, Optional name = {}); virtual ~CSSColorValue() override = default; virtual bool has_color() const override { return true; } @@ -42,11 +47,13 @@ public: LightDark, // This is used by CSSLightDark for light-dark(..., ...). }; ColorType color_type() const { return m_color_type; } + ColorSyntax color_syntax() const { return m_color_syntax; } protected: - explicit CSSColorValue(ColorType color_type) + explicit CSSColorValue(ColorType color_type, ColorSyntax color_syntax) : CSSStyleValue(Type::Color) , m_color_type(color_type) + , m_color_syntax(color_syntax) { } @@ -55,6 +62,7 @@ protected: static Optional resolve_alpha(CSSStyleValue const&); ColorType m_color_type; + ColorSyntax m_color_syntax; }; } diff --git a/Libraries/LibWeb/CSS/StyleValues/CSSHSL.h b/Libraries/LibWeb/CSS/StyleValues/CSSHSL.h index 0925f777410..29de8b36248 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CSSHSL.h +++ b/Libraries/LibWeb/CSS/StyleValues/CSSHSL.h @@ -14,13 +14,13 @@ namespace Web::CSS { // https://drafts.css-houdini.org/css-typed-om-1/#csshsl class CSSHSL final : public CSSColorValue { public: - static ValueComparingNonnullRefPtr create(ValueComparingNonnullRefPtr h, ValueComparingNonnullRefPtr s, ValueComparingNonnullRefPtr l, ValueComparingRefPtr alpha = {}) + static ValueComparingNonnullRefPtr create(ValueComparingNonnullRefPtr h, ValueComparingNonnullRefPtr s, ValueComparingNonnullRefPtr l, ValueComparingRefPtr alpha, ColorSyntax color_syntax) { // alpha defaults to 1 if (!alpha) - return adopt_ref(*new (nothrow) CSSHSL(move(h), move(s), move(l), NumberStyleValue::create(1))); + return adopt_ref(*new (nothrow) CSSHSL(move(h), move(s), move(l), NumberStyleValue::create(1), color_syntax)); - return adopt_ref(*new (nothrow) CSSHSL(move(h), move(s), move(l), alpha.release_nonnull())); + return adopt_ref(*new (nothrow) CSSHSL(move(h), move(s), move(l), alpha.release_nonnull(), color_syntax)); } virtual ~CSSHSL() override = default; @@ -36,8 +36,8 @@ public: virtual bool equals(CSSStyleValue const& other) const override; private: - CSSHSL(ValueComparingNonnullRefPtr h, ValueComparingNonnullRefPtr s, ValueComparingNonnullRefPtr l, ValueComparingNonnullRefPtr alpha) - : CSSColorValue(ColorType::HSL) + CSSHSL(ValueComparingNonnullRefPtr h, ValueComparingNonnullRefPtr s, ValueComparingNonnullRefPtr l, ValueComparingNonnullRefPtr alpha, ColorSyntax color_syntax) + : CSSColorValue(ColorType::HSL, color_syntax) , m_properties { .h = move(h), .s = move(s), .l = move(l), .alpha = move(alpha) } { } diff --git a/Libraries/LibWeb/CSS/StyleValues/CSSHWB.h b/Libraries/LibWeb/CSS/StyleValues/CSSHWB.h index fb22a1ab77d..fc84ae8bfdf 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CSSHWB.h +++ b/Libraries/LibWeb/CSS/StyleValues/CSSHWB.h @@ -37,7 +37,7 @@ public: private: CSSHWB(ValueComparingNonnullRefPtr h, ValueComparingNonnullRefPtr w, ValueComparingNonnullRefPtr b, ValueComparingNonnullRefPtr alpha) - : CSSColorValue(ColorType::HWB) + : CSSColorValue(ColorType::HWB, ColorSyntax::Modern) , m_properties { .h = move(h), .w = move(w), .b = move(b), .alpha = move(alpha) } { } diff --git a/Libraries/LibWeb/CSS/StyleValues/CSSLCHLike.h b/Libraries/LibWeb/CSS/StyleValues/CSSLCHLike.h index 47726dc4a27..49cefab4672 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CSSLCHLike.h +++ b/Libraries/LibWeb/CSS/StyleValues/CSSLCHLike.h @@ -33,7 +33,7 @@ public: protected: CSSLCHLike(ColorType color_type, ValueComparingNonnullRefPtr l, ValueComparingNonnullRefPtr c, ValueComparingNonnullRefPtr h, ValueComparingNonnullRefPtr alpha) - : CSSColorValue(color_type) + : CSSColorValue(color_type, ColorSyntax::Modern) , m_properties { .l = move(l), .c = move(c), .h = move(h), .alpha = move(alpha) } { } diff --git a/Libraries/LibWeb/CSS/StyleValues/CSSLabLike.h b/Libraries/LibWeb/CSS/StyleValues/CSSLabLike.h index ababccaaad1..03b85766698 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CSSLabLike.h +++ b/Libraries/LibWeb/CSS/StyleValues/CSSLabLike.h @@ -34,7 +34,7 @@ public: protected: CSSLabLike(ColorType color_type, ValueComparingNonnullRefPtr l, ValueComparingNonnullRefPtr a, ValueComparingNonnullRefPtr b, ValueComparingNonnullRefPtr alpha) - : CSSColorValue(color_type) + : CSSColorValue(color_type, ColorSyntax::Modern) , m_properties { .l = move(l), .a = move(a), .b = move(b), .alpha = move(alpha) } { } diff --git a/Libraries/LibWeb/CSS/StyleValues/CSSLightDark.h b/Libraries/LibWeb/CSS/StyleValues/CSSLightDark.h index 4d2bd6d94da..d4d9bea7299 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CSSLightDark.h +++ b/Libraries/LibWeb/CSS/StyleValues/CSSLightDark.h @@ -26,7 +26,7 @@ public: private: CSSLightDark(ValueComparingNonnullRefPtr light, ValueComparingNonnullRefPtr dark) - : CSSColorValue(CSSColorValue::ColorType::LightDark) + : CSSColorValue(CSSColorValue::ColorType::LightDark, ColorSyntax::Modern) , m_properties { .light = move(light), .dark = move(dark) } { } diff --git a/Libraries/LibWeb/CSS/StyleValues/CSSRGB.h b/Libraries/LibWeb/CSS/StyleValues/CSSRGB.h index e58d8b7b7e2..e668ceed75b 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CSSRGB.h +++ b/Libraries/LibWeb/CSS/StyleValues/CSSRGB.h @@ -14,13 +14,13 @@ namespace Web::CSS { // https://drafts.css-houdini.org/css-typed-om-1/#cssrgb class CSSRGB final : public CSSColorValue { public: - static ValueComparingNonnullRefPtr create(ValueComparingNonnullRefPtr r, ValueComparingNonnullRefPtr g, ValueComparingNonnullRefPtr b, ValueComparingRefPtr alpha = {}, Optional name = {}) + static ValueComparingNonnullRefPtr create(ValueComparingNonnullRefPtr r, ValueComparingNonnullRefPtr g, ValueComparingNonnullRefPtr b, ValueComparingRefPtr alpha, ColorSyntax color_syntax, Optional name = {}) { // alpha defaults to 1 if (!alpha) - return adopt_ref(*new (nothrow) CSSRGB(move(r), move(g), move(b), NumberStyleValue::create(1), name)); + return adopt_ref(*new (nothrow) CSSRGB(move(r), move(g), move(b), NumberStyleValue::create(1), color_syntax, name)); - return adopt_ref(*new (nothrow) CSSRGB(move(r), move(g), move(b), alpha.release_nonnull(), name)); + return adopt_ref(*new (nothrow) CSSRGB(move(r), move(g), move(b), alpha.release_nonnull(), color_syntax, name)); } virtual ~CSSRGB() override = default; @@ -36,8 +36,8 @@ public: virtual bool equals(CSSStyleValue const& other) const override; private: - CSSRGB(ValueComparingNonnullRefPtr r, ValueComparingNonnullRefPtr g, ValueComparingNonnullRefPtr b, ValueComparingNonnullRefPtr alpha, Optional name = {}) - : CSSColorValue(ColorType::RGB) + CSSRGB(ValueComparingNonnullRefPtr r, ValueComparingNonnullRefPtr g, ValueComparingNonnullRefPtr b, ValueComparingNonnullRefPtr alpha, ColorSyntax color_syntax, Optional name = {}) + : CSSColorValue(ColorType::RGB, color_syntax) , m_properties { .r = move(r), .g = move(g), .b = move(b), .alpha = move(alpha), .name = name } { } diff --git a/Libraries/LibWeb/CSS/StyleValues/ConicGradientStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/ConicGradientStyleValue.cpp index d6ecbc64733..0d91bab20f3 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ConicGradientStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/ConicGradientStyleValue.cpp @@ -22,6 +22,8 @@ String ConicGradientStyleValue::to_string(SerializationMode mode) const builder.append("conic-gradient("sv); bool has_from_angle = m_properties.from_angle.to_degrees() != 0; bool has_at_position = !m_properties.position->is_center(); + bool has_color_space = m_properties.interpolation_method.has_value() && m_properties.interpolation_method.value().color_space != InterpolationMethod::default_color_space(m_properties.color_syntax); + if (has_from_angle) builder.appendff("from {}", m_properties.from_angle.to_string()); if (has_at_position) { @@ -29,7 +31,12 @@ String ConicGradientStyleValue::to_string(SerializationMode mode) const builder.append(' '); builder.appendff("at {}"sv, m_properties.position->to_string(mode)); } - if (has_from_angle || has_at_position) + if (has_color_space) { + if (has_from_angle || has_at_position) + builder.append(' '); + builder.append(m_properties.interpolation_method.value().to_string()); + } + if (has_from_angle || has_at_position || has_color_space) builder.append(", "sv); serialize_color_stop_list(builder, m_properties.color_stop_list, mode); builder.append(')'); diff --git a/Libraries/LibWeb/CSS/StyleValues/ConicGradientStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/ConicGradientStyleValue.h index c5a7fa2fed9..1e9ba192e6d 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ConicGradientStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/ConicGradientStyleValue.h @@ -17,10 +17,11 @@ namespace Web::CSS { class ConicGradientStyleValue final : public AbstractImageStyleValue { public: - static ValueComparingNonnullRefPtr create(Angle from_angle, ValueComparingNonnullRefPtr position, Vector color_stop_list, GradientRepeating repeating) + static ValueComparingNonnullRefPtr create(Angle from_angle, ValueComparingNonnullRefPtr position, Vector color_stop_list, GradientRepeating repeating, Optional interpolation_method) { VERIFY(!color_stop_list.is_empty()); - return adopt_ref(*new (nothrow) ConicGradientStyleValue(from_angle, move(position), move(color_stop_list), repeating)); + bool any_non_legacy = color_stop_list.find_first_index_if([](auto const& stop) { return !stop.color_stop.color->is_keyword() && stop.color_stop.color->as_color().color_syntax() == ColorSyntax::Modern; }).has_value(); + return adopt_ref(*new (nothrow) ConicGradientStyleValue(from_angle, move(position), move(color_stop_list), repeating, interpolation_method, any_non_legacy ? ColorSyntax::Modern : ColorSyntax::Legacy)); } virtual String to_string(SerializationMode) const override; @@ -34,6 +35,14 @@ public: return m_properties.color_stop_list; } + InterpolationMethod interpolation_method() const + { + if (m_properties.interpolation_method.has_value()) + return m_properties.interpolation_method.value(); + + return InterpolationMethod { .color_space = InterpolationMethod::default_color_space(m_properties.color_syntax) }; + } + float angle_degrees() const; bool is_paintable() const override { return true; } @@ -45,18 +54,19 @@ public: bool is_repeating() const { return m_properties.repeating == GradientRepeating::Yes; } private: - ConicGradientStyleValue(Angle from_angle, ValueComparingNonnullRefPtr position, Vector color_stop_list, GradientRepeating repeating) + ConicGradientStyleValue(Angle from_angle, ValueComparingNonnullRefPtr position, Vector color_stop_list, GradientRepeating repeating, Optional interpolation_method, ColorSyntax color_syntax) : AbstractImageStyleValue(Type::ConicGradient) - , m_properties { .from_angle = from_angle, .position = move(position), .color_stop_list = move(color_stop_list), .repeating = repeating } + , m_properties { .from_angle = from_angle, .position = move(position), .color_stop_list = move(color_stop_list), .repeating = repeating, .interpolation_method = interpolation_method, .color_syntax = color_syntax } { } struct Properties { - // FIXME: Support Angle from_angle; ValueComparingNonnullRefPtr position; Vector color_stop_list; GradientRepeating repeating; + Optional interpolation_method; + ColorSyntax color_syntax; bool operator==(Properties const&) const = default; } m_properties; diff --git a/Libraries/LibWeb/CSS/StyleValues/LinearGradientStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/LinearGradientStyleValue.cpp index c6e17a0fbdd..11ea664adf6 100644 --- a/Libraries/LibWeb/CSS/StyleValues/LinearGradientStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/LinearGradientStyleValue.cpp @@ -27,30 +27,45 @@ String LinearGradientStyleValue::to_string(SerializationMode mode) const case SideOrCorner::Right: return "right"sv; case SideOrCorner::TopLeft: - return "top left"sv; + return "left top"sv; case SideOrCorner::TopRight: - return "top right"sv; + return "right top"sv; case SideOrCorner::BottomLeft: - return "bottom left"sv; + return "left bottom"sv; case SideOrCorner::BottomRight: - return "bottom right"sv; + return "right bottom"sv; default: VERIFY_NOT_REACHED(); } }; + auto default_direction = m_properties.gradient_type == GradientType::WebKit ? SideOrCorner::Top : SideOrCorner::Bottom; + bool has_direction = m_properties.direction != default_direction; + bool has_color_space = m_properties.interpolation_method.has_value() && m_properties.interpolation_method.value().color_space != InterpolationMethod::default_color_space(m_properties.color_syntax); + if (m_properties.gradient_type == GradientType::WebKit) builder.append("-webkit-"sv); if (is_repeating()) builder.append("repeating-"sv); builder.append("linear-gradient("sv); - m_properties.direction.visit( - [&](SideOrCorner side_or_corner) { - return builder.appendff("{}{}, "sv, m_properties.gradient_type == GradientType::Standard ? "to "sv : ""sv, side_or_corner_to_string(side_or_corner)); - }, - [&](Angle const& angle) { - return builder.appendff("{}, "sv, angle.to_string()); - }); + if (has_direction) { + m_properties.direction.visit( + [&](SideOrCorner side_or_corner) { + builder.appendff("{}{}"sv, m_properties.gradient_type == GradientType::Standard ? "to "sv : ""sv, side_or_corner_to_string(side_or_corner)); + }, + [&](Angle const& angle) { + builder.append(angle.to_string()); + }); + + if (has_color_space) + builder.append(' '); + } + + if (has_color_space) + builder.append(m_properties.interpolation_method.value().to_string()); + + if (has_direction || has_color_space) + builder.append(", "sv); serialize_color_stop_list(builder, m_properties.color_stop_list, mode); builder.append(")"sv); diff --git a/Libraries/LibWeb/CSS/StyleValues/LinearGradientStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/LinearGradientStyleValue.h index f675a955cdf..4dace89136f 100644 --- a/Libraries/LibWeb/CSS/StyleValues/LinearGradientStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/LinearGradientStyleValue.h @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace Web::CSS { @@ -38,10 +39,11 @@ public: WebKit }; - static ValueComparingNonnullRefPtr create(GradientDirection direction, Vector color_stop_list, GradientType type, GradientRepeating repeating) + static ValueComparingNonnullRefPtr create(GradientDirection direction, Vector color_stop_list, GradientType type, GradientRepeating repeating, Optional interpolation_method) { VERIFY(!color_stop_list.is_empty()); - return adopt_ref(*new (nothrow) LinearGradientStyleValue(direction, move(color_stop_list), type, repeating)); + bool any_non_legacy = color_stop_list.find_first_index_if([](auto const& stop) { return !stop.color_stop.color->is_keyword() && stop.color_stop.color->as_color().color_syntax() == ColorSyntax::Modern; }).has_value(); + return adopt_ref(*new (nothrow) LinearGradientStyleValue(direction, move(color_stop_list), type, repeating, interpolation_method, any_non_legacy ? ColorSyntax::Modern : ColorSyntax::Legacy)); } virtual String to_string(SerializationMode) const override; @@ -53,6 +55,17 @@ public: return m_properties.color_stop_list; } + // FIXME: This (and the any_non_legacy code in the constructor) is duplicated in the separate gradient classes, + // should this logic be pulled into some kind of GradientStyleValue superclass? + // It could also contain the "gradient related things" currently in AbstractImageStyleValue.h + InterpolationMethod interpolation_method() const + { + if (m_properties.interpolation_method.has_value()) + return m_properties.interpolation_method.value(); + + return InterpolationMethod { .color_space = InterpolationMethod::default_color_space(m_properties.color_syntax) }; + } + bool is_repeating() const { return m_properties.repeating == GradientRepeating::Yes; } float angle_degrees(CSSPixelSize gradient_size) const; @@ -63,9 +76,9 @@ public: void paint(PaintContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering image_rendering) const override; private: - LinearGradientStyleValue(GradientDirection direction, Vector color_stop_list, GradientType type, GradientRepeating repeating) + LinearGradientStyleValue(GradientDirection direction, Vector color_stop_list, GradientType type, GradientRepeating repeating, Optional interpolation_method, ColorSyntax color_syntax) : AbstractImageStyleValue(Type::LinearGradient) - , m_properties { .direction = direction, .color_stop_list = move(color_stop_list), .gradient_type = type, .repeating = repeating } + , m_properties { .direction = direction, .color_stop_list = move(color_stop_list), .gradient_type = type, .repeating = repeating, .interpolation_method = interpolation_method, .color_syntax = color_syntax } { } @@ -74,6 +87,8 @@ private: Vector color_stop_list; GradientType gradient_type; GradientRepeating repeating; + Optional interpolation_method; + ColorSyntax color_syntax; bool operator==(Properties const&) const = default; } m_properties; diff --git a/Libraries/LibWeb/CSS/StyleValues/RadialGradientStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/RadialGradientStyleValue.cpp index 206532654c1..3c4bbd600e4 100644 --- a/Libraries/LibWeb/CSS/StyleValues/RadialGradientStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/RadialGradientStyleValue.cpp @@ -19,8 +19,11 @@ String RadialGradientStyleValue::to_string(SerializationMode mode) const StringBuilder builder; if (is_repeating()) builder.append("repeating-"sv); - builder.appendff("radial-gradient({} "sv, - m_properties.ending_shape == EndingShape::Circle ? "circle"sv : "ellipse"sv); + builder.appendff("radial-gradient("sv); + + bool has_size = !m_properties.size.has() || m_properties.size.get() != Extent::FarthestCorner; + bool has_position = !m_properties.position->is_center(); + bool has_color_space = m_properties.interpolation_method.has_value() && m_properties.interpolation_method.value().color_space != InterpolationMethod::default_color_space(m_properties.color_syntax); m_properties.size.visit( [&](Extent extent) { @@ -31,7 +34,8 @@ String RadialGradientStyleValue::to_string(SerializationMode mode) const case Extent::ClosestSide: return "closest-side"sv; case Extent::FarthestCorner: - return "farthest-corner"sv; + // "farthest-corner" is the default value and isn't serialized + return ""sv; case Extent::FarthestSide: return "farthest-side"sv; default: @@ -46,10 +50,23 @@ String RadialGradientStyleValue::to_string(SerializationMode mode) const builder.appendff("{} {}", ellipse_size.radius_a.to_string(), ellipse_size.radius_b.to_string()); }); - if (!m_properties.position->is_center()) - builder.appendff(" at {}"sv, m_properties.position->to_string(mode)); + if (has_position) { + if (has_size) + builder.append(' '); + + builder.appendff("at {}"sv, m_properties.position->to_string(mode)); + } + + if (has_color_space) { + if (has_size || has_position) + builder.append(' '); + + builder.append(m_properties.interpolation_method.value().to_string()); + } + + if (has_size || has_position || has_color_space) + builder.append(", "sv); - builder.append(", "sv); serialize_color_stop_list(builder, m_properties.color_stop_list, mode); builder.append(')'); return MUST(builder.to_string()); diff --git a/Libraries/LibWeb/CSS/StyleValues/RadialGradientStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/RadialGradientStyleValue.h index 109193277f9..57a90b5e99a 100644 --- a/Libraries/LibWeb/CSS/StyleValues/RadialGradientStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/RadialGradientStyleValue.h @@ -43,10 +43,11 @@ public: using Size = Variant; - static ValueComparingNonnullRefPtr create(EndingShape ending_shape, Size size, ValueComparingNonnullRefPtr position, Vector color_stop_list, GradientRepeating repeating) + static ValueComparingNonnullRefPtr create(EndingShape ending_shape, Size size, ValueComparingNonnullRefPtr position, Vector color_stop_list, GradientRepeating repeating, Optional interpolation_method) { VERIFY(!color_stop_list.is_empty()); - return adopt_ref(*new (nothrow) RadialGradientStyleValue(ending_shape, size, move(position), move(color_stop_list), repeating)); + bool any_non_legacy = color_stop_list.find_first_index_if([](auto const& stop) { return !stop.color_stop.color->is_keyword() && stop.color_stop.color->as_color().color_syntax() == ColorSyntax::Modern; }).has_value(); + return adopt_ref(*new (nothrow) RadialGradientStyleValue(ending_shape, size, move(position), move(color_stop_list), repeating, interpolation_method, any_non_legacy ? ColorSyntax::Modern : ColorSyntax::Legacy)); } virtual String to_string(SerializationMode) const override; @@ -60,6 +61,14 @@ public: return m_properties.color_stop_list; } + InterpolationMethod interpolation_method() const + { + if (m_properties.interpolation_method.has_value()) + return m_properties.interpolation_method.value(); + + return InterpolationMethod { .color_space = InterpolationMethod::default_color_space(m_properties.color_syntax) }; + } + bool is_paintable() const override { return true; } void resolve_for_size(Layout::NodeWithStyle const&, CSSPixelSize) const override; @@ -71,9 +80,9 @@ public: virtual ~RadialGradientStyleValue() override = default; private: - RadialGradientStyleValue(EndingShape ending_shape, Size size, ValueComparingNonnullRefPtr position, Vector color_stop_list, GradientRepeating repeating) + RadialGradientStyleValue(EndingShape ending_shape, Size size, ValueComparingNonnullRefPtr position, Vector color_stop_list, GradientRepeating repeating, Optional interpolation_method, ColorSyntax color_syntax) : AbstractImageStyleValue(Type::RadialGradient) - , m_properties { .ending_shape = ending_shape, .size = size, .position = move(position), .color_stop_list = move(color_stop_list), .repeating = repeating } + , m_properties { .ending_shape = ending_shape, .size = size, .position = move(position), .color_stop_list = move(color_stop_list), .repeating = repeating, .interpolation_method = interpolation_method, .color_syntax = color_syntax } { } @@ -83,6 +92,8 @@ private: ValueComparingNonnullRefPtr position; Vector color_stop_list; GradientRepeating repeating; + Optional interpolation_method; + ColorSyntax color_syntax; bool operator==(Properties const&) const = default; } m_properties; diff --git a/Libraries/LibWeb/HTML/HTMLBodyElement.cpp b/Libraries/LibWeb/HTML/HTMLBodyElement.cpp index 3fd060ce209..ac297eaa75e 100644 --- a/Libraries/LibWeb/HTML/HTMLBodyElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLBodyElement.cpp @@ -65,12 +65,12 @@ void HTMLBodyElement::apply_presentational_hints(GC::Refset_property_from_presentational_hint(CSS::PropertyID::BackgroundColor, CSS::CSSColorValue::create_from_color(color.value())); + cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BackgroundColor, CSS::CSSColorValue::create_from_color(color.value(), CSS::ColorSyntax::Legacy)); } else if (name.equals_ignoring_ascii_case("text"sv)) { // https://html.spec.whatwg.org/multipage/rendering.html#the-page:rules-for-parsing-a-legacy-colour-value-2 auto color = parse_legacy_color_value(value); if (color.has_value()) - cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Color, CSS::CSSColorValue::create_from_color(color.value())); + cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Color, CSS::CSSColorValue::create_from_color(color.value(), CSS::ColorSyntax::Legacy)); } else if (name.equals_ignoring_ascii_case("background"sv)) { VERIFY(m_background_style_value); cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BackgroundImage, *m_background_style_value); diff --git a/Libraries/LibWeb/HTML/HTMLFontElement.cpp b/Libraries/LibWeb/HTML/HTMLFontElement.cpp index 6da8a15c876..78b865ddca7 100644 --- a/Libraries/LibWeb/HTML/HTMLFontElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLFontElement.cpp @@ -129,7 +129,7 @@ void HTMLFontElement::apply_presentational_hints(GC::Refset_property_from_presentational_hint(CSS::PropertyID::Color, CSS::CSSColorValue::create_from_color(color.value())); + cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Color, CSS::CSSColorValue::create_from_color(color.value(), CSS::ColorSyntax::Legacy)); } else if (name.equals_ignoring_ascii_case("size"sv)) { // When a font element has a size attribute, the user agent is expected to use the following steps, known as the rules for parsing a legacy font size, to treat the attribute as a presentational hint setting the element's 'font-size' property: auto font_size_or_empty = parse_legacy_font_size(value); diff --git a/Libraries/LibWeb/HTML/HTMLHRElement.cpp b/Libraries/LibWeb/HTML/HTMLHRElement.cpp index 44a61bbf85c..c392494a9b4 100644 --- a/Libraries/LibWeb/HTML/HTMLHRElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLHRElement.cpp @@ -68,7 +68,7 @@ void HTMLHRElement::apply_presentational_hints(GC::Ref // the user agent is expected to treat the attribute as a presentational hint setting the element's 'color' property to the resulting color. if (name == HTML::AttributeNames::color) { if (auto parsed_value = parse_legacy_color_value(value); parsed_value.has_value()) { - cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Color, CSS::CSSColorValue::create_from_color(*parsed_value)); + cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Color, CSS::CSSColorValue::create_from_color(*parsed_value, CSS::ColorSyntax::Legacy)); } } // https://html.spec.whatwg.org/multipage/rendering.html#the-hr-element-2:maps-to-the-dimension-property diff --git a/Libraries/LibWeb/HTML/HTMLMarqueeElement.cpp b/Libraries/LibWeb/HTML/HTMLMarqueeElement.cpp index 8fd62fb03a5..19a84b4dc38 100644 --- a/Libraries/LibWeb/HTML/HTMLMarqueeElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLMarqueeElement.cpp @@ -51,7 +51,7 @@ void HTMLMarqueeElement::apply_presentational_hints(GC::Refset_property_from_presentational_hint(CSS::PropertyID::BackgroundColor, CSS::CSSColorValue::create_from_color(color.value())); + cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BackgroundColor, CSS::CSSColorValue::create_from_color(color.value(), CSS::ColorSyntax::Legacy)); } else if (name == HTML::AttributeNames::height) { // https://html.spec.whatwg.org/multipage/rendering.html#the-marquee-element-2:maps-to-the-dimension-property if (auto parsed_value = parse_dimension_value(value)) { diff --git a/Libraries/LibWeb/HTML/HTMLTableCellElement.cpp b/Libraries/LibWeb/HTML/HTMLTableCellElement.cpp index c35d06e8891..f36ec827eda 100644 --- a/Libraries/LibWeb/HTML/HTMLTableCellElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLTableCellElement.cpp @@ -59,7 +59,7 @@ void HTMLTableCellElement::apply_presentational_hints(GC::Refset_property_from_presentational_hint(CSS::PropertyID::BackgroundColor, CSS::CSSColorValue::create_from_color(color.value())); + cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BackgroundColor, CSS::CSSColorValue::create_from_color(color.value(), CSS::ColorSyntax::Legacy)); return; } if (name == HTML::AttributeNames::valign) { diff --git a/Libraries/LibWeb/HTML/HTMLTableElement.cpp b/Libraries/LibWeb/HTML/HTMLTableElement.cpp index 7dd0343d813..0673d86504c 100644 --- a/Libraries/LibWeb/HTML/HTMLTableElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLTableElement.cpp @@ -102,7 +102,7 @@ void HTMLTableElement::apply_presentational_hints(GC::Refset_property_from_presentational_hint(CSS::PropertyID::BackgroundColor, CSS::CSSColorValue::create_from_color(color.value())); + cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BackgroundColor, CSS::CSSColorValue::create_from_color(color.value(), CSS::ColorSyntax::Legacy)); return; } if (name == HTML::AttributeNames::cellspacing) { @@ -118,7 +118,7 @@ void HTMLTableElement::apply_presentational_hints(GC::Refset_property_from_presentational_hint(style_property, legacy_line_style); cascaded_properties->set_property_from_presentational_hint(width_property, CSS::LengthStyleValue::create(CSS::Length::make_px(border))); - cascaded_properties->set_property_from_presentational_hint(color_property, CSS::CSSColorValue::create_from_color(Color(128, 128, 128))); + cascaded_properties->set_property_from_presentational_hint(color_property, CSS::CSSColorValue::create_from_color(Color(128, 128, 128), CSS::ColorSyntax::Legacy)); }; apply_border_style(CSS::PropertyID::BorderLeftStyle, CSS::PropertyID::BorderLeftWidth, CSS::PropertyID::BorderLeftColor); apply_border_style(CSS::PropertyID::BorderTopStyle, CSS::PropertyID::BorderTopWidth, CSS::PropertyID::BorderTopColor); @@ -131,7 +131,7 @@ void HTMLTableElement::apply_presentational_hints(GC::Refset_property_from_presentational_hint(CSS::PropertyID::BorderTopColor, color_value); cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BorderRightColor, color_value); cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BorderBottomColor, color_value); diff --git a/Libraries/LibWeb/HTML/HTMLTableRowElement.cpp b/Libraries/LibWeb/HTML/HTMLTableRowElement.cpp index fcdb8bcbe95..5a01ec24f44 100644 --- a/Libraries/LibWeb/HTML/HTMLTableRowElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLTableRowElement.cpp @@ -58,7 +58,7 @@ void HTMLTableRowElement::apply_presentational_hints(GC::Refset_property_from_presentational_hint(CSS::PropertyID::BackgroundColor, CSS::CSSColorValue::create_from_color(color.value())); + cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BackgroundColor, CSS::CSSColorValue::create_from_color(color.value(), CSS::ColorSyntax::Legacy)); } else if (name == HTML::AttributeNames::background) { // https://html.spec.whatwg.org/multipage/rendering.html#tables-2:encoding-parsing-and-serializing-a-url if (auto parsed_value = document().encoding_parse_url(value); parsed_value.has_value()) diff --git a/Libraries/LibWeb/HTML/HTMLTableSectionElement.cpp b/Libraries/LibWeb/HTML/HTMLTableSectionElement.cpp index 29dfe580246..572ce80ad7c 100644 --- a/Libraries/LibWeb/HTML/HTMLTableSectionElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLTableSectionElement.cpp @@ -122,7 +122,7 @@ void HTMLTableSectionElement::apply_presentational_hints(GC::Refset_property_from_presentational_hint(CSS::PropertyID::BackgroundColor, CSS::CSSColorValue::create_from_color(color.value())); + cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BackgroundColor, CSS::CSSColorValue::create_from_color(color.value(), CSS::ColorSyntax::Legacy)); } else if (name == HTML::AttributeNames::height) { if (auto parsed_value = parse_dimension_value(value)) cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Height, parsed_value.release_nonnull()); diff --git a/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp b/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp index 454820051bf..d4a5ed1fd46 100644 --- a/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp +++ b/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp @@ -315,6 +315,66 @@ static ColorStopList expand_repeat_length(ColorStopList const& color_stop_list, return color_stop_list_with_expanded_repeat; } +static SkGradientShader::Interpolation to_skia_interpolation(CSS::InterpolationMethod interpolation_method) +{ + SkGradientShader::Interpolation interpolation; + + switch (interpolation_method.color_space) { + case CSS::GradientSpace::sRGB: + interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kSRGB; + break; + case CSS::GradientSpace::sRGBLinear: + interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kSRGBLinear; + break; + case CSS::GradientSpace::Lab: + interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kLab; + break; + case CSS::GradientSpace::OKLab: + interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kOKLab; + break; + case CSS::GradientSpace::HSL: + interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kHSL; + break; + case CSS::GradientSpace::HWB: + interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kHWB; + break; + case CSS::GradientSpace::LCH: + interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kLCH; + break; + case CSS::GradientSpace::OKLCH: + interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kOKLCH; + break; + case CSS::GradientSpace::DisplayP3: + case CSS::GradientSpace::A98RGB: + case CSS::GradientSpace::ProPhotoRGB: + case CSS::GradientSpace::Rec2020: + case CSS::GradientSpace::XYZD50: + case CSS::GradientSpace::XYZD65: + dbgln("FIXME: Unsupported gradient color space"); + interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kOKLab; + break; + } + + switch (interpolation_method.hue_method) { + case CSS::HueMethod::Shorter: + interpolation.fHueMethod = SkGradientShader::Interpolation::HueMethod::kShorter; + break; + case CSS::HueMethod::Longer: + interpolation.fHueMethod = SkGradientShader::Interpolation::HueMethod::kLonger; + break; + case CSS::HueMethod::Increasing: + interpolation.fHueMethod = SkGradientShader::Interpolation::HueMethod::kIncreasing; + break; + case CSS::HueMethod::Decreasing: + interpolation.fHueMethod = SkGradientShader::Interpolation::HueMethod::kDecreasing; + break; + } + + interpolation.fInPremul = SkGradientShader::Interpolation::InPremul::kYes; + + return interpolation; +} + void DisplayListPlayerSkia::paint_linear_gradient(PaintLinearGradient const& command) { auto const& linear_gradient_data = command.linear_gradient_data; @@ -351,9 +411,7 @@ void DisplayListPlayerSkia::paint_linear_gradient(PaintLinearGradient const& com matrix.setRotate(linear_gradient_data.gradient_angle, center.x(), center.y()); auto color_space = SkColorSpace::MakeSRGB(); - SkGradientShader::Interpolation interpolation = {}; - interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kSRGB; - interpolation.fInPremul = SkGradientShader::Interpolation::InPremul::kYes; + auto interpolation = to_skia_interpolation(linear_gradient_data.interpolation_method); auto shader = SkGradientShader::MakeLinear(points.data(), colors.data(), color_space, positions.data(), positions.size(), SkTileMode::kClamp, interpolation, &matrix); SkPaint paint; @@ -766,9 +824,7 @@ void DisplayListPlayerSkia::paint_radial_gradient(PaintRadialGradient const& com tile_mode = SkTileMode::kRepeat; auto color_space = SkColorSpace::MakeSRGB(); - SkGradientShader::Interpolation interpolation = {}; - interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kSRGB; - interpolation.fInPremul = SkGradientShader::Interpolation::InPremul::kYes; + auto interpolation = to_skia_interpolation(radial_gradient_data.interpolation_method); auto shader = SkGradientShader::MakeRadial(center, size.height(), colors.data(), color_space, positions.data(), positions.size(), tile_mode, interpolation, &matrix); SkPaint paint; @@ -805,9 +861,7 @@ void DisplayListPlayerSkia::paint_conic_gradient(PaintConicGradient const& comma SkMatrix matrix; matrix.setRotate(-90 + conic_gradient_data.start_angle, center.x(), center.y()); auto color_space = SkColorSpace::MakeSRGB(); - SkGradientShader::Interpolation interpolation = {}; - interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kSRGB; - interpolation.fInPremul = SkGradientShader::Interpolation::InPremul::kYes; + auto interpolation = to_skia_interpolation(conic_gradient_data.interpolation_method); auto shader = SkGradientShader::MakeSweep(center.x(), center.y(), colors.data(), color_space, positions.data(), positions.size(), SkTileMode::kRepeat, 0, 360, interpolation, &matrix); SkPaint paint; diff --git a/Libraries/LibWeb/Painting/GradientData.h b/Libraries/LibWeb/Painting/GradientData.h index 9cd6140ad8c..9e31ad953ba 100644 --- a/Libraries/LibWeb/Painting/GradientData.h +++ b/Libraries/LibWeb/Painting/GradientData.h @@ -10,6 +10,7 @@ #include #include #include +#include #include namespace Web::Painting { @@ -24,15 +25,18 @@ struct ColorStopData { struct LinearGradientData { float gradient_angle; ColorStopData color_stops; + CSS::InterpolationMethod interpolation_method; }; struct ConicGradientData { float start_angle; ColorStopData color_stops; + CSS::InterpolationMethod interpolation_method; }; struct RadialGradientData { ColorStopData color_stops; + CSS::InterpolationMethod interpolation_method; }; } diff --git a/Libraries/LibWeb/Painting/GradientPainting.cpp b/Libraries/LibWeb/Painting/GradientPainting.cpp index 7fa45bd1997..703cc81240d 100644 --- a/Libraries/LibWeb/Painting/GradientPainting.cpp +++ b/Libraries/LibWeb/Painting/GradientPainting.cpp @@ -121,7 +121,7 @@ LinearGradientData resolve_linear_gradient_data(Layout::NodeWithStyle const& nod }, linear_gradient.is_repeating()); - return { gradient_angle, resolved_color_stops }; + return { gradient_angle, resolved_color_stops, linear_gradient.interpolation_method() }; } ConicGradientData resolve_conic_gradient_data(Layout::NodeWithStyle const& node, CSS::ConicGradientStyleValue const& conic_gradient) @@ -132,7 +132,7 @@ ConicGradientData resolve_conic_gradient_data(Layout::NodeWithStyle const& node, return angle_percentage.resolved(node, one_turn).to_degrees() / one_turn.to_degrees(); }, conic_gradient.is_repeating()); - return { conic_gradient.angle_degrees(), resolved_color_stops }; + return { conic_gradient.angle_degrees(), resolved_color_stops, conic_gradient.interpolation_method() }; } RadialGradientData resolve_radial_gradient_data(Layout::NodeWithStyle const& node, CSSPixelSize gradient_size, CSS::RadialGradientStyleValue const& radial_gradient) @@ -143,7 +143,7 @@ RadialGradientData resolve_radial_gradient_data(Layout::NodeWithStyle const& nod return length_percentage.to_px(node, gradient_size.width()).to_float() / gradient_size.width().to_float(); }, radial_gradient.is_repeating()); - return { resolved_color_stops }; + return { resolved_color_stops, radial_gradient.interpolation_method() }; } } diff --git a/Tests/LibWeb/Screenshot/expected/gradient-interpolation-method-ref.html b/Tests/LibWeb/Screenshot/expected/gradient-interpolation-method-ref.html new file mode 100644 index 00000000000..ebb839ff0b0 --- /dev/null +++ b/Tests/LibWeb/Screenshot/expected/gradient-interpolation-method-ref.html @@ -0,0 +1,10 @@ + + diff --git a/Tests/LibWeb/Screenshot/images/gradient-interpolation-method-ref.png b/Tests/LibWeb/Screenshot/images/gradient-interpolation-method-ref.png new file mode 100644 index 0000000000000000000000000000000000000000..bacf5b92544ae8801549cb41bffe60c5858bf147 GIT binary patch literal 4913 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYV2a>iV_;yIRn}C%z##O))5S5QV$R#UxAPvK zkZirUKSjOre>m?1g_+wzcM9Zw3{X)mPcMHI$t_T^f;ltUt(kw{jBC~JSs54v3Vz^V6z~Iosz{tRm#KOVAAn>`$YVp=rS68iSJHB|M zd;k9Iwa=^ffziMJuRi~c-_`K`RJ;QNLxTh(69dB$LdGc%W2G~$f7aB$`YQkG>-AS( z>0kbu{k3Xkn0{&Y4DZmrA^&am*H&%4SvvdJ?FYMf*B>vw_Bj1|sX-k7x%JJjKmPo* z%kTQV`>7^&_2;c(tN+~Dyz}?<_`B)5-_5k`|7Krx?cN_U5e5U;lC! z&$s>mt2*cW`~9nrU(c`K@189`*Sv1}! z)!*)X{PXeF#jhFbzwWj%`akpa>$IBc_m6*B?bzp69hZLnde&FF-F53%eb4%8HuwLE zRraf{?&p5+`;$0<6f&Hn6e&?Z`&x5q>#BdDq5s#d3frERd39Cj{BzI0Kc9blPXFtu z*!#y;zxsUdSJlpCW!LV!`+oD!th>K={NDJi?(VAf&#vd>RKNdoBki}|{kPB8Pq)AR zZSU{P*H3@9-}Nv5-QORZ>)!31cHTy+`cJJ~?Y*CSp5J@?-H z?CNi87T5MY&wpQAx_&t#U-@QL%wfEis*{mOAe{zGe^$3YHf(4tte(3b;FMkC6{S;KX zGh}~E-tjW)uDt6i9lGxR+udTZNX;+qQ*yUT>Y_{!M|JUZ)!_uoc z>yJk0UazrReXP26UjH+{<5^du@6O+9{l2btwzXVz%=-Gihnw?`zn#VY&2H!ZigRE3 zPZzx3ymwXF{_6*Szk6p`)xPak_U`kKYW?rOe;`?1z5U~j&DFB&s~%_`+D`~-E==6w>Lfh{_M50`%YJXzJK!Vj-RsMZ^{0z@r$2ZzsGz2UzxxA_I-a_ zG5=V0S)F~@*Vljl{3@)=y}I7CcG>6ew@nX}Kh;O82u5g%IrI8u&%M=q_l95H8nt!R za{KA}Yku~<&MsS{zvlnl_xXSSKmHZBw(9D1`|I-~viE+iZ~LlTTUL`MdtiR+e%!U{ z2n|f6cEtz{fithy-(I!WPX1R`octMUtNL&E!k_)=d;I_Py_4xGef!ha+uhhKe{QGj z?`Ku@XUpFG``q{J-0@@Cw@R+PdYa&O|4#JzyYvsOu zx|{c(zxa2rxbEuL7m}YJfBRSQx8hy$@z>1fZ=B04jXk^fT~BQL@!x-b7TT4?${c_G ze*W>>d%wTD_q*i%+NiyuSEED!t_Z6Sxw?PVEBX2V?=jTa|6>~I?Kw#0zh|C*=H)HZ zZ+;A_waT;4Eq>Eo{rYqN$B#G4{yYw5PnQ4wI$^!l-23gXuYb?l@w{sP+tRmh4DWsZ z`1STr_w4WI-dD@L-)>`nyZq<8+n@j1o}X82x39K+{e9{EzaJ-`E|=@KHGUqx##8Ql zz5eaTpMTG_Pu_j}=f2N77uSB9xA%L`(>ceV$@lAix0buV?|sJKe|w+q`dWSWegF0R z_w(1@?#o~Q{hjRH`jU5FQ_fcXsVcbhdyoJ8?KjyU>>h2lLHZ()X4~JutE;L)LvPpp zdU5Y*gQP^Kg;c_e%$x_Jg?vLb??vV*B->l_r8B~v;SOpwwzSFZT;S|@7(VjKe`{6 zlUuKTJj=S=aQ(d9`+qk5I<=84zHIBCNs(t}&aJQ8dFI*q_h8GEnMzwV! z2eyya+taWwI_nMl&W&bYT+WYzU zyyL%h&!7JmyYKn8n)>sbpU>&fzyI*{_haw>{kZXI?(yXFXMT!iPdfhh{^@T&{?t7` zf8$-@zV|inGUo41e{Y*?`F!Kg9t==85kHCJYD@<);T3K0RV=QzuN!+ literal 0 HcmV?d00001 diff --git a/Tests/LibWeb/Screenshot/input/gradient-interpolation-method.html b/Tests/LibWeb/Screenshot/input/gradient-interpolation-method.html new file mode 100644 index 00000000000..115fa2ad5f9 --- /dev/null +++ b/Tests/LibWeb/Screenshot/input/gradient-interpolation-method.html @@ -0,0 +1,19 @@ + + + +
+
+
+
+
+
+
+
+
+
+