diff --git a/Libraries/LibWeb/CSS/Parser/GradientParsing.cpp b/Libraries/LibWeb/CSS/Parser/GradientParsing.cpp index 0f198d4e4b8..1e135bb9cbc 100644 --- a/Libraries/LibWeb/CSS/Parser/GradientParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/GradientParsing.cpp @@ -18,8 +18,7 @@ namespace Web::CSS::Parser { -template -Optional> Parser::parse_color_stop_list(TokenStream& tokens, auto parse_position) +Optional> Parser::parse_color_stop_list(TokenStream& tokens, auto parse_position) { enum class ElementType { Garbage, @@ -27,20 +26,20 @@ Optional> Parser::parse_color_stop_list(TokenStream ElementType { + auto parse_color_stop_list_element = [&](auto& element) -> ElementType { tokens.discard_whitespace(); if (!tokens.has_next_token()) return ElementType::Garbage; RefPtr color; - Optional position; - Optional second_position; - if (position = parse_position(tokens); position.has_value()) { + RefPtr position; + RefPtr second_position; + if (position = parse_position(tokens); position) { // [ ] or [] tokens.discard_whitespace(); // if (!tokens.has_next_token() || tokens.next_token().is(Token::Type::Comma)) { - element.transition_hint = typename TElement::ColorHint { *position }; + element.transition_hint = ColorStopListElement::ColorHint { *position }; return ElementType::ColorHint; } // @@ -60,24 +59,24 @@ Optional> Parser::parse_color_stop_list(TokenStreamhas_value()) + if (!stop_position) return ElementType::Garbage; tokens.discard_whitespace(); } } } - element.color_stop = typename TElement::ColorStop { color, position, second_position }; + element.color_stop = ColorStopListElement::ColorStop { color, position, second_position }; return ElementType::ColorStop; }; - TElement first_element {}; + ColorStopListElement first_element {}; if (parse_color_stop_list_element(first_element) != ElementType::ColorStop) return {}; - Vector color_stops { first_element }; + Vector color_stops { first_element }; while (tokens.has_next_token()) { - TElement list_element {}; + ColorStopListElement list_element {}; tokens.discard_whitespace(); if (!tokens.consume_a_token().is(Token::Type::Comma)) return {}; @@ -110,34 +109,34 @@ static StringView consume_if_starts_with(StringView str, StringView start, auto return str; } -Optional> Parser::parse_linear_color_stop_list(TokenStream& tokens) +Optional> Parser::parse_linear_color_stop_list(TokenStream& tokens) { // = // , [ ? , ]# - return parse_color_stop_list( + return parse_color_stop_list( tokens, - [&](auto& it) { return parse_length_percentage(it); }); + [&](auto& it) { return parse_length_percentage_value(it); }); } -Optional> Parser::parse_angular_color_stop_list(TokenStream& tokens) +Optional> Parser::parse_angular_color_stop_list(TokenStream& tokens) { auto context_guard = push_temporary_value_parsing_context(SpecialContext::AngularColorStopList); // = // , [ ? , ]# - return parse_color_stop_list( + return parse_color_stop_list( tokens, - [&](TokenStream& it) -> Optional { + [&](TokenStream& it) -> RefPtr { if (tokens.next_token().is(Token::Type::Number)) { auto transaction = tokens.begin_transaction(); auto numeric_value = tokens.consume_a_token().token().number_value(); if (numeric_value == 0) { transaction.commit(); - return Angle::make_degrees(0); + return AngleStyleValue::create(Angle::make_degrees(0)); } } - return parse_angle_percentage(it); + return parse_angle_percentage_value(it); }); } diff --git a/Libraries/LibWeb/CSS/Parser/Parser.h b/Libraries/LibWeb/CSS/Parser/Parser.h index f4532725e63..42a16db4097 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Libraries/LibWeb/CSS/Parser/Parser.h @@ -356,10 +356,9 @@ private: RefPtr parse_fit_content_value(TokenStream&); - template - 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_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&); diff --git a/Libraries/LibWeb/CSS/StyleValues/AbstractImageStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/AbstractImageStyleValue.cpp index a5ce1d9e51b..e690e3d8b09 100644 --- a/Libraries/LibWeb/CSS/StyleValues/AbstractImageStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/AbstractImageStyleValue.cpp @@ -16,4 +16,23 @@ GC::Ref AbstractImageStyleValue::reify(JS::Realm& realm, FlyStrin return CSSImageValue::create(realm, *this); } +void serialize_color_stop_list(StringBuilder& builder, Vector const& color_stop_list, SerializationMode mode) +{ + bool first = true; + for (auto const& element : color_stop_list) { + if (!first) + builder.append(", "sv); + + if (element.transition_hint.has_value()) + builder.appendff("{}, "sv, element.transition_hint->value->to_string(mode)); + + builder.append(element.color_stop.color->to_string(mode)); + if (element.color_stop.position) + builder.appendff(" {}"sv, element.color_stop.position->to_string(mode)); + if (element.color_stop.second_position) + builder.appendff(" {}"sv, element.color_stop.second_position->to_string(mode)); + first = false; + } +} + } diff --git a/Libraries/LibWeb/CSS/StyleValues/AbstractImageStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/AbstractImageStyleValue.h index bf98094981d..b6c4949b1ef 100644 --- a/Libraries/LibWeb/CSS/StyleValues/AbstractImageStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/AbstractImageStyleValue.h @@ -157,45 +157,22 @@ struct InterpolationMethod { bool operator==(InterpolationMethod const&) const = default; }; -template struct ColorStopListElement { - using PositionType = TPosition; struct ColorHint { - TPosition value; - inline bool operator==(ColorHint const&) const = default; + NonnullRefPtr value; + bool operator==(ColorHint const&) const = default; }; Optional transition_hint; struct ColorStop { RefPtr color; - Optional position; - Optional second_position = {}; - inline bool operator==(ColorStop const&) const = default; + RefPtr position; + RefPtr second_position {}; + bool operator==(ColorStop const&) const = default; } color_stop; - inline bool operator==(ColorStopListElement const&) const = default; + bool operator==(ColorStopListElement const&) const = default; }; - -using LinearColorStopListElement = ColorStopListElement; -using AngularColorStopListElement = ColorStopListElement; - -static void serialize_color_stop_list(StringBuilder& builder, auto const& color_stop_list, SerializationMode mode) -{ - bool first = true; - for (auto const& element : color_stop_list) { - if (!first) - builder.append(", "sv); - - if (element.transition_hint.has_value()) - builder.appendff("{}, "sv, element.transition_hint->value.to_string(mode)); - - builder.append(element.color_stop.color->to_string(mode)); - for (auto position : Array { &element.color_stop.position, &element.color_stop.second_position }) { - if (position->has_value()) - builder.appendff(" {}"sv, (*position)->to_string(mode)); - } - first = false; - } -} +void serialize_color_stop_list(StringBuilder&, Vector const&, SerializationMode); } diff --git a/Libraries/LibWeb/CSS/StyleValues/ConicGradientStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/ConicGradientStyleValue.h index 8ddd72dd362..92db4d2f470 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ConicGradientStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/ConicGradientStyleValue.h @@ -9,8 +9,6 @@ #pragma once -#include -#include #include #include @@ -18,7 +16,7 @@ namespace Web::CSS { class ConicGradientStyleValue final : public AbstractImageStyleValue { public: - static ValueComparingNonnullRefPtr create(ValueComparingRefPtr from_angle, ValueComparingNonnullRefPtr position, Vector color_stop_list, GradientRepeating repeating, Optional interpolation_method) + static ValueComparingNonnullRefPtr create(ValueComparingRefPtr from_angle, ValueComparingNonnullRefPtr position, Vector color_stop_list, GradientRepeating repeating, Optional interpolation_method) { VERIFY(!color_stop_list.is_empty()); 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(); @@ -31,7 +29,7 @@ public: virtual bool equals(StyleValue const& other) const override; - Vector const& color_stop_list() const + Vector const& color_stop_list() const { return m_properties.color_stop_list; } @@ -55,7 +53,7 @@ public: bool is_repeating() const { return m_properties.repeating == GradientRepeating::Yes; } private: - ConicGradientStyleValue(ValueComparingRefPtr from_angle, ValueComparingNonnullRefPtr position, Vector color_stop_list, GradientRepeating repeating, Optional interpolation_method, ColorSyntax color_syntax) + ConicGradientStyleValue(ValueComparingRefPtr from_angle, ValueComparingNonnullRefPtr position, Vector color_stop_list, GradientRepeating repeating, Optional interpolation_method, ColorSyntax color_syntax) : AbstractImageStyleValue(Type::ConicGradient) , m_properties { .from_angle = move(from_angle), .position = move(position), .color_stop_list = move(color_stop_list), .repeating = repeating, .interpolation_method = interpolation_method, .color_syntax = color_syntax } { @@ -64,7 +62,7 @@ private: struct Properties { ValueComparingRefPtr from_angle; ValueComparingNonnullRefPtr position; - Vector color_stop_list; + Vector color_stop_list; GradientRepeating repeating; Optional interpolation_method; ColorSyntax color_syntax; diff --git a/Libraries/LibWeb/CSS/StyleValues/LinearGradientStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/LinearGradientStyleValue.h index a81df9bda5a..6d8f9ea72ac 100644 --- a/Libraries/LibWeb/CSS/StyleValues/LinearGradientStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/LinearGradientStyleValue.h @@ -39,7 +39,7 @@ public: WebKit }; - static ValueComparingNonnullRefPtr create(GradientDirection direction, Vector color_stop_list, GradientType type, GradientRepeating repeating, Optional interpolation_method) + static ValueComparingNonnullRefPtr create(GradientDirection direction, Vector color_stop_list, GradientType type, GradientRepeating repeating, Optional interpolation_method) { VERIFY(!color_stop_list.is_empty()); 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(); @@ -50,7 +50,7 @@ public: virtual ~LinearGradientStyleValue() override = default; virtual bool equals(StyleValue const& other) const override; - Vector const& color_stop_list() const + Vector const& color_stop_list() const { return m_properties.color_stop_list; } @@ -76,7 +76,7 @@ public: void paint(DisplayListRecordingContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering image_rendering) const override; private: - LinearGradientStyleValue(GradientDirection direction, Vector color_stop_list, GradientType type, GradientRepeating repeating, Optional interpolation_method, ColorSyntax color_syntax) + 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, .interpolation_method = interpolation_method, .color_syntax = color_syntax } { @@ -84,7 +84,7 @@ private: struct Properties { GradientDirection direction; - Vector color_stop_list; + Vector color_stop_list; GradientType gradient_type; GradientRepeating repeating; Optional interpolation_method; diff --git a/Libraries/LibWeb/CSS/StyleValues/RadialGradientStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/RadialGradientStyleValue.h index f7cb5f826c5..dfa66c469da 100644 --- a/Libraries/LibWeb/CSS/StyleValues/RadialGradientStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/RadialGradientStyleValue.h @@ -43,7 +43,7 @@ public: using Size = Variant; - static ValueComparingNonnullRefPtr create(EndingShape ending_shape, Size size, ValueComparingNonnullRefPtr position, Vector color_stop_list, GradientRepeating repeating, Optional interpolation_method) + 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()); 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(); @@ -56,7 +56,7 @@ public: virtual bool equals(StyleValue const& other) const override; - Vector const& color_stop_list() const + Vector const& color_stop_list() const { return m_properties.color_stop_list; } @@ -80,7 +80,7 @@ public: virtual ~RadialGradientStyleValue() override = default; private: - RadialGradientStyleValue(EndingShape ending_shape, Size size, ValueComparingNonnullRefPtr position, Vector color_stop_list, GradientRepeating repeating, Optional interpolation_method, ColorSyntax color_syntax) + 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, .interpolation_method = interpolation_method, .color_syntax = color_syntax } { @@ -90,7 +90,7 @@ private: EndingShape ending_shape; Size size; ValueComparingNonnullRefPtr position; - Vector color_stop_list; + Vector color_stop_list; GradientRepeating repeating; Optional interpolation_method; ColorSyntax color_syntax; diff --git a/Libraries/LibWeb/Painting/GradientPainting.cpp b/Libraries/LibWeb/Painting/GradientPainting.cpp index f4d1def6a07..c6e70101482 100644 --- a/Libraries/LibWeb/Painting/GradientPainting.cpp +++ b/Libraries/LibWeb/Painting/GradientPainting.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2022-2023, MacDue + * Copyright (c) 2025, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ @@ -7,7 +8,10 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -16,13 +20,13 @@ namespace Web::Painting { -static ColorStopData resolve_color_stop_positions(Layout::NodeWithStyle const& node, auto const& color_stop_list, auto resolve_position_to_float, bool repeating) +static ColorStopData resolve_color_stop_positions(Layout::NodeWithStyle const& node, Vector const& color_stop_list, auto resolve_position_to_float, bool repeating) { VERIFY(!color_stop_list.is_empty()); ColorStopList resolved_color_stops; auto color_stop_length = [&](auto& stop) { - return stop.color_stop.second_position.has_value() ? 2 : 1; + return stop.color_stop.second_position ? 2 : 1; }; size_t expanded_size = 0; @@ -46,7 +50,7 @@ static ColorStopData resolve_color_stop_positions(Layout::NodeWithStyle const& n // set its position to be equal to the largest specified position of any color stop // or transition hint before it. auto max_previous_color_stop_or_hint = resolved_color_stops[0].position; - auto resolve_stop_position = [&](auto& position) { + auto resolve_stop_position = [&](CSS::StyleValue const& position) { float value = resolve_position_to_float(position); value = max(value, max_previous_color_stop_or_hint); max_previous_color_stop_or_hint = value; @@ -55,10 +59,10 @@ static ColorStopData resolve_color_stop_positions(Layout::NodeWithStyle const& n size_t resolved_index = 0; for (auto& stop : color_stop_list) { if (stop.transition_hint.has_value()) - resolved_color_stops[resolved_index].transition_hint = resolve_stop_position(stop.transition_hint->value); - if (stop.color_stop.position.has_value()) + resolved_color_stops[resolved_index].transition_hint = resolve_stop_position(*stop.transition_hint->value); + if (stop.color_stop.position) resolved_color_stops[resolved_index].position = resolve_stop_position(*stop.color_stop.position); - if (stop.color_stop.second_position.has_value()) + if (stop.color_stop.second_position) resolved_color_stops[++resolved_index].position = resolve_stop_position(*stop.color_stop.second_position); ++resolved_index; } @@ -116,9 +120,17 @@ LinearGradientData resolve_linear_gradient_data(Layout::NodeWithStyle const& nod auto gradient_angle = linear_gradient.angle_degrees(gradient_size); auto gradient_length_px = Gfx::calculate_gradient_length(gradient_size.to_type(), gradient_angle); + CSS::CalculationResolutionContext context { + .percentage_basis = CSS::Length::make_px(gradient_length_px), + .length_resolution_context = CSS::Length::ResolutionContext::for_layout_node(node), + }; auto resolved_color_stops = resolve_color_stop_positions( - node, linear_gradient.color_stop_list(), [&](auto const& length_percentage) { - return length_percentage.to_px(node, CSSPixels::nearest_value_for(gradient_length_px)).to_float() / static_cast(gradient_length_px); + node, linear_gradient.color_stop_list(), [&](auto const& position) -> float { + if (position.is_length()) + return position.as_length().length().to_px_without_rounding(*context.length_resolution_context) / gradient_length_px; + if (position.is_percentage()) + return position.as_percentage().percentage().as_fraction(); + return position.as_calculated().resolve_length(context)->to_px_without_rounding(*context.length_resolution_context) / gradient_length_px; }, linear_gradient.is_repeating()); @@ -127,24 +139,38 @@ LinearGradientData resolve_linear_gradient_data(Layout::NodeWithStyle const& nod ConicGradientData resolve_conic_gradient_data(Layout::NodeWithStyle const& node, CSS::ConicGradientStyleValue const& conic_gradient) { - CSS::Angle one_turn(360.0f, CSS::AngleUnit::Deg); - auto resolved_color_stops = resolve_color_stop_positions( - node, conic_gradient.color_stop_list(), [&](auto const& angle_percentage) { - return angle_percentage.resolved(node, one_turn).to_degrees() / one_turn.to_degrees(); - }, - conic_gradient.is_repeating()); + CSS::Angle const one_turn { 360.0f, CSS::AngleUnit::Deg }; CSS::CalculationResolutionContext context { + .percentage_basis = one_turn, .length_resolution_context = CSS::Length::ResolutionContext::for_layout_node(node), }; + auto resolved_color_stops = resolve_color_stop_positions( + node, conic_gradient.color_stop_list(), [&](auto const& position) -> float { + if (position.is_angle()) + return position.as_angle().angle().to_degrees() / one_turn.to_degrees(); + if (position.is_percentage()) + return position.as_percentage().percentage().as_fraction(); + return position.as_calculated().resolve_angle(context)->to_degrees() / one_turn.to_degrees(); + }, + conic_gradient.is_repeating()); return { conic_gradient.angle_degrees(context), resolved_color_stops, conic_gradient.interpolation_method() }; } RadialGradientData resolve_radial_gradient_data(Layout::NodeWithStyle const& node, CSSPixelSize gradient_size, CSS::RadialGradientStyleValue const& radial_gradient) { + CSS::CalculationResolutionContext context { + .percentage_basis = CSS::Length::make_px(gradient_size.width()), + .length_resolution_context = CSS::Length::ResolutionContext::for_layout_node(node), + }; + // Start center, goes right to ending point, where the gradient line intersects the ending shape auto resolved_color_stops = resolve_color_stop_positions( - node, radial_gradient.color_stop_list(), [&](auto const& length_percentage) { - return length_percentage.to_px(node, gradient_size.width()).to_float() / gradient_size.width().to_float(); + node, radial_gradient.color_stop_list(), [&](auto const& position) -> float { + if (position.is_length()) + return position.as_length().length().to_px_without_rounding(*context.length_resolution_context) / gradient_size.width().to_float(); + if (position.is_percentage()) + return position.as_percentage().percentage().as_fraction(); + return position.as_calculated().resolve_length(context)->to_px_without_rounding(*context.length_resolution_context) / gradient_size.width().to_float(); }, radial_gradient.is_repeating()); return { resolved_color_stops, radial_gradient.interpolation_method() }; diff --git a/Tests/LibWeb/Screenshot/images/css-gradients-ref.png b/Tests/LibWeb/Screenshot/images/css-gradients-ref.png index e6f2e864a8c..7030d3ee4a4 100644 Binary files a/Tests/LibWeb/Screenshot/images/css-gradients-ref.png and b/Tests/LibWeb/Screenshot/images/css-gradients-ref.png differ diff --git a/Tests/LibWeb/Screenshot/input/css-gradients.html b/Tests/LibWeb/Screenshot/input/css-gradients.html index 6e4de61b6e0..e8b37d37bbc 100644 --- a/Tests/LibWeb/Screenshot/input/css-gradients.html +++ b/Tests/LibWeb/Screenshot/input/css-gradients.html @@ -1,6 +1,6 @@ - +