1 /*
2 * Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above
9 * copyright notice, this list of conditions and the following
10 * disclaimer.
11 * 2. Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following
13 * disclaimer in the documentation and/or other materials
14 * provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
19 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
20 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
21 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
25 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
27 * OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 #include "third_party/blink/renderer/platform/geometry/float_rounded_rect.h"
31
32 #include <algorithm>
33 #include "third_party/blink/renderer/platform/geometry/float_quad.h"
34 #include "third_party/blink/renderer/platform/geometry/layout_rect.h"
35 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
36
37 namespace blink {
38
FloatRoundedRect(float x,float y,float width,float height)39 FloatRoundedRect::FloatRoundedRect(float x, float y, float width, float height)
40 : rect_(x, y, width, height) {}
41
FloatRoundedRect(const FloatRect & rect,const Radii & radii)42 FloatRoundedRect::FloatRoundedRect(const FloatRect& rect, const Radii& radii)
43 : rect_(rect), radii_(radii) {}
44
FloatRoundedRect(const IntRect & rect,const Radii & radii)45 FloatRoundedRect::FloatRoundedRect(const IntRect& rect, const Radii& radii)
46 : rect_(FloatRect(rect)), radii_(radii) {}
47
FloatRoundedRect(const FloatRect & rect,const FloatSize & top_left,const FloatSize & top_right,const FloatSize & bottom_left,const FloatSize & bottom_right)48 FloatRoundedRect::FloatRoundedRect(const FloatRect& rect,
49 const FloatSize& top_left,
50 const FloatSize& top_right,
51 const FloatSize& bottom_left,
52 const FloatSize& bottom_right)
53 : rect_(rect), radii_(top_left, top_right, bottom_left, bottom_right) {}
54
Scale(float factor)55 void FloatRoundedRect::Radii::Scale(float factor) {
56 if (factor == 1)
57 return;
58
59 // If either radius on a corner becomes zero, reset both radii on that corner.
60 top_left_.Scale(factor);
61 if (!top_left_.Width() || !top_left_.Height())
62 top_left_ = FloatSize();
63 top_right_.Scale(factor);
64 if (!top_right_.Width() || !top_right_.Height())
65 top_right_ = FloatSize();
66 bottom_left_.Scale(factor);
67 if (!bottom_left_.Width() || !bottom_left_.Height())
68 bottom_left_ = FloatSize();
69 bottom_right_.Scale(factor);
70 if (!bottom_right_.Width() || !bottom_right_.Height())
71 bottom_right_ = FloatSize();
72 }
73
ScaleAndFloor(float factor)74 void FloatRoundedRect::Radii::ScaleAndFloor(float factor) {
75 if (factor == 1)
76 return;
77
78 // If either radius on a corner becomes zero, reset both radii on that corner.
79 top_left_.ScaleAndFloor(factor);
80 if (!top_left_.Width() || !top_left_.Height())
81 top_left_ = FloatSize();
82 top_right_.ScaleAndFloor(factor);
83 if (!top_right_.Width() || !top_right_.Height())
84 top_right_ = FloatSize();
85 bottom_left_.ScaleAndFloor(factor);
86 if (!bottom_left_.Width() || !bottom_left_.Height())
87 bottom_left_ = FloatSize();
88 bottom_right_.ScaleAndFloor(factor);
89 if (!bottom_right_.Width() || !bottom_right_.Height())
90 bottom_right_ = FloatSize();
91 }
92
Shrink(float top_width,float bottom_width,float left_width,float right_width)93 void FloatRoundedRect::Radii::Shrink(float top_width,
94 float bottom_width,
95 float left_width,
96 float right_width) {
97 DCHECK_GE(top_width, 0);
98 DCHECK_GE(bottom_width, 0);
99 DCHECK_GE(left_width, 0);
100 DCHECK_GE(right_width, 0);
101
102 top_left_.SetWidth(std::max<float>(0, top_left_.Width() - left_width));
103 top_left_.SetHeight(std::max<float>(0, top_left_.Height() - top_width));
104
105 top_right_.SetWidth(std::max<float>(0, top_right_.Width() - right_width));
106 top_right_.SetHeight(std::max<float>(0, top_right_.Height() - top_width));
107
108 bottom_left_.SetWidth(std::max<float>(0, bottom_left_.Width() - left_width));
109 bottom_left_.SetHeight(
110 std::max<float>(0, bottom_left_.Height() - bottom_width));
111
112 bottom_right_.SetWidth(
113 std::max<float>(0, bottom_right_.Width() - right_width));
114 bottom_right_.SetHeight(
115 std::max<float>(0, bottom_right_.Height() - bottom_width));
116 }
117
Expand(float top_width,float bottom_width,float left_width,float right_width)118 void FloatRoundedRect::Radii::Expand(float top_width,
119 float bottom_width,
120 float left_width,
121 float right_width) {
122 DCHECK_GE(top_width, 0);
123 DCHECK_GE(bottom_width, 0);
124 DCHECK_GE(left_width, 0);
125 DCHECK_GE(right_width, 0);
126 if (top_left_.Width() > 0 && top_left_.Height() > 0) {
127 top_left_.SetWidth(top_left_.Width() + left_width);
128 top_left_.SetHeight(top_left_.Height() + top_width);
129 }
130 if (top_right_.Width() > 0 && top_right_.Height() > 0) {
131 top_right_.SetWidth(top_right_.Width() + right_width);
132 top_right_.SetHeight(top_right_.Height() + top_width);
133 }
134 if (bottom_left_.Width() > 0 && bottom_left_.Height() > 0) {
135 bottom_left_.SetWidth(bottom_left_.Width() + left_width);
136 bottom_left_.SetHeight(bottom_left_.Height() + bottom_width);
137 }
138 if (bottom_right_.Width() > 0 && bottom_right_.Height() > 0) {
139 bottom_right_.SetWidth(bottom_right_.Width() + right_width);
140 bottom_right_.SetHeight(bottom_right_.Height() + bottom_width);
141 }
142 }
143
CornerRectIntercept(float y,const FloatRect & corner_rect)144 static inline float CornerRectIntercept(float y, const FloatRect& corner_rect) {
145 DCHECK_GT(corner_rect.Height(), 0);
146 return corner_rect.Width() *
147 sqrt(1 - (y * y) / (corner_rect.Height() * corner_rect.Height()));
148 }
149
RadiusCenterRect() const150 FloatRect FloatRoundedRect::RadiusCenterRect() const {
151 FloatRectOutsets maximum_radius_insets(
152 -std::max(radii_.TopLeft().Height(), radii_.TopRight().Height()),
153 -std::max(radii_.TopRight().Width(), radii_.BottomRight().Width()),
154 -std::max(radii_.BottomLeft().Height(), radii_.BottomRight().Height()),
155 -std::max(radii_.TopLeft().Width(), radii_.BottomLeft().Width()));
156 FloatRect center_rect(rect_);
157 center_rect.Expand(maximum_radius_insets);
158 return center_rect;
159 }
160
XInterceptsAtY(float y,float & min_x_intercept,float & max_x_intercept) const161 bool FloatRoundedRect::XInterceptsAtY(float y,
162 float& min_x_intercept,
163 float& max_x_intercept) const {
164 if (y < Rect().Y() || y > Rect().MaxY())
165 return false;
166
167 if (!IsRounded()) {
168 min_x_intercept = Rect().X();
169 max_x_intercept = Rect().MaxX();
170 return true;
171 }
172
173 const FloatRect& top_left_rect = TopLeftCorner();
174 const FloatRect& bottom_left_rect = BottomLeftCorner();
175
176 if (!top_left_rect.IsEmpty() && y >= top_left_rect.Y() &&
177 y < top_left_rect.MaxY())
178 min_x_intercept =
179 top_left_rect.MaxX() -
180 CornerRectIntercept(top_left_rect.MaxY() - y, top_left_rect);
181 else if (!bottom_left_rect.IsEmpty() && y >= bottom_left_rect.Y() &&
182 y <= bottom_left_rect.MaxY())
183 min_x_intercept =
184 bottom_left_rect.MaxX() -
185 CornerRectIntercept(y - bottom_left_rect.Y(), bottom_left_rect);
186 else
187 min_x_intercept = rect_.X();
188
189 const FloatRect& top_right_rect = TopRightCorner();
190 const FloatRect& bottom_right_rect = BottomRightCorner();
191
192 if (!top_right_rect.IsEmpty() && y >= top_right_rect.Y() &&
193 y <= top_right_rect.MaxY())
194 max_x_intercept =
195 top_right_rect.X() +
196 CornerRectIntercept(top_right_rect.MaxY() - y, top_right_rect);
197 else if (!bottom_right_rect.IsEmpty() && y >= bottom_right_rect.Y() &&
198 y <= bottom_right_rect.MaxY())
199 max_x_intercept =
200 bottom_right_rect.X() +
201 CornerRectIntercept(y - bottom_right_rect.Y(), bottom_right_rect);
202 else
203 max_x_intercept = rect_.MaxX();
204
205 return true;
206 }
207
InflateWithRadii(int size)208 void FloatRoundedRect::InflateWithRadii(int size) {
209 FloatRect old = rect_;
210
211 rect_.Inflate(size);
212 // Considering the inflation factor of shorter size to scale the radii seems
213 // appropriate here
214 float factor;
215 if (rect_.Width() < rect_.Height())
216 factor = old.Width() ? (float)rect_.Width() / old.Width() : int(0);
217 else
218 factor = old.Height() ? (float)rect_.Height() / old.Height() : int(0);
219
220 radii_.Scale(factor);
221 }
222
IntersectsQuad(const FloatQuad & quad) const223 bool FloatRoundedRect::IntersectsQuad(const FloatQuad& quad) const {
224 if (!quad.IntersectsRect(rect_))
225 return false;
226
227 const FloatSize& top_left = radii_.TopLeft();
228 if (!top_left.IsEmpty()) {
229 FloatRect rect(rect_.X(), rect_.Y(), top_left.Width(), top_left.Height());
230 if (quad.IntersectsRect(rect)) {
231 FloatPoint center(rect_.X() + top_left.Width(),
232 rect_.Y() + top_left.Height());
233 FloatSize size(top_left.Width(), top_left.Height());
234 if (!quad.IntersectsEllipse(center, size))
235 return false;
236 }
237 }
238
239 const FloatSize& top_right = radii_.TopRight();
240 if (!top_right.IsEmpty()) {
241 FloatRect rect(rect_.MaxX() - top_right.Width(), rect_.Y(),
242 top_right.Width(), top_right.Height());
243 if (quad.IntersectsRect(rect)) {
244 FloatPoint center(rect_.MaxX() - top_right.Width(),
245 rect_.Y() + top_right.Height());
246 FloatSize size(top_right.Width(), top_right.Height());
247 if (!quad.IntersectsEllipse(center, size))
248 return false;
249 }
250 }
251
252 const FloatSize& bottom_left = radii_.BottomLeft();
253 if (!bottom_left.IsEmpty()) {
254 FloatRect rect(rect_.X(), rect_.MaxY() - bottom_left.Height(),
255 bottom_left.Width(), bottom_left.Height());
256 if (quad.IntersectsRect(rect)) {
257 FloatPoint center(rect_.X() + bottom_left.Width(),
258 rect_.MaxY() - bottom_left.Height());
259 FloatSize size(bottom_left.Width(), bottom_left.Height());
260 if (!quad.IntersectsEllipse(center, size))
261 return false;
262 }
263 }
264
265 const FloatSize& bottom_right = radii_.BottomRight();
266 if (!bottom_right.IsEmpty()) {
267 FloatRect rect(rect_.MaxX() - bottom_right.Width(),
268 rect_.MaxY() - bottom_right.Height(), bottom_right.Width(),
269 bottom_right.Height());
270 if (quad.IntersectsRect(rect)) {
271 FloatPoint center(rect_.MaxX() - bottom_right.Width(),
272 rect_.MaxY() - bottom_right.Height());
273 FloatSize size(bottom_right.Width(), bottom_right.Height());
274 if (!quad.IntersectsEllipse(center, size))
275 return false;
276 }
277 }
278
279 return true;
280 }
281
CalcBorderRadiiConstraintScaleFor(const FloatRect & rect,const FloatRoundedRect::Radii & radii)282 float CalcBorderRadiiConstraintScaleFor(const FloatRect& rect,
283 const FloatRoundedRect::Radii& radii) {
284 float factor = 1;
285 float radii_sum;
286
287 // top
288 radii_sum = radii.TopLeft().Width() +
289 radii.TopRight().Width(); // Casts to avoid integer overflow.
290 if (radii_sum > rect.Width())
291 factor = std::min(rect.Width() / radii_sum, factor);
292
293 // bottom
294 radii_sum = radii.BottomLeft().Width() + radii.BottomRight().Width();
295 if (radii_sum > rect.Width())
296 factor = std::min(rect.Width() / radii_sum, factor);
297
298 // left
299 radii_sum = radii.TopLeft().Height() + radii.BottomLeft().Height();
300 if (radii_sum > rect.Height())
301 factor = std::min(rect.Height() / radii_sum, factor);
302
303 // right
304 radii_sum = radii.TopRight().Height() + radii.BottomRight().Height();
305 if (radii_sum > rect.Height())
306 factor = std::min(rect.Height() / radii_sum, factor);
307
308 DCHECK_LE(factor, 1);
309 return factor;
310 }
311
ConstrainRadii()312 void FloatRoundedRect::ConstrainRadii() {
313 radii_.ScaleAndFloor(CalcBorderRadiiConstraintScaleFor(Rect(), GetRadii()));
314 }
315
IsRenderable() const316 bool FloatRoundedRect::IsRenderable() const {
317 // FIXME: remove the 0.0001 slop once this class is converted to layout units.
318 return radii_.TopLeft().Width() + radii_.TopRight().Width() <=
319 rect_.Width() + 0.0001 &&
320 radii_.BottomLeft().Width() + radii_.BottomRight().Width() <=
321 rect_.Width() + 0.0001 &&
322 radii_.TopLeft().Height() + radii_.BottomLeft().Height() <=
323 rect_.Height() + 0.0001 &&
324 radii_.TopRight().Height() + radii_.BottomRight().Height() <=
325 rect_.Height() + 0.0001;
326 }
327
AdjustRadii()328 void FloatRoundedRect::AdjustRadii() {
329 float max_radius_width =
330 std::max(radii_.TopLeft().Width() + radii_.TopRight().Width(),
331 radii_.BottomLeft().Width() + radii_.BottomRight().Width());
332 float max_radius_height =
333 std::max(radii_.TopLeft().Height() + radii_.BottomLeft().Height(),
334 radii_.TopRight().Height() + radii_.BottomRight().Height());
335
336 if (max_radius_width <= 0 || max_radius_height <= 0) {
337 radii_.Scale(0.0f);
338 return;
339 }
340 float width_ratio = static_cast<float>(rect_.Width()) / max_radius_width;
341 float height_ratio = static_cast<float>(rect_.Height()) / max_radius_height;
342 radii_.Scale(width_ratio < height_ratio ? width_ratio : height_ratio);
343 }
344
operator <<(std::ostream & ostream,const FloatRoundedRect & rect)345 std::ostream& operator<<(std::ostream& ostream, const FloatRoundedRect& rect) {
346 return ostream << rect.ToString();
347 }
348
operator <<(std::ostream & ostream,const FloatRoundedRect::Radii & radii)349 std::ostream& operator<<(std::ostream& ostream,
350 const FloatRoundedRect::Radii& radii) {
351 return ostream << radii.ToString();
352 }
353
ToString() const354 String FloatRoundedRect::Radii::ToString() const {
355 return "tl:" + TopLeft().ToString() + "; tr:" + TopRight().ToString() +
356 "; bl:" + BottomLeft().ToString() + "; br:" + BottomRight().ToString();
357 }
358
ToString() const359 String FloatRoundedRect::ToString() const {
360 if (Rect() == FloatRect(LayoutRect::InfiniteIntRect()))
361 return "InfiniteIntRect";
362 if (GetRadii().IsZero())
363 return Rect().ToString();
364 return Rect().ToString() + " radii:(" + GetRadii().ToString() + ")";
365 }
366
367 } // namespace blink
368