LibWeb: Implement missing basic shapes

This commit is contained in:
Gingeh 2024-10-27 11:58:52 +11:00 committed by Sam Atkins
parent 76e638b4ca
commit a7e83c38ee
Notes: github-actions[bot] 2024-10-31 10:49:39 +00:00
8 changed files with 620 additions and 28 deletions

View file

@ -1236,6 +1236,37 @@ RefPtr<CSSStyleValue> Parser::parse_url_value(TokenStream<ComponentValue>& token
return URLStyleValue::create(*url);
}
// https://www.w3.org/TR/css-shapes-1/#typedef-shape-radius
Optional<ShapeRadius> Parser::parse_shape_radius(TokenStream<ComponentValue>& tokens)
{
auto transaction = tokens.begin_transaction();
tokens.discard_whitespace();
auto maybe_radius = parse_length_percentage(tokens);
if (maybe_radius.has_value()) {
// Negative radius is invalid.
auto radius = maybe_radius.value();
if ((radius.is_length() && radius.length().raw_value() < 0) || (radius.is_percentage() && radius.percentage().value() < 0))
return {};
transaction.commit();
return radius;
}
if (tokens.next_token().is_ident("closest-side"sv)) {
tokens.discard_a_token();
transaction.commit();
return FitSide::ClosestSide;
}
if (tokens.next_token().is_ident("farthest-side"sv)) {
tokens.discard_a_token();
transaction.commit();
return FitSide::FarthestSide;
}
return {};
}
RefPtr<CSSStyleValue> Parser::parse_basic_shape_value(TokenStream<ComponentValue>& tokens)
{
auto transaction = tokens.begin_transaction();
@ -1245,38 +1276,237 @@ RefPtr<CSSStyleValue> Parser::parse_basic_shape_value(TokenStream<ComponentValue
auto function_name = component_value.function().name.bytes_as_string_view();
// FIXME: Implement other shapes. See: https://www.w3.org/TR/css-shapes-1/#basic-shape-functions
if (!function_name.equals_ignoring_ascii_case("polygon"sv))
return nullptr;
// FIXME: Implement path(). See: https://www.w3.org/TR/css-shapes-1/#basic-shape-functions
if (function_name.equals_ignoring_ascii_case("inset"sv)) {
// inset() = inset( <length-percentage>{1,4} [ round <'border-radius'> ]? )
// FIXME: Parse the border-radius.
auto arguments_tokens = TokenStream { component_value.function().value };
// polygon() = polygon( <'fill-rule'>? , [<length-percentage> <length-percentage>]# )
// FIXME: Parse the fill-rule.
auto arguments_tokens = TokenStream { component_value.function().value };
auto arguments = parse_a_comma_separated_list_of_component_values(arguments_tokens);
// If less than four <length-percentage> values are provided,
// the omitted values default in the same way as the margin shorthand:
// an omitted second or third value defaults to the first, and an omitted fourth value defaults to the second.
Vector<Polygon::Point> points;
for (auto& argument : arguments) {
TokenStream argument_tokens { argument };
// The four <length-percentage>s define the position of the top, right, bottom, and left edges of a rectangle.
argument_tokens.discard_whitespace();
auto x_pos = parse_length_percentage(argument_tokens);
if (!x_pos.has_value())
arguments_tokens.discard_whitespace();
auto top = parse_length_percentage(arguments_tokens);
if (!top.has_value())
return nullptr;
argument_tokens.discard_whitespace();
auto y_pos = parse_length_percentage(argument_tokens);
if (!y_pos.has_value())
arguments_tokens.discard_whitespace();
auto right = parse_length_percentage(arguments_tokens);
if (!right.has_value())
right = top;
arguments_tokens.discard_whitespace();
auto bottom = parse_length_percentage(arguments_tokens);
if (!bottom.has_value())
bottom = top;
arguments_tokens.discard_whitespace();
auto left = parse_length_percentage(arguments_tokens);
if (!left.has_value())
left = right;
arguments_tokens.discard_whitespace();
if (arguments_tokens.has_next_token())
return nullptr;
argument_tokens.discard_whitespace();
if (argument_tokens.has_next_token())
return nullptr;
points.append(Polygon::Point { *x_pos, *y_pos });
transaction.commit();
return BasicShapeStyleValue::create(Inset { LengthBox(top.value(), right.value(), bottom.value(), left.value()) });
}
transaction.commit();
return BasicShapeStyleValue::create(Polygon { FillRule::Nonzero, move(points) });
if (function_name.equals_ignoring_ascii_case("xywh"sv)) {
// xywh() = xywh( <length-percentage>{2} <length-percentage [0,∞]>{2} [ round <'border-radius'> ]? )
// FIXME: Parse the border-radius.
auto arguments_tokens = TokenStream { component_value.function().value };
arguments_tokens.discard_whitespace();
auto x = parse_length_percentage(arguments_tokens);
if (!x.has_value())
return nullptr;
arguments_tokens.discard_whitespace();
auto y = parse_length_percentage(arguments_tokens);
if (!y.has_value())
return nullptr;
arguments_tokens.discard_whitespace();
auto width = parse_length_percentage(arguments_tokens);
if (!width.has_value())
return nullptr;
arguments_tokens.discard_whitespace();
auto height = parse_length_percentage(arguments_tokens);
if (!height.has_value())
return nullptr;
arguments_tokens.discard_whitespace();
if (arguments_tokens.has_next_token())
return nullptr;
// Negative width or height is invalid.
if ((width->is_length() && width->length().raw_value() < 0)
|| (width->is_percentage() && width->percentage().value() < 0)
|| (height->is_length() && height->length().raw_value() < 0)
|| (height->is_percentage() && height->percentage().value() < 0))
return nullptr;
transaction.commit();
return BasicShapeStyleValue::create(Xywh { x.value(), y.value(), width.value(), height.value() });
}
if (function_name.equals_ignoring_ascii_case("rect"sv)) {
// rect() = rect( [ <length-percentage> | auto ]{4} [ round <'border-radius'> ]? )
// FIXME: Parse the border-radius.
auto arguments_tokens = TokenStream { component_value.function().value };
auto parse_length_percentage_or_auto = [this](TokenStream<ComponentValue>& tokens) -> Optional<LengthPercentage> {
tokens.discard_whitespace();
auto value = parse_length_percentage(tokens);
if (!value.has_value()) {
if (tokens.consume_a_token().is_ident("auto"sv)) {
value = Length::make_auto();
}
}
return value;
};
auto top = parse_length_percentage_or_auto(arguments_tokens);
auto right = parse_length_percentage_or_auto(arguments_tokens);
auto bottom = parse_length_percentage_or_auto(arguments_tokens);
auto left = parse_length_percentage_or_auto(arguments_tokens);
if (!top.has_value() || !right.has_value() || !bottom.has_value() || !left.has_value())
return nullptr;
arguments_tokens.discard_whitespace();
if (arguments_tokens.has_next_token())
return nullptr;
transaction.commit();
return BasicShapeStyleValue::create(Rect { LengthBox(top.value(), right.value(), bottom.value(), left.value()) });
}
if (function_name.equals_ignoring_ascii_case("circle"sv)) {
// circle() = circle( <shape-radius>? [ at <position> ]? )
auto arguments_tokens = TokenStream { component_value.function().value };
auto radius = parse_shape_radius(arguments_tokens).value_or(FitSide::ClosestSide);
auto position = PositionStyleValue::create_center();
arguments_tokens.discard_whitespace();
if (arguments_tokens.next_token().is_ident("at"sv)) {
arguments_tokens.discard_a_token();
arguments_tokens.discard_whitespace();
auto maybe_position = parse_position_value(arguments_tokens);
if (maybe_position.is_null())
return nullptr;
position = maybe_position.release_nonnull();
}
arguments_tokens.discard_whitespace();
if (arguments_tokens.has_next_token())
return nullptr;
transaction.commit();
return BasicShapeStyleValue::create(Circle { radius, position });
}
if (function_name.equals_ignoring_ascii_case("ellipse"sv)) {
// ellipse() = ellipse( [ <shape-radius>{2} ]? [ at <position> ]? )
auto arguments_tokens = TokenStream { component_value.function().value };
Optional<ShapeRadius> radius_x = parse_shape_radius(arguments_tokens);
Optional<ShapeRadius> radius_y = parse_shape_radius(arguments_tokens);
if (radius_x.has_value() && !radius_y.has_value())
return nullptr;
if (!radius_x.has_value()) {
radius_x = FitSide::ClosestSide;
radius_y = FitSide::ClosestSide;
}
auto position = PositionStyleValue::create_center();
arguments_tokens.discard_whitespace();
if (arguments_tokens.next_token().is_ident("at"sv)) {
arguments_tokens.discard_a_token();
arguments_tokens.discard_whitespace();
auto maybe_position = parse_position_value(arguments_tokens);
if (maybe_position.is_null())
return nullptr;
position = maybe_position.release_nonnull();
}
arguments_tokens.discard_whitespace();
if (arguments_tokens.has_next_token())
return nullptr;
transaction.commit();
return BasicShapeStyleValue::create(Ellipse { radius_x.value(), radius_y.value(), position });
}
if (function_name.equals_ignoring_ascii_case("polygon"sv)) {
// polygon() = polygon( <'fill-rule'>? , [<length-percentage> <length-percentage>]# )
auto arguments_tokens = TokenStream { component_value.function().value };
auto arguments = parse_a_comma_separated_list_of_component_values(arguments_tokens);
if (arguments.size() < 1)
return nullptr;
Optional<FillRule> fill_rule;
auto first_argument = arguments[0];
TokenStream first_argument_tokens { first_argument };
first_argument_tokens.discard_whitespace();
if (first_argument_tokens.next_token().is_ident("nonzero"sv)) {
fill_rule = FillRule::Nonzero;
} else if (first_argument_tokens.next_token().is_ident("evenodd"sv)) {
fill_rule = FillRule::Evenodd;
}
if (fill_rule.has_value()) {
first_argument_tokens.discard_a_token();
if (first_argument_tokens.has_next_token())
return nullptr;
arguments.remove(0);
} else {
fill_rule = FillRule::Nonzero;
}
if (arguments.size() < 1)
return nullptr;
Vector<Polygon::Point> points;
for (auto& argument : arguments) {
TokenStream argument_tokens { argument };
argument_tokens.discard_whitespace();
auto x_pos = parse_length_percentage(argument_tokens);
if (!x_pos.has_value())
return nullptr;
argument_tokens.discard_whitespace();
auto y_pos = parse_length_percentage(argument_tokens);
if (!y_pos.has_value())
return nullptr;
argument_tokens.discard_whitespace();
if (argument_tokens.has_next_token())
return nullptr;
points.append(Polygon::Point { *x_pos, *y_pos });
}
transaction.commit();
return BasicShapeStyleValue::create(Polygon { fill_rule.value(), move(points) });
}
return nullptr;
}
Optional<FlyString> Parser::parse_layer_name(TokenStream<ComponentValue>& tokens, AllowBlankLayerName allow_blank_layer_name)