|
@@ -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())
|
|
|
+ transaction.commit();
|
|
|
+ return BasicShapeStyleValue::create(Inset { LengthBox(top.value(), right.value(), bottom.value(), left.value()) });
|
|
|
+ }
|
|
|
+
|
|
|
+ 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;
|
|
|
|
|
|
- points.append(Polygon::Point { *x_pos, *y_pos });
|
|
|
+ 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() });
|
|
|
}
|
|
|
|
|
|
- transaction.commit();
|
|
|
- return BasicShapeStyleValue::create(Polygon { FillRule::Nonzero, move(points) });
|
|
|
+ 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)
|