소스 검색

LibWeb: Implement RadialGradientStyleValue

Adds a style value for `radial-gradient()`s and implements some helpers
for resolving their properties.
MacDue 2 년 전
부모
커밋
040dac558e
3개의 변경된 파일237개의 추가작업 그리고 1개의 파일을 삭제
  1. 163 0
      Userland/Libraries/LibWeb/CSS/StyleValue.cpp
  2. 73 1
      Userland/Libraries/LibWeb/CSS/StyleValue.h
  3. 1 0
      Userland/Libraries/LibWeb/Forward.h

+ 163 - 0
Userland/Libraries/LibWeb/CSS/StyleValue.cpp

@@ -217,6 +217,12 @@ PositionStyleValue const& StyleValue::as_position() const
     return static_cast<PositionStyleValue const&>(*this);
     return static_cast<PositionStyleValue const&>(*this);
 }
 }
 
 
+RadialGradientStyleValue const& StyleValue::as_radial_gradient() const
+{
+    VERIFY(is_radial_gradient());
+    return static_cast<RadialGradientStyleValue const&>(*this);
+}
+
 RectStyleValue const& StyleValue::as_rect() const
 RectStyleValue const& StyleValue::as_rect() const
 {
 {
     VERIFY(is_rect());
     VERIFY(is_rect());
@@ -1984,6 +1990,163 @@ bool PositionValue::operator==(PositionValue const& other) const
         && variant_equals(vertical_position, other.vertical_position));
         && variant_equals(vertical_position, other.vertical_position));
 }
 }
 
 
+String RadialGradientStyleValue::to_string() const
+{
+    StringBuilder builder;
+    builder.appendff("radial-gradient({} "sv,
+        m_ending_shape == EndingShape::Circle ? "circle"sv : "ellipse"sv);
+
+    m_size.visit(
+        [&](Extent extent) {
+            builder.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) { builder.append(circle_size.radius.to_string()); },
+        [&](EllipseSize const& ellipse_size) { builder.appendff("{} {}", ellipse_size.radius_a.to_string(), ellipse_size.radius_b.to_string()); });
+
+    if (m_position != PositionValue::center()) {
+        builder.appendff(" at "sv);
+        m_position.serialize(builder);
+    }
+
+    builder.append(", "sv);
+    serialize_color_stop_list(builder, m_color_stop_list);
+    builder.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_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_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
+    return m_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 };
+        });
+}
+
+void RadialGradientStyleValue::resolve_for_size(Layout::Node const&, Gfx::FloatSize const&) const
+{
+}
+
+bool RadialGradientStyleValue::equals(StyleValue const& other) const
+{
+    if (type() != other.type())
+        return false;
+    auto& other_gradient = other.as_radial_gradient();
+    return (m_ending_shape == other_gradient.m_ending_shape
+        && variant_equals(m_size, other_gradient.m_size)
+        && m_position == other_gradient.m_position
+        && m_color_stop_list == other_gradient.m_color_stop_list);
+}
+
+void RadialGradientStyleValue::paint(PaintContext&, Gfx::IntRect const&, CSS::ImageRendering) const
+{
+}
+
 String ConicGradientStyleValue::to_string() const
 String ConicGradientStyleValue::to_string() const
 {
 {
     StringBuilder builder;
     StringBuilder builder;

+ 73 - 1
Userland/Libraries/LibWeb/CSS/StyleValue.h

@@ -241,6 +241,7 @@ public:
         Overflow,
         Overflow,
         Percentage,
         Percentage,
         Position,
         Position,
+        RadialGradient,
         Rect,
         Rect,
         Resolution,
         Resolution,
         Shadow,
         Shadow,
@@ -255,7 +256,7 @@ public:
 
 
     Type type() const { return m_type; }
     Type type() const { return m_type; }
 
 
-    bool is_abstract_image() const { return AK::first_is_one_of(type(), Type::Image, Type::LinearGradient, Type::ConicGradient); }
+    bool is_abstract_image() const { return AK::first_is_one_of(type(), Type::Image, Type::LinearGradient, Type::ConicGradient, Type::RadialGradient); }
     bool is_angle() const { return type() == Type::Angle; }
     bool is_angle() const { return type() == Type::Angle; }
     bool is_background() const { return type() == Type::Background; }
     bool is_background() const { return type() == Type::Background; }
     bool is_background_repeat() const { return type() == Type::BackgroundRepeat; }
     bool is_background_repeat() const { return type() == Type::BackgroundRepeat; }
@@ -286,6 +287,7 @@ public:
     bool is_overflow() const { return type() == Type::Overflow; }
     bool is_overflow() const { return type() == Type::Overflow; }
     bool is_percentage() const { return type() == Type::Percentage; }
     bool is_percentage() const { return type() == Type::Percentage; }
     bool is_position() const { return type() == Type::Position; }
     bool is_position() const { return type() == Type::Position; }
+    bool is_radial_gradient() const { return type() == Type::RadialGradient; }
     bool is_rect() const { return type() == Type::Rect; }
     bool is_rect() const { return type() == Type::Rect; }
     bool is_resolution() const { return type() == Type::Resolution; }
     bool is_resolution() const { return type() == Type::Resolution; }
     bool is_shadow() const { return type() == Type::Shadow; }
     bool is_shadow() const { return type() == Type::Shadow; }
@@ -330,6 +332,7 @@ public:
     OverflowStyleValue const& as_overflow() const;
     OverflowStyleValue const& as_overflow() const;
     PercentageStyleValue const& as_percentage() const;
     PercentageStyleValue const& as_percentage() const;
     PositionStyleValue const& as_position() const;
     PositionStyleValue const& as_position() const;
+    RadialGradientStyleValue const& as_radial_gradient() const;
     RectStyleValue const& as_rect() const;
     RectStyleValue const& as_rect() const;
     ResolutionStyleValue const& as_resolution() const;
     ResolutionStyleValue const& as_resolution() const;
     ShadowStyleValue const& as_shadow() const;
     ShadowStyleValue const& as_shadow() const;
@@ -372,6 +375,7 @@ public:
     OverflowStyleValue& as_overflow() { return const_cast<OverflowStyleValue&>(const_cast<StyleValue const&>(*this).as_overflow()); }
     OverflowStyleValue& as_overflow() { return const_cast<OverflowStyleValue&>(const_cast<StyleValue const&>(*this).as_overflow()); }
     PercentageStyleValue& as_percentage() { return const_cast<PercentageStyleValue&>(const_cast<StyleValue const&>(*this).as_percentage()); }
     PercentageStyleValue& as_percentage() { return const_cast<PercentageStyleValue&>(const_cast<StyleValue const&>(*this).as_percentage()); }
     PositionStyleValue& as_position() { return const_cast<PositionStyleValue&>(const_cast<StyleValue const&>(*this).as_position()); }
     PositionStyleValue& as_position() { return const_cast<PositionStyleValue&>(const_cast<StyleValue const&>(*this).as_position()); }
+    RadialGradientStyleValue& as_radial_gradient() { return const_cast<RadialGradientStyleValue&>(const_cast<StyleValue const&>(*this).as_radial_gradient()); }
     RectStyleValue& as_rect() { return const_cast<RectStyleValue&>(const_cast<StyleValue const&>(*this).as_rect()); }
     RectStyleValue& as_rect() { return const_cast<RectStyleValue&>(const_cast<StyleValue const&>(*this).as_rect()); }
     ResolutionStyleValue& as_resolution() { return const_cast<ResolutionStyleValue&>(const_cast<StyleValue const&>(*this).as_resolution()); }
     ResolutionStyleValue& as_resolution() { return const_cast<ResolutionStyleValue&>(const_cast<StyleValue const&>(*this).as_resolution()); }
     ShadowStyleValue& as_shadow() { return const_cast<ShadowStyleValue&>(const_cast<StyleValue const&>(*this).as_shadow()); }
     ShadowStyleValue& as_shadow() { return const_cast<ShadowStyleValue&>(const_cast<StyleValue const&>(*this).as_shadow()); }
@@ -1201,6 +1205,74 @@ enum class GradientRepeating {
     No
     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 NonnullRefPtr<RadialGradientStyleValue> create(EndingShape ending_shape, Size size, PositionValue position, Vector<LinearColorStopListElement> color_stop_list)
+    {
+        VERIFY(color_stop_list.size() >= 2);
+        return adopt_ref(*new RadialGradientStyleValue(ending_shape, size, position, move(color_stop_list)));
+    }
+
+    virtual String to_string() const override;
+
+    void paint(PaintContext&, Gfx::IntRect const& dest_rect, CSS::ImageRendering) const override;
+
+    virtual bool equals(StyleValue const& other) const override;
+
+    Vector<LinearColorStopListElement> const& color_stop_list() const
+    {
+        return m_color_stop_list;
+    }
+
+    bool is_paintable() const override { return true; }
+
+    void resolve_for_size(Layout::Node const&, Gfx::FloatSize const&) const override;
+
+    Gfx::FloatSize resolve_size(Layout::Node const&, Gfx::FloatPoint, Gfx::FloatRect const&) const;
+
+    virtual ~RadialGradientStyleValue() override = default;
+
+private:
+    RadialGradientStyleValue(EndingShape ending_shape, Size size, PositionValue position, Vector<LinearColorStopListElement> color_stop_list)
+        : AbstractImageStyleValue(Type::RadialGradient)
+        , m_ending_shape(ending_shape)
+        , m_size(size)
+        , m_position(position)
+        , m_color_stop_list(move(color_stop_list))
+    {
+    }
+
+    EndingShape m_ending_shape;
+    Size m_size;
+    PositionValue m_position;
+    Vector<LinearColorStopListElement> m_color_stop_list;
+};
+
 class ConicGradientStyleValue final : public AbstractImageStyleValue {
 class ConicGradientStyleValue final : public AbstractImageStyleValue {
 public:
 public:
     static NonnullRefPtr<ConicGradientStyleValue> create(Angle from_angle, PositionValue position, Vector<AngularColorStopListElement> color_stop_list, GradientRepeating repeating)
     static NonnullRefPtr<ConicGradientStyleValue> create(Angle from_angle, PositionValue position, Vector<AngularColorStopListElement> color_stop_list, GradientRepeating repeating)

+ 1 - 0
Userland/Libraries/LibWeb/Forward.h

@@ -90,6 +90,7 @@ class Percentage;
 class PercentageStyleValue;
 class PercentageStyleValue;
 class PositionStyleValue;
 class PositionStyleValue;
 class PropertyOwningCSSStyleDeclaration;
 class PropertyOwningCSSStyleDeclaration;
+class RadialGradientStyleValue;
 class RectStyleValue;
 class RectStyleValue;
 class Resolution;
 class Resolution;
 class ResolutionStyleValue;
 class ResolutionStyleValue;