2024-05-25 23:06:47 +01:00
|
|
|
|
/*
|
|
|
|
|
|
* Copyright (c) 2024, MacDue <macdue@dueutil.tech>
|
2025-07-17 16:39:45 +01:00
|
|
|
|
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
|
2024-05-25 23:06:47 +01:00
|
|
|
|
*
|
|
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#include "BasicShapeStyleValue.h"
|
2024-08-20 17:27:08 +02:00
|
|
|
|
#include <LibGfx/Path.h>
|
2025-07-17 16:39:45 +01:00
|
|
|
|
#include <LibWeb/CSS/Serialize.h>
|
2025-10-02 22:57:13 +13:00
|
|
|
|
#include <LibWeb/CSS/StyleValues/KeywordStyleValue.h>
|
2025-07-17 16:39:45 +01:00
|
|
|
|
#include <LibWeb/SVG/Path.h>
|
2024-05-25 23:06:47 +01:00
|
|
|
|
|
|
|
|
|
|
namespace Web::CSS {
|
|
|
|
|
|
|
2024-10-27 11:58:52 +11:00
|
|
|
|
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%.
|
|
|
|
|
|
|
2025-10-02 22:57:13 +13:00
|
|
|
|
auto resolved_top = LengthPercentageOrAuto::from_style_value(top).to_px_or_zero(node, reference_box.height()).to_float();
|
|
|
|
|
|
auto resolved_right = reference_box.width().to_float() - LengthPercentageOrAuto::from_style_value(right).to_px_or_zero(node, reference_box.width()).to_float();
|
|
|
|
|
|
auto resolved_bottom = reference_box.height().to_float() - LengthPercentageOrAuto::from_style_value(bottom).to_px_or_zero(node, reference_box.height()).to_float();
|
|
|
|
|
|
auto resolved_left = LengthPercentageOrAuto::from_style_value(left).to_px_or_zero(node, reference_box.width()).to_float();
|
2024-10-27 11:58:52 +11:00
|
|
|
|
|
2025-10-02 22:57:13 +13:00
|
|
|
|
return path_from_resolved_rect(resolved_top, resolved_right, resolved_bottom, resolved_left);
|
2024-10-27 11:58:52 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-02 22:57:13 +13:00
|
|
|
|
String Inset::to_string(SerializationMode mode) const
|
2024-10-27 11:58:52 +11:00
|
|
|
|
{
|
2025-10-02 22:57:13 +13:00
|
|
|
|
return MUST(String::formatted("inset({} {} {} {})", top->to_string(mode), right->to_string(mode), bottom->to_string(mode), left->to_string(mode)));
|
2024-10-27 11:58:52 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Gfx::Path Xywh::to_path(CSSPixelRect reference_box, Layout::Node const& node) const
|
|
|
|
|
|
{
|
2025-10-02 22:57:13 +13:00
|
|
|
|
auto top = LengthPercentage::from_style_value(y).to_px(node, reference_box.height()).to_float();
|
|
|
|
|
|
auto bottom = top + max(0.0f, LengthPercentage::from_style_value(height).to_px(node, reference_box.height()).to_float());
|
|
|
|
|
|
auto left = LengthPercentage::from_style_value(x).to_px(node, reference_box.width()).to_float();
|
|
|
|
|
|
auto right = left + max(0.0f, LengthPercentage::from_style_value(width).to_px(node, reference_box.width()).to_float());
|
2024-10-27 11:58:52 +11:00
|
|
|
|
|
|
|
|
|
|
return path_from_resolved_rect(top, right, bottom, left);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-02 22:57:13 +13:00
|
|
|
|
String Xywh::to_string(SerializationMode mode) const
|
2024-10-27 11:58:52 +11:00
|
|
|
|
{
|
2025-10-02 22:57:13 +13:00
|
|
|
|
return MUST(String::formatted("xywh({} {} {} {})", x->to_string(mode), y->to_string(mode), width->to_string(mode), height->to_string(mode)));
|
2024-10-27 11:58:52 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
2025-10-02 22:57:13 +13:00
|
|
|
|
auto resolved_top = top->has_auto() ? 0 : LengthPercentageOrAuto::from_style_value(top).to_px_or_zero(node, reference_box.height()).to_float();
|
|
|
|
|
|
auto resolved_right = right->has_auto() ? reference_box.width().to_float() : LengthPercentageOrAuto::from_style_value(right).to_px_or_zero(node, reference_box.width()).to_float();
|
|
|
|
|
|
auto resolved_bottom = bottom->has_auto() ? reference_box.height().to_float() : LengthPercentageOrAuto::from_style_value(bottom).to_px_or_zero(node, reference_box.height()).to_float();
|
|
|
|
|
|
auto resolved_left = left->has_auto() ? 0 : LengthPercentageOrAuto::from_style_value(left).to_px_or_zero(node, reference_box.width()).to_float();
|
2024-10-27 11:58:52 +11:00
|
|
|
|
|
|
|
|
|
|
// The second (right) and third (bottom) values are floored by the fourth (left) and second (top) values, respectively.
|
2025-10-02 22:57:13 +13:00
|
|
|
|
return path_from_resolved_rect(resolved_top, max(resolved_right, resolved_left), max(resolved_bottom, resolved_top), resolved_left);
|
2024-10-27 11:58:52 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-02 22:57:13 +13:00
|
|
|
|
String Rect::to_string(SerializationMode mode) const
|
2024-10-27 11:58:52 +11:00
|
|
|
|
{
|
2025-10-02 22:57:13 +13:00
|
|
|
|
return MUST(String::formatted("rect({} {} {} {})", top->to_string(mode), right->to_string(mode), bottom->to_string(mode), left->to_string(mode)));
|
2024-10-27 11:58:52 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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()));
|
|
|
|
|
|
|
2025-10-02 22:57:13 +13:00
|
|
|
|
auto radius_px = [&]() {
|
|
|
|
|
|
if (radius->is_keyword()) {
|
|
|
|
|
|
switch (*keyword_to_fit_side(radius->to_keyword())) {
|
2024-10-27 11:58:52 +11:00
|
|
|
|
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();
|
2025-10-02 22:57:13 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
auto radius_ref = sqrt(pow(reference_box.width().to_float(), 2) + pow(reference_box.height().to_float(), 2)) / AK::Sqrt2<float>;
|
|
|
|
|
|
return max(0.0f, LengthPercentage::from_style_value(radius).to_px(node, CSSPixels(radius_ref)).to_float());
|
|
|
|
|
|
}();
|
2024-10-27 11:58:52 +11:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-16 19:20:24 +01:00
|
|
|
|
String Circle::to_string(SerializationMode mode) const
|
2024-10-27 11:58:52 +11:00
|
|
|
|
{
|
2025-10-02 22:57:13 +13:00
|
|
|
|
return MUST(String::formatted("circle({} at {})", radius->to_string(mode), position->to_string(mode)));
|
2024-10-27 11:58:52 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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()));
|
|
|
|
|
|
|
2025-10-02 22:57:13 +13:00
|
|
|
|
auto radius_x_px = [&]() {
|
|
|
|
|
|
if (radius_x->is_keyword()) {
|
|
|
|
|
|
switch (*keyword_to_fit_side(radius_x->to_keyword())) {
|
2024-10-27 11:58:52 +11:00
|
|
|
|
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();
|
2025-10-02 22:57:13 +13:00
|
|
|
|
}
|
|
|
|
|
|
return max(0.0f, LengthPercentage::from_style_value(radius_x).to_px(node, reference_box.width()).to_float());
|
|
|
|
|
|
}();
|
|
|
|
|
|
|
|
|
|
|
|
auto radius_y_px = [&]() {
|
|
|
|
|
|
if (radius_y->is_keyword()) {
|
|
|
|
|
|
switch (*keyword_to_fit_side(radius_y->to_keyword())) {
|
2024-10-27 11:58:52 +11:00
|
|
|
|
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();
|
2025-10-02 22:57:13 +13:00
|
|
|
|
}
|
|
|
|
|
|
return max(0.0f, LengthPercentage::from_style_value(radius_y).to_px(node, reference_box.height()).to_float());
|
|
|
|
|
|
}();
|
2024-10-27 11:58:52 +11:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-16 19:20:24 +01:00
|
|
|
|
String Ellipse::to_string(SerializationMode mode) const
|
2024-10-27 11:58:52 +11:00
|
|
|
|
{
|
2025-10-02 22:57:13 +13:00
|
|
|
|
return MUST(String::formatted("ellipse({} {} at {})", radius_x->to_string(mode), radius_y->to_string(mode), position->to_string(mode)));
|
2024-10-27 11:58:52 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-08-20 17:27:08 +02:00
|
|
|
|
Gfx::Path Polygon::to_path(CSSPixelRect reference_box, Layout::Node const& node) const
|
2024-05-25 23:06:47 +01:00
|
|
|
|
{
|
2024-08-20 17:27:08 +02:00
|
|
|
|
Gfx::Path path;
|
2024-10-28 13:51:23 +11:00
|
|
|
|
path.set_fill_type(fill_rule);
|
2024-05-25 23:06:47 +01:00
|
|
|
|
bool first = true;
|
|
|
|
|
|
for (auto const& point : points) {
|
|
|
|
|
|
Gfx::FloatPoint resolved_point {
|
2025-10-02 22:57:13 +13:00
|
|
|
|
LengthPercentage::from_style_value(point.x).to_px(node, reference_box.width()).to_float(),
|
|
|
|
|
|
LengthPercentage::from_style_value(point.y).to_px(node, reference_box.height()).to_float()
|
2024-05-25 23:06:47 +01:00
|
|
|
|
};
|
|
|
|
|
|
if (first)
|
|
|
|
|
|
path.move_to(resolved_point);
|
|
|
|
|
|
else
|
|
|
|
|
|
path.line_to(resolved_point);
|
|
|
|
|
|
first = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
path.close();
|
|
|
|
|
|
return path;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-02 22:57:13 +13:00
|
|
|
|
String Polygon::to_string(SerializationMode mode) const
|
2024-05-25 23:06:47 +01:00
|
|
|
|
{
|
|
|
|
|
|
StringBuilder builder;
|
|
|
|
|
|
builder.append("polygon("sv);
|
2024-10-28 13:51:23 +11:00
|
|
|
|
switch (fill_rule) {
|
|
|
|
|
|
case Gfx::WindingRule::Nonzero:
|
|
|
|
|
|
builder.append("nonzero"sv);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case Gfx::WindingRule::EvenOdd:
|
|
|
|
|
|
builder.append("evenodd"sv);
|
|
|
|
|
|
}
|
2024-05-25 23:06:47 +01:00
|
|
|
|
for (auto const& point : points) {
|
2025-10-02 22:57:13 +13:00
|
|
|
|
builder.appendff(", {} {}", point.x->to_string(mode), point.y->to_string(mode));
|
2024-05-25 23:06:47 +01:00
|
|
|
|
}
|
|
|
|
|
|
builder.append(')');
|
|
|
|
|
|
return MUST(builder.to_string());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-17 16:39:45 +01:00
|
|
|
|
Gfx::Path Path::to_path(CSSPixelRect, Layout::Node const&) const
|
|
|
|
|
|
{
|
|
|
|
|
|
auto result = path_instructions.to_gfx_path();
|
|
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-25 23:06:47 +01:00
|
|
|
|
BasicShapeStyleValue::~BasicShapeStyleValue() = default;
|
|
|
|
|
|
|
2024-08-20 17:27:08 +02:00
|
|
|
|
Gfx::Path BasicShapeStyleValue::to_path(CSSPixelRect reference_box, Layout::Node const& node) const
|
2024-05-25 23:06:47 +01:00
|
|
|
|
{
|
|
|
|
|
|
return m_basic_shape.visit([&](auto const& shape) {
|
|
|
|
|
|
return shape.to_path(reference_box, node);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-29 22:44:14 +11:00
|
|
|
|
String BasicShapeStyleValue::to_string(SerializationMode mode) const
|
2024-05-25 23:06:47 +01:00
|
|
|
|
{
|
2024-11-29 22:44:14 +11:00
|
|
|
|
return m_basic_shape.visit([mode](auto const& shape) {
|
|
|
|
|
|
return shape.to_string(mode);
|
2024-05-25 23:06:47 +01:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-02 23:19:17 +13:00
|
|
|
|
ValueComparingNonnullRefPtr<StyleValue const> BasicShapeStyleValue::absolutized(ComputationContext const& computation_context) const
|
|
|
|
|
|
{
|
|
|
|
|
|
auto absolutized_shape = m_basic_shape.visit(
|
|
|
|
|
|
[&](Inset const& shape) -> BasicShape {
|
|
|
|
|
|
auto absolutized_top = shape.top->absolutized(computation_context);
|
|
|
|
|
|
auto absolutized_right = shape.right->absolutized(computation_context);
|
|
|
|
|
|
auto absolutized_bottom = shape.bottom->absolutized(computation_context);
|
|
|
|
|
|
auto absolutized_left = shape.left->absolutized(computation_context);
|
|
|
|
|
|
|
|
|
|
|
|
if (absolutized_top == shape.top && absolutized_right == shape.right && absolutized_bottom == shape.bottom && absolutized_left == shape.left)
|
|
|
|
|
|
return shape;
|
|
|
|
|
|
|
|
|
|
|
|
return Inset { absolutized_top, absolutized_right, absolutized_bottom, absolutized_left };
|
|
|
|
|
|
},
|
|
|
|
|
|
[&](Xywh const& shape) -> BasicShape {
|
|
|
|
|
|
auto absolutized_x = shape.x->absolutized(computation_context);
|
|
|
|
|
|
auto absolutized_y = shape.y->absolutized(computation_context);
|
|
|
|
|
|
auto absolutized_width = shape.width->absolutized(computation_context);
|
|
|
|
|
|
auto absolutized_height = shape.height->absolutized(computation_context);
|
|
|
|
|
|
|
|
|
|
|
|
if (absolutized_x == shape.x && absolutized_y == shape.y && absolutized_width == shape.width && absolutized_height == shape.height)
|
|
|
|
|
|
return shape;
|
|
|
|
|
|
|
|
|
|
|
|
return Xywh { absolutized_x, absolutized_y, absolutized_width, absolutized_height };
|
|
|
|
|
|
},
|
|
|
|
|
|
[&](Rect const& shape) -> BasicShape {
|
|
|
|
|
|
auto absolutized_top = shape.top->absolutized(computation_context);
|
|
|
|
|
|
auto absolutized_right = shape.right->absolutized(computation_context);
|
|
|
|
|
|
auto absolutized_bottom = shape.bottom->absolutized(computation_context);
|
|
|
|
|
|
auto absolutized_left = shape.left->absolutized(computation_context);
|
|
|
|
|
|
|
|
|
|
|
|
if (absolutized_top == shape.top && absolutized_right == shape.right && absolutized_bottom == shape.bottom && absolutized_left == shape.left)
|
|
|
|
|
|
return shape;
|
|
|
|
|
|
|
|
|
|
|
|
return Rect { absolutized_top, absolutized_right, absolutized_bottom, absolutized_left };
|
|
|
|
|
|
},
|
|
|
|
|
|
[&](Circle const& shape) -> BasicShape {
|
|
|
|
|
|
auto absolutized_radius = shape.radius->absolutized(computation_context);
|
|
|
|
|
|
auto absolutized_position = shape.position->absolutized(computation_context);
|
|
|
|
|
|
|
|
|
|
|
|
if (absolutized_radius == shape.radius && absolutized_position->as_position() == *shape.position)
|
|
|
|
|
|
return shape;
|
|
|
|
|
|
|
|
|
|
|
|
return Circle { absolutized_radius, absolutized_position->as_position() };
|
|
|
|
|
|
},
|
|
|
|
|
|
[&](Ellipse const& shape) -> BasicShape {
|
|
|
|
|
|
auto absolutized_radius_x = shape.radius_x->absolutized(computation_context);
|
|
|
|
|
|
auto absolutized_radius_y = shape.radius_y->absolutized(computation_context);
|
|
|
|
|
|
auto absolutized_position = shape.position->absolutized(computation_context);
|
|
|
|
|
|
|
|
|
|
|
|
if (absolutized_radius_x == shape.radius_x && absolutized_radius_y == shape.radius_y && absolutized_position->as_position() == *shape.position)
|
|
|
|
|
|
return shape;
|
|
|
|
|
|
|
|
|
|
|
|
return Ellipse { absolutized_radius_x, absolutized_radius_y, absolutized_position->as_position() };
|
|
|
|
|
|
},
|
|
|
|
|
|
[&](Polygon const& shape) -> BasicShape {
|
|
|
|
|
|
Vector<Polygon::Point> absolutized_points;
|
|
|
|
|
|
absolutized_points.ensure_capacity(shape.points.size());
|
|
|
|
|
|
|
|
|
|
|
|
bool any_point_required_absolutization = false;
|
|
|
|
|
|
|
|
|
|
|
|
for (auto const& point : shape.points) {
|
|
|
|
|
|
auto absolutized_x = point.x->absolutized(computation_context);
|
|
|
|
|
|
auto absolutized_y = point.y->absolutized(computation_context);
|
|
|
|
|
|
|
|
|
|
|
|
if (absolutized_x == point.x && absolutized_y == point.y) {
|
|
|
|
|
|
absolutized_points.append(point);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
any_point_required_absolutization = true;
|
|
|
|
|
|
absolutized_points.append({ absolutized_x, absolutized_y });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!any_point_required_absolutization)
|
|
|
|
|
|
return shape;
|
|
|
|
|
|
|
|
|
|
|
|
return Polygon { shape.fill_rule, absolutized_points };
|
|
|
|
|
|
},
|
|
|
|
|
|
[&](Path const& shape) -> BasicShape {
|
|
|
|
|
|
return shape;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (absolutized_shape == m_basic_shape)
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
|
|
|
|
|
|
return BasicShapeStyleValue::create(absolutized_shape);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-25 23:06:47 +01:00
|
|
|
|
}
|