diff --git a/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp b/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp index f0f30d67c4a..8f0590f86ef 100644 --- a/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp @@ -2965,6 +2965,25 @@ RefPtr Parser::parse_basic_shape_value(TokenStream const& component_values) -> Optional { + TokenStream tokens { component_values }; + + tokens.discard_whitespace(); + auto& maybe_ident = tokens.consume_a_token(); + tokens.discard_whitespace(); + + if (tokens.has_next_token()) + return {}; + + if (maybe_ident.is_ident("nonzero"sv)) + return Gfx::WindingRule::Nonzero; + + if (maybe_ident.is_ident("evenodd"sv)) + return Gfx::WindingRule::EvenOdd; + + return {}; + }; + // 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( {1,4} [ round <'border-radius'> ]? ) @@ -3147,21 +3166,9 @@ RefPtr Parser::parse_basic_shape_value(TokenStream fill_rule; - auto const& 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 = Gfx::WindingRule::Nonzero; - } else if (first_argument_tokens.next_token().is_ident("evenodd"sv)) { - fill_rule = Gfx::WindingRule::EvenOdd; - } + fill_rule = parse_fill_rule_argument(arguments[0]); 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 = Gfx::WindingRule::Nonzero; @@ -3195,6 +3202,39 @@ RefPtr Parser::parse_basic_shape_value(TokenStream = path( <'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 || arguments.size() > 2) + return nullptr; + + // <'fill-rule'>? + Gfx::WindingRule fill_rule { Gfx::WindingRule::Nonzero }; + if (arguments.size() == 2) { + auto maybe_fill_rule = parse_fill_rule_argument(arguments[0]); + if (!maybe_fill_rule.has_value()) + return nullptr; + fill_rule = maybe_fill_rule.release_value(); + } + + // , which is a path string + TokenStream path_argument_tokens { arguments.last() }; + path_argument_tokens.discard_whitespace(); + auto& maybe_string = path_argument_tokens.consume_a_token(); + path_argument_tokens.discard_whitespace(); + + if (!maybe_string.is(Token::Type::String) || path_argument_tokens.has_next_token()) + return nullptr; + auto path_data = SVG::AttributeParser::parse_path_data(maybe_string.token().string().to_string()); + if (path_data.instructions().is_empty()) + return nullptr; + + transaction.commit(); + return BasicShapeStyleValue::create(Path { fill_rule, move(path_data) }); + } + return nullptr; } diff --git a/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.cpp index cbfbab7176a..1789ab0938c 100644 --- a/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.cpp @@ -1,11 +1,14 @@ /* * Copyright (c) 2024, MacDue + * Copyright (c) 2025, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ #include "BasicShapeStyleValue.h" #include +#include +#include namespace Web::CSS { @@ -211,6 +214,43 @@ String Polygon::to_string(SerializationMode) const return MUST(builder.to_string()); } +Gfx::Path Path::to_path(CSSPixelRect, Layout::Node const&) const +{ + auto result = path_instructions.to_gfx_path(); + // The UA must close a path with an implicit closepath command ("z" or "Z") if it is not present in the string for + // properties that require a closed loop (such as shape-outside and clip-path). + // https://drafts.csswg.org/css-shapes/#funcdef-basic-shape-path + // FIXME: For now, all users want a closed path, so we'll always close it. + result.close_all_subpaths(); + result.set_fill_type(fill_rule); + return result; +} + +// https://drafts.csswg.org/css-shapes/#basic-shape-serialization +String Path::to_string(SerializationMode mode) const +{ + StringBuilder builder; + builder.append("path("sv); + + // For serializing computed values, component values are computed, and omitted when possible without changing the meaning. + // NB: So, we don't include `nonzero` in that case. + if (!(mode == SerializationMode::ResolvedValue && fill_rule == Gfx::WindingRule::Nonzero)) { + switch (fill_rule) { + case Gfx::WindingRule::Nonzero: + builder.append("nonzero, "sv); + break; + case Gfx::WindingRule::EvenOdd: + builder.append("evenodd, "sv); + } + } + + serialize_a_string(builder, path_instructions.serialize()); + + builder.append(')'); + + return builder.to_string_without_validation(); +} + BasicShapeStyleValue::~BasicShapeStyleValue() = default; Gfx::Path BasicShapeStyleValue::to_path(CSSPixelRect reference_box, Layout::Node const& node) const diff --git a/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.h index fdf58309ee6..fd77c470c29 100644 --- a/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2024, MacDue + * Copyright (c) 2025, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ @@ -12,6 +13,7 @@ #include #include #include +#include namespace Web::CSS { @@ -89,8 +91,19 @@ struct Polygon { Vector points; }; -// FIXME: Implement path(). See: https://www.w3.org/TR/css-shapes-1/#basic-shape-functions -using BasicShape = Variant; +// https://drafts.csswg.org/css-shapes/#funcdef-basic-shape-path +struct Path { + Gfx::Path to_path(CSSPixelRect reference_box, Layout::Node const&) const; + String to_string(SerializationMode) const; + + bool operator==(Path const&) const = default; + + Gfx::WindingRule fill_rule; + SVG::Path path_instructions; +}; + +// https://www.w3.org/TR/css-shapes-1/#basic-shape-functions +using BasicShape = Variant; class BasicShapeStyleValue : public StyleValueWithDefaultOperators { public: diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-shapes/shape-functions/path-function-computed.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-shapes/shape-functions/path-function-computed.txt index 88604063bb1..59553ecd404 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-shapes/shape-functions/path-function-computed.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-shapes/shape-functions/path-function-computed.txt @@ -2,7 +2,7 @@ Harness status: OK Found 3 tests -3 Fail -Fail Property clip-path value 'path(nonzero, 'M10,10h80v80h-80zM25,25h50v50h-50z')' -Fail Property clip-path value 'path(evenodd, 'M10,10h80v80h-80zM25,25h50v50h-50z')' -Fail Property clip-path value 'path(evenodd, 'M10,10h80v80h-80zM25,25h50v50h-50')' \ No newline at end of file +3 Pass +Pass Property clip-path value 'path(nonzero, 'M10,10h80v80h-80zM25,25h50v50h-50z')' +Pass Property clip-path value 'path(evenodd, 'M10,10h80v80h-80zM25,25h50v50h-50z')' +Pass Property clip-path value 'path(evenodd, 'M10,10h80v80h-80zM25,25h50v50h-50')' \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-shapes/shape-functions/path-function-valid.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-shapes/shape-functions/path-function-valid.txt index a23b19dc1d4..487ef7ee33f 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-shapes/shape-functions/path-function-valid.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-shapes/shape-functions/path-function-valid.txt @@ -2,6 +2,6 @@ Harness status: OK Found 2 tests -2 Fail -Fail e.style['clip-path'] = "path(evenodd, 'M10,10h80v80h-80zM25,25h50v50h-50z')" should set the property value -Fail e.style['clip-path'] = "path(evenodd, 'M10,10h80v80h-80zM25,25h50v50h-50')" should set the property value \ No newline at end of file +2 Pass +Pass e.style['clip-path'] = "path(evenodd, 'M10,10h80v80h-80zM25,25h50v50h-50z')" should set the property value +Pass e.style['clip-path'] = "path(evenodd, 'M10,10h80v80h-80zM25,25h50v50h-50')" should set the property value \ No newline at end of file