mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-29 04:09:13 +00:00
LibWeb/CSS: Implement path()
basic shape
This commit is contained in:
parent
8538186ca5
commit
0ff61e5e7b
Notes:
github-actions[bot]
2025-07-17 18:00:31 +00:00
Author: https://github.com/AtkinsSJ
Commit: 0ff61e5e7b
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5491
Reviewed-by: https://github.com/kalenikaliaksandr ✅
5 changed files with 115 additions and 22 deletions
|
@ -2965,6 +2965,25 @@ RefPtr<CSSStyleValue const> Parser::parse_basic_shape_value(TokenStream<Componen
|
||||||
|
|
||||||
auto function_name = component_value.function().name.bytes_as_string_view();
|
auto function_name = component_value.function().name.bytes_as_string_view();
|
||||||
|
|
||||||
|
auto parse_fill_rule_argument = [](Vector<ComponentValue> const& component_values) -> Optional<Gfx::WindingRule> {
|
||||||
|
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
|
// FIXME: Implement path(). See: https://www.w3.org/TR/css-shapes-1/#basic-shape-functions
|
||||||
if (function_name.equals_ignoring_ascii_case("inset"sv)) {
|
if (function_name.equals_ignoring_ascii_case("inset"sv)) {
|
||||||
// inset() = inset( <length-percentage>{1,4} [ round <'border-radius'> ]? )
|
// inset() = inset( <length-percentage>{1,4} [ round <'border-radius'> ]? )
|
||||||
|
@ -3147,21 +3166,9 @@ RefPtr<CSSStyleValue const> Parser::parse_basic_shape_value(TokenStream<Componen
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
Optional<Gfx::WindingRule> fill_rule;
|
Optional<Gfx::WindingRule> fill_rule;
|
||||||
auto const& first_argument = arguments[0];
|
fill_rule = parse_fill_rule_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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fill_rule.has_value()) {
|
if (fill_rule.has_value()) {
|
||||||
first_argument_tokens.discard_a_token();
|
|
||||||
if (first_argument_tokens.has_next_token())
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
arguments.remove(0);
|
arguments.remove(0);
|
||||||
} else {
|
} else {
|
||||||
fill_rule = Gfx::WindingRule::Nonzero;
|
fill_rule = Gfx::WindingRule::Nonzero;
|
||||||
|
@ -3195,6 +3202,39 @@ RefPtr<CSSStyleValue const> Parser::parse_basic_shape_value(TokenStream<Componen
|
||||||
return BasicShapeStyleValue::create(Polygon { fill_rule.value(), move(points) });
|
return BasicShapeStyleValue::create(Polygon { fill_rule.value(), move(points) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (function_name.equals_ignoring_ascii_case("path"sv)) {
|
||||||
|
// <path()> = path( <'fill-rule'>?, <string> )
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
// <string>, 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;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2024, MacDue <macdue@dueutil.tech>
|
* Copyright (c) 2024, MacDue <macdue@dueutil.tech>
|
||||||
|
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "BasicShapeStyleValue.h"
|
#include "BasicShapeStyleValue.h"
|
||||||
#include <LibGfx/Path.h>
|
#include <LibGfx/Path.h>
|
||||||
|
#include <LibWeb/CSS/Serialize.h>
|
||||||
|
#include <LibWeb/SVG/Path.h>
|
||||||
|
|
||||||
namespace Web::CSS {
|
namespace Web::CSS {
|
||||||
|
|
||||||
|
@ -211,6 +214,43 @@ String Polygon::to_string(SerializationMode) const
|
||||||
return MUST(builder.to_string());
|
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;
|
BasicShapeStyleValue::~BasicShapeStyleValue() = default;
|
||||||
|
|
||||||
Gfx::Path BasicShapeStyleValue::to_path(CSSPixelRect reference_box, Layout::Node const& node) const
|
Gfx::Path BasicShapeStyleValue::to_path(CSSPixelRect reference_box, Layout::Node const& node) const
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2024, MacDue <macdue@dueutil.tech>
|
* Copyright (c) 2024, MacDue <macdue@dueutil.tech>
|
||||||
|
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
@ -12,6 +13,7 @@
|
||||||
#include <LibWeb/CSS/LengthBox.h>
|
#include <LibWeb/CSS/LengthBox.h>
|
||||||
#include <LibWeb/CSS/PercentageOr.h>
|
#include <LibWeb/CSS/PercentageOr.h>
|
||||||
#include <LibWeb/CSS/StyleValues/PositionStyleValue.h>
|
#include <LibWeb/CSS/StyleValues/PositionStyleValue.h>
|
||||||
|
#include <LibWeb/SVG/AttributeParser.h>
|
||||||
|
|
||||||
namespace Web::CSS {
|
namespace Web::CSS {
|
||||||
|
|
||||||
|
@ -89,8 +91,19 @@ struct Polygon {
|
||||||
Vector<Point> points;
|
Vector<Point> points;
|
||||||
};
|
};
|
||||||
|
|
||||||
// FIXME: Implement path(). See: https://www.w3.org/TR/css-shapes-1/#basic-shape-functions
|
// https://drafts.csswg.org/css-shapes/#funcdef-basic-shape-path
|
||||||
using BasicShape = Variant<Inset, Xywh, Rect, Circle, Ellipse, Polygon>;
|
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<Inset, Xywh, Rect, Circle, Ellipse, Polygon, Path>;
|
||||||
|
|
||||||
class BasicShapeStyleValue : public StyleValueWithDefaultOperators<BasicShapeStyleValue> {
|
class BasicShapeStyleValue : public StyleValueWithDefaultOperators<BasicShapeStyleValue> {
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -2,7 +2,7 @@ Harness status: OK
|
||||||
|
|
||||||
Found 3 tests
|
Found 3 tests
|
||||||
|
|
||||||
3 Fail
|
3 Pass
|
||||||
Fail Property clip-path value 'path(nonzero, 'M10,10h80v80h-80zM25,25h50v50h-50z')'
|
Pass Property clip-path value 'path(nonzero, 'M10,10h80v80h-80zM25,25h50v50h-50z')'
|
||||||
Fail Property clip-path value 'path(evenodd, 'M10,10h80v80h-80zM25,25h50v50h-50z')'
|
Pass Property clip-path value 'path(evenodd, 'M10,10h80v80h-80zM25,25h50v50h-50z')'
|
||||||
Fail Property clip-path value 'path(evenodd, 'M10,10h80v80h-80zM25,25h50v50h-50')'
|
Pass Property clip-path value 'path(evenodd, 'M10,10h80v80h-80zM25,25h50v50h-50')'
|
|
@ -2,6 +2,6 @@ Harness status: OK
|
||||||
|
|
||||||
Found 2 tests
|
Found 2 tests
|
||||||
|
|
||||||
2 Fail
|
2 Pass
|
||||||
Fail 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-50z')" should set the property value
|
||||||
Fail e.style['clip-path'] = "path(evenodd, 'M10,10h80v80h-80zM25,25h50v50h-50')" should set the property value
|
Pass e.style['clip-path'] = "path(evenodd, 'M10,10h80v80h-80zM25,25h50v50h-50')" should set the property value
|
Loading…
Add table
Add a link
Reference in a new issue