Parcourir la source

LibWeb: Parse `radial-gradient()`s

MacDue il y a 2 ans
Parent
commit
22a7611e1c

+ 150 - 3
Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp

@@ -2579,8 +2579,8 @@ RefPtr<StyleValue> Parser::parse_conic_gradient_function(ComponentValue const& c
     bool got_color_interpolation_method = false;
     bool got_at_position = false;
     while (token.is(Token::Type::Ident)) {
-        auto token_string = token.token().ident();
         auto consume_identifier = [&](auto identifier) {
+            auto token_string = token.token().ident();
             if (token_string.equals_ignoring_case(identifier)) {
                 (void)tokens.next_token();
                 tokens.skip_whitespace();
@@ -2644,6 +2644,151 @@ RefPtr<StyleValue> Parser::parse_conic_gradient_function(ComponentValue const& c
     return ConicGradientStyleValue::create(from_angle, at_position, move(*color_stops), repeating_gradient);
 }
 
+RefPtr<StyleValue> Parser::parse_radial_gradient_function(ComponentValue const& component_value)
+{
+    using EndingShape = RadialGradientStyleValue::EndingShape;
+    using Extent = RadialGradientStyleValue::Extent;
+    using CircleSize = RadialGradientStyleValue::CircleSize;
+    using EllipseSize = RadialGradientStyleValue::EllipseSize;
+    using Size = RadialGradientStyleValue::Size;
+
+    if (!component_value.is_function())
+        return {};
+
+    auto function_name = component_value.function().name();
+
+    if (!function_name.equals_ignoring_case("radial-gradient"sv))
+        return {};
+
+    TokenStream tokens { component_value.function().values() };
+    tokens.skip_whitespace();
+    if (!tokens.has_next_token())
+        return {};
+
+    bool expect_comma = false;
+
+    auto commit_value = [&]<typename... T>(auto value, T&... transactions) {
+        (transactions.commit(), ...);
+        return value;
+    };
+
+    // radial-gradient( [ <ending-shape> || <size> ]? [ at <position> ]? , <color-stop-list> )
+
+    Size size = Extent::FarthestCorner;
+    EndingShape ending_shape = EndingShape::Circle;
+    PositionValue at_position = PositionValue::center();
+
+    auto parse_ending_shape = [&]() -> Optional<EndingShape> {
+        auto transaction = tokens.begin_transaction();
+        tokens.skip_whitespace();
+        auto& token = tokens.next_token();
+        if (!token.is(Token::Type::Ident))
+            return {};
+        auto ident = token.token().ident();
+        if (ident.equals_ignoring_case("circle"sv))
+            return commit_value(EndingShape::Circle, transaction);
+        if (ident.equals_ignoring_case("ellipse"sv))
+            return commit_value(EndingShape::Ellipse, transaction);
+        return {};
+    };
+
+    auto parse_extent_keyword = [](StringView keyword) -> Optional<Extent> {
+        if (keyword.equals_ignoring_case("closest-corner"sv))
+            return Extent::ClosestCorner;
+        if (keyword.equals_ignoring_case("closest-side"sv))
+            return Extent::ClosestSide;
+        if (keyword.equals_ignoring_case("farthest-corner"sv))
+            return Extent::FarthestCorner;
+        if (keyword.equals_ignoring_case("farthest-side"sv))
+            return Extent::FarthestSide;
+        return {};
+    };
+
+    auto parse_size = [&]() -> Optional<Size> {
+        // <size> =
+        //      <extent-keyword>              |
+        //      <length [0,∞]>                |
+        //      <length-percentage [0,∞]>{2}
+        auto transaction_size = tokens.begin_transaction();
+        tokens.skip_whitespace();
+        if (!tokens.has_next_token())
+            return {};
+        auto& token = tokens.next_token();
+        if (token.is(Token::Type::Ident)) {
+            auto extent = parse_extent_keyword(token.token().ident());
+            if (!extent.has_value())
+                return {};
+            return commit_value(*extent, transaction_size);
+        }
+        auto first_dimension = parse_dimension(token);
+        if (!first_dimension.has_value())
+            return {};
+        if (!first_dimension->is_length_percentage())
+            return {};
+        auto transaction_second_dimension = tokens.begin_transaction();
+        tokens.skip_whitespace();
+        if (tokens.has_next_token()) {
+            auto& second_token = tokens.next_token();
+            auto second_dimension = parse_dimension(second_token);
+            if (second_dimension.has_value() && second_dimension->is_length_percentage())
+                return commit_value(EllipseSize { first_dimension->length_percentage(), second_dimension->length_percentage() },
+                    transaction_size, transaction_second_dimension);
+        }
+        if (first_dimension->is_length())
+            return commit_value(CircleSize { first_dimension->length() }, transaction_size);
+        return {};
+    };
+
+    {
+        // [ <ending-shape> || <size> ]?
+        auto maybe_ending_shape = parse_ending_shape();
+        auto maybe_size = parse_size();
+        if (!maybe_ending_shape.has_value() && maybe_size.has_value())
+            maybe_ending_shape = parse_ending_shape();
+        if (maybe_size.has_value()) {
+            size = *maybe_size;
+            expect_comma = true;
+        }
+        if (maybe_ending_shape.has_value()) {
+            expect_comma = true;
+            ending_shape = *maybe_ending_shape;
+            if (ending_shape == EndingShape::Circle && size.has<EllipseSize>())
+                return {};
+            if (ending_shape == EndingShape::Ellipse && size.has<CircleSize>())
+                return {};
+        } else {
+            ending_shape = size.has<CircleSize>() ? EndingShape::Circle : EndingShape::Ellipse;
+        }
+    }
+
+    tokens.skip_whitespace();
+    if (!tokens.has_next_token())
+        return {};
+
+    auto& token = tokens.peek_token();
+    if (token.is(Token::Type::Ident) && token.token().ident().equals_ignoring_case("at"sv)) {
+        (void)tokens.next_token();
+        auto position = parse_position(tokens);
+        if (!position.has_value())
+            return {};
+        at_position = *position;
+        expect_comma = true;
+    }
+
+    tokens.skip_whitespace();
+    if (!tokens.has_next_token())
+        return {};
+    if (expect_comma && !tokens.next_token().is(Token::Type::Comma))
+        return {};
+
+    // <color-stop-list>
+    auto color_stops = parse_linear_color_stop_list(tokens);
+    if (!color_stops.has_value())
+        return {};
+
+    return RadialGradientStyleValue::create(ending_shape, size, at_position, move(*color_stops));
+}
+
 Optional<PositionValue> Parser::parse_position(TokenStream<ComponentValue>& tokens, PositionValue initial_value)
 {
     auto transaction = tokens.begin_transaction();
@@ -3825,11 +3970,13 @@ RefPtr<StyleValue> Parser::parse_image_value(ComponentValue const& component_val
     auto url = parse_url_function(component_value, AllowedDataUrlType::Image);
     if (url.has_value())
         return ImageStyleValue::create(url.value());
-    // FIXME: Implement other kinds of gradient
     auto linear_gradient = parse_linear_gradient_function(component_value);
     if (linear_gradient)
         return linear_gradient;
-    return parse_conic_gradient_function(component_value);
+    auto conic_gradient = parse_conic_gradient_function(component_value);
+    if (conic_gradient)
+        return conic_gradient;
+    return parse_radial_gradient_function(component_value);
 }
 
 template<typename ParseFunction>

+ 1 - 0
Userland/Libraries/LibWeb/CSS/Parser/Parser.h

@@ -269,6 +269,7 @@ private:
 
     RefPtr<StyleValue> parse_linear_gradient_function(ComponentValue const&);
     RefPtr<StyleValue> parse_conic_gradient_function(ComponentValue const&);
+    RefPtr<StyleValue> parse_radial_gradient_function(ComponentValue const&);
 
     ParseErrorOr<NonnullRefPtr<StyleValue>> parse_css_value(PropertyID, TokenStream<ComponentValue>&);
     RefPtr<StyleValue> parse_css_value(ComponentValue const&);