2007-01-02 Eric Seidel <eric@webkit.org>
[WebKit-https.git] / WebCore / platform / graphics / Path.cpp
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 double QUARTER = 0.552; // approximation of control point positions on a bezier
39                               // to simulate a quarter of a circle.
40 namespace WebCore {
41
42 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_totalLength > traversalState.m_desiredLength)) {
70         // FIXME: Need to actually find the exact point and change m_current
71         traversalState.m_success = true;
72     } else if ((traversalState.m_action == PathTraversalState::TraversalNormalAngleAtLength)
73            && (traversalState.m_totalLength > traversalState.m_desiredLength)) {
74         FloatSize change = traversalState.m_previous - traversalState.m_current;
75         // tangent slope = -1/slope = -1/yChange/xChange = -xChange/yChange; arc-tangent converts a slope into an angle
76         static float rad2deg = 360 / 2 * M_PI;
77         traversalState.m_normalAngle = (change.height() == 0) ? 0 : (atan2(-change.width(), change.height()) * rad2deg);
78         traversalState.m_success = true;
79     }
80 }
81
82 float Path::length()
83 {
84     PathTraversalState traversalState(PathTraversalState::TraversalTotalLength);
85     apply(&traversalState, pathLengthApplierFunction);
86     return traversalState.m_totalLength;
87 }
88
89 FloatPoint Path::pointAtLength(float length, bool& ok)
90 {
91     PathTraversalState traversalState(PathTraversalState::TraversalPointAtLength);
92     traversalState.m_desiredLength = length;
93     apply(&traversalState, pathLengthApplierFunction);
94     ok = traversalState.m_success;
95     return traversalState.m_current;
96 }
97
98 float Path::normalAngleAtLength(float length, bool& ok)
99 {
100     PathTraversalState traversalState(PathTraversalState::TraversalNormalAngleAtLength);
101     traversalState.m_desiredLength = length;
102     apply(&traversalState, pathLengthApplierFunction);
103     ok = traversalState.m_success;
104     return traversalState.m_normalAngle;
105 }
106
107 Path Path::createRoundedRectangle(const FloatRect& rectangle, const FloatSize& roundingRadii)
108 {
109     Path path;
110     double x = rectangle.x();
111     double y = rectangle.y();
112     double width = rectangle.width();
113     double height = rectangle.height();
114     double rx = roundingRadii.width();
115     double ry = roundingRadii.height();
116     if (width <= 0.0 || height <= 0.0)
117         return path;
118
119     double dx = rx, dy = ry;
120     // If rx is greater than half of the width of the rectangle
121     // then set rx to half of the width (required in SVG spec)
122     if (dx > width * 0.5)
123         dx = width * 0.5;
124
125     // If ry is greater than half of the height of the rectangle
126     // then set ry to half of the height (required in SVG spec)
127     if (dy > height * 0.5)
128         dy = height * 0.5;
129
130     path.moveTo(FloatPoint(x + dx, y));
131
132     if (dx < width * 0.5)
133         path.addLineTo(FloatPoint(x + width - rx, y));
134
135     path.addBezierCurveTo(FloatPoint(x + width - dx * (1 - QUARTER), y), FloatPoint(x + width, y + dy * (1 - QUARTER)), FloatPoint(x + width, y + dy));
136
137     if (dy < height * 0.5)
138         path.addLineTo(FloatPoint(x + width, y + height - dy));
139
140     path.addBezierCurveTo(FloatPoint(x + width, y + height - dy * (1 - QUARTER)), FloatPoint(x + width - dx * (1 - QUARTER), y + height), FloatPoint(x + width - dx, y + height));
141
142     if (dx < width * 0.5)
143         path.addLineTo(FloatPoint(x + dx, y + height));
144
145     path.addBezierCurveTo(FloatPoint(x + dx * (1 - QUARTER), y + height), FloatPoint(x, y + height - dy * (1 - QUARTER)), FloatPoint(x, y + height - dy));
146
147     if (dy < height * 0.5)
148         path.addLineTo(FloatPoint(x, y + dy));
149
150     path.addBezierCurveTo(FloatPoint(x, y + dy * (1 - QUARTER)), FloatPoint(x + dx * (1 - QUARTER), y), FloatPoint(x + dx, y));
151
152     path.closeSubpath();
153
154     return path;
155 }
156
157 Path Path::createRectangle(const FloatRect& rectangle)
158 {
159     Path path;
160     double x = rectangle.x();
161     double y = rectangle.y();
162     double width = rectangle.width();
163     double height = rectangle.height();
164     if (width < 0.0 || height < 0.0)
165         return path;
166     
167     path.moveTo(FloatPoint(x, y));
168     path.addLineTo(FloatPoint(x + width, y));
169     path.addLineTo(FloatPoint(x + width, y + height));
170     path.addLineTo(FloatPoint(x, y + height));
171     path.closeSubpath();
172
173     return path;
174 }
175
176 Path Path::createEllipse(const FloatPoint& center, float rx, float ry)
177 {
178     double cx = center.x();
179     double cy = center.y();
180     Path path;
181     if (rx <= 0.0 || ry <= 0.0)
182         return path;
183
184     double x = cx, y = cy;
185
186     unsigned step = 0, num = 100;
187     bool running = true;
188     while (running)
189     {
190         if (step == num)
191         {
192             running = false;
193             break;
194         }
195
196         double angle = double(step) / double(num) * 2.0 * M_PI;
197         x = cx + cos(angle) * rx;
198         y = cy + sin(angle) * ry;
199
200         step++;
201         if (step == 1)
202             path.moveTo(FloatPoint(x, y));
203         else
204             path.addLineTo(FloatPoint(x, y));
205     }
206
207     path.closeSubpath();
208
209     return path;
210 }
211
212 Path Path::createCircle(const FloatPoint& center, float r)
213 {
214     return createEllipse(center, r, r);
215 }
216
217 Path Path::createLine(const FloatPoint& start, const FloatPoint& end)
218 {
219     Path path;
220     if (start.x() == end.x() && start.y() == end.y())
221         return path;
222
223     path.moveTo(start);
224     path.addLineTo(end);
225
226     return path;
227 }
228
229 }