ladybird/Libraries/LibWeb/CSS/StyleValues/RadialSizeStyleValue.cpp
Callum Law f0434655f9 LibWeb: Reduce recompilation from editing Enums.json
Reduces the recompilation caused by editing `Enums.json` from ~1528 to
~327
2026-02-19 11:27:06 +00:00

235 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2025, Callum Law <callumlaw1709@outlook.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "RadialSizeStyleValue.h"
#include <LibWeb/CSS/Enums.h>
#include <LibWeb/CSS/PercentageOr.h>
namespace Web::CSS {
ValueComparingNonnullRefPtr<StyleValue const> RadialSizeStyleValue::absolutized(ComputationContext const& computation_context) const
{
bool any_component_required_absolutization = false;
Vector<Component> absolutized_components;
for (auto const& component : m_components) {
if (component.has<RadialExtent>()) {
absolutized_components.append(component);
} else {
auto const& absolutized_length_percentage = component.get<NonnullRefPtr<StyleValue const>>()->absolutized(computation_context);
if (!absolutized_length_percentage->equals(component.get<NonnullRefPtr<StyleValue const>>()))
any_component_required_absolutization = true;
absolutized_components.append(absolutized_length_percentage);
}
}
if (!any_component_required_absolutization)
return *this;
return RadialSizeStyleValue::create(move(absolutized_components));
}
void RadialSizeStyleValue::serialize(StringBuilder& builder, SerializationMode serialization_mode) const
{
bool first = true;
for (auto const& component : m_components) {
if (!first)
builder.append(' ');
first = false;
component.visit(
[&](RadialExtent extent) {
builder.append(CSS::to_string(extent));
},
[&](NonnullRefPtr<StyleValue const> const& length_percentage) {
length_percentage->serialize(builder, serialization_mode);
});
}
}
static CSSPixelSize side_shape(CSSPixelPoint const& center, CSSPixelRect const& reference_box, Function<CSSPixels(CSSPixels, CSSPixels)> distance_function)
{
auto x_dist = distance_function(abs(reference_box.left() - center.x()), abs(reference_box.right() - center.x()));
auto y_dist = distance_function(abs(reference_box.top() - center.y()), abs(reference_box.bottom() - center.y()));
return CSSPixelSize { x_dist, y_dist };
}
static CSSPixelSize closest_side_shape(CSSPixelPoint const& center, CSSPixelRect const& size)
{
return side_shape(center, size, AK::min<CSSPixels>);
}
static CSSPixelSize farthest_side_shape(CSSPixelPoint const& center, CSSPixelRect const& size)
{
return side_shape(center, size, AK::max<CSSPixels>);
}
static CSSPixels corner_distance(CSSPixelPoint const& center, CSSPixelRect const& reference_box, Function<bool(CSSPixels, CSSPixels)> distance_compare_function, CSSPixelPoint& corner)
{
auto top_left_distance_squared = square_distance_between(reference_box.top_left(), center);
auto top_right_distance_squared = square_distance_between(reference_box.top_right(), center);
auto bottom_right_distance_squared = square_distance_between(reference_box.bottom_right(), center);
auto bottom_left_distance_squared = square_distance_between(reference_box.bottom_left(), center);
auto distance_squared = top_left_distance_squared;
corner = reference_box.top_left();
if (distance_compare_function(top_right_distance_squared, distance_squared)) {
corner = reference_box.top_right();
distance_squared = top_right_distance_squared;
}
if (distance_compare_function(bottom_right_distance_squared, distance_squared)) {
corner = reference_box.bottom_right();
distance_squared = bottom_right_distance_squared;
}
if (distance_compare_function(bottom_left_distance_squared, distance_squared)) {
corner = reference_box.bottom_left();
distance_squared = bottom_left_distance_squared;
}
return sqrt(distance_squared);
}
static CSSPixels closest_corner_distance(CSSPixelPoint const& center, CSSPixelRect const& reference_box, CSSPixelPoint& corner)
{
return corner_distance(center, reference_box, [](CSSPixels a, CSSPixels b) { return a < b; }, corner);
}
static CSSPixels farthest_corner_distance(CSSPixelPoint const& center, CSSPixelRect const& reference_box, CSSPixelPoint& corner)
{
return corner_distance(center, reference_box, [](CSSPixels a, CSSPixels b) { return a > b; }, corner);
}
CSSPixels RadialSizeStyleValue::resolve_circle_size(CSSPixelPoint const& center, CSSPixelRect const& reference_box, Layout::Node const& node) const
{
VERIFY(m_components.size() == 1);
auto resolved_size = m_components[0].visit(
[&](RadialExtent const& radial_extent) {
switch (radial_extent) {
case RadialExtent::ClosestSide: {
auto side_distances = closest_side_shape(center, reference_box);
return AK::min(side_distances.width(), side_distances.height());
}
case RadialExtent::FarthestSide: {
auto side_distances = farthest_side_shape(center, reference_box);
return AK::max(side_distances.width(), side_distances.height());
}
case RadialExtent::ClosestCorner: {
CSSPixelPoint corner {};
return closest_corner_distance(center, reference_box, corner);
}
case RadialExtent::FarthestCorner: {
CSSPixelPoint corner {};
return farthest_corner_distance(center, reference_box, corner);
}
}
VERIFY_NOT_REACHED();
},
[&](NonnullRefPtr<StyleValue const> const& length_percentage) {
auto radius_ref = sqrt(pow(reference_box.width().to_float(), 2) + pow(reference_box.height().to_float(), 2)) / AK::Sqrt2<float>;
// FIXME: We don't need to pass `node` here since we know that all relative lengths have already been absolutized
return CSSPixels::nearest_value_for(max(0.0f, LengthPercentage::from_style_value(length_percentage).to_px(node, CSSPixels::nearest_value_for(radius_ref)).to_float()));
});
// https://w3c.github.io/csswg-drafts/css-images/#degenerate-radials
// If the ending shape is a circle with zero radius:
if (resolved_size == 0) {
// Render as if the ending shape was a circle whose radius was an arbitrary very small number greater than zero.
// This will make the gradient continue to look like a circle.
return CSSPixels::smallest_positive_value();
}
return resolved_size;
}
static CSSPixelSize ellipse_corner_shape(CSSPixelPoint const& center, CSSPixelRect const& reference_box, Function<CSSPixels(CSSPixelPoint const&, CSSPixelRect const&, CSSPixelPoint&)> get_corner, Function<CSSPixelSize(CSSPixelPoint const&, CSSPixelRect const&)> get_shape)
{
CSSPixelPoint corner {};
get_corner(center, reference_box, corner);
auto shape = get_shape(center, reference_box);
CSSPixels height = shape.height();
CSSPixels width = shape.width();
// Prevent division by zero
// https://w3c.github.io/csswg-drafts/css-images/#degenerate-radials
if (height == 0) {
// Render as if the ending shape was an ellipse whose width was an arbitrary very large number and whose height
// was an arbitrary very small number greater than zero. This will make the gradient look like a solid-color image equal
// to the color of the last color-stop, or equal to the average color of the gradient if its repeating.
constexpr auto arbitrary_small_number = CSSPixels::smallest_positive_value();
constexpr auto arbitrary_large_number = CSSPixels::max();
return CSSPixelSize { arbitrary_large_number, arbitrary_small_number };
}
auto aspect_ratio = width / height;
auto p = corner - center;
auto radius_a = sqrt(p.y() * p.y() * aspect_ratio * aspect_ratio + p.x() * p.x());
auto radius_b = radius_a / aspect_ratio;
return CSSPixelSize { radius_a, radius_b };
}
CSSPixelSize RadialSizeStyleValue::resolve_ellipse_size(CSSPixelPoint const& center, CSSPixelRect const& reference_box, Layout::Node const& node) const
{
VERIFY(m_components.size() == 1 || m_components.size() == 2);
auto const resolve_component = [&](Component const& component, CSSPixels const& reference_size) -> CSSPixelSize {
return component.visit(
[&](RadialExtent const& radial_extent) {
switch (radial_extent) {
case RadialExtent::ClosestSide:
return closest_side_shape(center, reference_box);
case RadialExtent::FarthestSide:
return farthest_side_shape(center, reference_box);
case RadialExtent::ClosestCorner:
return ellipse_corner_shape(center, reference_box, closest_corner_distance, closest_side_shape);
case RadialExtent::FarthestCorner:
return ellipse_corner_shape(center, reference_box, farthest_corner_distance, farthest_side_shape);
}
VERIFY_NOT_REACHED();
},
[&](NonnullRefPtr<StyleValue const> const& length_percentage) {
// FIXME: We don't need to pass `node` here since we know that all relative lengths have already been absolutized
auto value = LengthPercentage::from_style_value(length_percentage).to_px(node, reference_size);
return CSSPixelSize { value, value };
});
};
CSSPixelSize resolved_size = CSSPixelSize {
resolve_component(m_components[0], reference_box.width()).width(),
resolve_component(m_components.size() == 1 ? m_components[0] : m_components[1], reference_box.height()).height()
};
// Handle degenerate cases
// https://w3c.github.io/csswg-drafts/css-images/#degenerate-radials
constexpr auto arbitrary_small_number = CSSPixels::smallest_positive_value();
constexpr auto arbitrary_large_number = CSSPixels::max();
// If the ending shape has zero width (regardless of the height):
if (resolved_size.width() <= 0) {
// Render as if the ending shape was an ellipse whose height was an arbitrary very large number
// and whose width was an arbitrary very small number greater than zero.
// This will make the gradient look similar to a horizontal linear gradient that is mirrored across the center of the ellipse.
// It also means that all color-stop positions specified with a percentage resolve to 0px.
return CSSPixelSize { arbitrary_small_number, arbitrary_large_number };
}
// Otherwise, if the ending shape has zero height:
if (resolved_size.height() <= 0) {
// Render as if the ending shape was an ellipse whose width was an arbitrary very large number and whose height
// was an arbitrary very small number greater than zero. This will make the gradient look like a solid-color image equal
// to the color of the last color-stop, or equal to the average color of the gradient if its repeating.
return CSSPixelSize { arbitrary_large_number, arbitrary_small_number };
}
return resolved_size;
}
}