LibWeb: Properly serialize position/edge style values

This commit is contained in:
Gingeh 2024-11-29 22:44:14 +11:00 committed by Sam Atkins
commit 84150f972f
Notes: github-actions[bot] 2024-12-13 11:36:34 +00:00
21 changed files with 461 additions and 288 deletions

View file

@ -34,7 +34,7 @@ Gfx::Path Inset::to_path(CSSPixelRect reference_box, Layout::Node const& node) c
return path_from_resolved_rect(top, right, bottom, left);
}
String Inset::to_string() const
String Inset::to_string(CSSStyleValue::SerializationMode) const
{
return MUST(String::formatted("inset({} {} {} {})", inset_box.top(), inset_box.right(), inset_box.bottom(), inset_box.left()));
}
@ -49,7 +49,7 @@ Gfx::Path Xywh::to_path(CSSPixelRect reference_box, Layout::Node const& node) co
return path_from_resolved_rect(top, right, bottom, left);
}
String Xywh::to_string() const
String Xywh::to_string(CSSStyleValue::SerializationMode) const
{
return MUST(String::formatted("xywh({} {} {} {})", x, y, width, height));
}
@ -68,7 +68,7 @@ Gfx::Path Rect::to_path(CSSPixelRect reference_box, Layout::Node const& node) co
return path_from_resolved_rect(top, max(right, left), max(bottom, top), left);
}
String Rect::to_string() const
String Rect::to_string(CSSStyleValue::SerializationMode) const
{
return MUST(String::formatted("rect({} {} {} {})", box.top(), box.right(), box.bottom(), box.left()));
}
@ -123,9 +123,9 @@ Gfx::Path Circle::to_path(CSSPixelRect reference_box, Layout::Node const& node)
return path;
}
String Circle::to_string() const
String Circle::to_string(CSSStyleValue::SerializationMode mode) const
{
return MUST(String::formatted("circle({} at {})", radius_to_string(radius), position->to_string(CSSStyleValue::SerializationMode::Normal)));
return MUST(String::formatted("circle({} at {})", radius_to_string(radius), position->to_string(mode)));
}
Gfx::Path Ellipse::to_path(CSSPixelRect reference_box, Layout::Node const& node) const
@ -168,9 +168,9 @@ Gfx::Path Ellipse::to_path(CSSPixelRect reference_box, Layout::Node const& node)
return path;
}
String Ellipse::to_string() const
String Ellipse::to_string(CSSStyleValue::SerializationMode mode) const
{
return MUST(String::formatted("ellipse({} {} at {})", radius_to_string(radius_x), radius_to_string(radius_y), position->to_string(CSSStyleValue::SerializationMode::Normal)));
return MUST(String::formatted("ellipse({} {} at {})", radius_to_string(radius_x), radius_to_string(radius_y), position->to_string(mode)));
}
Gfx::Path Polygon::to_path(CSSPixelRect reference_box, Layout::Node const& node) const
@ -193,7 +193,7 @@ Gfx::Path Polygon::to_path(CSSPixelRect reference_box, Layout::Node const& node)
return path;
}
String Polygon::to_string() const
String Polygon::to_string(CSSStyleValue::SerializationMode) const
{
StringBuilder builder;
builder.append("polygon("sv);
@ -220,10 +220,10 @@ Gfx::Path BasicShapeStyleValue::to_path(CSSPixelRect reference_box, Layout::Node
});
}
String BasicShapeStyleValue::to_string(SerializationMode) const
String BasicShapeStyleValue::to_string(SerializationMode mode) const
{
return m_basic_shape.visit([](auto const& shape) {
return shape.to_string();
return m_basic_shape.visit([mode](auto const& shape) {
return shape.to_string(mode);
});
}

View file

@ -17,7 +17,7 @@ namespace Web::CSS {
struct Inset {
Gfx::Path to_path(CSSPixelRect reference_box, Layout::Node const&) const;
String to_string() const;
String to_string(CSSStyleValue::SerializationMode) const;
bool operator==(Inset const&) const = default;
@ -26,7 +26,7 @@ struct Inset {
struct Xywh {
Gfx::Path to_path(CSSPixelRect reference_box, Layout::Node const&) const;
String to_string() const;
String to_string(CSSStyleValue::SerializationMode) const;
bool operator==(Xywh const&) const = default;
@ -38,7 +38,7 @@ struct Xywh {
struct Rect {
Gfx::Path to_path(CSSPixelRect reference_box, Layout::Node const&) const;
String to_string() const;
String to_string(CSSStyleValue::SerializationMode) const;
bool operator==(Rect const&) const = default;
@ -54,7 +54,7 @@ using ShapeRadius = Variant<LengthPercentage, FitSide>;
struct Circle {
Gfx::Path to_path(CSSPixelRect reference_box, Layout::Node const&) const;
String to_string() const;
String to_string(CSSStyleValue::SerializationMode) const;
bool operator==(Circle const&) const = default;
@ -64,7 +64,7 @@ struct Circle {
struct Ellipse {
Gfx::Path to_path(CSSPixelRect reference_box, Layout::Node const&) const;
String to_string() const;
String to_string(CSSStyleValue::SerializationMode) const;
bool operator==(Ellipse const&) const = default;
@ -81,7 +81,7 @@ struct Polygon {
};
Gfx::Path to_path(CSSPixelRect reference_box, Layout::Node const&) const;
String to_string() const;
String to_string(CSSStyleValue::SerializationMode) const;
bool operator==(Polygon const&) const = default;

View file

@ -8,9 +8,40 @@
namespace Web::CSS {
String EdgeStyleValue::to_string(SerializationMode) const
String EdgeStyleValue::to_string(SerializationMode mode) const
{
return MUST(String::formatted("{} {}", CSS::to_string(m_properties.edge), m_properties.offset.to_string()));
if (mode == CSSStyleValue::SerializationMode::ResolvedValue) {
if (edge() == PositionEdge::Right || edge() == PositionEdge::Bottom) {
if (offset().is_percentage()) {
auto flipped_percentage = 100 - offset().percentage().value();
return Percentage(flipped_percentage).to_string();
}
Vector<NonnullOwnPtr<CalculationNode>> sum_parts;
sum_parts.append(NumericCalculationNode::create(Percentage(100)));
if (offset().is_length()) {
sum_parts.append(NegateCalculationNode::create(NumericCalculationNode::create(offset().length())));
} else {
// FIXME: Flip calculated offsets (convert CSSMathValue to CalculationNode, then negate and append)
return to_string(CSSStyleValue::SerializationMode::Normal);
}
auto flipped_absolute = CSSMathValue::create(SumCalculationNode::create(move(sum_parts)), CSSNumericType(CSSNumericType::BaseType::Length, 1));
return flipped_absolute->to_string(mode);
}
return offset().to_string();
}
StringBuilder builder;
if (m_properties.edge.has_value())
builder.append(CSS::to_string(m_properties.edge.value()));
if (m_properties.edge.has_value() && m_properties.offset.has_value())
builder.append(' ');
if (m_properties.offset.has_value())
builder.append(m_properties.offset->to_string());
return builder.to_string_without_validation();
}
}

View file

@ -14,31 +14,45 @@ namespace Web::CSS {
class EdgeStyleValue final : public StyleValueWithDefaultOperators<EdgeStyleValue> {
public:
static ValueComparingNonnullRefPtr<EdgeStyleValue> create(PositionEdge edge, LengthPercentage const& offset)
static ValueComparingNonnullRefPtr<EdgeStyleValue> create(Optional<PositionEdge> edge, Optional<LengthPercentage> const& offset)
{
VERIFY(edge != PositionEdge::Center);
return adopt_ref(*new (nothrow) EdgeStyleValue(edge, offset));
}
virtual ~EdgeStyleValue() override = default;
// NOTE: `center` is converted to `left 50%` or `top 50%` in parsing, so is never returned here.
PositionEdge edge() const { return m_properties.edge; }
LengthPercentage const& offset() const { return m_properties.offset; }
Optional<PositionEdge> edge() const
{
if (m_properties.edge == PositionEdge::Center)
return {};
return m_properties.edge;
}
LengthPercentage offset() const
{
if (m_properties.edge == PositionEdge::Center)
return Percentage(50);
if (!m_properties.offset.has_value())
return Percentage(0);
return m_properties.offset.value();
}
virtual String to_string(SerializationMode) const override;
bool properties_equal(EdgeStyleValue const& other) const { return m_properties == other.m_properties; }
private:
EdgeStyleValue(PositionEdge edge, LengthPercentage const& offset)
EdgeStyleValue(Optional<PositionEdge> edge, Optional<LengthPercentage> const& offset)
: StyleValueWithDefaultOperators(Type::Edge)
, m_properties { .edge = edge, .offset = offset }
{
}
struct Properties {
PositionEdge edge;
LengthPercentage offset;
Optional<PositionEdge> edge;
Optional<LengthPercentage> offset;
bool operator==(Properties const&) const = default;
} m_properties;
};

View file

@ -13,10 +13,8 @@ namespace Web::CSS {
bool PositionStyleValue::is_center() const
{
return (edge_x()->edge() == PositionEdge::Left
&& edge_x()->offset().is_percentage() && edge_x()->offset().percentage() == Percentage { 50 })
&& (edge_y()->edge() == PositionEdge::Top
&& edge_y()->offset().is_percentage() && edge_y()->offset().percentage() == Percentage { 50 });
return (edge_x()->offset().is_percentage() && edge_x()->offset().percentage() == Percentage { 50 })
&& (edge_y()->offset().is_percentage() && edge_y()->offset().percentage() == Percentage { 50 });
}
CSSPixelPoint PositionStyleValue::resolved(Layout::Node const& node, CSSPixelRect const& rect) const

View file

@ -25,8 +25,8 @@ public:
static ValueComparingNonnullRefPtr<PositionStyleValue> create_center()
{
return adopt_ref(*new (nothrow) PositionStyleValue(
EdgeStyleValue::create(PositionEdge::Left, Percentage { 50 }),
EdgeStyleValue::create(PositionEdge::Top, Percentage { 50 })));
EdgeStyleValue::create(PositionEdge::Center, {}),
EdgeStyleValue::create(PositionEdge::Center, {})));
}
virtual ~PositionStyleValue() override = default;

View file

@ -65,6 +65,8 @@ String ShorthandStyleValue::to_string(SerializationMode mode) const
auto color = longhand(PropertyID::BackgroundColor);
auto image = longhand(PropertyID::BackgroundImage);
auto position = longhand(PropertyID::BackgroundPosition);
auto position_x = position->as_shorthand().longhand(PropertyID::BackgroundPositionX);
auto position_y = position->as_shorthand().longhand(PropertyID::BackgroundPositionY);
auto size = longhand(PropertyID::BackgroundSize);
auto repeat = longhand(PropertyID::BackgroundRepeat);
auto attachment = longhand(PropertyID::BackgroundAttachment);
@ -75,10 +77,10 @@ String ShorthandStyleValue::to_string(SerializationMode mode) const
return style_value->is_value_list() ? style_value->as_value_list().size() : 1;
};
auto layer_count = max(get_layer_count(image), max(get_layer_count(position), max(get_layer_count(size), max(get_layer_count(repeat), max(get_layer_count(attachment), max(get_layer_count(origin), get_layer_count(clip)))))));
auto layer_count = max(get_layer_count(image), max(get_layer_count(position_x), max(get_layer_count(position_y), max(get_layer_count(size), max(get_layer_count(repeat), max(get_layer_count(attachment), max(get_layer_count(origin), get_layer_count(clip))))))));
if (layer_count == 1) {
return MUST(String::formatted("{} {} {} {} {} {} {} {}", color->to_string(mode), image->to_string(mode), position->to_string(mode), size->to_string(mode), repeat->to_string(mode), attachment->to_string(mode), origin->to_string(mode), clip->to_string(mode)));
return MUST(String::formatted("{} {} {} {} {} {} {} {} {}", color->to_string(mode), image->to_string(mode), position_x->to_string(mode), position_y->to_string(mode), size->to_string(mode), repeat->to_string(mode), attachment->to_string(mode), origin->to_string(mode), clip->to_string(mode)));
}
auto get_layer_value_string = [mode](ValueComparingRefPtr<CSSStyleValue const> const& style_value, size_t index) {
@ -93,7 +95,38 @@ String ShorthandStyleValue::to_string(SerializationMode mode) const
builder.append(", "sv);
if (i == layer_count - 1)
builder.appendff("{} ", color->to_string(mode));
builder.appendff("{} {} {} {} {} {} {}", get_layer_value_string(image, i), get_layer_value_string(position, i), get_layer_value_string(size, i), get_layer_value_string(repeat, i), get_layer_value_string(attachment, i), get_layer_value_string(origin, i), get_layer_value_string(clip, i));
builder.appendff("{} {} {} {} {} {} {} {}", get_layer_value_string(image, i), get_layer_value_string(position_x, i), get_layer_value_string(position_y, i), get_layer_value_string(size, i), get_layer_value_string(repeat, i), get_layer_value_string(attachment, i), get_layer_value_string(origin, i), get_layer_value_string(clip, i));
}
return MUST(builder.to_string());
}
case Web::CSS::PropertyID::BackgroundPosition: {
auto x_edges = longhand(PropertyID::BackgroundPositionX);
auto y_edges = longhand(PropertyID::BackgroundPositionY);
auto get_layer_count = [](auto style_value) -> size_t {
return style_value->is_value_list() ? style_value->as_value_list().size() : 1;
};
// FIXME: The spec is unclear about how differing layer counts should be handled
auto layer_count = max(get_layer_count(x_edges), get_layer_count(y_edges));
if (layer_count == 1) {
return MUST(String::formatted("{} {}", x_edges->to_string(mode), y_edges->to_string(mode)));
}
auto get_layer_value_string = [mode](ValueComparingRefPtr<CSSStyleValue const> const& style_value, size_t index) {
if (style_value->is_value_list())
return style_value->as_value_list().value_at(index, true)->to_string(mode);
return style_value->to_string(mode);
};
StringBuilder builder;
for (size_t i = 0; i < layer_count; i++) {
if (i)
builder.append(", "sv);
builder.appendff("{} {}", get_layer_value_string(x_edges, i), get_layer_value_string(y_edges, i));
}
return MUST(builder.to_string());