summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMacDue <macdue@dueutil.tech>2022-10-30 19:25:00 +0100
committerLinus Groh <mail@linusgroh.de>2022-11-01 23:07:05 +0000
commit49497044de25c2e0bfb4714f2faeb2ccee2862d7 (patch)
tree0b707b5824fc6149961dd141776acaf07a2da3a9
parentee72dcd52327f06084f9683dd8c0c1a1b7a7f5cf (diff)
downloadserenity-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.
-rw-r--r--Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp268
-rw-r--r--Userland/Libraries/LibWeb/CSS/Parser/Parser.h1
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&);