| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  | /*
 | 
					
						
							| 
									
										
										
										
											2024-10-04 13:19:50 +02:00
										 |  |  |  * Copyright (c) 2018-2022, Andreas Kling <andreas@ladybird.org> | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |  * Copyright (c) 2020-2021, the SerenityOS developers. | 
					
						
							|  |  |  |  * Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org> | 
					
						
							|  |  |  |  * Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org> | 
					
						
							|  |  |  |  * Copyright (c) 2022, MacDue <macdue@dueutil.tech> | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * SPDX-License-Identifier: BSD-2-Clause | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <AK/Debug.h>
 | 
					
						
							|  |  |  | #include <LibWeb/CSS/Parser/Parser.h>
 | 
					
						
							|  |  |  | #include <LibWeb/CSS/StyleValues/ConicGradientStyleValue.h>
 | 
					
						
							|  |  |  | #include <LibWeb/CSS/StyleValues/LinearGradientStyleValue.h>
 | 
					
						
							| 
									
										
										
										
											2023-11-07 12:11:04 +00:00
										 |  |  | #include <LibWeb/CSS/StyleValues/PositionStyleValue.h>
 | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  | #include <LibWeb/CSS/StyleValues/RadialGradientStyleValue.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace Web::CSS::Parser { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | template<typename TElement> | 
					
						
							| 
									
										
										
										
											2023-12-27 11:48:36 +00:00
										 |  |  | Optional<Vector<TElement>> Parser::parse_color_stop_list(TokenStream<ComponentValue>& tokens, auto is_position, auto get_position) | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  | { | 
					
						
							|  |  |  |     enum class ElementType { | 
					
						
							|  |  |  |         Garbage, | 
					
						
							|  |  |  |         ColorStop, | 
					
						
							|  |  |  |         ColorHint | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto parse_color_stop_list_element = [&](TElement& element) -> ElementType { | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |         tokens.discard_whitespace(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |         if (!tokens.has_next_token()) | 
					
						
							|  |  |  |             return ElementType::Garbage; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-14 11:10:54 +01:00
										 |  |  |         RefPtr<CSSStyleValue> color; | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |         Optional<typename TElement::PositionType> position; | 
					
						
							|  |  |  |         Optional<typename TElement::PositionType> second_position; | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |         if (auto dimension = parse_dimension(tokens.next_token()); dimension.has_value() && is_position(*dimension)) { | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |             // [<T-percentage> <color>] or [<T-percentage>]
 | 
					
						
							|  |  |  |             position = get_position(*dimension); | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |             tokens.discard_a_token(); // dimension
 | 
					
						
							|  |  |  |             tokens.discard_whitespace(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |             // <T-percentage>
 | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |             if (!tokens.has_next_token() || tokens.next_token().is(Token::Type::Comma)) { | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |                 element.transition_hint = typename TElement::ColorHint { *position }; | 
					
						
							|  |  |  |                 return ElementType::ColorHint; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             // <T-percentage> <color>
 | 
					
						
							| 
									
										
										
										
											2024-04-21 18:35:50 +01:00
										 |  |  |             auto maybe_color = parse_color_value(tokens); | 
					
						
							| 
									
										
										
										
											2023-08-19 14:48:27 +01:00
										 |  |  |             if (!maybe_color) | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |                 return ElementType::Garbage; | 
					
						
							| 
									
										
										
										
											2023-08-19 14:48:27 +01:00
										 |  |  |             color = maybe_color.release_nonnull(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |         } else { | 
					
						
							|  |  |  |             // [<color> <T-percentage>?]
 | 
					
						
							| 
									
										
										
										
											2024-04-21 18:35:50 +01:00
										 |  |  |             auto maybe_color = parse_color_value(tokens); | 
					
						
							| 
									
										
										
										
											2023-08-19 14:48:27 +01:00
										 |  |  |             if (!maybe_color) | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |                 return ElementType::Garbage; | 
					
						
							| 
									
										
										
										
											2023-08-19 14:48:27 +01:00
										 |  |  |             color = maybe_color.release_nonnull(); | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |             tokens.discard_whitespace(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |             // Allow up to [<color> <T-percentage> <T-percentage>] (double-position color stops)
 | 
					
						
							|  |  |  |             // Note: Double-position color stops only appear to be valid in this order.
 | 
					
						
							|  |  |  |             for (auto stop_position : Array { &position, &second_position }) { | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |                 if (tokens.has_next_token() && !tokens.next_token().is(Token::Type::Comma)) { | 
					
						
							|  |  |  |                     auto dimension = parse_dimension(tokens.consume_a_token()); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |                     if (!dimension.has_value() || !is_position(*dimension)) | 
					
						
							|  |  |  |                         return ElementType::Garbage; | 
					
						
							|  |  |  |                     *stop_position = get_position(*dimension); | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |                     tokens.discard_whitespace(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         element.color_stop = typename TElement::ColorStop { color, position, second_position }; | 
					
						
							|  |  |  |         return ElementType::ColorStop; | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     TElement first_element {}; | 
					
						
							|  |  |  |     if (parse_color_stop_list_element(first_element) != ElementType::ColorStop) | 
					
						
							|  |  |  |         return {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!tokens.has_next_token()) | 
					
						
							|  |  |  |         return {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Vector<TElement> color_stops { first_element }; | 
					
						
							|  |  |  |     while (tokens.has_next_token()) { | 
					
						
							|  |  |  |         TElement list_element {}; | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |         tokens.discard_whitespace(); | 
					
						
							|  |  |  |         if (!tokens.consume_a_token().is(Token::Type::Comma)) | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |             return {}; | 
					
						
							|  |  |  |         auto element_type = parse_color_stop_list_element(list_element); | 
					
						
							|  |  |  |         if (element_type == ElementType::ColorHint) { | 
					
						
							|  |  |  |             // <color-hint>, <color-stop>
 | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |             tokens.discard_whitespace(); | 
					
						
							|  |  |  |             if (!tokens.consume_a_token().is(Token::Type::Comma)) | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |                 return {}; | 
					
						
							|  |  |  |             // Note: This fills in the color stop on the same list_element as the color hint (it does not overwrite it).
 | 
					
						
							|  |  |  |             if (parse_color_stop_list_element(list_element) != ElementType::ColorStop) | 
					
						
							|  |  |  |                 return {}; | 
					
						
							|  |  |  |         } else if (element_type == ElementType::ColorStop) { | 
					
						
							|  |  |  |             // <color-stop>
 | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             return {}; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         color_stops.append(list_element); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return color_stops; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static StringView consume_if_starts_with(StringView str, StringView start, auto found_callback) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (str.starts_with(start, CaseSensitivity::CaseInsensitive)) { | 
					
						
							|  |  |  |         found_callback(); | 
					
						
							|  |  |  |         return str.substring_view(start.length()); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return str; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Optional<Vector<LinearColorStopListElement>> Parser::parse_linear_color_stop_list(TokenStream<ComponentValue>& tokens) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     // <color-stop-list> =
 | 
					
						
							|  |  |  |     //   <linear-color-stop> , [ <linear-color-hint>? , <linear-color-stop> ]#
 | 
					
						
							|  |  |  |     return parse_color_stop_list<LinearColorStopListElement>( | 
					
						
							|  |  |  |         tokens, | 
					
						
							|  |  |  |         [](Dimension& dimension) { return dimension.is_length_percentage(); }, | 
					
						
							| 
									
										
										
										
											2023-12-27 11:48:36 +00:00
										 |  |  |         [](Dimension& dimension) { return dimension.length_percentage(); }); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Optional<Vector<AngularColorStopListElement>> Parser::parse_angular_color_stop_list(TokenStream<ComponentValue>& tokens) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     // <angular-color-stop-list> =
 | 
					
						
							|  |  |  |     //   <angular-color-stop> , [ <angular-color-hint>? , <angular-color-stop> ]#
 | 
					
						
							|  |  |  |     return parse_color_stop_list<AngularColorStopListElement>( | 
					
						
							|  |  |  |         tokens, | 
					
						
							|  |  |  |         [](Dimension& dimension) { return dimension.is_angle_percentage(); }, | 
					
						
							| 
									
										
										
										
											2023-12-27 11:48:36 +00:00
										 |  |  |         [](Dimension& dimension) { return dimension.angle_percentage(); }); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-14 11:10:54 +01:00
										 |  |  | RefPtr<CSSStyleValue> Parser::parse_linear_gradient_function(TokenStream<ComponentValue>& outer_tokens) | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  | { | 
					
						
							|  |  |  |     using GradientType = LinearGradientStyleValue::GradientType; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-09 13:56:17 +01:00
										 |  |  |     auto transaction = outer_tokens.begin_transaction(); | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |     auto& component_value = outer_tokens.consume_a_token(); | 
					
						
							| 
									
										
										
										
											2024-08-09 13:56:17 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |     if (!component_value.is_function()) | 
					
						
							|  |  |  |         return nullptr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     GradientRepeating repeating_gradient = GradientRepeating::No; | 
					
						
							|  |  |  |     GradientType gradient_type { GradientType::Standard }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-11 11:17:10 +01:00
										 |  |  |     auto function_name = component_value.function().name.bytes_as_string_view(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     function_name = consume_if_starts_with(function_name, "-webkit-"sv, [&] { | 
					
						
							|  |  |  |         gradient_type = GradientType::WebKit; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function_name = consume_if_starts_with(function_name, "repeating-"sv, [&] { | 
					
						
							|  |  |  |         repeating_gradient = GradientRepeating::Yes; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!function_name.equals_ignoring_ascii_case("linear-gradient"sv)) | 
					
						
							|  |  |  |         return nullptr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // linear-gradient() = linear-gradient([ <angle> | to <side-or-corner> ]?, <color-stop-list>)
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-11 11:17:10 +01:00
										 |  |  |     TokenStream tokens { component_value.function().value }; | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |     tokens.discard_whitespace(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (!tokens.has_next_token()) | 
					
						
							|  |  |  |         return nullptr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     bool has_direction_param = true; | 
					
						
							|  |  |  |     LinearGradientStyleValue::GradientDirection gradient_direction = gradient_type == GradientType::Standard | 
					
						
							|  |  |  |         ? SideOrCorner::Bottom | 
					
						
							|  |  |  |         : SideOrCorner::Top; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto to_side = [](StringView value) -> Optional<SideOrCorner> { | 
					
						
							|  |  |  |         if (value.equals_ignoring_ascii_case("top"sv)) | 
					
						
							|  |  |  |             return SideOrCorner::Top; | 
					
						
							|  |  |  |         if (value.equals_ignoring_ascii_case("bottom"sv)) | 
					
						
							|  |  |  |             return SideOrCorner::Bottom; | 
					
						
							|  |  |  |         if (value.equals_ignoring_ascii_case("left"sv)) | 
					
						
							|  |  |  |             return SideOrCorner::Left; | 
					
						
							|  |  |  |         if (value.equals_ignoring_ascii_case("right"sv)) | 
					
						
							|  |  |  |             return SideOrCorner::Right; | 
					
						
							|  |  |  |         return {}; | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto is_to_side_or_corner = [&](auto const& token) { | 
					
						
							|  |  |  |         if (!token.is(Token::Type::Ident)) | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         if (gradient_type == GradientType::WebKit) | 
					
						
							|  |  |  |             return to_side(token.token().ident()).has_value(); | 
					
						
							|  |  |  |         return token.token().ident().equals_ignoring_ascii_case("to"sv); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |     auto const& first_param = tokens.next_token(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |     if (first_param.is(Token::Type::Dimension)) { | 
					
						
							|  |  |  |         // <angle>
 | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |         tokens.discard_a_token(); | 
					
						
							| 
									
										
										
										
											2023-08-20 13:02:41 +01:00
										 |  |  |         auto angle_value = first_param.token().dimension_value(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |         auto unit_string = first_param.token().dimension_unit(); | 
					
						
							|  |  |  |         auto angle_type = Angle::unit_from_name(unit_string); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!angle_type.has_value()) | 
					
						
							|  |  |  |             return nullptr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         gradient_direction = Angle { angle_value, angle_type.release_value() }; | 
					
						
							|  |  |  |     } else if (is_to_side_or_corner(first_param)) { | 
					
						
							|  |  |  |         // <side-or-corner> = [left | right] || [top | bottom]
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Note: -webkit-linear-gradient does not include to the "to" prefix on the side or corner
 | 
					
						
							|  |  |  |         if (gradient_type == GradientType::Standard) { | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |             tokens.discard_a_token(); | 
					
						
							|  |  |  |             tokens.discard_whitespace(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |             if (!tokens.has_next_token()) | 
					
						
							|  |  |  |                 return nullptr; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // [left | right] || [top | bottom]
 | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |         auto const& first_side = tokens.consume_a_token(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |         if (!first_side.is(Token::Type::Ident)) | 
					
						
							|  |  |  |             return nullptr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         auto side_a = to_side(first_side.token().ident()); | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |         tokens.discard_whitespace(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |         Optional<SideOrCorner> side_b; | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |         if (tokens.has_next_token() && tokens.next_token().is(Token::Type::Ident)) | 
					
						
							|  |  |  |             side_b = to_side(tokens.consume_a_token().token().ident()); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if (side_a.has_value() && !side_b.has_value()) { | 
					
						
							|  |  |  |             gradient_direction = *side_a; | 
					
						
							|  |  |  |         } else if (side_a.has_value() && side_b.has_value()) { | 
					
						
							|  |  |  |             // Convert two sides to a corner
 | 
					
						
							|  |  |  |             if (to_underlying(*side_b) < to_underlying(*side_a)) | 
					
						
							|  |  |  |                 swap(side_a, side_b); | 
					
						
							|  |  |  |             if (side_a == SideOrCorner::Top && side_b == SideOrCorner::Left) | 
					
						
							|  |  |  |                 gradient_direction = SideOrCorner::TopLeft; | 
					
						
							|  |  |  |             else if (side_a == SideOrCorner::Top && side_b == SideOrCorner::Right) | 
					
						
							|  |  |  |                 gradient_direction = SideOrCorner::TopRight; | 
					
						
							|  |  |  |             else if (side_a == SideOrCorner::Bottom && side_b == SideOrCorner::Left) | 
					
						
							|  |  |  |                 gradient_direction = SideOrCorner::BottomLeft; | 
					
						
							|  |  |  |             else if (side_a == SideOrCorner::Bottom && side_b == SideOrCorner::Right) | 
					
						
							|  |  |  |                 gradient_direction = SideOrCorner::BottomRight; | 
					
						
							|  |  |  |             else | 
					
						
							|  |  |  |                 return nullptr; | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             return nullptr; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         has_direction_param = false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |     tokens.discard_whitespace(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |     if (!tokens.has_next_token()) | 
					
						
							|  |  |  |         return nullptr; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |     if (has_direction_param && !tokens.consume_a_token().is(Token::Type::Comma)) | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |         return nullptr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto color_stops = parse_linear_color_stop_list(tokens); | 
					
						
							|  |  |  |     if (!color_stops.has_value()) | 
					
						
							|  |  |  |         return nullptr; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-09 13:56:17 +01:00
										 |  |  |     transaction.commit(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |     return LinearGradientStyleValue::create(gradient_direction, move(*color_stops), gradient_type, repeating_gradient); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-14 11:10:54 +01:00
										 |  |  | RefPtr<CSSStyleValue> Parser::parse_conic_gradient_function(TokenStream<ComponentValue>& outer_tokens) | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2024-08-09 13:56:17 +01:00
										 |  |  |     auto transaction = outer_tokens.begin_transaction(); | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |     auto& component_value = outer_tokens.consume_a_token(); | 
					
						
							| 
									
										
										
										
											2024-08-09 13:56:17 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |     if (!component_value.is_function()) | 
					
						
							|  |  |  |         return nullptr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     GradientRepeating repeating_gradient = GradientRepeating::No; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-11 11:17:10 +01:00
										 |  |  |     auto function_name = component_value.function().name.bytes_as_string_view(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     function_name = consume_if_starts_with(function_name, "repeating-"sv, [&] { | 
					
						
							|  |  |  |         repeating_gradient = GradientRepeating::Yes; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!function_name.equals_ignoring_ascii_case("conic-gradient"sv)) | 
					
						
							|  |  |  |         return nullptr; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-11 11:17:10 +01:00
										 |  |  |     TokenStream tokens { component_value.function().value }; | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |     tokens.discard_whitespace(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (!tokens.has_next_token()) | 
					
						
							|  |  |  |         return nullptr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Angle from_angle(0, Angle::Type::Deg); | 
					
						
							| 
									
										
										
										
											2023-11-07 12:16:42 +00:00
										 |  |  |     RefPtr<PositionStyleValue> at_position; | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // conic-gradient( [ [ from <angle> ]? [ at <position> ]? ]  ||
 | 
					
						
							|  |  |  |     // <color-interpolation-method> , <angular-color-stop-list> )
 | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |     auto token = tokens.next_token(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |     bool got_from_angle = false; | 
					
						
							|  |  |  |     bool got_color_interpolation_method = false; | 
					
						
							|  |  |  |     bool got_at_position = false; | 
					
						
							|  |  |  |     while (token.is(Token::Type::Ident)) { | 
					
						
							|  |  |  |         auto consume_identifier = [&](auto identifier) { | 
					
						
							|  |  |  |             auto token_string = token.token().ident(); | 
					
						
							|  |  |  |             if (token_string.equals_ignoring_ascii_case(identifier)) { | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |                 tokens.discard_a_token(); | 
					
						
							|  |  |  |                 tokens.discard_whitespace(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |                 return true; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (consume_identifier("from"sv)) { | 
					
						
							|  |  |  |             // from <angle>
 | 
					
						
							|  |  |  |             if (got_from_angle || got_at_position) | 
					
						
							|  |  |  |                 return nullptr; | 
					
						
							|  |  |  |             if (!tokens.has_next_token()) | 
					
						
							|  |  |  |                 return nullptr; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |             auto angle_token = tokens.consume_a_token(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |             if (!angle_token.is(Token::Type::Dimension)) | 
					
						
							|  |  |  |                 return nullptr; | 
					
						
							| 
									
										
										
										
											2023-08-20 13:02:41 +01:00
										 |  |  |             auto angle = angle_token.token().dimension_value(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |             auto angle_unit = angle_token.token().dimension_unit(); | 
					
						
							|  |  |  |             auto angle_type = Angle::unit_from_name(angle_unit); | 
					
						
							|  |  |  |             if (!angle_type.has_value()) | 
					
						
							|  |  |  |                 return nullptr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             from_angle = Angle(angle, *angle_type); | 
					
						
							|  |  |  |             got_from_angle = true; | 
					
						
							|  |  |  |         } else if (consume_identifier("at"sv)) { | 
					
						
							|  |  |  |             // at <position>
 | 
					
						
							|  |  |  |             if (got_at_position) | 
					
						
							|  |  |  |                 return nullptr; | 
					
						
							| 
									
										
										
										
											2023-11-07 12:16:42 +00:00
										 |  |  |             auto position = parse_position_value(tokens); | 
					
						
							|  |  |  |             if (!position) | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |                 return nullptr; | 
					
						
							| 
									
										
										
										
											2023-11-07 12:16:42 +00:00
										 |  |  |             at_position = position; | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |             got_at_position = true; | 
					
						
							|  |  |  |         } else if (consume_identifier("in"sv)) { | 
					
						
							|  |  |  |             // <color-interpolation-method>
 | 
					
						
							|  |  |  |             if (got_color_interpolation_method) | 
					
						
							|  |  |  |                 return nullptr; | 
					
						
							|  |  |  |             dbgln("FIXME: Parse color interpolation method for conic-gradient()"); | 
					
						
							|  |  |  |             got_color_interpolation_method = true; | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |         tokens.discard_whitespace(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |         if (!tokens.has_next_token()) | 
					
						
							|  |  |  |             return nullptr; | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |         token = tokens.next_token(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |     tokens.discard_whitespace(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |     if (!tokens.has_next_token()) | 
					
						
							|  |  |  |         return nullptr; | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |     if ((got_from_angle || got_at_position || got_color_interpolation_method) && !tokens.consume_a_token().is(Token::Type::Comma)) | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |         return nullptr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto color_stops = parse_angular_color_stop_list(tokens); | 
					
						
							|  |  |  |     if (!color_stops.has_value()) | 
					
						
							|  |  |  |         return nullptr; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-07 12:16:42 +00:00
										 |  |  |     if (!at_position) | 
					
						
							|  |  |  |         at_position = PositionStyleValue::create_center(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-09 13:56:17 +01:00
										 |  |  |     transaction.commit(); | 
					
						
							| 
									
										
										
										
											2023-11-07 12:16:42 +00:00
										 |  |  |     return ConicGradientStyleValue::create(from_angle, at_position.release_nonnull(), move(*color_stops), repeating_gradient); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-14 11:10:54 +01:00
										 |  |  | RefPtr<CSSStyleValue> Parser::parse_radial_gradient_function(TokenStream<ComponentValue>& outer_tokens) | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  | { | 
					
						
							|  |  |  |     using EndingShape = RadialGradientStyleValue::EndingShape; | 
					
						
							|  |  |  |     using Extent = RadialGradientStyleValue::Extent; | 
					
						
							|  |  |  |     using CircleSize = RadialGradientStyleValue::CircleSize; | 
					
						
							|  |  |  |     using EllipseSize = RadialGradientStyleValue::EllipseSize; | 
					
						
							|  |  |  |     using Size = RadialGradientStyleValue::Size; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-09 13:56:17 +01:00
										 |  |  |     auto transaction = outer_tokens.begin_transaction(); | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |     auto& component_value = outer_tokens.consume_a_token(); | 
					
						
							| 
									
										
										
										
											2024-08-09 13:56:17 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |     if (!component_value.is_function()) | 
					
						
							|  |  |  |         return nullptr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto repeating_gradient = GradientRepeating::No; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-11 11:17:10 +01:00
										 |  |  |     auto function_name = component_value.function().name.bytes_as_string_view(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     function_name = consume_if_starts_with(function_name, "repeating-"sv, [&] { | 
					
						
							|  |  |  |         repeating_gradient = GradientRepeating::Yes; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!function_name.equals_ignoring_ascii_case("radial-gradient"sv)) | 
					
						
							|  |  |  |         return nullptr; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-11 11:17:10 +01:00
										 |  |  |     TokenStream tokens { component_value.function().value }; | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |     tokens.discard_whitespace(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |     if (!tokens.has_next_token()) | 
					
						
							|  |  |  |         return nullptr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     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; | 
					
						
							| 
									
										
										
										
											2023-11-07 12:11:04 +00:00
										 |  |  |     RefPtr<PositionStyleValue> at_position; | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     auto parse_ending_shape = [&]() -> Optional<EndingShape> { | 
					
						
							|  |  |  |         auto transaction = tokens.begin_transaction(); | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |         tokens.discard_whitespace(); | 
					
						
							|  |  |  |         auto& token = tokens.consume_a_token(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |         if (!token.is(Token::Type::Ident)) | 
					
						
							|  |  |  |             return {}; | 
					
						
							|  |  |  |         auto ident = token.token().ident(); | 
					
						
							|  |  |  |         if (ident.equals_ignoring_ascii_case("circle"sv)) | 
					
						
							|  |  |  |             return commit_value(EndingShape::Circle, transaction); | 
					
						
							|  |  |  |         if (ident.equals_ignoring_ascii_case("ellipse"sv)) | 
					
						
							|  |  |  |             return commit_value(EndingShape::Ellipse, transaction); | 
					
						
							|  |  |  |         return {}; | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto parse_extent_keyword = [](StringView keyword) -> Optional<Extent> { | 
					
						
							|  |  |  |         if (keyword.equals_ignoring_ascii_case("closest-corner"sv)) | 
					
						
							|  |  |  |             return Extent::ClosestCorner; | 
					
						
							|  |  |  |         if (keyword.equals_ignoring_ascii_case("closest-side"sv)) | 
					
						
							|  |  |  |             return Extent::ClosestSide; | 
					
						
							|  |  |  |         if (keyword.equals_ignoring_ascii_case("farthest-corner"sv)) | 
					
						
							|  |  |  |             return Extent::FarthestCorner; | 
					
						
							|  |  |  |         if (keyword.equals_ignoring_ascii_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(); | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |         tokens.discard_whitespace(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |         if (!tokens.has_next_token()) | 
					
						
							|  |  |  |             return {}; | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |         if (tokens.next_token().is(Token::Type::Ident)) { | 
					
						
							|  |  |  |             auto extent = parse_extent_keyword(tokens.consume_a_token().token().ident()); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |             if (!extent.has_value()) | 
					
						
							|  |  |  |                 return {}; | 
					
						
							|  |  |  |             return commit_value(*extent, transaction_size); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-12-28 12:51:56 +00:00
										 |  |  |         auto first_radius = parse_length_percentage(tokens); | 
					
						
							|  |  |  |         if (!first_radius.has_value()) | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |             return {}; | 
					
						
							|  |  |  |         auto transaction_second_dimension = tokens.begin_transaction(); | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |         tokens.discard_whitespace(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |         if (tokens.has_next_token()) { | 
					
						
							| 
									
										
										
										
											2023-12-28 12:51:56 +00:00
										 |  |  |             auto second_radius = parse_length_percentage(tokens); | 
					
						
							|  |  |  |             if (second_radius.has_value()) | 
					
						
							|  |  |  |                 return commit_value(EllipseSize { first_radius.release_value(), second_radius.release_value() }, | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |                     transaction_size, transaction_second_dimension); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-12-28 12:51:56 +00:00
										 |  |  |         // FIXME: Support calculated lengths
 | 
					
						
							|  |  |  |         if (first_radius->is_length()) | 
					
						
							|  |  |  |             return commit_value(CircleSize { first_radius->length() }, transaction_size); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |         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 nullptr; | 
					
						
							|  |  |  |             if (ending_shape == EndingShape::Ellipse && size.has<CircleSize>()) | 
					
						
							|  |  |  |                 return nullptr; | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             ending_shape = size.has<CircleSize>() ? EndingShape::Circle : EndingShape::Ellipse; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |     tokens.discard_whitespace(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |     if (!tokens.has_next_token()) | 
					
						
							|  |  |  |         return nullptr; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |     auto& token = tokens.next_token(); | 
					
						
							| 
									
										
										
										
											2023-11-21 12:08:39 +00:00
										 |  |  |     if (token.is_ident("at"sv)) { | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |         tokens.discard_a_token(); | 
					
						
							| 
									
										
										
										
											2023-11-07 12:11:04 +00:00
										 |  |  |         auto position = parse_position_value(tokens); | 
					
						
							|  |  |  |         if (!position) | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |             return nullptr; | 
					
						
							| 
									
										
										
										
											2023-11-07 12:11:04 +00:00
										 |  |  |         at_position = position; | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |         expect_comma = true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |     tokens.discard_whitespace(); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |     if (!tokens.has_next_token()) | 
					
						
							|  |  |  |         return nullptr; | 
					
						
							| 
									
										
										
										
											2024-10-09 12:29:29 +01:00
										 |  |  |     if (expect_comma && !tokens.consume_a_token().is(Token::Type::Comma)) | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  |         return nullptr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // <color-stop-list>
 | 
					
						
							|  |  |  |     auto color_stops = parse_linear_color_stop_list(tokens); | 
					
						
							|  |  |  |     if (!color_stops.has_value()) | 
					
						
							|  |  |  |         return nullptr; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-07 12:11:04 +00:00
										 |  |  |     if (!at_position) | 
					
						
							|  |  |  |         at_position = PositionStyleValue::create_center(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-09 13:56:17 +01:00
										 |  |  |     transaction.commit(); | 
					
						
							| 
									
										
										
										
											2023-11-07 12:11:04 +00:00
										 |  |  |     return RadialGradientStyleValue::create(ending_shape, size, at_position.release_nonnull(), move(*color_stops), repeating_gradient); | 
					
						
							| 
									
										
										
										
											2023-08-17 15:27:38 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } |