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 "config.h"
31#include "BoxShape.h"
32
33#include "RenderBox.h"
34#include <wtf/MathExtras.h>
35
36namespace WebCore {
37
38static inline LayoutUnit adjustRadiusForMarginBoxShape(LayoutUnit radius, LayoutUnit margin)
39{
40    // This algorithm is defined in the CSS Shapes specifcation
41    if (!margin)
42        return radius;
43
44    LayoutUnit ratio = radius / margin;
45    if (ratio < 1)
46        return radius + (margin * (1 + pow(ratio - 1, 3)));
47
48    return radius + margin;
49}
50
51static inline LayoutSize computeMarginBoxShapeRadius(const LayoutSize& radius, const LayoutSize& adjacentMargins)
52{
53    return LayoutSize(adjustRadiusForMarginBoxShape(radius.width(), adjacentMargins.width()),
54        adjustRadiusForMarginBoxShape(radius.height(), adjacentMargins.height()));
55}
56
57static inline RoundedRect::Radii computeMarginBoxShapeRadii(const RoundedRect::Radii& radii, const RenderBox& renderer)
58{
59    return RoundedRect::Radii(computeMarginBoxShapeRadius(radii.topLeft(), LayoutSize(renderer.marginLeft(), renderer.marginTop())),
60        computeMarginBoxShapeRadius(radii.topRight(), LayoutSize(renderer.marginRight(), renderer.marginTop())),
61        computeMarginBoxShapeRadius(radii.bottomLeft(), LayoutSize(renderer.marginLeft(), renderer.marginBottom())),
62        computeMarginBoxShapeRadius(radii.bottomRight(), LayoutSize(renderer.marginRight(), renderer.marginBottom())));
63}
64
65RoundedRect computeRoundedRectForBoxShape(CSSBoxType box, const RenderBox& renderer)
66{
67    const RenderStyle& style = renderer.style();
68    switch (box) {
69    case MarginBox: {
70        if (!style.hasBorderRadius())
71            return RoundedRect(renderer.marginBoxRect(), RoundedRect::Radii());
72
73        LayoutRect marginBox = renderer.marginBoxRect();
74        RoundedRect::Radii radii = computeMarginBoxShapeRadii(style.getRoundedBorderFor(renderer.borderBoxRect(), &(renderer.view())).radii(), renderer);
75        radii.scale(calcBorderRadiiConstraintScaleFor(marginBox, radii));
76        return RoundedRect(marginBox, radii);
77    }
78    case PaddingBox:
79        return style.getRoundedInnerBorderFor(renderer.borderBoxRect());
80    case ContentBox:
81        return style.getRoundedInnerBorderFor(renderer.borderBoxRect(),
82            renderer.paddingTop() + renderer.borderTop(), renderer.paddingBottom() + renderer.borderBottom(),
83            renderer.paddingLeft() + renderer.borderLeft(), renderer.paddingRight() + renderer.borderRight());
84    // fill, stroke, view-box compute to border-box for HTML elements.
85    case BorderBox:
86    case Fill:
87    case Stroke:
88    case ViewBox:
89    case BoxMissing:
90        return style.getRoundedBorderFor(renderer.borderBoxRect(), &(renderer.view()));
91    }
92
93    ASSERT_NOT_REACHED();
94    return style.getRoundedBorderFor(renderer.borderBoxRect(), &(renderer.view()));
95}
96
97LayoutRect BoxShape::shapeMarginLogicalBoundingBox() const
98{
99    FloatRect marginBounds(m_bounds.rect());
100    if (shapeMargin() > 0)
101        marginBounds.inflate(shapeMargin());
102    return static_cast<LayoutRect>(marginBounds);
103}
104
105FloatRoundedRect BoxShape::shapeMarginBounds() const
106{
107    FloatRoundedRect marginBounds(m_bounds);
108    if (shapeMargin() > 0) {
109        marginBounds.inflate(shapeMargin());
110        marginBounds.expandRadii(shapeMargin());
111    }
112    return marginBounds;
113}
114
115void BoxShape::getExcludedIntervals(LayoutUnit logicalTop, LayoutUnit logicalHeight, SegmentList& result) const
116{
117    const FloatRoundedRect& marginBounds = shapeMarginBounds();
118    if (marginBounds.isEmpty() || !lineOverlapsShapeMarginBounds(logicalTop, logicalHeight))
119        return;
120
121    float y1 = logicalTop;
122    float y2 = logicalTop + logicalHeight;
123    const FloatRect& rect = marginBounds.rect();
124
125    if (!marginBounds.isRounded()) {
126        result.append(LineSegment(rect.x(), rect.maxX()));
127        return;
128    }
129
130    float topCornerMaxY = std::max<float>(marginBounds.topLeftCorner().maxY(), marginBounds.topRightCorner().maxY());
131    float bottomCornerMinY = std::min<float>(marginBounds.bottomLeftCorner().y(), marginBounds.bottomRightCorner().y());
132
133    if (topCornerMaxY <= bottomCornerMinY && y1 <= topCornerMaxY && y2 >= bottomCornerMinY) {
134        result.append(LineSegment(rect.x(), rect.maxX()));
135        return;
136    }
137
138    float x1 = rect.maxX();
139    float x2 = rect.x();
140    float minXIntercept;
141    float maxXIntercept;
142
143    if (y1 <= marginBounds.topLeftCorner().maxY() && y2 >= marginBounds.bottomLeftCorner().y())
144        x1 = rect.x();
145
146    if (y1 <= marginBounds.topRightCorner().maxY() && y2 >= marginBounds.bottomRightCorner().y())
147        x2 = rect.maxX();
148
149    if (marginBounds.xInterceptsAtY(y1, minXIntercept, maxXIntercept)) {
150        x1 = std::min<float>(x1, minXIntercept);
151        x2 = std::max<float>(x2, maxXIntercept);
152    }
153
154    if (marginBounds.xInterceptsAtY(y2, minXIntercept, maxXIntercept)) {
155        x1 = std::min<float>(x1, minXIntercept);
156        x2 = std::max<float>(x2, maxXIntercept);
157    }
158
159    ASSERT(x2 >= x1);
160    result.append(LineSegment(x1, x2));
161}
162
163void BoxShape::buildDisplayPaths(DisplayPaths& paths) const
164{
165    paths.shape.addRoundedRect(m_bounds, Path::PreferBezierRoundedRect);
166    if (shapeMargin())
167        paths.marginShape.addRoundedRect(shapeMarginBounds(), Path::PreferBezierRoundedRect);
168}
169
170} // namespace WebCore
171