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>
2026-03-15 23:29:48 +01:00
# include <LibWeb/CSS/StyleValues/BorderRadiusRectStyleValue.h>
2025-10-02 22:57:13 +13:00
# include <LibWeb/CSS/StyleValues/KeywordStyleValue.h>
2025-12-11 17:45:49 +13:00
# include <LibWeb/CSS/StyleValues/RadialSizeStyleValue.h>
2025-10-28 21:11:17 +13:00
# include <LibWeb/CSS/ValueType.h>
2026-03-15 23:29:48 +01:00
# include <LibWeb/Painting/BorderRadiiData.h>
# include <LibWeb/Painting/Paintable.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 ;
}
2026-03-15 23:29:48 +01:00
// https://drafts.csswg.org/css-shapes/#funcdef-basic-shape-inset
2024-10-27 11:58:52 +11:00
Gfx : : Path Inset : : to_path ( CSSPixelRect reference_box , Layout : : Node const & node ) const
{
2025-10-28 23:23:00 +13:00
auto resolved_top = LengthPercentageOrAuto : : from_style_value ( top ) . to_px_or_zero ( node , reference_box . height ( ) ) . to_float ( ) ;
auto resolved_right = LengthPercentageOrAuto : : from_style_value ( right ) . to_px_or_zero ( node , reference_box . width ( ) ) . to_float ( ) ;
auto resolved_bottom = 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 ( ) ;
// A pair of insets in either dimension that add up to more than the used dimension
2024-10-27 11:58:52 +11:00
// (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-28 23:23:00 +13:00
if ( resolved_top + resolved_bottom > reference_box . height ( ) . to_float ( ) | | resolved_left + resolved_right > reference_box . width ( ) . to_float ( ) ) {
// https://drafts.csswg.org/css-backgrounds-3/#corner-overlap
// Let f = min(Li/Si), where i ∈ {top, right, bottom, left}, Si is the sum of the two corresponding radii of the
// corners on side i, and Ltop = Lbottom = the width of the box, and Lleft = Lright = the height of the box. If
// f < 1, then all corner radii are reduced by multiplying them by f.
2024-10-27 11:58:52 +11:00
2025-10-28 23:23:00 +13:00
// NB: We only care about vertical and horizontal here as top = bottom and left = right
auto s_vertical = resolved_top + resolved_bottom ;
auto s_horizontal = resolved_left + resolved_right ;
auto f = min ( reference_box . height ( ) / s_vertical , reference_box . width ( ) / s_horizontal ) ;
resolved_top * = f ;
resolved_right * = f ;
resolved_bottom * = f ;
resolved_left * = f ;
}
2024-10-27 11:58:52 +11:00
2026-03-15 23:29:48 +01:00
float left_edge = resolved_left ;
float top_edge = resolved_top ;
float right_edge = reference_box . width ( ) . to_float ( ) - resolved_right ;
float bottom_edge = reference_box . height ( ) . to_float ( ) - resolved_bottom ;
CSSPixelRect inset_rect {
CSSPixels ( left_edge ) , CSSPixels ( top_edge ) ,
CSSPixels ( right_edge - left_edge ) , CSSPixels ( bottom_edge - top_edge )
} ;
auto const & border_radius_rect = border_radius - > as_border_radius_rect ( ) ;
auto to_border_radius_data = [ ] ( StyleValue const & corner ) - > CSS : : BorderRadiusData {
auto const & br = corner . as_border_radius ( ) ;
return CSS : : BorderRadiusData {
LengthPercentage : : from_style_value ( br . horizontal_radius ( ) ) ,
LengthPercentage : : from_style_value ( br . vertical_radius ( ) )
} ;
} ;
auto radii = Painting : : normalize_border_radii_data (
node ,
inset_rect ,
reference_box ,
to_border_radius_data ( * border_radius_rect . top_left ( ) ) ,
to_border_radius_data ( * border_radius_rect . top_right ( ) ) ,
to_border_radius_data ( * border_radius_rect . bottom_right ( ) ) ,
to_border_radius_data ( * border_radius_rect . bottom_left ( ) ) ) ;
if ( ! radii . has_any_radius ( ) )
return path_from_resolved_rect ( top_edge , right_edge , bottom_edge , left_edge ) ;
auto top_left_horizontal_radius = radii . top_left . horizontal_radius . to_float ( ) ;
auto top_left_vertical_radius = radii . top_left . vertical_radius . to_float ( ) ;
auto top_right_horizontal_radius = radii . top_right . horizontal_radius . to_float ( ) ;
auto top_right_vertical_radius = radii . top_right . vertical_radius . to_float ( ) ;
auto bottom_right_horizontal_radius = radii . bottom_right . horizontal_radius . to_float ( ) ;
auto bottom_right_vertical_radius = radii . bottom_right . vertical_radius . to_float ( ) ;
auto bottom_left_horizontal_radius = radii . bottom_left . horizontal_radius . to_float ( ) ;
auto bottom_left_vertical_radius = radii . bottom_left . vertical_radius . to_float ( ) ;
Gfx : : Path path ;
path . move_to ( { left_edge + top_left_horizontal_radius , top_edge } ) ;
path . line_to ( { right_edge - top_right_horizontal_radius , top_edge } ) ;
if ( top_right_horizontal_radius > 0 & & top_right_vertical_radius > 0 )
path . elliptical_arc_to ( { right_edge , top_edge + top_right_vertical_radius } , { top_right_horizontal_radius , top_right_vertical_radius } , 0 , false , true ) ;
path . line_to ( { right_edge , bottom_edge - bottom_right_vertical_radius } ) ;
if ( bottom_right_horizontal_radius > 0 & & bottom_right_vertical_radius > 0 )
path . elliptical_arc_to ( { right_edge - bottom_right_horizontal_radius , bottom_edge } , { bottom_right_horizontal_radius , bottom_right_vertical_radius } , 0 , false , true ) ;
path . line_to ( { left_edge + bottom_left_horizontal_radius , bottom_edge } ) ;
if ( bottom_left_horizontal_radius > 0 & & bottom_left_vertical_radius > 0 )
path . elliptical_arc_to ( { left_edge , bottom_edge - bottom_left_vertical_radius } , { bottom_left_horizontal_radius , bottom_left_vertical_radius } , 0 , false , true ) ;
path . line_to ( { left_edge , top_edge + top_left_vertical_radius } ) ;
if ( top_left_horizontal_radius > 0 & & top_left_vertical_radius > 0 )
path . elliptical_arc_to ( { left_edge + top_left_horizontal_radius , top_edge } , { top_left_horizontal_radius , top_left_vertical_radius } , 0 , false , true ) ;
path . close ( ) ;
return path ;
2024-10-27 11:58:52 +11:00
}
2026-01-08 12:28:46 +00:00
void Inset : : serialize ( StringBuilder & builder , SerializationMode mode ) const
2024-10-27 11:58:52 +11:00
{
2026-01-08 12:28:46 +00:00
builder . append ( " inset( " sv ) ;
2026-01-05 22:29:49 +13:00
builder . append ( serialize_a_positional_value_list ( { top , right , bottom , left } , mode ) ) ;
auto serialized_border_radius = border_radius - > to_string ( mode ) ;
if ( serialized_border_radius ! = " 0px " sv )
builder . appendff ( " round {} " , serialized_border_radius ) ;
2026-01-08 12:28:46 +00:00
builder . append ( ' ) ' ) ;
2024-10-27 11:58:52 +11:00
}
2026-01-08 12:28:46 +00:00
void Xywh : : serialize ( StringBuilder & builder , SerializationMode mode ) const
2024-10-27 11:58:52 +11:00
{
2026-01-08 12:28:46 +00:00
builder . append ( " xywh( " sv ) ;
x - > serialize ( builder , mode ) ;
builder . append ( ' ' ) ;
y - > serialize ( builder , mode ) ;
builder . append ( ' ' ) ;
width - > serialize ( builder , mode ) ;
builder . append ( ' ' ) ;
height - > serialize ( builder , mode ) ;
2026-01-05 22:52:28 +13:00
2026-01-08 12:28:46 +00:00
auto serialized_border_radius = border_radius - > to_string ( mode ) ;
if ( serialized_border_radius ! = " 0px " sv )
builder . appendff ( " round {} " , serialized_border_radius ) ;
2026-01-05 22:52:28 +13:00
2026-01-08 12:28:46 +00:00
builder . append ( ' ) ' ) ;
2024-10-27 11:58:52 +11:00
}
2026-01-08 12:28:46 +00:00
void Rect : : serialize ( StringBuilder & builder , SerializationMode mode ) const
2024-10-27 11:58:52 +11:00
{
2026-01-08 12:28:46 +00:00
builder . append ( " rect( " sv ) ;
top - > serialize ( builder , mode ) ;
builder . append ( ' ' ) ;
right - > serialize ( builder , mode ) ;
builder . append ( ' ' ) ;
bottom - > serialize ( builder , mode ) ;
builder . append ( ' ' ) ;
left - > serialize ( builder , mode ) ;
2026-01-05 22:44:11 +13:00
auto serialized_border_radius = border_radius - > to_string ( mode ) ;
if ( serialized_border_radius ! = " 0px " sv )
builder . appendff ( " round {} " , serialized_border_radius ) ;
2026-01-08 12:28:46 +00:00
builder . append ( ' ) ' ) ;
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.
2025-12-11 17:45:49 +13:00
auto translated_reference_box = reference_box . translated ( - reference_box . x ( ) , - reference_box . y ( ) ) ;
2024-10-27 11:58:52 +11:00
2025-12-10 13:33:22 +13:00
// https://www.w3.org/TR/css-shapes/#funcdef-basic-shape-circle
// The <position> argument defines the center of the circle. Unless otherwise specified, this defaults to center if omitted.
RefPtr < PositionStyleValue const > resolved_position = PositionStyleValue : : create_computed_center ( ) ;
if ( position )
resolved_position = position - > as_position ( ) ;
auto center = resolved_position - > resolved ( node , translated_reference_box ) ;
2025-12-11 17:45:49 +13:00
auto radius_px = radius - > as_radial_size ( ) . resolve_circle_size ( center , translated_reference_box , node ) . 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 ;
}
2026-01-08 12:28:46 +00:00
void Circle : : serialize ( StringBuilder & builder , SerializationMode mode ) const
2024-10-27 11:58:52 +11:00
{
2026-01-08 12:28:46 +00:00
builder . append ( " circle( " sv ) ;
2025-12-10 13:44:03 +13:00
auto serialized_radius = radius - > to_string ( mode ) ;
2025-12-10 13:33:22 +13:00
2026-01-08 12:28:46 +00:00
bool has_radius = serialized_radius ! = " closest-side " sv ;
if ( has_radius )
builder . append ( serialized_radius ) ;
2025-12-10 13:44:03 +13:00
if ( position ) {
2026-01-08 12:28:46 +00:00
if ( has_radius )
builder . append ( ' ' ) ;
builder . append ( " at " sv ) ;
position - > serialize ( builder , mode ) ;
2025-12-10 13:44:03 +13:00
}
2025-12-10 13:33:22 +13:00
2026-01-08 12:28:46 +00:00
builder . append ( ' ) ' ) ;
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.
2025-12-12 14:21:35 +13:00
auto translated_reference_box = reference_box . translated ( - reference_box . x ( ) , - reference_box . y ( ) ) ;
2025-12-12 14:42:40 +13:00
// https://www.w3.org/TR/css-shapes/#funcdef-basic-shape-circle
// The <position> argument defines the center of the ellipse. Unless otherwise specified, this defaults to center if omitted.
RefPtr < PositionStyleValue const > resolved_position = PositionStyleValue : : create_computed_center ( ) ;
if ( position )
resolved_position = position - > as_position ( ) ;
auto center = resolved_position - > resolved ( node , translated_reference_box ) ;
2025-12-12 14:21:35 +13:00
auto size = radius - > as_radial_size ( ) . resolve_ellipse_size ( center , translated_reference_box , node ) ;
2024-10-27 11:58:52 +11:00
Gfx : : Path path ;
2025-12-12 14:21:35 +13:00
path . move_to ( Gfx : : FloatPoint { center . x ( ) . to_float ( ) , center . y ( ) . to_float ( ) + size . height ( ) . to_float ( ) } ) ;
path . elliptical_arc_to ( Gfx : : FloatPoint { center . x ( ) . to_float ( ) , center . y ( ) . to_float ( ) - size . height ( ) . to_float ( ) } , Gfx : : FloatSize { size . width ( ) . to_float ( ) , size . height ( ) . to_float ( ) } , 0 , true , true ) ;
path . elliptical_arc_to ( Gfx : : FloatPoint { center . x ( ) . to_float ( ) , center . y ( ) . to_float ( ) + size . height ( ) . to_float ( ) } , Gfx : : FloatSize { size . width ( ) . to_float ( ) , size . height ( ) . to_float ( ) } , 0 , true , true ) ;
2024-10-27 11:58:52 +11:00
return path ;
}
2026-01-08 12:28:46 +00:00
void Ellipse : : serialize ( StringBuilder & builder , SerializationMode mode ) const
2024-10-27 11:58:52 +11:00
{
2026-01-08 12:28:46 +00:00
builder . append ( " ellipse( " sv ) ;
2025-12-12 14:46:19 +13:00
auto serialized_radius = radius - > to_string ( mode ) ;
2025-12-12 14:42:40 +13:00
2026-01-08 12:28:46 +00:00
bool has_radius = serialized_radius ! = " closest-side closest-side " sv ;
if ( has_radius )
builder . append ( serialized_radius ) ;
2025-12-12 14:42:40 +13:00
2025-12-12 14:46:19 +13:00
if ( position ) {
2026-01-08 12:28:46 +00:00
if ( has_radius )
builder . append ( ' ' ) ;
builder . append ( " at " sv ) ;
position - > serialize ( builder , mode ) ;
2025-12-12 14:46:19 +13:00
}
2025-12-12 14:42:40 +13:00
2026-01-08 12:28:46 +00:00
builder . append ( ' ) ' ) ;
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 ;
}
2026-01-08 12:28:46 +00:00
void Polygon : : serialize ( StringBuilder & builder , SerializationMode mode ) const
2024-05-25 23:06:47 +01:00
{
builder . append ( " polygon( " sv ) ;
2026-01-06 22:46:08 +13:00
bool first = true ;
2024-10-28 13:51:23 +11:00
switch ( fill_rule ) {
case Gfx : : WindingRule : : Nonzero :
break ;
case Gfx : : WindingRule : : EvenOdd :
2026-01-06 22:46:08 +13:00
first = false ;
2024-10-28 13:51:23 +11:00
builder . append ( " evenodd " sv ) ;
}
2024-05-25 23:06:47 +01:00
for ( auto const & point : points ) {
2026-01-06 22:46:08 +13:00
if ( ! first )
builder . append ( " , " sv ) ;
first = false ;
2026-01-08 12:28:46 +00:00
point . x - > serialize ( builder , mode ) ;
builder . append ( ' ' ) ;
point . y - > serialize ( builder , mode ) ;
2024-05-25 23:06:47 +01:00
}
builder . append ( ' ) ' ) ;
}
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
2026-01-08 12:28:46 +00:00
void Path : : serialize ( StringBuilder & builder , SerializationMode mode ) const
2025-07-17 16:39:45 +01:00
{
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 ( ' ) ' ) ;
}
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
{
2025-10-28 21:11:17 +13:00
return m_basic_shape . visit ( [ & ] ( auto const & shape ) - > Gfx : : Path {
// NB: Xywh and Rect don't require to_path functions as we should have already converted them to their
// respective Inset equivalents during absolutization
if constexpr ( requires { shape . to_path ( reference_box , node ) ; } ) {
return shape . to_path ( reference_box , node ) ;
}
VERIFY_NOT_REACHED ( ) ;
2024-05-25 23:06:47 +01:00
} ) ;
}
2026-01-08 12:02:18 +00:00
void BasicShapeStyleValue : : serialize ( StringBuilder & builder , SerializationMode mode ) const
2024-05-25 23:06:47 +01:00
{
2026-01-08 12:28:46 +00:00
m_basic_shape . visit ( [ & ] ( auto const & shape ) {
shape . serialize ( builder , mode ) ;
} ) ;
2024-05-25 23:06:47 +01:00
}
2025-10-28 21:11:17 +13:00
// https://www.w3.org/TR/css-shapes-1/#basic-shape-computed-values
2025-10-02 23:19:17 +13:00
ValueComparingNonnullRefPtr < StyleValue const > BasicShapeStyleValue : : absolutized ( ComputationContext const & computation_context ) const
{
2025-10-28 21:11:17 +13:00
// The values in a <basic-shape> function are computed as specified, with these exceptions:
// - Omitted values are included and compute to their defaults.
// FIXME: - A <position> value in circle() or ellipse() is computed as a pair of offsets (horizontal then vertical) from the top left origin, each given as a <length-percentage>.
// FIXME: - A <'border-radius'> value in a <basic-shape-rect> function is computed as an expanded list of all eight <length-percentage> values.
// - All <basic-shape-rect> functions compute to the equivalent inset() function.
CalculationContext calculation_context { . percentages_resolve_as = ValueType : : Length } ;
auto const one_hundred_percent_minus = [ & ] ( Vector < NonnullRefPtr < StyleValue const > > const & values , CalculationContext const & calculation_context ) {
Vector < NonnullRefPtr < CalculationNode const > > sum_components = { NumericCalculationNode : : create ( Percentage { 100 } , calculation_context ) } ;
for ( auto const & value : values )
sum_components . append ( NegateCalculationNode : : create ( CalculationNode : : from_style_value ( value , calculation_context ) ) ) ;
return CalculatedStyleValue : : create ( SumCalculationNode : : create ( sum_components ) , NumericType { NumericType : : BaseType : : Length , 1 } , calculation_context ) ;
} ;
2025-12-10 13:33:22 +13:00
auto const absolutize_if_nonnull = [ & ] ( RefPtr < StyleValue const > const & value ) - > ValueComparingRefPtr < StyleValue const > {
if ( ! value )
return nullptr ;
return value - > absolutized ( computation_context ) ;
} ;
2025-10-02 23:19:17 +13:00
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 ) ;
2026-01-05 22:29:49 +13:00
auto absolutized_border_radius = shape . border_radius - > absolutized ( computation_context ) ;
if ( absolutized_top = = shape . top & & absolutized_right = = shape . right & & absolutized_bottom = = shape . bottom & & absolutized_left = = shape . left & & absolutized_border_radius = = shape . border_radius )
2025-10-02 23:19:17 +13:00
return shape ;
2026-01-05 22:29:49 +13:00
return Inset { absolutized_top , absolutized_right , absolutized_bottom , absolutized_left , absolutized_border_radius } ;
2025-10-02 23:19:17 +13:00
} ,
[ & ] ( Xywh const & shape ) - > BasicShape {
2025-10-28 21:11:17 +13:00
// Note: Given xywh(x y w h), the equivalent function is inset(y calc(100% - x - w) calc(100% - y - h) x).
auto absolutized_top = shape . y - > absolutized ( computation_context ) ;
auto absolutized_right = one_hundred_percent_minus ( { shape . x , shape . width } , calculation_context ) - > absolutized ( computation_context ) ;
auto absolutized_bottom = one_hundred_percent_minus ( { shape . y , shape . height } , calculation_context ) - > absolutized ( computation_context ) ;
auto absolutized_left = shape . x - > absolutized ( computation_context ) ;
2026-01-05 22:52:28 +13:00
auto absolutized_border_radius = shape . border_radius - > absolutized ( computation_context ) ;
2025-10-02 23:19:17 +13:00
2026-01-05 22:52:28 +13:00
return Inset { * absolutized_top , * absolutized_right , * absolutized_bottom , * absolutized_left , absolutized_border_radius } ;
2025-10-02 23:19:17 +13:00
} ,
[ & ] ( Rect const & shape ) - > BasicShape {
2025-10-28 21:11:17 +13:00
// Note: Given rect(t r b l), the equivalent function is inset(t calc(100% - r) calc(100% - b) l).
auto resolve_auto = [ ] ( ValueComparingNonnullRefPtr < StyleValue const > const & style_value , Percentage value_of_auto ) - > ValueComparingNonnullRefPtr < StyleValue 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.
if ( style_value - > is_keyword ( ) ) {
VERIFY ( style_value - > to_keyword ( ) = = Keyword : : Auto ) ;
return PercentageStyleValue : : create ( value_of_auto ) ;
}
return style_value ;
} ;
auto absolutized_top = resolve_auto ( shape . top , Percentage { 0 } ) - > absolutized ( computation_context ) ;
auto absolutized_right = one_hundred_percent_minus ( { resolve_auto ( shape . right , Percentage { 100 } ) } , calculation_context ) - > absolutized ( computation_context ) ;
auto absolutized_bottom = one_hundred_percent_minus ( { resolve_auto ( shape . bottom , Percentage { 100 } ) } , calculation_context ) - > absolutized ( computation_context ) ;
auto absolutized_left = resolve_auto ( shape . left , Percentage { 0 } ) - > absolutized ( computation_context ) ;
2026-01-05 22:44:11 +13:00
auto absolutized_border_radius = shape . border_radius - > absolutized ( computation_context ) ;
2025-10-28 21:11:17 +13:00
2026-01-05 22:44:11 +13:00
return Inset { * absolutized_top , * absolutized_right , * absolutized_bottom , * absolutized_left , absolutized_border_radius } ;
2025-10-02 23:19:17 +13:00
} ,
[ & ] ( Circle const & shape ) - > BasicShape {
auto absolutized_radius = shape . radius - > absolutized ( computation_context ) ;
2025-12-10 13:33:22 +13:00
auto absolutized_position = absolutize_if_nonnull ( shape . position ) ;
2025-10-02 23:19:17 +13:00
2025-12-10 13:33:22 +13:00
if ( absolutized_radius = = shape . radius & & absolutized_position = = shape . position )
2025-10-02 23:19:17 +13:00
return shape ;
2025-12-10 13:33:22 +13:00
return Circle { absolutized_radius , absolutized_position } ;
2025-10-02 23:19:17 +13:00
} ,
[ & ] ( Ellipse const & shape ) - > BasicShape {
2025-12-12 14:21:35 +13:00
auto absolutized_radius = shape . radius - > absolutized ( computation_context ) ;
2025-12-12 14:42:40 +13:00
auto absolutized_position = absolutize_if_nonnull ( shape . position ) ;
2025-10-02 23:19:17 +13:00
2025-12-12 14:42:40 +13:00
if ( absolutized_radius = = shape . radius & & absolutized_position = = shape . position )
2025-10-02 23:19:17 +13:00
return shape ;
2025-12-12 14:42:40 +13:00
return Ellipse { absolutized_radius , absolutized_position } ;
2025-10-02 23:19:17 +13:00
} ,
[ & ] ( 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
}