1/*
2 * Copyright (C) 2003, 2006, 2009 Apple Inc. All rights reserved.
3 * Copyright (C) 2010 Google Inc. All rights reserved.
4 * Copyright (C) 2013 Xidorn Quan (quanxunzhen@gmail.com)
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "config.h"
29#include "RoundedRect.h"
30
31#include "FloatRoundedRect.h"
32#include "GeometryUtilities.h"
33#include "LayoutRect.h"
34#include "LayoutUnit.h"
35#include "Region.h"
36#include <algorithm>
37#include <wtf/MathExtras.h>
38
39namespace WebCore {
40
41bool RoundedRect::Radii::isZero() const
42{
43 return m_topLeft.isZero() && m_topRight.isZero() && m_bottomLeft.isZero() && m_bottomRight.isZero();
44}
45
46void RoundedRect::Radii::scale(float factor)
47{
48 if (factor == 1)
49 return;
50
51 // If either radius on a corner becomes zero, reset both radii on that corner.
52 m_topLeft.scale(factor);
53 if (!m_topLeft.width() || !m_topLeft.height())
54 m_topLeft = LayoutSize();
55 m_topRight.scale(factor);
56 if (!m_topRight.width() || !m_topRight.height())
57 m_topRight = LayoutSize();
58 m_bottomLeft.scale(factor);
59 if (!m_bottomLeft.width() || !m_bottomLeft.height())
60 m_bottomLeft = LayoutSize();
61 m_bottomRight.scale(factor);
62 if (!m_bottomRight.width() || !m_bottomRight.height())
63 m_bottomRight = LayoutSize();
64}
65
66void RoundedRect::Radii::expand(const LayoutUnit& topWidth, const LayoutUnit& bottomWidth, const LayoutUnit& leftWidth, const LayoutUnit& rightWidth)
67{
68 if (m_topLeft.width() > 0 && m_topLeft.height() > 0) {
69 m_topLeft.setWidth(std::max<LayoutUnit>(0, m_topLeft.width() + leftWidth));
70 m_topLeft.setHeight(std::max<LayoutUnit>(0, m_topLeft.height() + topWidth));
71 }
72 if (m_topRight.width() > 0 && m_topRight.height() > 0) {
73 m_topRight.setWidth(std::max<LayoutUnit>(0, m_topRight.width() + rightWidth));
74 m_topRight.setHeight(std::max<LayoutUnit>(0, m_topRight.height() + topWidth));
75 }
76 if (m_bottomLeft.width() > 0 && m_bottomLeft.height() > 0) {
77 m_bottomLeft.setWidth(std::max<LayoutUnit>(0, m_bottomLeft.width() + leftWidth));
78 m_bottomLeft.setHeight(std::max<LayoutUnit>(0, m_bottomLeft.height() + bottomWidth));
79 }
80 if (m_bottomRight.width() > 0 && m_bottomRight.height() > 0) {
81 m_bottomRight.setWidth(std::max<LayoutUnit>(0, m_bottomRight.width() + rightWidth));
82 m_bottomRight.setHeight(std::max<LayoutUnit>(0, m_bottomRight.height() + bottomWidth));
83 }
84}
85
86void RoundedRect::inflateWithRadii(const LayoutUnit& size)
87{
88 LayoutRect old = m_rect;
89
90 m_rect.inflate(size);
91 // Considering the inflation factor of shorter size to scale the radii seems appropriate here
92 float factor;
93 if (m_rect.width() < m_rect.height())
94 factor = old.width() ? (float)m_rect.width() / old.width() : int(0);
95 else
96 factor = old.height() ? (float)m_rect.height() / old.height() : int(0);
97
98 m_radii.scale(factor);
99}
100
101void RoundedRect::Radii::includeLogicalEdges(const RoundedRect::Radii& edges, bool isHorizontal, bool includeLogicalLeftEdge, bool includeLogicalRightEdge)
102{
103 if (includeLogicalLeftEdge) {
104 if (isHorizontal)
105 m_bottomLeft = edges.bottomLeft();
106 else
107 m_topRight = edges.topRight();
108 m_topLeft = edges.topLeft();
109 }
110
111 if (includeLogicalRightEdge) {
112 if (isHorizontal)
113 m_topRight = edges.topRight();
114 else
115 m_bottomLeft = edges.bottomLeft();
116 m_bottomRight = edges.bottomRight();
117 }
118}
119
120void RoundedRect::Radii::excludeLogicalEdges(bool isHorizontal, bool excludeLogicalLeftEdge, bool excludeLogicalRightEdge)
121{
122 if (excludeLogicalLeftEdge) {
123 if (isHorizontal)
124 m_bottomLeft = IntSize();
125 else
126 m_topRight = IntSize();
127 m_topLeft = IntSize();
128 }
129
130 if (excludeLogicalRightEdge) {
131 if (isHorizontal)
132 m_topRight = IntSize();
133 else
134 m_bottomLeft = IntSize();
135 m_bottomRight = IntSize();
136 }
137}
138
139RoundedRect::RoundedRect(const LayoutUnit& x, const LayoutUnit& y, const LayoutUnit& width, const LayoutUnit& height)
140 : m_rect(x, y, width, height)
141{
142}
143
144RoundedRect::RoundedRect(const LayoutRect& rect, const Radii& radii)
145 : m_rect(rect)
146 , m_radii(radii)
147{
148}
149
150RoundedRect::RoundedRect(const LayoutRect& rect, const LayoutSize& topLeft, const LayoutSize& topRight, const LayoutSize& bottomLeft, const LayoutSize& bottomRight)
151 : m_rect(rect)
152 , m_radii(topLeft, topRight, bottomLeft, bottomRight)
153{
154}
155
156void RoundedRect::includeLogicalEdges(const Radii& edges, bool isHorizontal, bool includeLogicalLeftEdge, bool includeLogicalRightEdge)
157{
158 m_radii.includeLogicalEdges(edges, isHorizontal, includeLogicalLeftEdge, includeLogicalRightEdge);
159}
160
161void RoundedRect::excludeLogicalEdges(bool isHorizontal, bool excludeLogicalLeftEdge, bool excludeLogicalRightEdge)
162{
163 m_radii.excludeLogicalEdges(isHorizontal, excludeLogicalLeftEdge, excludeLogicalRightEdge);
164}
165
166bool RoundedRect::isRenderable() const
167{
168 return m_radii.topLeft().width() + m_radii.topRight().width() <= m_rect.width()
169 && m_radii.bottomLeft().width() + m_radii.bottomRight().width() <= m_rect.width()
170 && m_radii.topLeft().height() + m_radii.bottomLeft().height() <= m_rect.height()
171 && m_radii.topRight().height() + m_radii.bottomRight().height() <= m_rect.height();
172}
173
174void RoundedRect::adjustRadii()
175{
176 int maxRadiusWidth = std::max(m_radii.topLeft().width() + m_radii.topRight().width(), m_radii.bottomLeft().width() + m_radii.bottomRight().width());
177 int maxRadiusHeight = std::max(m_radii.topLeft().height() + m_radii.bottomLeft().height(), m_radii.topRight().height() + m_radii.bottomRight().height());
178
179 if (maxRadiusWidth <= 0 || maxRadiusHeight <= 0) {
180 m_radii.scale(0.0f);
181 return;
182 }
183 float widthRatio = static_cast<float>(m_rect.width()) / maxRadiusWidth;
184 float heightRatio = static_cast<float>(m_rect.height()) / maxRadiusHeight;
185 m_radii.scale(widthRatio < heightRatio ? widthRatio : heightRatio);
186}
187
188bool RoundedRect::intersectsQuad(const FloatQuad& quad) const
189{
190 FloatRect rect(m_rect);
191 if (!quad.intersectsRect(rect))
192 return false;
193
194 const LayoutSize& topLeft = m_radii.topLeft();
195 if (!topLeft.isEmpty()) {
196 FloatRect rect(m_rect.x(), m_rect.y(), topLeft.width(), topLeft.height());
197 if (quad.intersectsRect(rect)) {
198 FloatPoint center(m_rect.x() + topLeft.width(), m_rect.y() + topLeft.height());
199 FloatSize size(topLeft.width(), topLeft.height());
200 if (!quad.intersectsEllipse(center, size))
201 return false;
202 }
203 }
204
205 const LayoutSize& topRight = m_radii.topRight();
206 if (!topRight.isEmpty()) {
207 FloatRect rect(m_rect.maxX() - topRight.width(), m_rect.y(), topRight.width(), topRight.height());
208 if (quad.intersectsRect(rect)) {
209 FloatPoint center(m_rect.maxX() - topRight.width(), m_rect.y() + topRight.height());
210 FloatSize size(topRight.width(), topRight.height());
211 if (!quad.intersectsEllipse(center, size))
212 return false;
213 }
214 }
215
216 const LayoutSize& bottomLeft = m_radii.bottomLeft();
217 if (!bottomLeft.isEmpty()) {
218 FloatRect rect(m_rect.x(), m_rect.maxY() - bottomLeft.height(), bottomLeft.width(), bottomLeft.height());
219 if (quad.intersectsRect(rect)) {
220 FloatPoint center(m_rect.x() + bottomLeft.width(), m_rect.maxY() - bottomLeft.height());
221 FloatSize size(bottomLeft.width(), bottomLeft.height());
222 if (!quad.intersectsEllipse(center, size))
223 return false;
224 }
225 }
226
227 const LayoutSize& bottomRight = m_radii.bottomRight();
228 if (!bottomRight.isEmpty()) {
229 FloatRect rect(m_rect.maxX() - bottomRight.width(), m_rect.maxY() - bottomRight.height(), bottomRight.width(), bottomRight.height());
230 if (quad.intersectsRect(rect)) {
231 FloatPoint center(m_rect.maxX() - bottomRight.width(), m_rect.maxY() - bottomRight.height());
232 FloatSize size(bottomRight.width(), bottomRight.height());
233 if (!quad.intersectsEllipse(center, size))
234 return false;
235 }
236 }
237
238 return true;
239}
240
241bool RoundedRect::contains(const LayoutRect& otherRect) const
242{
243 if (!rect().contains(otherRect) || !isRenderable())
244 return false;
245
246 const LayoutSize& topLeft = m_radii.topLeft();
247 if (!topLeft.isEmpty()) {
248 FloatPoint center = { m_rect.x() + topLeft.width(), m_rect.y() + topLeft.height() };
249 if (otherRect.x() <= center.x() && otherRect.y() <= center.y()) {
250 if (!ellipseContainsPoint(center, topLeft, otherRect.minXMinYCorner()))
251 return false;
252 }
253 }
254
255 const LayoutSize& topRight = m_radii.topRight();
256 if (!topRight.isEmpty()) {
257 FloatPoint center = { m_rect.maxX() - topRight.width(), m_rect.y() + topRight.height() };
258 if (otherRect.maxX() >= center.x() && otherRect.y() <= center.y()) {
259 if (!ellipseContainsPoint(center, topRight, otherRect.maxXMinYCorner()))
260 return false;
261 }
262 }
263
264 const LayoutSize& bottomLeft = m_radii.bottomLeft();
265 if (!bottomLeft.isEmpty()) {
266 FloatPoint center = { m_rect.x() + bottomLeft.width(), m_rect.maxY() - bottomLeft.height() };
267 if (otherRect.x() <= center.x() && otherRect.maxY() >= center.y()) {
268 if (!ellipseContainsPoint(center, bottomLeft, otherRect.minXMaxYCorner()))
269 return false;
270 }
271 }
272
273 const LayoutSize& bottomRight = m_radii.bottomRight();
274 if (!bottomRight.isEmpty()) {
275 FloatPoint center = { m_rect.maxX() - bottomRight.width(), m_rect.maxY() - bottomRight.height() };
276 if (otherRect.maxX() >= center.x() && otherRect.maxY() >= center.y()) {
277 if (!ellipseContainsPoint(center, bottomRight, otherRect.maxXMaxYCorner()))
278 return false;
279 }
280 }
281
282 return true;
283}
284
285FloatRoundedRect RoundedRect::pixelSnappedRoundedRectForPainting(float deviceScaleFactor) const
286{
287 LayoutRect originalRect = rect();
288 if (originalRect.isEmpty())
289 return FloatRoundedRect(originalRect, radii());
290
291 FloatRect pixelSnappedRect = snapRectToDevicePixels(originalRect, deviceScaleFactor);
292
293 if (!isRenderable())
294 return FloatRoundedRect(pixelSnappedRect, radii());
295
296 // Snapping usually does not alter size, but when it does, we need to make sure that the final rect is still renderable by distributing the size delta proportionally.
297 FloatRoundedRect::Radii adjustedRadii = radii();
298 adjustedRadii.scale(pixelSnappedRect.width() / originalRect.width().toFloat(), pixelSnappedRect.height() / originalRect.height().toFloat());
299 FloatRoundedRect snappedRoundedRect = FloatRoundedRect(pixelSnappedRect, adjustedRadii);
300 if (!snappedRoundedRect.isRenderable()) {
301 // Floating point mantissa overflow can produce a non-renderable rounded rect.
302 adjustedRadii.shrink(1 / deviceScaleFactor);
303 snappedRoundedRect.setRadii(adjustedRadii);
304 }
305 ASSERT(snappedRoundedRect.isRenderable());
306 return snappedRoundedRect;
307}
308
309Region approximateAsRegion(const RoundedRect& roundedRect, unsigned stepLength)
310{
311 Region region;
312
313 auto& rect = roundedRect.rect();
314 region.unite(enclosingIntRect(rect));
315
316 if (!roundedRect.isRounded())
317 return region;
318
319 auto& radii = roundedRect.radii();
320
321 auto makeIntRect = [] (LayoutPoint a, LayoutPoint b) {
322 return enclosingIntRect(LayoutRect {
323 LayoutPoint { std::min(a.x(), b.x()), std::min(a.y(), b.y()) },
324 LayoutPoint { std::max(a.x(), b.x()), std::max(a.y(), b.y()) }
325 });
326 };
327
328 auto subtractCornerRects = [&] (LayoutPoint corner, LayoutPoint ellipsisCenter, LayoutSize axes, double fromAngle) {
329 double toAngle = fromAngle + piDouble / 2;
330
331 // Substract more rects for longer, more rounded arcs.
332 auto arcLengthFactor = roundToInt(std::min(axes.width(), axes.height()));
333 auto count = (arcLengthFactor + (stepLength / 2)) / stepLength;
334
335 for (auto i = 0u; i < count; ++i) {
336 auto angle = fromAngle + (i + 1) * (toAngle - fromAngle) / (count + 1);
337 auto ellipsisPoint = LayoutPoint { axes.width() * cos(angle), axes.height() * sin(angle) };
338 auto cornerRect = makeIntRect(corner, ellipsisCenter + ellipsisPoint);
339 region.subtract(cornerRect);
340 }
341 };
342
343 {
344 auto corner = rect.maxXMaxYCorner();
345 auto axes = radii.bottomRight();
346 auto ellipsisCenter = LayoutPoint(corner.x() - axes.width(), corner.y() - axes.height());
347 subtractCornerRects(corner, ellipsisCenter, axes, 0);
348 }
349
350 {
351 auto corner = rect.minXMaxYCorner();
352 auto axes = radii.bottomLeft();
353 auto ellipsisCenter = LayoutPoint(corner.x() + axes.width(), corner.y() - axes.height());
354 subtractCornerRects(corner, ellipsisCenter, axes, piDouble / 2);
355 }
356
357 {
358 auto corner = rect.minXMinYCorner();
359 auto axes = radii.topLeft();
360 auto ellipsisCenter = LayoutPoint(corner.x() + axes.width(), corner.y() + axes.height());
361 subtractCornerRects(corner, ellipsisCenter, axes, piDouble);
362 }
363
364 {
365 auto corner = rect.maxXMinYCorner();
366 auto axes = radii.topRight();
367 auto ellipsisCenter = LayoutPoint(corner.x() - axes.width(), corner.y() + axes.height());
368 subtractCornerRects(corner, ellipsisCenter, axes, piDouble * 3 / 2);
369 }
370
371 return region;
372}
373
374} // namespace WebCore
375