mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-16 05:51:55 +00:00
LibWeb: Implement missing basic shapes
This commit is contained in:
parent
76e638b4ca
commit
a7e83c38ee
Notes:
github-actions[bot]
2024-10-31 10:49:39 +00:00
Author: https://github.com/Gingeh
Commit: a7e83c38ee
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/1996
Reviewed-by: https://github.com/AtkinsSJ ✅
8 changed files with 620 additions and 28 deletions
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue