diff --git a/Tests/LibWeb/Screenshot/clip-path-basic-shapes.html b/Tests/LibWeb/Screenshot/clip-path-basic-shapes.html
new file mode 100644
index 00000000000..3aa88ff9475
--- /dev/null
+++ b/Tests/LibWeb/Screenshot/clip-path-basic-shapes.html
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/LibWeb/Screenshot/images/clip-path-basic-shapes-ref.png b/Tests/LibWeb/Screenshot/images/clip-path-basic-shapes-ref.png
new file mode 100644
index 00000000000..e8ec2cb0f86
Binary files /dev/null and b/Tests/LibWeb/Screenshot/images/clip-path-basic-shapes-ref.png differ
diff --git a/Tests/LibWeb/Screenshot/reference/clip-path-basic-shapes-ref.html b/Tests/LibWeb/Screenshot/reference/clip-path-basic-shapes-ref.html
new file mode 100644
index 00000000000..9db24d323e4
--- /dev/null
+++ b/Tests/LibWeb/Screenshot/reference/clip-path-basic-shapes-ref.html
@@ -0,0 +1,9 @@
+
+
diff --git a/Userland/Libraries/LibWeb/CSS/LengthBox.h b/Userland/Libraries/LibWeb/CSS/LengthBox.h
index bdd7be3641c..f97e507cd1d 100644
--- a/Userland/Libraries/LibWeb/CSS/LengthBox.h
+++ b/Userland/Libraries/LibWeb/CSS/LengthBox.h
@@ -27,6 +27,8 @@ public:
LengthPercentage const& bottom() const { return m_bottom; }
LengthPercentage const& left() const { return m_left; }
+ bool operator==(LengthBox const&) const = default;
+
private:
LengthPercentage m_top;
LengthPercentage m_right;
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
index 89c4d543f5f..072497de9b0 100644
--- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
+++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
@@ -1236,6 +1236,37 @@ RefPtr Parser::parse_url_value(TokenStream& token
return URLStyleValue::create(*url);
}
+// https://www.w3.org/TR/css-shapes-1/#typedef-shape-radius
+Optional Parser::parse_shape_radius(TokenStream& 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 Parser::parse_basic_shape_value(TokenStream& tokens)
{
auto transaction = tokens.begin_transaction();
@@ -1245,38 +1276,237 @@ RefPtr Parser::parse_basic_shape_value(TokenStream{1,4} [ round <'border-radius'> ]? )
+ // FIXME: Parse the border-radius.
+ auto arguments_tokens = TokenStream { component_value.function().value };
- // polygon() = polygon( <'fill-rule'>? , [ ]# )
- // 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 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 points;
- for (auto& argument : arguments) {
- TokenStream argument_tokens { argument };
+ // The four 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( {2} {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( [ | 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& tokens) -> Optional {
+ 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( ? [ at ]? )
+ 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( [ {2} ]? [ at ]? )
+ auto arguments_tokens = TokenStream { component_value.function().value };
+
+ Optional radius_x = parse_shape_radius(arguments_tokens);
+ Optional 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'>? , [ ]# )
+ 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 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 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 Parser::parse_layer_name(TokenStream& tokens, AllowBlankLayerName allow_blank_layer_name)
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h
index 9316341c507..763fae505df 100644
--- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h
+++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h
@@ -221,6 +221,7 @@ private:
Optional parse_url_function(TokenStream&);
RefPtr parse_url_value(TokenStream&);
+ Optional parse_shape_radius(TokenStream&);
RefPtr parse_basic_shape_value(TokenStream&);
template
diff --git a/Userland/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.cpp b/Userland/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.cpp
index e26a2ddb48f..a1091c556ae 100644
--- a/Userland/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.cpp
+++ b/Userland/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.cpp
@@ -9,14 +9,178 @@
namespace Web::CSS {
+static Gfx::Path path_from_resolved_rect(float top, float right, float bottom, float left)
+{
+ Gfx::Path path;
+ path.move_to(Gfx::FloatPoint { left, top });
+ path.line_to(Gfx::FloatPoint { right, top });
+ path.line_to(Gfx::FloatPoint { right, bottom });
+ path.line_to(Gfx::FloatPoint { left, bottom });
+ path.close();
+ return path;
+}
+
+Gfx::Path Inset::to_path(CSSPixelRect reference_box, Layout::Node const& node) const
+{
+ // FIXME: A pair of insets in either dimension that add up to more than the used dimension
+ // (such as left and right insets of 75% apiece) use the CSS Backgrounds 3 § 4.5 Overlapping Curves rules
+ // to proportionally reduce the inset effect to 100%.
+
+ auto top = inset_box.top().to_px(node, reference_box.height()).to_float();
+ auto right = reference_box.width().to_float() - inset_box.right().to_px(node, reference_box.width()).to_float();
+ auto bottom = reference_box.height().to_float() - inset_box.bottom().to_px(node, reference_box.height()).to_float();
+ auto left = inset_box.left().to_px(node, reference_box.width()).to_float();
+
+ return path_from_resolved_rect(top, right, bottom, left);
+}
+
+String Inset::to_string() const
+{
+ return MUST(String::formatted("inset({} {} {} {})", inset_box.top(), inset_box.right(), inset_box.bottom(), inset_box.left()));
+}
+
+Gfx::Path Xywh::to_path(CSSPixelRect reference_box, Layout::Node const& node) const
+{
+ auto top = y.to_px(node, reference_box.height()).to_float();
+ auto bottom = top + max(0.0f, height.to_px(node, reference_box.height()).to_float());
+ auto left = x.to_px(node, reference_box.width()).to_float();
+ auto right = left + max(0.0f, width.to_px(node, reference_box.width()).to_float());
+
+ return path_from_resolved_rect(top, right, bottom, left);
+}
+
+String Xywh::to_string() const
+{
+ return MUST(String::formatted("xywh({} {} {} {})", x, y, width, height));
+}
+
+Gfx::Path Rect::to_path(CSSPixelRect reference_box, Layout::Node const& node) const
+{
+ // An auto value makes the edge of the box coincide with the corresponding edge of the reference box:
+ // it’s equivalent to 0% as the first (top) or fourth (left) value, and equivalent to 100% as the second (right) or third (bottom) value.
+
+ auto top = box.top().is_auto() ? 0 : box.top().to_px(node, reference_box.height()).to_float();
+ auto right = box.right().is_auto() ? reference_box.width().to_float() : box.right().to_px(node, reference_box.width()).to_float();
+ auto bottom = box.bottom().is_auto() ? reference_box.height().to_float() : box.bottom().to_px(node, reference_box.height()).to_float();
+ auto left = box.left().is_auto() ? 0 : box.left().to_px(node, reference_box.width()).to_float();
+
+ // The second (right) and third (bottom) values are floored by the fourth (left) and second (top) values, respectively.
+ return path_from_resolved_rect(top, max(right, left), max(bottom, top), left);
+}
+
+String Rect::to_string() const
+{
+ return MUST(String::formatted("rect({} {} {} {})", box.top(), box.right(), box.bottom(), box.left()));
+}
+
+static String radius_to_string(ShapeRadius radius)
+{
+ return radius.visit(
+ [](LengthPercentage const& length_percentage) { return length_percentage.to_string(); },
+ [](FitSide const& side) {
+ switch (side) {
+ case FitSide::ClosestSide:
+ return "closest-side"_string;
+ case FitSide::FarthestSide:
+ return "farthest-side"_string;
+ }
+ VERIFY_NOT_REACHED();
+ });
+}
+
+Gfx::Path Circle::to_path(CSSPixelRect reference_box, Layout::Node const& node) const
+{
+ // Translating the reference box because PositionStyleValues are resolved to an absolute position.
+ auto center = position->resolved(node, reference_box.translated(-reference_box.x(), -reference_box.y()));
+
+ float radius_px = radius.visit(
+ [&](LengthPercentage const& length_percentage) {
+ auto radius_ref = sqrt(pow(reference_box.width().to_float(), 2) + pow(reference_box.height().to_float(), 2)) / AK::Sqrt2;
+ return max(0.0f, length_percentage.to_px(node, CSSPixels(radius_ref)).to_float());
+ },
+ [&](FitSide const& side) {
+ switch (side) {
+ case FitSide::ClosestSide:
+ float closest;
+ closest = min(abs(center.x()), abs(center.y())).to_float();
+ closest = min(closest, abs(reference_box.width() - center.x()).to_float());
+ closest = min(closest, abs(reference_box.height() - center.y()).to_float());
+ return closest;
+ case FitSide::FarthestSide:
+ float farthest;
+ farthest = max(abs(center.x()), abs(center.y())).to_float();
+ farthest = max(farthest, abs(reference_box.width() - center.x()).to_float());
+ farthest = max(farthest, abs(reference_box.height() - center.y()).to_float());
+ return farthest;
+ }
+ VERIFY_NOT_REACHED();
+ });
+
+ Gfx::Path path;
+ path.move_to(Gfx::FloatPoint { center.x().to_float(), center.y().to_float() + radius_px });
+ path.arc_to(Gfx::FloatPoint { center.x().to_float(), center.y().to_float() - radius_px }, radius_px, true, true);
+ path.arc_to(Gfx::FloatPoint { center.x().to_float(), center.y().to_float() + radius_px }, radius_px, true, true);
+ return path;
+}
+
+String Circle::to_string() const
+{
+ return MUST(String::formatted("circle({} at {})", radius_to_string(radius), position->to_string()));
+}
+
+Gfx::Path Ellipse::to_path(CSSPixelRect reference_box, Layout::Node const& node) const
+{
+ // Translating the reference box because PositionStyleValues are resolved to an absolute position.
+ auto center = position->resolved(node, reference_box.translated(-reference_box.x(), -reference_box.y()));
+
+ float radius_x_px = radius_x.visit(
+ [&](LengthPercentage const& length_percentage) {
+ return max(0.0f, length_percentage.to_px(node, reference_box.width()).to_float());
+ },
+ [&](FitSide const& side) {
+ switch (side) {
+ case FitSide::ClosestSide:
+ return min(abs(center.x()), abs(reference_box.width() - center.x())).to_float();
+ case FitSide::FarthestSide:
+ return max(abs(center.x()), abs(reference_box.width() - center.x())).to_float();
+ }
+ VERIFY_NOT_REACHED();
+ });
+
+ float radius_y_px = radius_y.visit(
+ [&](LengthPercentage const& length_percentage) {
+ return max(0.0f, length_percentage.to_px(node, reference_box.height()).to_float());
+ },
+ [&](FitSide const& side) {
+ switch (side) {
+ case FitSide::ClosestSide:
+ return min(abs(center.y()), abs(reference_box.height() - center.y())).to_float();
+ case FitSide::FarthestSide:
+ return max(abs(center.y()), abs(reference_box.height() - center.y())).to_float();
+ }
+ VERIFY_NOT_REACHED();
+ });
+
+ Gfx::Path path;
+ path.move_to(Gfx::FloatPoint { center.x().to_float(), center.y().to_float() + radius_y_px });
+ path.elliptical_arc_to(Gfx::FloatPoint { center.x().to_float(), center.y().to_float() - radius_y_px }, Gfx::FloatSize { radius_x_px, radius_y_px }, 0, true, true);
+ path.elliptical_arc_to(Gfx::FloatPoint { center.x().to_float(), center.y().to_float() + radius_y_px }, Gfx::FloatSize { radius_x_px, radius_y_px }, 0, true, true);
+ return path;
+}
+
+String Ellipse::to_string() const
+{
+ return MUST(String::formatted("ellipse({} {} at {})", radius_to_string(radius_x), radius_to_string(radius_y), position->to_string()));
+}
+
Gfx::Path Polygon::to_path(CSSPixelRect reference_box, Layout::Node const& node) const
{
Gfx::Path path;
bool first = true;
for (auto const& point : points) {
Gfx::FloatPoint resolved_point {
- static_cast(point.x.to_px(node, reference_box.width())),
- static_cast(point.y.to_px(node, reference_box.height()))
+ point.x.to_px(node, reference_box.width()).to_float(),
+ point.y.to_px(node, reference_box.height()).to_float()
};
if (first)
path.move_to(resolved_point);
diff --git a/Userland/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.h b/Userland/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.h
index 02436566be1..b046648ccbf 100644
--- a/Userland/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.h
+++ b/Userland/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.h
@@ -7,12 +7,71 @@
#pragma once
#include
-#include
#include
+#include
#include
+#include
namespace Web::CSS {
+struct Inset {
+ Gfx::Path to_path(CSSPixelRect reference_box, Layout::Node const&) const;
+ String to_string() const;
+
+ bool operator==(Inset const&) const = default;
+
+ LengthBox inset_box;
+};
+
+struct Xywh {
+ Gfx::Path to_path(CSSPixelRect reference_box, Layout::Node const&) const;
+ String to_string() const;
+
+ bool operator==(Xywh const&) const = default;
+
+ LengthPercentage x;
+ LengthPercentage y;
+ LengthPercentage width;
+ LengthPercentage height;
+};
+
+struct Rect {
+ Gfx::Path to_path(CSSPixelRect reference_box, Layout::Node const&) const;
+ String to_string() const;
+
+ bool operator==(Rect const&) const = default;
+
+ LengthBox box;
+};
+
+enum class FitSide {
+ ClosestSide,
+ FarthestSide,
+};
+
+using ShapeRadius = Variant;
+
+struct Circle {
+ Gfx::Path to_path(CSSPixelRect reference_box, Layout::Node const&) const;
+ String to_string() const;
+
+ bool operator==(Circle const&) const = default;
+
+ ShapeRadius radius;
+ ValueComparingNonnullRefPtr position;
+};
+
+struct Ellipse {
+ Gfx::Path to_path(CSSPixelRect reference_box, Layout::Node const&) const;
+ String to_string() const;
+
+ bool operator==(Ellipse const&) const = default;
+
+ ShapeRadius radius_x;
+ ShapeRadius radius_y;
+ ValueComparingNonnullRefPtr position;
+};
+
struct Polygon {
struct Point {
bool operator==(Point const&) const = default;
@@ -25,12 +84,13 @@ struct Polygon {
bool operator==(Polygon const&) const = default;
+ // FIXME: Actually use the fill rule
FillRule fill_rule;
Vector points;
};
-// FIXME: Implement other basic shapes. See: https://www.w3.org/TR/css-shapes-1/#basic-shape-functions
-using BasicShape = Variant;
+// FIXME: Implement path(). See: https://www.w3.org/TR/css-shapes-1/#basic-shape-functions
+using BasicShape = Variant;
class BasicShapeStyleValue : public StyleValueWithDefaultOperators {
public: