/src/serenity/Userland/Libraries/LibWeb/CSS/StyleValues/RadialGradientStyleValue.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> |
3 | | * Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org> |
4 | | * Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org> |
5 | | * Copyright (c) 2022-2023, MacDue <macdue@dueutil.tech> |
6 | | * |
7 | | * SPDX-License-Identifier: BSD-2-Clause |
8 | | */ |
9 | | |
10 | | #include "RadialGradientStyleValue.h" |
11 | | #include <LibWeb/CSS/StyleValues/PositionStyleValue.h> |
12 | | #include <LibWeb/Layout/Node.h> |
13 | | |
14 | | namespace Web::CSS { |
15 | | |
16 | | String RadialGradientStyleValue::to_string() const |
17 | 0 | { |
18 | 0 | StringBuilder builder; |
19 | 0 | if (is_repeating()) |
20 | 0 | builder.append("repeating-"sv); |
21 | 0 | builder.appendff("radial-gradient({} "sv, |
22 | 0 | m_properties.ending_shape == EndingShape::Circle ? "circle"sv : "ellipse"sv); |
23 | |
|
24 | 0 | m_properties.size.visit( |
25 | 0 | [&](Extent extent) { |
26 | 0 | builder.append([&] { |
27 | 0 | switch (extent) { |
28 | 0 | case Extent::ClosestCorner: |
29 | 0 | return "closest-corner"sv; |
30 | 0 | case Extent::ClosestSide: |
31 | 0 | return "closest-side"sv; |
32 | 0 | case Extent::FarthestCorner: |
33 | 0 | return "farthest-corner"sv; |
34 | 0 | case Extent::FarthestSide: |
35 | 0 | return "farthest-side"sv; |
36 | 0 | default: |
37 | 0 | VERIFY_NOT_REACHED(); |
38 | 0 | } |
39 | 0 | }()); |
40 | 0 | }, |
41 | 0 | [&](CircleSize const& circle_size) { |
42 | 0 | builder.append(circle_size.radius.to_string()); |
43 | 0 | }, |
44 | 0 | [&](EllipseSize const& ellipse_size) { |
45 | 0 | builder.appendff("{} {}", ellipse_size.radius_a.to_string(), ellipse_size.radius_b.to_string()); |
46 | 0 | }); |
47 | |
|
48 | 0 | if (!m_properties.position->is_center()) |
49 | 0 | builder.appendff(" at {}"sv, m_properties.position->to_string()); |
50 | |
|
51 | 0 | builder.append(", "sv); |
52 | 0 | serialize_color_stop_list(builder, m_properties.color_stop_list); |
53 | 0 | builder.append(')'); |
54 | 0 | return MUST(builder.to_string()); |
55 | 0 | } |
56 | | |
57 | | CSSPixelSize RadialGradientStyleValue::resolve_size(Layout::Node const& node, CSSPixelPoint center, CSSPixelRect const& size) const |
58 | 0 | { |
59 | 0 | auto const side_shape = [&](auto distance_function) { |
60 | 0 | auto const distance_from = [&](CSSPixels v, CSSPixels a, CSSPixels b, auto distance_function) { |
61 | 0 | return distance_function(abs(a - v), abs(b - v)); |
62 | 0 | }; |
63 | 0 | auto x_dist = distance_from(center.x(), size.left(), size.right(), distance_function); |
64 | 0 | auto y_dist = distance_from(center.y(), size.top(), size.bottom(), distance_function); |
65 | 0 | if (m_properties.ending_shape == EndingShape::Circle) { |
66 | 0 | auto dist = distance_function(x_dist, y_dist); |
67 | 0 | return CSSPixelSize { dist, dist }; |
68 | 0 | } else { |
69 | 0 | return CSSPixelSize { x_dist, y_dist }; |
70 | 0 | } |
71 | 0 | }; |
72 | |
|
73 | 0 | auto const closest_side_shape = [&] { |
74 | 0 | return side_shape(AK::min<CSSPixels>); |
75 | 0 | }; |
76 | |
|
77 | 0 | auto const farthest_side_shape = [&] { |
78 | 0 | return side_shape(AK::max<CSSPixels>); |
79 | 0 | }; |
80 | |
|
81 | 0 | auto const corner_distance = [&](auto distance_compare, CSSPixelPoint& corner) { |
82 | 0 | auto top_left_distance_squared = square_distance_between(size.top_left(), center); |
83 | 0 | auto top_right_distance_squared = square_distance_between(size.top_right(), center); |
84 | 0 | auto bottom_right_distance_squared = square_distance_between(size.bottom_right(), center); |
85 | 0 | auto bottom_left_distance_squared = square_distance_between(size.bottom_left(), center); |
86 | 0 | auto distance_squared = top_left_distance_squared; |
87 | 0 | corner = size.top_left(); |
88 | 0 | if (distance_compare(top_right_distance_squared, distance_squared)) { |
89 | 0 | corner = size.top_right(); |
90 | 0 | distance_squared = top_right_distance_squared; |
91 | 0 | } |
92 | 0 | if (distance_compare(bottom_right_distance_squared, distance_squared)) { |
93 | 0 | corner = size.bottom_right(); |
94 | 0 | distance_squared = bottom_right_distance_squared; |
95 | 0 | } |
96 | 0 | if (distance_compare(bottom_left_distance_squared, distance_squared)) { |
97 | 0 | corner = size.bottom_left(); |
98 | 0 | distance_squared = bottom_left_distance_squared; |
99 | 0 | } |
100 | 0 | return sqrt(distance_squared); |
101 | 0 | }; Unexecuted instantiation: RadialGradientStyleValue.cpp:auto Web::CSS::RadialGradientStyleValue::resolve_size(Web::Layout::Node const&, Gfx::Point<Web::CSSPixels>, Gfx::Rect<Web::CSSPixels> const&) const::$_1::operator()<Web::CSS::RadialGradientStyleValue::resolve_size(Web::Layout::Node const&, Gfx::Point<Web::CSSPixels>, Gfx::Rect<Web::CSSPixels> const&) const::$_2::operator()(Gfx::Point<Web::CSSPixels>&) const::{lambda(Web::CSSPixels, Web::CSSPixels)#1}>(Web::CSS::RadialGradientStyleValue::resolve_size(Web::Layout::Node const&, Gfx::Point<Web::CSSPixels>, Gfx::Rect<Web::CSSPixels> const&) const::$_2::operator()(Gfx::Point<Web::CSSPixels>&) const::{lambda(Web::CSSPixels, Web::CSSPixels)#1}, Gfx::Point<Web::CSSPixels>&) constUnexecuted instantiation: RadialGradientStyleValue.cpp:auto Web::CSS::RadialGradientStyleValue::resolve_size(Web::Layout::Node const&, Gfx::Point<Web::CSSPixels>, Gfx::Rect<Web::CSSPixels> const&) const::$_1::operator()<Web::CSS::RadialGradientStyleValue::resolve_size(Web::Layout::Node const&, Gfx::Point<Web::CSSPixels>, Gfx::Rect<Web::CSSPixels> const&) const::$_3::operator()(Gfx::Point<Web::CSSPixels>&) const::{lambda(Web::CSSPixels, Web::CSSPixels)#1}>(Web::CSS::RadialGradientStyleValue::resolve_size(Web::Layout::Node const&, Gfx::Point<Web::CSSPixels>, Gfx::Rect<Web::CSSPixels> const&) const::$_3::operator()(Gfx::Point<Web::CSSPixels>&) const::{lambda(Web::CSSPixels, Web::CSSPixels)#1}, Gfx::Point<Web::CSSPixels>&) const |
102 | |
|
103 | 0 | auto const closest_corner_distance = [&](CSSPixelPoint& corner) { |
104 | 0 | return corner_distance([](CSSPixels a, CSSPixels b) { return a < b; }, corner); |
105 | 0 | }; |
106 | |
|
107 | 0 | auto const farthest_corner_distance = [&](CSSPixelPoint& corner) { |
108 | 0 | return corner_distance([](CSSPixels a, CSSPixels b) { return a > b; }, corner); |
109 | 0 | }; |
110 | |
|
111 | 0 | auto const corner_shape = [&](auto corner_distance, auto get_shape) { |
112 | 0 | CSSPixelPoint corner {}; |
113 | 0 | auto distance = corner_distance(corner); |
114 | 0 | if (m_properties.ending_shape == EndingShape::Ellipse) { |
115 | 0 | auto shape = get_shape(); |
116 | 0 | auto aspect_ratio = shape.width() / shape.height(); |
117 | 0 | auto p = corner - center; |
118 | 0 | auto radius_a = sqrt(p.y() * p.y() * aspect_ratio * aspect_ratio + p.x() * p.x()); |
119 | 0 | auto radius_b = radius_a / aspect_ratio; |
120 | 0 | return CSSPixelSize { radius_a, radius_b }; |
121 | 0 | } |
122 | 0 | return CSSPixelSize { distance, distance }; |
123 | 0 | }; Unexecuted instantiation: RadialGradientStyleValue.cpp:auto Web::CSS::RadialGradientStyleValue::resolve_size(Web::Layout::Node const&, Gfx::Point<Web::CSSPixels>, Gfx::Rect<Web::CSSPixels> const&) const::$_4::operator()<Web::CSS::RadialGradientStyleValue::resolve_size(Web::Layout::Node const&, Gfx::Point<Web::CSSPixels>, Gfx::Rect<Web::CSSPixels> const&) const::$_2, Web::CSS::RadialGradientStyleValue::resolve_size(Web::Layout::Node const&, Gfx::Point<Web::CSSPixels>, Gfx::Rect<Web::CSSPixels> const&) const::$_5>(Web::CSS::RadialGradientStyleValue::resolve_size(Web::Layout::Node const&, Gfx::Point<Web::CSSPixels>, Gfx::Rect<Web::CSSPixels> const&) const::$_2, Web::CSS::RadialGradientStyleValue::resolve_size(Web::Layout::Node const&, Gfx::Point<Web::CSSPixels>, Gfx::Rect<Web::CSSPixels> const&) const::$_5) const Unexecuted instantiation: RadialGradientStyleValue.cpp:auto Web::CSS::RadialGradientStyleValue::resolve_size(Web::Layout::Node const&, Gfx::Point<Web::CSSPixels>, Gfx::Rect<Web::CSSPixels> const&) const::$_4::operator()<Web::CSS::RadialGradientStyleValue::resolve_size(Web::Layout::Node const&, Gfx::Point<Web::CSSPixels>, Gfx::Rect<Web::CSSPixels> const&) const::$_3, Web::CSS::RadialGradientStyleValue::resolve_size(Web::Layout::Node const&, Gfx::Point<Web::CSSPixels>, Gfx::Rect<Web::CSSPixels> const&) const::$_6>(Web::CSS::RadialGradientStyleValue::resolve_size(Web::Layout::Node const&, Gfx::Point<Web::CSSPixels>, Gfx::Rect<Web::CSSPixels> const&) const::$_3, Web::CSS::RadialGradientStyleValue::resolve_size(Web::Layout::Node const&, Gfx::Point<Web::CSSPixels>, Gfx::Rect<Web::CSSPixels> const&) const::$_6) const |
124 | | |
125 | | // https://w3c.github.io/csswg-drafts/css-images/#radial-gradient-syntax |
126 | 0 | auto resolved_size = m_properties.size.visit( |
127 | 0 | [&](Extent extent) { |
128 | 0 | switch (extent) { |
129 | 0 | case Extent::ClosestSide: |
130 | | // The ending shape is sized so that it exactly meets the side of the gradient box closest to the gradient’s center. |
131 | | // If the shape is an ellipse, it exactly meets the closest side in each dimension. |
132 | 0 | return closest_side_shape(); |
133 | 0 | case Extent::ClosestCorner: |
134 | | // The ending shape is sized so that it passes through the corner of the gradient box closest to the gradient’s center. |
135 | | // If the shape is an ellipse, the ending shape is given the same aspect-ratio it would have if closest-side were specified |
136 | 0 | return corner_shape(closest_corner_distance, closest_side_shape); |
137 | 0 | case Extent::FarthestCorner: |
138 | | // Same as closest-corner, except the ending shape is sized based on the farthest corner. |
139 | | // If the shape is an ellipse, the ending shape is given the same aspect ratio it would have if farthest-side were specified. |
140 | 0 | return corner_shape(farthest_corner_distance, farthest_side_shape); |
141 | 0 | case Extent::FarthestSide: |
142 | | // Same as closest-side, except the ending shape is sized based on the farthest side(s). |
143 | 0 | return farthest_side_shape(); |
144 | 0 | default: |
145 | 0 | VERIFY_NOT_REACHED(); |
146 | 0 | } |
147 | 0 | }, |
148 | 0 | [&](CircleSize const& circle_size) { |
149 | 0 | auto radius = circle_size.radius.to_px(node); |
150 | 0 | return CSSPixelSize { radius, radius }; |
151 | 0 | }, |
152 | 0 | [&](EllipseSize const& ellipse_size) { |
153 | 0 | auto radius_a = ellipse_size.radius_a.resolved(node, size.width()).to_px(node); |
154 | 0 | auto radius_b = ellipse_size.radius_b.resolved(node, size.height()).to_px(node); |
155 | 0 | return CSSPixelSize { radius_a, radius_b }; |
156 | 0 | }); |
157 | | |
158 | | // Handle degenerate cases |
159 | | // https://w3c.github.io/csswg-drafts/css-images/#degenerate-radials |
160 | |
|
161 | 0 | constexpr auto arbitrary_small_number = CSSPixels::smallest_positive_value(); |
162 | 0 | constexpr auto arbitrary_large_number = CSSPixels::max(); |
163 | | |
164 | | // If the ending shape is a circle with zero radius: |
165 | 0 | if (m_properties.ending_shape == EndingShape::Circle && resolved_size.is_empty()) { |
166 | | // Render as if the ending shape was a circle whose radius was an arbitrary very small number greater than zero. |
167 | | // This will make the gradient continue to look like a circle. |
168 | 0 | return CSSPixelSize { arbitrary_small_number, arbitrary_small_number }; |
169 | 0 | } |
170 | | // If the ending shape has zero width (regardless of the height): |
171 | 0 | if (resolved_size.width() <= 0) { |
172 | | // Render as if the ending shape was an ellipse whose height was an arbitrary very large number |
173 | | // and whose width was an arbitrary very small number greater than zero. |
174 | | // This will make the gradient look similar to a horizontal linear gradient that is mirrored across the center of the ellipse. |
175 | | // It also means that all color-stop positions specified with a percentage resolve to 0px. |
176 | 0 | return CSSPixelSize { arbitrary_small_number, arbitrary_large_number }; |
177 | 0 | } |
178 | | // Otherwise, if the ending shape has zero height: |
179 | 0 | if (resolved_size.height() <= 0) { |
180 | | // Render as if the ending shape was an ellipse whose width was an arbitrary very large number and whose height |
181 | | // was an arbitrary very small number greater than zero. This will make the gradient look like a solid-color image equal |
182 | | // to the color of the last color-stop, or equal to the average color of the gradient if it’s repeating. |
183 | 0 | return CSSPixelSize { arbitrary_large_number, arbitrary_small_number }; |
184 | 0 | } |
185 | 0 | return resolved_size; |
186 | 0 | } |
187 | | |
188 | | void RadialGradientStyleValue::resolve_for_size(Layout::NodeWithStyleAndBoxModelMetrics const& node, CSSPixelSize paint_size) const |
189 | 0 | { |
190 | 0 | CSSPixelRect gradient_box { { 0, 0 }, paint_size }; |
191 | 0 | auto center = m_properties.position->resolved(node, gradient_box); |
192 | 0 | auto gradient_size = resolve_size(node, center, gradient_box); |
193 | 0 | if (m_resolved.has_value() && m_resolved->gradient_size == gradient_size) |
194 | 0 | return; |
195 | 0 | m_resolved = ResolvedData { |
196 | 0 | Painting::resolve_radial_gradient_data(node, gradient_size, *this), |
197 | 0 | gradient_size, |
198 | 0 | center, |
199 | 0 | }; |
200 | 0 | } |
201 | | |
202 | | bool RadialGradientStyleValue::equals(CSSStyleValue const& other) const |
203 | 0 | { |
204 | 0 | if (type() != other.type()) |
205 | 0 | return false; |
206 | 0 | auto& other_gradient = other.as_radial_gradient(); |
207 | 0 | return m_properties == other_gradient.m_properties; |
208 | 0 | } |
209 | | |
210 | | void RadialGradientStyleValue::paint(PaintContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering, RefPtr<Painting::DisplayList> text_clip) const |
211 | 0 | { |
212 | 0 | VERIFY(m_resolved.has_value()); |
213 | 0 | auto center = context.rounded_device_point(m_resolved->center).to_type<int>(); |
214 | 0 | auto size = context.rounded_device_size(m_resolved->gradient_size).to_type<int>(); |
215 | 0 | context.display_list_recorder().fill_rect_with_radial_gradient(dest_rect.to_type<int>(), m_resolved->data, center, size, text_clip); |
216 | 0 | } |
217 | | |
218 | | } |