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