1/*
2 * Copyright (C) 2003, 2006 Apple Inc. All rights reserved.
3 * 2006 Rob Buis <buis@kde.org>
4 * Copyright (C) 2007 Eric Seidel <eric@webkit.org>
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 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28
29#include "config.h"
30#include "Path.h"
31
32#include "FloatPoint.h"
33#include "FloatRect.h"
34#include "FloatRoundedRect.h"
35#include "PathTraversalState.h"
36#include "RoundedRect.h"
37#include <math.h>
38#include <wtf/MathExtras.h>
39#include <wtf/text/TextStream.h>
40
41namespace WebCore {
42
43#if !USE(DIRECT2D)
44float Path::length() const
45{
46 PathTraversalState traversalState(PathTraversalState::Action::TotalLength);
47
48 apply([&traversalState](const PathElement& element) {
49 traversalState.processPathElement(element);
50 });
51
52 return traversalState.totalLength();
53}
54#endif
55
56PathTraversalState Path::traversalStateAtLength(float length, bool& success) const
57{
58 PathTraversalState traversalState(PathTraversalState::Action::VectorAtLength, length);
59
60 apply([&traversalState](const PathElement& element) {
61 traversalState.processPathElement(element);
62 });
63
64 success = traversalState.success();
65 return traversalState;
66}
67
68FloatPoint Path::pointAtLength(float length, bool& success) const
69{
70 return traversalStateAtLength(length, success).current();
71}
72
73float Path::normalAngleAtLength(float length, bool& success) const
74{
75 return traversalStateAtLength(length, success).normalAngle();
76}
77
78void Path::addRoundedRect(const FloatRect& rect, const FloatSize& roundingRadii, RoundedRectStrategy strategy)
79{
80 if (rect.isEmpty())
81 return;
82
83 FloatSize radius(roundingRadii);
84 FloatSize halfSize = rect.size() / 2;
85
86 // Apply the SVG corner radius constraints, per the rect section of the SVG shapes spec: if
87 // one of rx,ry is negative, then the other corner radius value is used. If both values are
88 // negative then rx = ry = 0. If rx is greater than half of the width of the rectangle
89 // then set rx to half of the width; ry is handled similarly.
90
91 if (radius.width() < 0)
92 radius.setWidth((radius.height() < 0) ? 0 : radius.height());
93
94 if (radius.height() < 0)
95 radius.setHeight(radius.width());
96
97 if (radius.width() > halfSize.width())
98 radius.setWidth(halfSize.width());
99
100 if (radius.height() > halfSize.height())
101 radius.setHeight(halfSize.height());
102
103 addRoundedRect(FloatRoundedRect(rect, radius, radius, radius, radius), strategy);
104}
105
106void Path::addRoundedRect(const FloatRoundedRect& r, RoundedRectStrategy strategy)
107{
108 if (r.isEmpty())
109 return;
110
111 const FloatRoundedRect::Radii& radii = r.radii();
112 const FloatRect& rect = r.rect();
113
114 if (!r.isRenderable()) {
115 // If all the radii cannot be accommodated, return a rect.
116 addRect(rect);
117 return;
118 }
119
120 if (strategy == PreferNativeRoundedRect) {
121#if USE(CG) || USE(DIRECT2D)
122 platformAddPathForRoundedRect(rect, radii.topLeft(), radii.topRight(), radii.bottomLeft(), radii.bottomRight());
123 return;
124#endif
125 }
126
127 addBeziersForRoundedRect(rect, radii.topLeft(), radii.topRight(), radii.bottomLeft(), radii.bottomRight());
128}
129
130void Path::addRoundedRect(const RoundedRect& r)
131{
132 addRoundedRect(FloatRoundedRect(r));
133}
134
135void Path::addBeziersForRoundedRect(const FloatRect& rect, const FloatSize& topLeftRadius, const FloatSize& topRightRadius, const FloatSize& bottomLeftRadius, const FloatSize& bottomRightRadius)
136{
137 moveTo(FloatPoint(rect.x() + topLeftRadius.width(), rect.y()));
138
139 addLineTo(FloatPoint(rect.maxX() - topRightRadius.width(), rect.y()));
140 if (topRightRadius.width() > 0 || topRightRadius.height() > 0)
141 addBezierCurveTo(FloatPoint(rect.maxX() - topRightRadius.width() * circleControlPoint(), rect.y()),
142 FloatPoint(rect.maxX(), rect.y() + topRightRadius.height() * circleControlPoint()),
143 FloatPoint(rect.maxX(), rect.y() + topRightRadius.height()));
144 addLineTo(FloatPoint(rect.maxX(), rect.maxY() - bottomRightRadius.height()));
145 if (bottomRightRadius.width() > 0 || bottomRightRadius.height() > 0)
146 addBezierCurveTo(FloatPoint(rect.maxX(), rect.maxY() - bottomRightRadius.height() * circleControlPoint()),
147 FloatPoint(rect.maxX() - bottomRightRadius.width() * circleControlPoint(), rect.maxY()),
148 FloatPoint(rect.maxX() - bottomRightRadius.width(), rect.maxY()));
149 addLineTo(FloatPoint(rect.x() + bottomLeftRadius.width(), rect.maxY()));
150 if (bottomLeftRadius.width() > 0 || bottomLeftRadius.height() > 0)
151 addBezierCurveTo(FloatPoint(rect.x() + bottomLeftRadius.width() * circleControlPoint(), rect.maxY()),
152 FloatPoint(rect.x(), rect.maxY() - bottomLeftRadius.height() * circleControlPoint()),
153 FloatPoint(rect.x(), rect.maxY() - bottomLeftRadius.height()));
154 addLineTo(FloatPoint(rect.x(), rect.y() + topLeftRadius.height()));
155 if (topLeftRadius.width() > 0 || topLeftRadius.height() > 0)
156 addBezierCurveTo(FloatPoint(rect.x(), rect.y() + topLeftRadius.height() * circleControlPoint()),
157 FloatPoint(rect.x() + topLeftRadius.width() * circleControlPoint(), rect.y()),
158 FloatPoint(rect.x() + topLeftRadius.width(), rect.y()));
159
160 closeSubpath();
161}
162
163#if !USE(CG) && !USE(DIRECT2D)
164Path Path::polygonPathFromPoints(const Vector<FloatPoint>& points)
165{
166 Path path;
167 if (points.size() < 2)
168 return path;
169
170 path.moveTo(points[0]);
171 for (size_t i = 1; i < points.size(); ++i)
172 path.addLineTo(points[i]);
173
174 path.closeSubpath();
175 return path;
176}
177
178FloatRect Path::fastBoundingRect() const
179{
180 return boundingRect();
181}
182#endif
183
184#ifndef NDEBUG
185void Path::dump() const
186{
187 TextStream stream;
188 stream << *this;
189 WTFLogAlways("%s", stream.release().utf8().data());
190}
191#endif
192
193TextStream& operator<<(TextStream& stream, const Path& path)
194{
195 bool isFirst = true;
196 path.apply([&stream, &isFirst](const PathElement& element) {
197 if (!isFirst)
198 stream << ", ";
199 isFirst = false;
200 switch (element.type) {
201 case PathElementMoveToPoint: // The points member will contain 1 value.
202 stream << "move to " << element.points[0];
203 break;
204 case PathElementAddLineToPoint: // The points member will contain 1 value.
205 stream << "add line to " << element.points[0];
206 break;
207 case PathElementAddQuadCurveToPoint: // The points member will contain 2 values.
208 stream << "add quad curve to " << element.points[0] << " " << element.points[1];
209 break;
210 case PathElementAddCurveToPoint: // The points member will contain 3 values.
211 stream << "add curve to " << element.points[0] << " " << element.points[1] << " " << element.points[2];
212 break;
213 case PathElementCloseSubpath: // The points member will contain no values.
214 stream << "close subpath";
215 break;
216 }
217 });
218
219 return stream;
220}
221
222}
223