diff options
author | MacDue <macdue@dueutil.tech> | 2022-10-30 19:25:00 +0100 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2022-11-01 23:07:05 +0000 |
commit | 49497044de25c2e0bfb4714f2faeb2ccee2862d7 (patch) | |
tree | 0b707b5824fc6149961dd141776acaf07a2da3a9 /Userland/Libraries | |
parent | ee72dcd52327f06084f9683dd8c0c1a1b7a7f5cf (diff) | |
download | serenity-49497044de25c2e0bfb4714f2faeb2ccee2862d7.zip |
LibWeb: Parse `conic-gradient()`s
This parses conic-gradient()s while also attempting to share the bulk
of the parsing code with linear-gradient()s. This is done by extracting
the <color-stop-list> parsing to a "fill in the blacks" generic
function. This is a little awkward but cuts down on a lot of copy
pasting of code.
Diffstat (limited to 'Userland/Libraries')
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp | 268 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/Parser.h | 1 |
2 files changed, 197 insertions, 72 deletions
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index ea184fd9d9..5ee33377fe 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -2297,6 +2297,97 @@ Optional<AK::URL> Parser::parse_url_function(ComponentValue const& component_val return {}; } +template<typename TElement> +static Optional<Vector<TElement>> parse_color_stop_list(auto& tokens, auto is_position, auto get_position, auto parse_color, auto parse_dimension) +{ + enum class ElementType { + Garbage, + ColorStop, + ColorHint + }; + + auto parse_color_stop_list_element = [&](TElement& element) -> ElementType { + tokens.skip_whitespace(); + if (!tokens.has_next_token()) + return ElementType::Garbage; + auto const& token = tokens.next_token(); + + Gfx::Color color; + Optional<typename TElement::PositionType> position; + Optional<typename TElement::PositionType> second_position; + auto dimension = parse_dimension(token); + if (dimension.has_value() && is_position(*dimension)) { + // [<T-percentage> <color>] or [<T-percentage>] + position = get_position(*dimension); + tokens.skip_whitespace(); + // <T-percentage> + if (!tokens.has_next_token() || tokens.peek_token().is(Token::Type::Comma)) { + element.transition_hint = typename TElement::ColorHint { *position }; + return ElementType::ColorHint; + } + // <T-percentage> <color> + auto maybe_color = parse_color(tokens.next_token()); + if (!maybe_color.has_value()) + return ElementType::Garbage; + color = *maybe_color; + } else { + // [<color> <T-percentage>?] + auto maybe_color = parse_color(token); + if (!maybe_color.has_value()) + return ElementType::Garbage; + color = *maybe_color; + tokens.skip_whitespace(); + // 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 }) { + if (tokens.has_next_token() && !tokens.peek_token().is(Token::Type::Comma)) { + auto token = tokens.next_token(); + auto dimension = parse_dimension(token); + if (!dimension.has_value() || !is_position(*dimension)) + return ElementType::Garbage; + *stop_position = get_position(*dimension); + tokens.skip_whitespace(); + } + } + } + + 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 {}; + tokens.skip_whitespace(); + if (!tokens.next_token().is(Token::Type::Comma)) + return {}; + auto element_type = parse_color_stop_list_element(list_element); + if (element_type == ElementType::ColorHint) { + // <color-hint>, <color-stop> + tokens.skip_whitespace(); + if (!tokens.next_token().is(Token::Type::Comma)) + 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; +} + RefPtr<StyleValue> Parser::parse_linear_gradient_function(ComponentValue const& component_value) { using GradientType = LinearGradientStyleValue::GradientType; @@ -2429,93 +2520,123 @@ RefPtr<StyleValue> Parser::parse_linear_gradient_function(ComponentValue const& // <color-stop-list> = // <linear-color-stop> , [ <linear-color-hint>? , <linear-color-stop> ]# - - enum class ElementType { - Garbage, - ColorStop, - ColorHint + auto is_length_percentage = [](Dimension& dimension) { + return dimension.is_length_percentage(); + }; + auto get_length_percentage = [](Dimension& dimension) { + return dimension.length_percentage(); }; + auto color_stops = parse_color_stop_list<LinearColorStopListElement>( + tokens, is_length_percentage, get_length_percentage, + [&](auto& token) { return parse_color(token); }, + [&](auto& token) { return parse_dimension(token); }); - auto parse_color_stop_list_element = [&](ColorStopListElement& element) -> ElementType { - tokens.skip_whitespace(); - if (!tokens.has_next_token()) - return ElementType::Garbage; - auto const& token = tokens.next_token(); + if (!color_stops.has_value()) + return {}; - Gfx::Color color; - Optional<LengthPercentage> position; - Optional<LengthPercentage> second_position; - auto dimension = parse_dimension(token); - if (dimension.has_value() && dimension->is_length_percentage()) { - // [<length-percentage> <color>] or [<length-percentage>] - position = dimension->length_percentage(); - tokens.skip_whitespace(); - // <length-percentage> - if (!tokens.has_next_token() || tokens.peek_token().is(Token::Type::Comma)) { - element.transition_hint = GradientColorHint { *position }; - return ElementType::ColorHint; - } - // <length-percentage> <color> - auto maybe_color = parse_color(tokens.next_token()); - if (!maybe_color.has_value()) - return ElementType::Garbage; - color = *maybe_color; - } else { - // [<color> <length-percentage>?] - auto maybe_color = parse_color(token); - if (!maybe_color.has_value()) - return ElementType::Garbage; - color = *maybe_color; - tokens.skip_whitespace(); - // Allow up to [<color> <length-percentage> <length-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 }) { - if (tokens.has_next_token() && !tokens.peek_token().is(Token::Type::Comma)) { - auto token = tokens.next_token(); - auto dimension = parse_dimension(token); - if (!dimension.has_value() || !dimension->is_length_percentage()) - return ElementType::Garbage; - *stop_position = dimension->length_percentage(); - tokens.skip_whitespace(); - } - } - } + return LinearGradientStyleValue::create(gradient_direction, move(*color_stops), gradient_type, repeating_gradient); +} - element.color_stop = GradientColorStop { color, position, second_position }; - return ElementType::ColorStop; - }; +RefPtr<StyleValue> Parser::parse_conic_gradient_function(ComponentValue const& component_value) +{ + if (!component_value.is_function()) + return {}; + auto function_name = component_value.function().name(); - ColorStopListElement first_element {}; - if (parse_color_stop_list_element(first_element) != ElementType::ColorStop) + if (!function_name.equals_ignoring_case("conic-gradient"sv)) return {}; + TokenStream tokens { component_value.function().values() }; + tokens.skip_whitespace(); + if (!tokens.has_next_token()) return {}; - Vector<ColorStopListElement> color_stops { first_element }; - while (tokens.has_next_token()) { - ColorStopListElement list_element {}; - tokens.skip_whitespace(); - if (!tokens.next_token().is(Token::Type::Comma)) - return {}; - auto element_type = parse_color_stop_list_element(list_element); - if (element_type == ElementType::ColorHint) { - // <linear-color-hint>, <linear-color-stop> - tokens.skip_whitespace(); - if (!tokens.next_token().is(Token::Type::Comma)) + Angle from_angle(0, Angle::Type::Deg); + PositionValue at_position = PositionValue::center(); + + // conic-gradient( [ [ from <angle> ]? [ at <position> ]? ] || + // <color-interpolation-method> , <angular-color-stop-list> ) + auto token = tokens.peek_token(); + bool got_from_angle = false; + 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) { + if (token_string.equals_ignoring_case(identifier)) { + (void)tokens.next_token(); + tokens.skip_whitespace(); + return true; + } + return false; + }; + + if (consume_identifier("from"sv)) { + // from <angle> + if (got_from_angle || got_at_position) 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) + if (!tokens.has_next_token()) return {}; - } else if (element_type == ElementType::ColorStop) { - // <linear-color-stop> + + auto angle_token = tokens.next_token(); + if (!angle_token.is(Token::Type::Dimension)) + return {}; + float angle = angle_token.token().dimension_value(); + auto angle_unit = angle_token.token().dimension_unit(); + auto angle_type = Angle::unit_from_name(angle_unit); + if (!angle_type.has_value()) + return {}; + + from_angle = Angle(angle, *angle_type); + got_from_angle = true; + } else if (consume_identifier("at"sv)) { + // at <position> + if (got_at_position) + return {}; + auto position = parse_position(tokens); + if (!position.has_value()) + return {}; + at_position = *position; + got_at_position = true; + } else if (consume_identifier("in"sv)) { + // <color-interpolation-method> + if (got_color_interpolation_method) + return {}; + dbgln("FIXME: Parse color interpolation method for conic-gradient()"); + got_color_interpolation_method = true; } else { - return {}; + break; } - color_stops.append(list_element); + tokens.skip_whitespace(); + if (!tokens.has_next_token()) + return {}; + token = tokens.peek_token(); } - return LinearGradientStyleValue::create(gradient_direction, move(color_stops), gradient_type, repeating_gradient); + tokens.skip_whitespace(); + if (!tokens.has_next_token()) + return {}; + if ((got_from_angle || got_color_interpolation_method) && !tokens.next_token().is(Token::Type::Comma)) + return {}; + + // <angular-color-stop-list> = + // <angular-color-stop> , [ <angular-color-hint>? , <angular-color-stop> ]# + auto is_angle_percentage = [](Dimension& dimension) { + return dimension.is_angle_percentage(); + }; + auto get_angle_percentage = [](Dimension& dimension) { + return dimension.angle_percentage(); + }; + auto color_stops = parse_color_stop_list<AngularColorStopListElement>( + tokens, is_angle_percentage, get_angle_percentage, + [&](auto& token) { return parse_color(token); }, + [&](auto& token) { return parse_dimension(token); }); + + if (!color_stops.has_value()) + return {}; + + return ConicGradientStyleValue::create(from_angle, at_position, move(*color_stops)); } Optional<PositionValue> Parser::parse_position(TokenStream<ComponentValue>& tokens) @@ -3692,7 +3813,10 @@ RefPtr<StyleValue> Parser::parse_image_value(ComponentValue const& component_val if (url.has_value()) return ImageStyleValue::create(url.value()); // FIXME: Implement other kinds of gradient - return parse_linear_gradient_function(component_value); + auto linear_gradient = parse_linear_gradient_function(component_value); + if (linear_gradient) + return linear_gradient; + return parse_conic_gradient_function(component_value); } template<typename ParseFunction> diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h index 7ba16298f0..28f0e6e2ea 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -264,6 +264,7 @@ private: Optional<AK::URL> parse_url_function(ComponentValue const&, AllowedDataUrlType = AllowedDataUrlType::None); RefPtr<StyleValue> parse_linear_gradient_function(ComponentValue const&); + RefPtr<StyleValue> parse_conic_gradient_function(ComponentValue const&); ParseErrorOr<NonnullRefPtr<StyleValue>> parse_css_value(PropertyID, TokenStream<ComponentValue>&); RefPtr<StyleValue> parse_css_value(ComponentValue const&); |