|
1 /* |
|
2 * Copyright (C) 2003, 2006 Apple Computer, 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 COMPUTER, 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 COMPUTER, 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 "PathTraversalState.h" |
|
35 #include <math.h> |
|
36 #include <wtf/MathExtras.h> |
|
37 |
|
38 const float QUARTER = 0.552f; // approximation of control point positions on a bezier |
|
39 // to simulate a quarter of a circle. |
|
40 namespace WebCore { |
|
41 |
|
42 static void pathLengthApplierFunction(void* info, const PathElement* element) |
|
43 { |
|
44 PathTraversalState& traversalState = *static_cast<PathTraversalState*>(info); |
|
45 if (traversalState.m_success) |
|
46 return; |
|
47 traversalState.m_previous = traversalState.m_current; |
|
48 FloatPoint* points = element->points; |
|
49 float segmentLength = 0.0f; |
|
50 switch (element->type) { |
|
51 case PathElementMoveToPoint: |
|
52 segmentLength = traversalState.moveTo(points[0]); |
|
53 break; |
|
54 case PathElementAddLineToPoint: |
|
55 segmentLength = traversalState.lineTo(points[0]); |
|
56 break; |
|
57 case PathElementAddQuadCurveToPoint: |
|
58 segmentLength = traversalState.quadraticBezierTo(points[0], points[1]); |
|
59 break; |
|
60 case PathElementAddCurveToPoint: |
|
61 segmentLength = traversalState.cubicBezierTo(points[0], points[1], points[2]); |
|
62 break; |
|
63 case PathElementCloseSubpath: |
|
64 segmentLength = traversalState.closeSubpath(); |
|
65 break; |
|
66 } |
|
67 traversalState.m_totalLength += segmentLength; |
|
68 if ((traversalState.m_action == PathTraversalState::TraversalPointAtLength || |
|
69 traversalState.m_action == PathTraversalState::TraversalNormalAngleAtLength) && |
|
70 (traversalState.m_totalLength >= traversalState.m_desiredLength)) { |
|
71 FloatSize change = traversalState.m_current - traversalState.m_previous; |
|
72 float slope = atan2f(change.height(), change.width()); |
|
73 |
|
74 if (traversalState.m_action == PathTraversalState::TraversalPointAtLength) { |
|
75 float offset = traversalState.m_desiredLength - traversalState.m_totalLength; |
|
76 traversalState.m_current.move(offset * cosf(slope), offset * sinf(slope)); |
|
77 } else { |
|
78 static const float rad2deg = 180.0f / piFloat; |
|
79 traversalState.m_normalAngle = slope * rad2deg; |
|
80 } |
|
81 |
|
82 traversalState.m_success = true; |
|
83 } |
|
84 } |
|
85 |
|
86 float Path::length() |
|
87 { |
|
88 PathTraversalState traversalState(PathTraversalState::TraversalTotalLength); |
|
89 apply(&traversalState, pathLengthApplierFunction); |
|
90 return traversalState.m_totalLength; |
|
91 } |
|
92 |
|
93 FloatPoint Path::pointAtLength(float length, bool& ok) |
|
94 { |
|
95 PathTraversalState traversalState(PathTraversalState::TraversalPointAtLength); |
|
96 traversalState.m_desiredLength = length; |
|
97 apply(&traversalState, pathLengthApplierFunction); |
|
98 ok = traversalState.m_success; |
|
99 return traversalState.m_current; |
|
100 } |
|
101 |
|
102 float Path::normalAngleAtLength(float length, bool& ok) |
|
103 { |
|
104 PathTraversalState traversalState(PathTraversalState::TraversalNormalAngleAtLength); |
|
105 traversalState.m_desiredLength = length; |
|
106 apply(&traversalState, pathLengthApplierFunction); |
|
107 ok = traversalState.m_success; |
|
108 return traversalState.m_normalAngle; |
|
109 } |
|
110 |
|
111 Path Path::createRoundedRectangle(const FloatRect& rectangle, const FloatSize& roundingRadii) |
|
112 { |
|
113 Path path; |
|
114 float x = rectangle.x(); |
|
115 float y = rectangle.y(); |
|
116 float width = rectangle.width(); |
|
117 float height = rectangle.height(); |
|
118 float rx = roundingRadii.width(); |
|
119 float ry = roundingRadii.height(); |
|
120 if (width <= 0.0f || height <= 0.0f) |
|
121 return path; |
|
122 |
|
123 float dx = rx, dy = ry; |
|
124 // If rx is greater than half of the width of the rectangle |
|
125 // then set rx to half of the width (required in SVG spec) |
|
126 if (dx > width * 0.5f) |
|
127 dx = width * 0.5f; |
|
128 |
|
129 // If ry is greater than half of the height of the rectangle |
|
130 // then set ry to half of the height (required in SVG spec) |
|
131 if (dy > height * 0.5f) |
|
132 dy = height * 0.5f; |
|
133 |
|
134 path.moveTo(FloatPoint(x + dx, y)); |
|
135 |
|
136 if (dx < width * 0.5f) |
|
137 path.addLineTo(FloatPoint(x + width - rx, y)); |
|
138 |
|
139 path.addBezierCurveTo(FloatPoint(x + width - dx * (1 - QUARTER), y), FloatPoint(x + width, y + dy * (1 - QUARTER)), FloatPoint(x + width, y + dy)); |
|
140 |
|
141 if (dy < height * 0.5) |
|
142 path.addLineTo(FloatPoint(x + width, y + height - dy)); |
|
143 |
|
144 path.addBezierCurveTo(FloatPoint(x + width, y + height - dy * (1 - QUARTER)), FloatPoint(x + width - dx * (1 - QUARTER), y + height), FloatPoint(x + width - dx, y + height)); |
|
145 |
|
146 if (dx < width * 0.5) |
|
147 path.addLineTo(FloatPoint(x + dx, y + height)); |
|
148 |
|
149 path.addBezierCurveTo(FloatPoint(x + dx * (1 - QUARTER), y + height), FloatPoint(x, y + height - dy * (1 - QUARTER)), FloatPoint(x, y + height - dy)); |
|
150 |
|
151 if (dy < height * 0.5) |
|
152 path.addLineTo(FloatPoint(x, y + dy)); |
|
153 |
|
154 path.addBezierCurveTo(FloatPoint(x, y + dy * (1 - QUARTER)), FloatPoint(x + dx * (1 - QUARTER), y), FloatPoint(x + dx, y)); |
|
155 |
|
156 path.closeSubpath(); |
|
157 |
|
158 return path; |
|
159 } |
|
160 |
|
161 Path Path::createRoundedRectangle(const FloatRect& rectangle, const FloatSize& topLeftRadius, const FloatSize& topRightRadius, const FloatSize& bottomLeftRadius, const FloatSize& bottomRightRadius) |
|
162 { |
|
163 Path path; |
|
164 |
|
165 float width = rectangle.width(); |
|
166 float height = rectangle.height(); |
|
167 if (width <= 0.0 || height <= 0.0) |
|
168 return path; |
|
169 |
|
170 if (width < topLeftRadius.width() + topRightRadius.width() |
|
171 || width < bottomLeftRadius.width() + bottomRightRadius.width() |
|
172 || height < topLeftRadius.height() + bottomLeftRadius.height() |
|
173 || height < topRightRadius.height() + bottomRightRadius.height()) |
|
174 // If all the radii cannot be accommodated, return a rect. |
|
175 return createRectangle(rectangle); |
|
176 |
|
177 float x = rectangle.x(); |
|
178 float y = rectangle.y(); |
|
179 |
|
180 path.moveTo(FloatPoint(x + topLeftRadius.width(), y)); |
|
181 |
|
182 path.addLineTo(FloatPoint(x + width - topRightRadius.width(), y)); |
|
183 |
|
184 path.addBezierCurveTo(FloatPoint(x + width - topRightRadius.width() * (1 - QUARTER), y), FloatPoint(x + width, y + topRightRadius.height() * (1 - QUARTER)), FloatPoint(x + width, y + topRightRadius.height())); |
|
185 |
|
186 path.addLineTo(FloatPoint(x + width, y + height - bottomRightRadius.height())); |
|
187 |
|
188 path.addBezierCurveTo(FloatPoint(x + width, y + height - bottomRightRadius.height() * (1 - QUARTER)), FloatPoint(x + width - bottomRightRadius.width() * (1 - QUARTER), y + height), FloatPoint(x + width - bottomRightRadius.width(), y + height)); |
|
189 |
|
190 path.addLineTo(FloatPoint(x + bottomLeftRadius.width(), y + height)); |
|
191 |
|
192 path.addBezierCurveTo(FloatPoint(x + bottomLeftRadius.width() * (1 - QUARTER), y + height), FloatPoint(x, y + height - bottomLeftRadius.height() * (1 - QUARTER)), FloatPoint(x, y + height - bottomLeftRadius.height())); |
|
193 |
|
194 path.addLineTo(FloatPoint(x, y + topLeftRadius.height())); |
|
195 |
|
196 path.addBezierCurveTo(FloatPoint(x, y + topLeftRadius.height() * (1 - QUARTER)), FloatPoint(x + topLeftRadius.width() * (1 - QUARTER), y), FloatPoint(x + topLeftRadius.width(), y)); |
|
197 |
|
198 path.closeSubpath(); |
|
199 |
|
200 return path; |
|
201 } |
|
202 |
|
203 Path Path::createRectangle(const FloatRect& rectangle) |
|
204 { |
|
205 Path path; |
|
206 float x = rectangle.x(); |
|
207 float y = rectangle.y(); |
|
208 float width = rectangle.width(); |
|
209 float height = rectangle.height(); |
|
210 if (width <= 0.0f || height <= 0.0f) |
|
211 return path; |
|
212 |
|
213 path.moveTo(FloatPoint(x, y)); |
|
214 path.addLineTo(FloatPoint(x + width, y)); |
|
215 path.addLineTo(FloatPoint(x + width, y + height)); |
|
216 path.addLineTo(FloatPoint(x, y + height)); |
|
217 path.closeSubpath(); |
|
218 |
|
219 return path; |
|
220 } |
|
221 |
|
222 Path Path::createEllipse(const FloatPoint& center, float rx, float ry) |
|
223 { |
|
224 float cx = center.x(); |
|
225 float cy = center.y(); |
|
226 Path path; |
|
227 if (rx <= 0.0f || ry <= 0.0f) |
|
228 return path; |
|
229 |
|
230 float x = cx; |
|
231 float y = cy; |
|
232 |
|
233 unsigned step = 0, num = 100; |
|
234 bool running = true; |
|
235 while (running) |
|
236 { |
|
237 if (step == num) |
|
238 { |
|
239 running = false; |
|
240 break; |
|
241 } |
|
242 |
|
243 float angle = static_cast<float>(step) / static_cast<float>(num) * 2.0f * piFloat; |
|
244 x = cx + cosf(angle) * rx; |
|
245 y = cy + sinf(angle) * ry; |
|
246 |
|
247 step++; |
|
248 if (step == 1) |
|
249 path.moveTo(FloatPoint(x, y)); |
|
250 else |
|
251 path.addLineTo(FloatPoint(x, y)); |
|
252 } |
|
253 |
|
254 path.closeSubpath(); |
|
255 |
|
256 return path; |
|
257 } |
|
258 |
|
259 Path Path::createCircle(const FloatPoint& center, float r) |
|
260 { |
|
261 return createEllipse(center, r, r); |
|
262 } |
|
263 |
|
264 Path Path::createLine(const FloatPoint& start, const FloatPoint& end) |
|
265 { |
|
266 Path path; |
|
267 if (start.x() == end.x() && start.y() == end.y()) |
|
268 return path; |
|
269 |
|
270 path.moveTo(start); |
|
271 path.addLineTo(end); |
|
272 |
|
273 return path; |
|
274 } |
|
275 |
|
276 } |