60a451f9ef9a48f6390e3111934a96b97cecb60d
[WebKit-https.git] / Source / WebCore / platform / graphics / RoundedRect.cpp
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
36 #include <algorithm>
37
38 namespace WebCore {
39
40 bool RoundedRect::Radii::isZero() const
41 {
42     return m_topLeft.isZero() && m_topRight.isZero() && m_bottomLeft.isZero() && m_bottomRight.isZero();
43 }
44
45 void RoundedRect::Radii::scale(float factor)
46 {
47     if (factor == 1)
48         return;
49
50     // If either radius on a corner becomes zero, reset both radii on that corner.
51     m_topLeft.scale(factor);
52     if (!m_topLeft.width() || !m_topLeft.height())
53         m_topLeft = LayoutSize();
54     m_topRight.scale(factor);
55     if (!m_topRight.width() || !m_topRight.height())
56         m_topRight = LayoutSize();
57     m_bottomLeft.scale(factor);
58     if (!m_bottomLeft.width() || !m_bottomLeft.height())
59         m_bottomLeft = LayoutSize();
60     m_bottomRight.scale(factor);
61     if (!m_bottomRight.width() || !m_bottomRight.height())
62         m_bottomRight = LayoutSize();
63 }
64
65 void RoundedRect::Radii::expand(const LayoutUnit& topWidth, const LayoutUnit& bottomWidth, const LayoutUnit& leftWidth, const LayoutUnit& rightWidth)
66 {
67     if (m_topLeft.width() > 0 && m_topLeft.height() > 0) {
68         m_topLeft.setWidth(std::max<LayoutUnit>(0, m_topLeft.width() + leftWidth));
69         m_topLeft.setHeight(std::max<LayoutUnit>(0, m_topLeft.height() + topWidth));
70     }
71     if (m_topRight.width() > 0 && m_topRight.height() > 0) {
72         m_topRight.setWidth(std::max<LayoutUnit>(0, m_topRight.width() + rightWidth));
73         m_topRight.setHeight(std::max<LayoutUnit>(0, m_topRight.height() + topWidth));
74     }
75     if (m_bottomLeft.width() > 0 && m_bottomLeft.height() > 0) {
76         m_bottomLeft.setWidth(std::max<LayoutUnit>(0, m_bottomLeft.width() + leftWidth));
77         m_bottomLeft.setHeight(std::max<LayoutUnit>(0, m_bottomLeft.height() + bottomWidth));
78     }
79     if (m_bottomRight.width() > 0 && m_bottomRight.height() > 0) {
80         m_bottomRight.setWidth(std::max<LayoutUnit>(0, m_bottomRight.width() + rightWidth));
81         m_bottomRight.setHeight(std::max<LayoutUnit>(0, m_bottomRight.height() + bottomWidth));
82     }
83 }
84
85 void RoundedRect::inflateWithRadii(const LayoutUnit& size)
86 {
87     LayoutRect old = m_rect;
88
89     m_rect.inflate(size);
90     // Considering the inflation factor of shorter size to scale the radii seems appropriate here
91     float factor;
92     if (m_rect.width() < m_rect.height())
93         factor = old.width() ? (float)m_rect.width() / old.width() : int(0);
94     else
95         factor = old.height() ? (float)m_rect.height() / old.height() : int(0);
96
97     m_radii.scale(factor);
98 }
99
100 void RoundedRect::Radii::includeLogicalEdges(const RoundedRect::Radii& edges, bool isHorizontal, bool includeLogicalLeftEdge, bool includeLogicalRightEdge)
101 {
102     if (includeLogicalLeftEdge) {
103         if (isHorizontal)
104             m_bottomLeft = edges.bottomLeft();
105         else
106             m_topRight = edges.topRight();
107         m_topLeft = edges.topLeft();
108     }
109
110     if (includeLogicalRightEdge) {
111         if (isHorizontal)
112             m_topRight = edges.topRight();
113         else
114             m_bottomLeft = edges.bottomLeft();
115         m_bottomRight = edges.bottomRight();
116     }
117 }
118
119 void RoundedRect::Radii::excludeLogicalEdges(bool isHorizontal, bool excludeLogicalLeftEdge, bool excludeLogicalRightEdge)
120 {
121     if (excludeLogicalLeftEdge) {
122         if (isHorizontal)
123             m_bottomLeft = IntSize();
124         else
125             m_topRight = IntSize();
126         m_topLeft = IntSize();
127     }
128         
129     if (excludeLogicalRightEdge) {
130         if (isHorizontal)
131             m_topRight = IntSize();
132         else
133             m_bottomLeft = IntSize();
134         m_bottomRight = IntSize();
135     }
136 }
137
138 RoundedRect::RoundedRect(const LayoutUnit& x, const LayoutUnit& y, const LayoutUnit& width, const LayoutUnit& height)
139     : m_rect(x, y, width, height)
140 {
141 }
142
143 RoundedRect::RoundedRect(const LayoutRect& rect, const Radii& radii)
144     : m_rect(rect)
145     , m_radii(radii)
146 {
147 }
148
149 RoundedRect::RoundedRect(const LayoutRect& rect, const LayoutSize& topLeft, const LayoutSize& topRight, const LayoutSize& bottomLeft, const LayoutSize& bottomRight)
150     : m_rect(rect)
151     , m_radii(topLeft, topRight, bottomLeft, bottomRight)
152 {
153 }
154
155 void RoundedRect::includeLogicalEdges(const Radii& edges, bool isHorizontal, bool includeLogicalLeftEdge, bool includeLogicalRightEdge)
156 {
157     m_radii.includeLogicalEdges(edges, isHorizontal, includeLogicalLeftEdge, includeLogicalRightEdge);
158 }
159
160 void RoundedRect::excludeLogicalEdges(bool isHorizontal, bool excludeLogicalLeftEdge, bool excludeLogicalRightEdge)
161 {
162     m_radii.excludeLogicalEdges(isHorizontal, excludeLogicalLeftEdge, excludeLogicalRightEdge);
163 }
164
165 bool RoundedRect::isRenderable() const
166 {
167     return m_radii.topLeft().width() + m_radii.topRight().width() <= m_rect.width()
168         && m_radii.bottomLeft().width() + m_radii.bottomRight().width() <= m_rect.width()
169         && m_radii.topLeft().height() + m_radii.bottomLeft().height() <= m_rect.height()
170         && m_radii.topRight().height() + m_radii.bottomRight().height() <= m_rect.height();
171 }
172
173 void RoundedRect::adjustRadii()
174 {
175     int maxRadiusWidth = std::max(m_radii.topLeft().width() + m_radii.topRight().width(), m_radii.bottomLeft().width() + m_radii.bottomRight().width());
176     int maxRadiusHeight = std::max(m_radii.topLeft().height() + m_radii.bottomLeft().height(), m_radii.topRight().height() + m_radii.bottomRight().height());
177
178     if (maxRadiusWidth <= 0 || maxRadiusHeight <= 0) {
179         m_radii.scale(0.0f);
180         return;
181     }
182     float widthRatio = static_cast<float>(m_rect.width()) / maxRadiusWidth;
183     float heightRatio = static_cast<float>(m_rect.height()) / maxRadiusHeight;
184     m_radii.scale(widthRatio < heightRatio ? widthRatio : heightRatio);
185 }
186
187 bool RoundedRect::intersectsQuad(const FloatQuad& quad) const
188 {
189     FloatRect rect(m_rect);
190     if (!quad.intersectsRect(rect))
191         return false;
192
193     const LayoutSize& topLeft = m_radii.topLeft();
194     if (!topLeft.isEmpty()) {
195         FloatRect rect(m_rect.x(), m_rect.y(), topLeft.width(), topLeft.height());
196         if (quad.intersectsRect(rect)) {
197             FloatPoint center(m_rect.x() + topLeft.width(), m_rect.y() + topLeft.height());
198             FloatSize size(topLeft.width(), topLeft.height());
199             if (!quad.intersectsEllipse(center, size))
200                 return false;
201         }
202     }
203
204     const LayoutSize& topRight = m_radii.topRight();
205     if (!topRight.isEmpty()) {
206         FloatRect rect(m_rect.maxX() - topRight.width(), m_rect.y(), topRight.width(), topRight.height());
207         if (quad.intersectsRect(rect)) {
208             FloatPoint center(m_rect.maxX() - topRight.width(), m_rect.y() + topRight.height());
209             FloatSize size(topRight.width(), topRight.height());
210             if (!quad.intersectsEllipse(center, size))
211                 return false;
212         }
213     }
214
215     const LayoutSize& bottomLeft = m_radii.bottomLeft();
216     if (!bottomLeft.isEmpty()) {
217         FloatRect rect(m_rect.x(), m_rect.maxY() - bottomLeft.height(), bottomLeft.width(), bottomLeft.height());
218         if (quad.intersectsRect(rect)) {
219             FloatPoint center(m_rect.x() + bottomLeft.width(), m_rect.maxY() - bottomLeft.height());
220             FloatSize size(bottomLeft.width(), bottomLeft.height());
221             if (!quad.intersectsEllipse(center, size))
222                 return false;
223         }
224     }
225
226     const LayoutSize& bottomRight = m_radii.bottomRight();
227     if (!bottomRight.isEmpty()) {
228         FloatRect rect(m_rect.maxX() - bottomRight.width(), m_rect.maxY() - bottomRight.height(), bottomRight.width(), bottomRight.height());
229         if (quad.intersectsRect(rect)) {
230             FloatPoint center(m_rect.maxX() - bottomRight.width(), m_rect.maxY() - bottomRight.height());
231             FloatSize size(bottomRight.width(), bottomRight.height());
232             if (!quad.intersectsEllipse(center, size))
233                 return false;
234         }
235     }
236
237     return true;
238 }
239
240 bool RoundedRect::contains(const LayoutRect& otherRect) const
241 {
242     if (!rect().contains(otherRect))
243         return false;
244
245     const LayoutSize& topLeft = m_radii.topLeft();
246     if (!topLeft.isEmpty()) {
247         FloatPoint center = { m_rect.x() + topLeft.width(), m_rect.y() + topLeft.height() };
248         if (otherRect.x() <= center.x() && otherRect.y() <= center.y()) {
249             if (!ellipseContainsPoint(center, topLeft, otherRect.location()))
250                 return false;
251         }
252     }
253
254     const LayoutSize& topRight = m_radii.topRight();
255     if (!topRight.isEmpty()) {
256         FloatPoint center = { m_rect.maxX() - topRight.width(), m_rect.y() + topRight.height() };
257         if (otherRect.maxX() >= center.x() && otherRect.y() <= center.y()) {
258             if (!ellipseContainsPoint(center, topRight, otherRect.location()))
259                 return false;
260         }
261     }
262
263     const LayoutSize& bottomLeft = m_radii.bottomLeft();
264     if (!bottomLeft.isEmpty()) {
265         FloatPoint center = { m_rect.x() + bottomLeft.width(), m_rect.maxY() - bottomLeft.height() };
266         if (otherRect.maxX() >= center.x() && otherRect.maxY() >= center.y()) {
267             if (!ellipseContainsPoint(center, bottomLeft, otherRect.location()))
268                 return false;
269         }
270     }
271
272     const LayoutSize& bottomRight = m_radii.bottomRight();
273     if (!bottomRight.isEmpty()) {
274         FloatPoint center = { m_rect.maxX() - bottomRight.width(), m_rect.maxY() - bottomRight.height() };
275         if (otherRect.x() <= center.x() && otherRect.maxY() >= center.y()) {
276             if (!ellipseContainsPoint(center, bottomRight, otherRect.location()))
277                 return false;
278         }
279     }
280
281     return true;
282 }
283
284 FloatRoundedRect RoundedRect::pixelSnappedRoundedRectForPainting(float deviceScaleFactor) const
285 {
286     LayoutRect originalRect = rect();
287     if (originalRect.isEmpty())
288         return FloatRoundedRect(originalRect, radii());
289
290     FloatRect pixelSnappedRect = snapRectToDevicePixels(originalRect, deviceScaleFactor);
291
292     if (!isRenderable())
293         return FloatRoundedRect(pixelSnappedRect, radii());
294
295     // 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.
296     FloatRoundedRect::Radii adjustedRadii = radii();
297     adjustedRadii.scale(pixelSnappedRect.width() / originalRect.width().toFloat(), pixelSnappedRect.height() / originalRect.height().toFloat());
298     FloatRoundedRect snappedRoundedRect = FloatRoundedRect(pixelSnappedRect, adjustedRadii);
299     if (!snappedRoundedRect.isRenderable()) {
300         // Floating point mantissa overflow can produce a non-renderable rounded rect.
301         adjustedRadii.shrink(1 / deviceScaleFactor);
302         snappedRoundedRect.setRadii(adjustedRadii);
303     }
304     ASSERT(snappedRoundedRect.isRenderable());
305     return snappedRoundedRect;
306 }
307
308 } // namespace WebCore