mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 11:38:11 +00:00
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.
This commit is contained in:
parent
ee72dcd523
commit
49497044de
2 changed files with 199 additions and 74 deletions
|
@ -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 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();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
element.color_stop = GradientColorStop { color, position, second_position };
|
||||
return ElementType::ColorStop;
|
||||
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); });
|
||||
|
||||
ColorStopListElement first_element {};
|
||||
if (parse_color_stop_list_element(first_element) != ElementType::ColorStop)
|
||||
if (!color_stops.has_value())
|
||||
return {};
|
||||
|
||||
return LinearGradientStyleValue::create(gradient_direction, move(*color_stops), gradient_type, repeating_gradient);
|
||||
}
|
||||
|
||||
RefPtr<StyleValue> Parser::parse_conic_gradient_function(ComponentValue const& component_value)
|
||||
{
|
||||
if (!component_value.is_function())
|
||||
return {};
|
||||
auto function_name = component_value.function().name();
|
||||
|
||||
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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue