瀏覽代碼

LibWeb: Split RadialGradientStyleValue out of StyleValue.{h,cpp}

Sam Atkins 2 年之前
父節點
當前提交
0f04fa2e6e

+ 1 - 0
Userland/Libraries/LibWeb/CMakeLists.txt

@@ -86,6 +86,7 @@ set(SOURCES
     CSS/StyleValues/IdentifierStyleValue.cpp
     CSS/StyleValues/ImageStyleValue.cpp
     CSS/StyleValues/LinearGradientStyleValue.cpp
+    CSS/StyleValues/RadialGradientStyleValue.cpp
     CSS/Supports.cpp
     CSS/SyntaxHighlighter/SyntaxHighlighter.cpp
     CSS/Time.cpp

+ 1 - 0
Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp

@@ -52,6 +52,7 @@
 #include <LibWeb/CSS/StyleValues/IdentifierStyleValue.h>
 #include <LibWeb/CSS/StyleValues/ImageStyleValue.h>
 #include <LibWeb/CSS/StyleValues/LinearGradientStyleValue.h>
+#include <LibWeb/CSS/StyleValues/RadialGradientStyleValue.h>
 #include <LibWeb/DOM/Document.h>
 #include <LibWeb/Dump.h>
 #include <LibWeb/Infra/Strings.h>

+ 1 - 203
Userland/Libraries/LibWeb/CSS/StyleValue.cpp

@@ -34,6 +34,7 @@
 #include <LibWeb/CSS/StyleValues/IdentifierStyleValue.h>
 #include <LibWeb/CSS/StyleValues/ImageStyleValue.h>
 #include <LibWeb/CSS/StyleValues/LinearGradientStyleValue.h>
+#include <LibWeb/CSS/StyleValues/RadialGradientStyleValue.h>
 #include <LibWeb/DOM/Document.h>
 #include <LibWeb/HTML/BrowsingContext.h>
 #include <LibWeb/Loader/LoadRequest.h>
@@ -1150,209 +1151,6 @@ ErrorOr<void> PositionValue::serialize(StringBuilder& builder) const
     return {};
 }
 
-ErrorOr<String> RadialGradientStyleValue::to_string() const
-{
-    StringBuilder builder;
-    if (is_repeating())
-        TRY(builder.try_append("repeating-"sv));
-    TRY(builder.try_appendff("radial-gradient({} "sv,
-        m_properties.ending_shape == EndingShape::Circle ? "circle"sv : "ellipse"sv));
-
-    TRY(m_properties.size.visit(
-        [&](Extent extent) -> ErrorOr<void> {
-            return builder.try_append([&] {
-                switch (extent) {
-                case Extent::ClosestCorner:
-                    return "closest-corner"sv;
-                case Extent::ClosestSide:
-                    return "closest-side"sv;
-                case Extent::FarthestCorner:
-                    return "farthest-corner"sv;
-                case Extent::FarthestSide:
-                    return "farthest-side"sv;
-                default:
-                    VERIFY_NOT_REACHED();
-                }
-            }());
-        },
-        [&](CircleSize const& circle_size) -> ErrorOr<void> {
-            return builder.try_append(TRY(circle_size.radius.to_string()));
-        },
-        [&](EllipseSize const& ellipse_size) -> ErrorOr<void> {
-            return builder.try_appendff("{} {}", TRY(ellipse_size.radius_a.to_string()), TRY(ellipse_size.radius_b.to_string()));
-        }));
-
-    if (m_properties.position != PositionValue::center()) {
-        TRY(builder.try_appendff(" at "sv));
-        TRY(m_properties.position.serialize(builder));
-    }
-
-    TRY(builder.try_append(", "sv));
-    TRY(serialize_color_stop_list(builder, m_properties.color_stop_list));
-    TRY(builder.try_append(')'));
-    return builder.to_string();
-}
-
-Gfx::FloatSize RadialGradientStyleValue::resolve_size(Layout::Node const& node, Gfx::FloatPoint center, Gfx::FloatRect const& size) const
-{
-    auto const side_shape = [&](auto distance_function) {
-        auto const distance_from = [&](float v, float a, float b, auto distance_function) {
-            return distance_function(fabs(a - v), fabs(b - v));
-        };
-        auto x_dist = distance_from(center.x(), size.left(), size.right(), distance_function);
-        auto y_dist = distance_from(center.y(), size.top(), size.bottom(), distance_function);
-        if (m_properties.ending_shape == EndingShape::Circle) {
-            auto dist = distance_function(x_dist, y_dist);
-            return Gfx::FloatSize { dist, dist };
-        } else {
-            return Gfx::FloatSize { x_dist, y_dist };
-        }
-    };
-
-    auto const closest_side_shape = [&] {
-        return side_shape(AK::min<float>);
-    };
-
-    auto const farthest_side_shape = [&] {
-        return side_shape(AK::max<float>);
-    };
-
-    auto const corner_distance = [&](auto distance_compare, Gfx::FloatPoint& corner) {
-        auto top_left_distance = size.top_left().distance_from(center);
-        auto top_right_distance = size.top_right().distance_from(center);
-        auto bottom_right_distance = size.bottom_right().distance_from(center);
-        auto bottom_left_distance = size.bottom_left().distance_from(center);
-        auto distance = top_left_distance;
-        if (distance_compare(top_right_distance, distance)) {
-            corner = size.top_right();
-            distance = top_right_distance;
-        }
-        if (distance_compare(bottom_right_distance, distance)) {
-            corner = size.top_right();
-            distance = bottom_right_distance;
-        }
-        if (distance_compare(bottom_left_distance, distance)) {
-            corner = size.top_right();
-            distance = bottom_left_distance;
-        }
-        return distance;
-    };
-
-    auto const closest_corner_distance = [&](Gfx::FloatPoint& corner) {
-        return corner_distance([](float a, float b) { return a < b; }, corner);
-    };
-
-    auto const farthest_corner_distance = [&](Gfx::FloatPoint& corner) {
-        return corner_distance([](float a, float b) { return a > b; }, corner);
-    };
-
-    auto const corner_shape = [&](auto corner_distance, auto get_shape) {
-        Gfx::FloatPoint corner {};
-        auto distance = corner_distance(corner);
-        if (m_properties.ending_shape == EndingShape::Ellipse) {
-            auto shape = get_shape();
-            auto aspect_ratio = shape.width() / shape.height();
-            auto p = corner - center;
-            auto radius_a = AK::sqrt(p.y() * p.y() * aspect_ratio * aspect_ratio + p.x() * p.x());
-            auto radius_b = radius_a / aspect_ratio;
-            return Gfx::FloatSize { radius_a, radius_b };
-        }
-        return Gfx::FloatSize { distance, distance };
-    };
-
-    // https://w3c.github.io/csswg-drafts/css-images/#radial-gradient-syntax
-    auto resolved_size = m_properties.size.visit(
-        [&](Extent extent) {
-            switch (extent) {
-            case Extent::ClosestSide:
-                // The ending shape is sized so that it exactly meets the side of the gradient box closest to the gradient’s center.
-                // If the shape is an ellipse, it exactly meets the closest side in each dimension.
-                return closest_side_shape();
-            case Extent::ClosestCorner:
-                // The ending shape is sized so that it passes through the corner of the gradient box closest to the gradient’s center.
-                // If the shape is an ellipse, the ending shape is given the same aspect-ratio it would have if closest-side were specified
-                return corner_shape(closest_corner_distance, closest_side_shape);
-            case Extent::FarthestCorner:
-                // Same as closest-corner, except the ending shape is sized based on the farthest corner.
-                // If the shape is an ellipse, the ending shape is given the same aspect ratio it would have if farthest-side were specified.
-                return corner_shape(farthest_corner_distance, farthest_side_shape);
-            case Extent::FarthestSide:
-                // Same as closest-side, except the ending shape is sized based on the farthest side(s).
-                return farthest_side_shape();
-            default:
-                VERIFY_NOT_REACHED();
-            }
-        },
-        [&](CircleSize const& circle_size) {
-            auto radius = circle_size.radius.to_px(node);
-            return Gfx::FloatSize { radius, radius };
-        },
-        [&](EllipseSize const& ellipse_size) {
-            auto radius_a = ellipse_size.radius_a.resolved(node, CSS::Length::make_px(size.width())).to_px(node);
-            auto radius_b = ellipse_size.radius_b.resolved(node, CSS::Length::make_px(size.height())).to_px(node);
-            return Gfx::FloatSize { radius_a, radius_b };
-        });
-
-    // Handle degenerate cases
-    // https://w3c.github.io/csswg-drafts/css-images/#degenerate-radials
-
-    constexpr auto arbitrary_small_number = 1e-10;
-    constexpr auto arbitrary_large_number = 1e10;
-
-    // If the ending shape is a circle with zero radius:
-    if (m_properties.ending_shape == EndingShape::Circle && resolved_size.is_empty()) {
-        // 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 Gfx::FloatSize { arbitrary_small_number, arbitrary_small_number };
-    }
-    // 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 Gfx::FloatSize { 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 it’s repeating.
-        return Gfx::FloatSize { arbitrary_large_number, arbitrary_small_number };
-    }
-    return resolved_size;
-}
-
-void RadialGradientStyleValue::resolve_for_size(Layout::Node const& node, CSSPixelSize paint_size) const
-{
-    CSSPixelRect gradient_box { { 0, 0 }, paint_size };
-    auto center = m_properties.position.resolved(node, gradient_box).to_type<float>();
-    auto gradient_size = resolve_size(node, center, gradient_box.to_type<float>());
-    if (m_resolved.has_value() && m_resolved->gradient_size == gradient_size)
-        return;
-    m_resolved = ResolvedData {
-        Painting::resolve_radial_gradient_data(node, gradient_size.to_type<CSSPixels>(), *this),
-        gradient_size,
-        center,
-    };
-}
-
-bool RadialGradientStyleValue::equals(StyleValue const& other) const
-{
-    if (type() != other.type())
-        return false;
-    auto& other_gradient = other.as_radial_gradient();
-    return m_properties == other_gradient.m_properties;
-}
-
-void RadialGradientStyleValue::paint(PaintContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering) const
-{
-    VERIFY(m_resolved.has_value());
-    Painting::paint_radial_gradient(context, dest_rect, m_resolved->data,
-        context.rounded_device_point(m_resolved->center.to_type<CSSPixels>()),
-        context.rounded_device_size(m_resolved->gradient_size.to_type<CSSPixels>()));
-}
-
 ErrorOr<String> ListStyleStyleValue::to_string() const
 {
     return String::formatted("{} {} {}", TRY(m_properties.position->to_string()), TRY(m_properties.image->to_string()), TRY(m_properties.style_type->to_string()));

+ 0 - 79
Userland/Libraries/LibWeb/CSS/StyleValue.h

@@ -668,85 +668,6 @@ enum class GradientRepeating {
     No
 };
 
-class RadialGradientStyleValue final : public AbstractImageStyleValue {
-public:
-    enum class EndingShape {
-        Circle,
-        Ellipse
-    };
-
-    enum class Extent {
-        ClosestCorner,
-        ClosestSide,
-        FarthestCorner,
-        FarthestSide
-    };
-
-    struct CircleSize {
-        Length radius;
-        bool operator==(CircleSize const&) const = default;
-    };
-
-    struct EllipseSize {
-        LengthPercentage radius_a;
-        LengthPercentage radius_b;
-        bool operator==(EllipseSize const&) const = default;
-    };
-
-    using Size = Variant<Extent, CircleSize, EllipseSize>;
-
-    static ValueComparingNonnullRefPtr<RadialGradientStyleValue> create(EndingShape ending_shape, Size size, PositionValue position, Vector<LinearColorStopListElement> color_stop_list, GradientRepeating repeating)
-    {
-        VERIFY(color_stop_list.size() >= 2);
-        return adopt_ref(*new RadialGradientStyleValue(ending_shape, size, position, move(color_stop_list), repeating));
-    }
-
-    virtual ErrorOr<String> to_string() const override;
-
-    void paint(PaintContext&, DevicePixelRect const& dest_rect, CSS::ImageRendering) const override;
-
-    virtual bool equals(StyleValue const& other) const override;
-
-    Vector<LinearColorStopListElement> const& color_stop_list() const
-    {
-        return m_properties.color_stop_list;
-    }
-
-    bool is_paintable() const override { return true; }
-
-    void resolve_for_size(Layout::Node const&, CSSPixelSize) const override;
-
-    Gfx::FloatSize resolve_size(Layout::Node const&, Gfx::FloatPoint, Gfx::FloatRect const&) const;
-
-    bool is_repeating() const { return m_properties.repeating == GradientRepeating::Yes; }
-
-    virtual ~RadialGradientStyleValue() override = default;
-
-private:
-    RadialGradientStyleValue(EndingShape ending_shape, Size size, PositionValue position, Vector<LinearColorStopListElement> color_stop_list, GradientRepeating repeating)
-        : AbstractImageStyleValue(Type::RadialGradient)
-        , m_properties { .ending_shape = ending_shape, .size = size, .position = position, .color_stop_list = move(color_stop_list), .repeating = repeating }
-    {
-    }
-
-    struct Properties {
-        EndingShape ending_shape;
-        Size size;
-        PositionValue position;
-        Vector<LinearColorStopListElement> color_stop_list;
-        GradientRepeating repeating;
-        bool operator==(Properties const&) const = default;
-    } m_properties;
-
-    struct ResolvedData {
-        Painting::RadialGradientData data;
-        Gfx::FloatSize gradient_size;
-        Gfx::FloatPoint center;
-    };
-
-    mutable Optional<ResolvedData> m_resolved;
-};
-
 class InheritStyleValue final : public StyleValueWithDefaultOperators<InheritStyleValue> {
 public:
     static ValueComparingNonnullRefPtr<InheritStyleValue> the()

+ 239 - 0
Userland/Libraries/LibWeb/CSS/StyleValues/RadialGradientStyleValue.cpp

@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
+ * Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
+ * Copyright (c) 2022-2023, MacDue <macdue@dueutil.tech>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "RadialGradientStyleValue.h"
+#include <LibWeb/CSS/Serialize.h>
+
+namespace Web::CSS {
+
+// FIXME: Temporary until AbstractImageStyleValue.h exists.
+static ErrorOr<void> serialize_color_stop_list(StringBuilder& builder, auto const& color_stop_list)
+{
+    bool first = true;
+    for (auto const& element : color_stop_list) {
+        if (!first)
+            TRY(builder.try_append(", "sv));
+
+        if (element.transition_hint.has_value())
+            TRY(builder.try_appendff("{}, "sv, TRY(element.transition_hint->value.to_string())));
+
+        TRY(serialize_a_srgb_value(builder, element.color_stop.color));
+        for (auto position : Array { &element.color_stop.position, &element.color_stop.second_position }) {
+            if (position->has_value())
+                TRY(builder.try_appendff(" {}"sv, TRY((*position)->to_string())));
+        }
+        first = false;
+    }
+    return {};
+}
+
+ErrorOr<String> RadialGradientStyleValue::to_string() const
+{
+    StringBuilder builder;
+    if (is_repeating())
+        TRY(builder.try_append("repeating-"sv));
+    TRY(builder.try_appendff("radial-gradient({} "sv,
+        m_properties.ending_shape == EndingShape::Circle ? "circle"sv : "ellipse"sv));
+
+    TRY(m_properties.size.visit(
+        [&](Extent extent) -> ErrorOr<void> {
+            return builder.try_append([&] {
+                switch (extent) {
+                case Extent::ClosestCorner:
+                    return "closest-corner"sv;
+                case Extent::ClosestSide:
+                    return "closest-side"sv;
+                case Extent::FarthestCorner:
+                    return "farthest-corner"sv;
+                case Extent::FarthestSide:
+                    return "farthest-side"sv;
+                default:
+                    VERIFY_NOT_REACHED();
+                }
+            }());
+        },
+        [&](CircleSize const& circle_size) -> ErrorOr<void> {
+            return builder.try_append(TRY(circle_size.radius.to_string()));
+        },
+        [&](EllipseSize const& ellipse_size) -> ErrorOr<void> {
+            return builder.try_appendff("{} {}", TRY(ellipse_size.radius_a.to_string()), TRY(ellipse_size.radius_b.to_string()));
+        }));
+
+    if (m_properties.position != PositionValue::center()) {
+        TRY(builder.try_appendff(" at "sv));
+        TRY(m_properties.position.serialize(builder));
+    }
+
+    TRY(builder.try_append(", "sv));
+    TRY(serialize_color_stop_list(builder, m_properties.color_stop_list));
+    TRY(builder.try_append(')'));
+    return builder.to_string();
+}
+
+Gfx::FloatSize RadialGradientStyleValue::resolve_size(Layout::Node const& node, Gfx::FloatPoint center, Gfx::FloatRect const& size) const
+{
+    auto const side_shape = [&](auto distance_function) {
+        auto const distance_from = [&](float v, float a, float b, auto distance_function) {
+            return distance_function(fabs(a - v), fabs(b - v));
+        };
+        auto x_dist = distance_from(center.x(), size.left(), size.right(), distance_function);
+        auto y_dist = distance_from(center.y(), size.top(), size.bottom(), distance_function);
+        if (m_properties.ending_shape == EndingShape::Circle) {
+            auto dist = distance_function(x_dist, y_dist);
+            return Gfx::FloatSize { dist, dist };
+        } else {
+            return Gfx::FloatSize { x_dist, y_dist };
+        }
+    };
+
+    auto const closest_side_shape = [&] {
+        return side_shape(AK::min<float>);
+    };
+
+    auto const farthest_side_shape = [&] {
+        return side_shape(AK::max<float>);
+    };
+
+    auto const corner_distance = [&](auto distance_compare, Gfx::FloatPoint& corner) {
+        auto top_left_distance = size.top_left().distance_from(center);
+        auto top_right_distance = size.top_right().distance_from(center);
+        auto bottom_right_distance = size.bottom_right().distance_from(center);
+        auto bottom_left_distance = size.bottom_left().distance_from(center);
+        auto distance = top_left_distance;
+        if (distance_compare(top_right_distance, distance)) {
+            corner = size.top_right();
+            distance = top_right_distance;
+        }
+        if (distance_compare(bottom_right_distance, distance)) {
+            corner = size.top_right();
+            distance = bottom_right_distance;
+        }
+        if (distance_compare(bottom_left_distance, distance)) {
+            corner = size.top_right();
+            distance = bottom_left_distance;
+        }
+        return distance;
+    };
+
+    auto const closest_corner_distance = [&](Gfx::FloatPoint& corner) {
+        return corner_distance([](float a, float b) { return a < b; }, corner);
+    };
+
+    auto const farthest_corner_distance = [&](Gfx::FloatPoint& corner) {
+        return corner_distance([](float a, float b) { return a > b; }, corner);
+    };
+
+    auto const corner_shape = [&](auto corner_distance, auto get_shape) {
+        Gfx::FloatPoint corner {};
+        auto distance = corner_distance(corner);
+        if (m_properties.ending_shape == EndingShape::Ellipse) {
+            auto shape = get_shape();
+            auto aspect_ratio = shape.width() / shape.height();
+            auto p = corner - center;
+            auto radius_a = AK::sqrt(p.y() * p.y() * aspect_ratio * aspect_ratio + p.x() * p.x());
+            auto radius_b = radius_a / aspect_ratio;
+            return Gfx::FloatSize { radius_a, radius_b };
+        }
+        return Gfx::FloatSize { distance, distance };
+    };
+
+    // https://w3c.github.io/csswg-drafts/css-images/#radial-gradient-syntax
+    auto resolved_size = m_properties.size.visit(
+        [&](Extent extent) {
+            switch (extent) {
+            case Extent::ClosestSide:
+                // The ending shape is sized so that it exactly meets the side of the gradient box closest to the gradient’s center.
+                // If the shape is an ellipse, it exactly meets the closest side in each dimension.
+                return closest_side_shape();
+            case Extent::ClosestCorner:
+                // The ending shape is sized so that it passes through the corner of the gradient box closest to the gradient’s center.
+                // If the shape is an ellipse, the ending shape is given the same aspect-ratio it would have if closest-side were specified
+                return corner_shape(closest_corner_distance, closest_side_shape);
+            case Extent::FarthestCorner:
+                // Same as closest-corner, except the ending shape is sized based on the farthest corner.
+                // If the shape is an ellipse, the ending shape is given the same aspect ratio it would have if farthest-side were specified.
+                return corner_shape(farthest_corner_distance, farthest_side_shape);
+            case Extent::FarthestSide:
+                // Same as closest-side, except the ending shape is sized based on the farthest side(s).
+                return farthest_side_shape();
+            default:
+                VERIFY_NOT_REACHED();
+            }
+        },
+        [&](CircleSize const& circle_size) {
+            auto radius = circle_size.radius.to_px(node);
+            return Gfx::FloatSize { radius, radius };
+        },
+        [&](EllipseSize const& ellipse_size) {
+            auto radius_a = ellipse_size.radius_a.resolved(node, CSS::Length::make_px(size.width())).to_px(node);
+            auto radius_b = ellipse_size.radius_b.resolved(node, CSS::Length::make_px(size.height())).to_px(node);
+            return Gfx::FloatSize { radius_a, radius_b };
+        });
+
+    // Handle degenerate cases
+    // https://w3c.github.io/csswg-drafts/css-images/#degenerate-radials
+
+    constexpr auto arbitrary_small_number = 1e-10;
+    constexpr auto arbitrary_large_number = 1e10;
+
+    // If the ending shape is a circle with zero radius:
+    if (m_properties.ending_shape == EndingShape::Circle && resolved_size.is_empty()) {
+        // 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 Gfx::FloatSize { arbitrary_small_number, arbitrary_small_number };
+    }
+    // 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 Gfx::FloatSize { 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 it’s repeating.
+        return Gfx::FloatSize { arbitrary_large_number, arbitrary_small_number };
+    }
+    return resolved_size;
+}
+
+void RadialGradientStyleValue::resolve_for_size(Layout::Node const& node, CSSPixelSize paint_size) const
+{
+    CSSPixelRect gradient_box { { 0, 0 }, paint_size };
+    auto center = m_properties.position.resolved(node, gradient_box).to_type<float>();
+    auto gradient_size = resolve_size(node, center, gradient_box.to_type<float>());
+    if (m_resolved.has_value() && m_resolved->gradient_size == gradient_size)
+        return;
+    m_resolved = ResolvedData {
+        Painting::resolve_radial_gradient_data(node, gradient_size.to_type<CSSPixels>(), *this),
+        gradient_size,
+        center,
+    };
+}
+
+bool RadialGradientStyleValue::equals(StyleValue const& other) const
+{
+    if (type() != other.type())
+        return false;
+    auto& other_gradient = other.as_radial_gradient();
+    return m_properties == other_gradient.m_properties;
+}
+
+void RadialGradientStyleValue::paint(PaintContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering) const
+{
+    VERIFY(m_resolved.has_value());
+    Painting::paint_radial_gradient(context, dest_rect, m_resolved->data,
+        context.rounded_device_point(m_resolved->center.to_type<CSSPixels>()),
+        context.rounded_device_size(m_resolved->gradient_size.to_type<CSSPixels>()));
+}
+
+}

+ 97 - 0
Userland/Libraries/LibWeb/CSS/StyleValues/RadialGradientStyleValue.h

@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
+ * Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
+ * Copyright (c) 2022-2023, MacDue <macdue@dueutil.tech>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/Vector.h>
+#include <LibWeb/CSS/Enums.h>
+#include <LibWeb/CSS/StyleValue.h>
+
+namespace Web::CSS {
+
+class RadialGradientStyleValue final : public AbstractImageStyleValue {
+public:
+    enum class EndingShape {
+        Circle,
+        Ellipse
+    };
+
+    enum class Extent {
+        ClosestCorner,
+        ClosestSide,
+        FarthestCorner,
+        FarthestSide
+    };
+
+    struct CircleSize {
+        Length radius;
+        bool operator==(CircleSize const&) const = default;
+    };
+
+    struct EllipseSize {
+        LengthPercentage radius_a;
+        LengthPercentage radius_b;
+        bool operator==(EllipseSize const&) const = default;
+    };
+
+    using Size = Variant<Extent, CircleSize, EllipseSize>;
+
+    static ValueComparingNonnullRefPtr<RadialGradientStyleValue> create(EndingShape ending_shape, Size size, PositionValue position, Vector<LinearColorStopListElement> color_stop_list, GradientRepeating repeating)
+    {
+        VERIFY(color_stop_list.size() >= 2);
+        return adopt_ref(*new RadialGradientStyleValue(ending_shape, size, position, move(color_stop_list), repeating));
+    }
+
+    virtual ErrorOr<String> to_string() const override;
+
+    void paint(PaintContext&, DevicePixelRect const& dest_rect, CSS::ImageRendering) const override;
+
+    virtual bool equals(StyleValue const& other) const override;
+
+    Vector<LinearColorStopListElement> const& color_stop_list() const
+    {
+        return m_properties.color_stop_list;
+    }
+
+    bool is_paintable() const override { return true; }
+
+    void resolve_for_size(Layout::Node const&, CSSPixelSize) const override;
+
+    Gfx::FloatSize resolve_size(Layout::Node const&, Gfx::FloatPoint, Gfx::FloatRect const&) const;
+
+    bool is_repeating() const { return m_properties.repeating == GradientRepeating::Yes; }
+
+    virtual ~RadialGradientStyleValue() override = default;
+
+private:
+    RadialGradientStyleValue(EndingShape ending_shape, Size size, PositionValue position, Vector<LinearColorStopListElement> color_stop_list, GradientRepeating repeating)
+        : AbstractImageStyleValue(Type::RadialGradient)
+        , m_properties { .ending_shape = ending_shape, .size = size, .position = position, .color_stop_list = move(color_stop_list), .repeating = repeating }
+    {
+    }
+
+    struct Properties {
+        EndingShape ending_shape;
+        Size size;
+        PositionValue position;
+        Vector<LinearColorStopListElement> color_stop_list;
+        GradientRepeating repeating;
+        bool operator==(Properties const&) const = default;
+    } m_properties;
+
+    struct ResolvedData {
+        Painting::RadialGradientData data;
+        Gfx::FloatSize gradient_size;
+        Gfx::FloatPoint center;
+    };
+
+    mutable Optional<ResolvedData> m_resolved;
+};
+
+}

+ 1 - 0
Userland/Libraries/LibWeb/Painting/GradientPainting.cpp

@@ -9,6 +9,7 @@
 #include <LibWeb/CSS/StyleValue.h>
 #include <LibWeb/CSS/StyleValues/ConicGradientStyleValue.h>
 #include <LibWeb/CSS/StyleValues/LinearGradientStyleValue.h>
+#include <LibWeb/CSS/StyleValues/RadialGradientStyleValue.h>
 #include <LibWeb/Painting/GradientPainting.h>
 
 namespace Web::Painting {