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