LibWeb/CSS: Absolutize GradientStyleValues

This lets us not care about non-absolute Length units when resolving
gradient data, as they'll already have been converted to px.

We can also use Angle::from_style_value() safely on absolutized angles,
which reduces some boilerplate code.
This commit is contained in:
Sam Atkins 2025-11-20 17:16:15 +00:00
parent fbe0567f90
commit f81bb1bd8c
Notes: github-actions[bot] 2025-12-01 11:09:51 +00:00
10 changed files with 205 additions and 161 deletions

View file

@ -16,6 +16,24 @@ GC::Ref<CSSStyleValue> AbstractImageStyleValue::reify(JS::Realm& realm, FlyStrin
return CSSImageValue::create(realm, *this);
}
ColorStopListElement ColorStopListElement::absolutized(ComputationContext const& context) const
{
auto absolutize_if_nonnull = [&context](RefPtr<StyleValue const> const& input) -> RefPtr<StyleValue const> {
if (!input)
return {};
return input->absolutized(context);
};
return {
.transition_hint = transition_hint,
.color_stop = {
.color = absolutize_if_nonnull(color_stop.color),
.position = absolutize_if_nonnull(color_stop.position),
.second_position = absolutize_if_nonnull(color_stop.second_position),
},
};
}
void serialize_color_stop_list(StringBuilder& builder, Vector<ColorStopListElement> const& color_stop_list, SerializationMode mode)
{
bool first = true;

View file

@ -172,6 +172,7 @@ struct ColorStopListElement {
} color_stop;
bool operator==(ColorStopListElement const&) const = default;
ColorStopListElement absolutized(ComputationContext const& context) const;
};
void serialize_color_stop_list(StringBuilder&, Vector<ColorStopListElement> const&, SerializationMode);

View file

@ -65,6 +65,20 @@ void ConicGradientStyleValue::paint(DisplayListRecordingContext& context, Device
context.display_list_recorder().fill_rect_with_conic_gradient(destination_rect, m_resolved->data, position);
}
ValueComparingNonnullRefPtr<StyleValue const> ConicGradientStyleValue::absolutized(ComputationContext const& context) const
{
Vector<ColorStopListElement> absolutized_color_stops;
absolutized_color_stops.ensure_capacity(m_properties.color_stop_list.size());
for (auto const& color_stop : m_properties.color_stop_list) {
absolutized_color_stops.unchecked_append(color_stop.absolutized(context));
}
RefPtr<StyleValue const> absolutized_from_angle;
if (m_properties.from_angle)
absolutized_from_angle = m_properties.from_angle->absolutized(context);
ValueComparingNonnullRefPtr<PositionStyleValue const> absolutized_position = m_properties.position->absolutized(context)->as_position();
return create(move(absolutized_from_angle), move(absolutized_position), move(absolutized_color_stops), m_properties.repeating, m_properties.interpolation_method);
}
bool ConicGradientStyleValue::equals(StyleValue const& other) const
{
if (type() != other.type())
@ -73,18 +87,11 @@ bool ConicGradientStyleValue::equals(StyleValue const& other) const
return m_properties == other_gradient.m_properties;
}
float ConicGradientStyleValue::angle_degrees(CalculationResolutionContext const& context) const
float ConicGradientStyleValue::angle_degrees() const
{
if (!m_properties.from_angle)
return 0;
if (m_properties.from_angle->is_angle())
return m_properties.from_angle->as_angle().angle().to_degrees();
if (m_properties.from_angle->is_calculated()) {
if (auto maybe_angle = m_properties.from_angle->as_calculated().resolve_angle(context); maybe_angle.has_value())
return maybe_angle->to_degrees();
return 0;
}
VERIFY_NOT_REACHED();
return Angle::from_style_value(*m_properties.from_angle, {}).to_degrees();
}
}

View file

@ -27,6 +27,7 @@ public:
void paint(DisplayListRecordingContext&, DevicePixelRect const& dest_rect, CSS::ImageRendering) const override;
virtual ValueComparingNonnullRefPtr<StyleValue const> absolutized(ComputationContext const&) const override;
virtual bool equals(StyleValue const& other) const override;
Vector<ColorStopListElement> const& color_stop_list() const
@ -42,7 +43,7 @@ public:
return InterpolationMethod { .color_space = InterpolationMethod::default_color_space(m_properties.color_syntax) };
}
float angle_degrees(CalculationResolutionContext const&) const;
float angle_degrees() const;
bool is_paintable() const override { return true; }

View file

@ -74,6 +74,16 @@ String LinearGradientStyleValue::to_string(SerializationMode mode) const
return MUST(builder.to_string());
}
ValueComparingNonnullRefPtr<StyleValue const> LinearGradientStyleValue::absolutized(ComputationContext const& context) const
{
Vector<ColorStopListElement> absolutized_color_stops;
absolutized_color_stops.ensure_capacity(m_properties.color_stop_list.size());
for (auto const& color_stop : m_properties.color_stop_list) {
absolutized_color_stops.unchecked_append(color_stop.absolutized(context));
}
return create(m_properties.direction, move(absolutized_color_stops), m_properties.gradient_type, m_properties.repeating, m_properties.interpolation_method);
}
bool LinearGradientStyleValue::equals(StyleValue const& other_) const
{
if (type() != other_.type())
@ -117,13 +127,7 @@ float LinearGradientStyleValue::angle_degrees(CSSPixelSize gradient_size) const
return angle;
},
[&](NonnullRefPtr<StyleValue const> const& style_value) {
if (style_value->is_angle())
return style_value->as_angle().angle().to_degrees();
if (style_value->is_calculated()) {
if (auto maybe_angle = style_value->as_calculated().resolve_angle({}); maybe_angle.has_value())
return maybe_angle->to_degrees();
}
return 0.0;
return Angle::from_style_value(style_value, {}).to_degrees();
});
}

View file

@ -46,6 +46,7 @@ public:
virtual String to_string(SerializationMode) const override;
virtual ~LinearGradientStyleValue() override = default;
virtual ValueComparingNonnullRefPtr<StyleValue const> absolutized(ComputationContext const&) const override;
virtual bool equals(StyleValue const& other) const override;
Vector<ColorStopListElement> const& color_stop_list() const

View file

@ -72,7 +72,7 @@ String RadialGradientStyleValue::to_string(SerializationMode mode) const
return MUST(builder.to_string());
}
CSSPixelSize RadialGradientStyleValue::resolve_size(Layout::Node const& node, CSSPixelPoint center, CSSPixelRect const& size) const
CSSPixelSize RadialGradientStyleValue::resolve_size(CSSPixelPoint center, CSSPixelRect const& size) const
{
auto const side_shape = [&](auto distance_function) {
auto const distance_from = [&](CSSPixels v, CSSPixels a, CSSPixels b, auto distance_function) {
@ -180,31 +180,23 @@ CSSPixelSize RadialGradientStyleValue::resolve_size(Layout::Node const& node, CS
},
[&](CircleSize const& circle_size) {
if (circle_size.radius->is_length()) {
auto radius = circle_size.radius->as_length().length().to_px(node);
auto radius = circle_size.radius->as_length().length().absolute_length_to_px();
return CSSPixelSize { radius, radius };
}
if (circle_size.radius->is_calculated()) {
CalculationResolutionContext context {
.length_resolution_context = Length::ResolutionContext::for_layout_node(node),
};
auto radius = circle_size.radius->as_calculated().resolve_length(context)->to_px(node);
auto radius = circle_size.radius->as_calculated().resolve_length({})->absolute_length_to_px();
return CSSPixelSize { radius, radius };
}
VERIFY_NOT_REACHED();
},
[&](EllipseSize const& ellipse_size) {
auto resolve = [&](StyleValue const& radius_value, auto percentage_basis_pixels) {
auto percentage_basis = Length::make_px(percentage_basis_pixels);
CalculationResolutionContext context {
.percentage_basis = percentage_basis,
.length_resolution_context = Length::ResolutionContext::for_layout_node(node),
};
if (radius_value.is_length())
return radius_value.as_length().length().to_px(node);
return radius_value.as_length().length().absolute_length_to_px();
if (radius_value.is_percentage())
return percentage_basis.percentage_of(radius_value.as_percentage().percentage()).to_px(node);
return CSSPixels { radius_value.as_percentage().percentage().as_fraction() * percentage_basis_pixels };
if (radius_value.is_calculated())
return radius_value.as_calculated().resolve_length(context)->to_px(node);
return radius_value.as_calculated().resolve_length({})->absolute_length_to_px();
VERIFY_NOT_REACHED();
};
auto radius_a = resolve(*ellipse_size.radius_a, size.width());
@ -246,7 +238,7 @@ void RadialGradientStyleValue::resolve_for_size(Layout::NodeWithStyle const& nod
{
CSSPixelRect gradient_box { { 0, 0 }, paint_size };
auto center = m_properties.position->resolved(node, gradient_box);
auto gradient_size = resolve_size(node, center, gradient_box);
auto gradient_size = resolve_size(center, gradient_box);
ResolvedDataCacheKey cache_key {
.length_resolution_context = Length::ResolutionContext::for_layout_node(node),
@ -262,6 +254,35 @@ void RadialGradientStyleValue::resolve_for_size(Layout::NodeWithStyle const& nod
}
}
ValueComparingNonnullRefPtr<StyleValue const> RadialGradientStyleValue::absolutized(ComputationContext const& context) const
{
Vector<ColorStopListElement> absolutized_color_stops;
absolutized_color_stops.ensure_capacity(m_properties.color_stop_list.size());
for (auto const& color_stop : m_properties.color_stop_list) {
absolutized_color_stops.unchecked_append(color_stop.absolutized(context));
}
auto absolutized_size = m_properties.size.visit(
[&](Extent extent) -> Size {
return extent;
},
[&](CircleSize const& circle_size) -> Size {
return CircleSize {
.radius = circle_size.radius->absolutized(context),
};
},
[&](EllipseSize const& ellipse_size) -> Size {
return EllipseSize {
.radius_a = ellipse_size.radius_a->absolutized(context),
.radius_b = ellipse_size.radius_b->absolutized(context),
};
});
NonnullRefPtr absolutized_position = m_properties.position->absolutized(context)->as_position();
return create(m_properties.ending_shape, move(absolutized_size), move(absolutized_position), move(absolutized_color_stops), m_properties.repeating, m_properties.interpolation_method);
}
bool RadialGradientStyleValue::equals(StyleValue const& other) const
{
if (type() != other.type())

View file

@ -54,6 +54,7 @@ public:
void paint(DisplayListRecordingContext&, DevicePixelRect const& dest_rect, CSS::ImageRendering) const override;
virtual ValueComparingNonnullRefPtr<StyleValue const> absolutized(ComputationContext const&) const override;
virtual bool equals(StyleValue const& other) const override;
Vector<ColorStopListElement> const& color_stop_list() const
@ -73,7 +74,7 @@ public:
void resolve_for_size(Layout::NodeWithStyle const&, CSSPixelSize) const override;
CSSPixelSize resolve_size(Layout::Node const&, CSSPixelPoint, CSSPixelRect const&) const;
CSSPixelSize resolve_size(CSSPixelPoint, CSSPixelRect const&) const;
bool is_repeating() const { return m_properties.repeating == GradientRepeating::Yes; }