1/*
2    Copyright (C) 2007 Krzysztof Kowalczyk <kkowalczyk@gmail.com>
3    Copyright (C) 2004, 2005, 2006 Nikolas Zimmermann <wildfox@kde.org>
4                  2004, 2005, 2006 Rob Buis <buis@kde.org>
5                  2005, 2007 Apple Inc. All Rights reserved.
6                  2007 Alp Toker <alp@atoker.com>
7                  2008 Dirk Schulze <krit@webkit.org>
8
9    This library is free software; you can redistribute it and/or
10    modify it under the terms of the GNU Library General Public
11    License as published by the Free Software Foundation; either
12    version 2 of the License, or (at your option) any later version.
13
14    This library is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17    Library General Public License for more details.
18
19    You should have received a copy of the GNU Library General Public License
20    aint with this library; see the file COPYING.LIB.  If not, write to
21    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22    Boston, MA 02110-1301, USA.
23*/
24
25#include "config.h"
26#include "Path.h"
27
28#include "AffineTransform.h"
29#include "CairoPath.h"
30#include "FloatRect.h"
31#include "GraphicsContext.h"
32#include "PlatformString.h"
33#include "StrokeStyleApplier.h"
34
35#include <cairo.h>
36#include <math.h>
37#include <wtf/MathExtras.h>
38
39namespace WebCore {
40
41Path::Path()
42    : m_path(new CairoPath())
43{
44}
45
46Path::~Path()
47{
48    delete m_path;
49}
50
51Path::Path(const Path& other)
52    : m_path(new CairoPath())
53{
54    cairo_t* cr = platformPath()->m_cr;
55    cairo_path_t* p = cairo_copy_path(other.platformPath()->m_cr);
56    cairo_append_path(cr, p);
57    cairo_path_destroy(p);
58}
59
60Path& Path::operator=(const Path& other)
61{
62    if (&other == this)
63        return *this;
64
65    clear();
66    cairo_t* cr = platformPath()->m_cr;
67    cairo_path_t* p = cairo_copy_path(other.platformPath()->m_cr);
68    cairo_append_path(cr, p);
69    cairo_path_destroy(p);
70    return *this;
71}
72
73void Path::clear()
74{
75    cairo_t* cr = platformPath()->m_cr;
76    cairo_new_path(cr);
77}
78
79bool Path::isEmpty() const
80{
81    return !cairo_has_current_point(platformPath()->m_cr);
82}
83
84bool Path::hasCurrentPoint() const
85{
86    return !isEmpty();
87}
88
89void Path::translate(const FloatSize& p)
90{
91    cairo_t* cr = platformPath()->m_cr;
92    cairo_translate(cr, p.width(), p.height());
93}
94
95void Path::moveTo(const FloatPoint& p)
96{
97    cairo_t* cr = platformPath()->m_cr;
98    cairo_move_to(cr, p.x(), p.y());
99}
100
101void Path::addLineTo(const FloatPoint& p)
102{
103    cairo_t* cr = platformPath()->m_cr;
104    cairo_line_to(cr, p.x(), p.y());
105}
106
107void Path::addRect(const FloatRect& rect)
108{
109    cairo_t* cr = platformPath()->m_cr;
110    cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
111}
112
113/*
114 * inspired by libsvg-cairo
115 */
116void Path::addQuadCurveTo(const FloatPoint& controlPoint, const FloatPoint& point)
117{
118    cairo_t* cr = platformPath()->m_cr;
119    double x, y;
120    double x1 = controlPoint.x();
121    double y1 = controlPoint.y();
122    double x2 = point.x();
123    double y2 = point.y();
124    cairo_get_current_point(cr, &x, &y);
125    cairo_curve_to(cr,
126                   x  + 2.0 / 3.0 * (x1 - x),  y  + 2.0 / 3.0 * (y1 - y),
127                   x2 + 2.0 / 3.0 * (x1 - x2), y2 + 2.0 / 3.0 * (y1 - y2),
128                   x2, y2);
129}
130
131void Path::addBezierCurveTo(const FloatPoint& controlPoint1, const FloatPoint& controlPoint2, const FloatPoint& controlPoint3)
132{
133    cairo_t* cr = platformPath()->m_cr;
134    cairo_curve_to(cr, controlPoint1.x(), controlPoint1.y(),
135                   controlPoint2.x(), controlPoint2.y(),
136                   controlPoint3.x(), controlPoint3.y());
137}
138
139void Path::addArc(const FloatPoint& p, float r, float sa, float ea, bool anticlockwise)
140{
141    // http://bugs.webkit.org/show_bug.cgi?id=16449
142    // cairo_arc() functions hang or crash when passed inf as radius or start/end angle
143    if (!isfinite(r) || !isfinite(sa) || !isfinite(ea))
144        return;
145
146    cairo_t* cr = platformPath()->m_cr;
147    if (anticlockwise)
148        cairo_arc_negative(cr, p.x(), p.y(), r, sa, ea);
149    else
150        cairo_arc(cr, p.x(), p.y(), r, sa, ea);
151}
152
153void Path::addArcTo(const FloatPoint& p1, const FloatPoint& p2, float radius)
154{
155    if (isEmpty())
156        return;
157
158    cairo_t* cr = platformPath()->m_cr;
159
160    double x0, y0;
161    cairo_get_current_point(cr, &x0, &y0);
162    FloatPoint p0(x0, y0);
163    if ((p1.x() == p0.x() && p1.y() == p0.y()) || (p1.x() == p2.x() && p1.y() == p2.y()) || radius == 0.f) {
164        cairo_line_to(cr, p1.x(), p1.y());
165        return;
166    }
167
168    FloatPoint p1p0((p0.x() - p1.x()),(p0.y() - p1.y()));
169    FloatPoint p1p2((p2.x() - p1.x()),(p2.y() - p1.y()));
170    float p1p0_length = sqrtf(p1p0.x() * p1p0.x() + p1p0.y() * p1p0.y());
171    float p1p2_length = sqrtf(p1p2.x() * p1p2.x() + p1p2.y() * p1p2.y());
172
173    double cos_phi = (p1p0.x() * p1p2.x() + p1p0.y() * p1p2.y()) / (p1p0_length * p1p2_length);
174    // all points on a line logic
175    if (cos_phi == -1) {
176        cairo_line_to(cr, p1.x(), p1.y());
177        return;
178    }
179    if (cos_phi == 1) {
180        // add infinite far away point
181        unsigned int max_length = 65535;
182        double factor_max = max_length / p1p0_length;
183        FloatPoint ep((p0.x() + factor_max * p1p0.x()), (p0.y() + factor_max * p1p0.y()));
184        cairo_line_to(cr, ep.x(), ep.y());
185        return;
186    }
187
188    float tangent = radius / tan(acos(cos_phi) / 2);
189    float factor_p1p0 = tangent / p1p0_length;
190    FloatPoint t_p1p0((p1.x() + factor_p1p0 * p1p0.x()), (p1.y() + factor_p1p0 * p1p0.y()));
191
192    FloatPoint orth_p1p0(p1p0.y(), -p1p0.x());
193    float orth_p1p0_length = sqrt(orth_p1p0.x() * orth_p1p0.x() + orth_p1p0.y() * orth_p1p0.y());
194    float factor_ra = radius / orth_p1p0_length;
195
196    // angle between orth_p1p0 and p1p2 to get the right vector orthographic to p1p0
197    double cos_alpha = (orth_p1p0.x() * p1p2.x() + orth_p1p0.y() * p1p2.y()) / (orth_p1p0_length * p1p2_length);
198    if (cos_alpha < 0.f)
199        orth_p1p0 = FloatPoint(-orth_p1p0.x(), -orth_p1p0.y());
200
201    FloatPoint p((t_p1p0.x() + factor_ra * orth_p1p0.x()), (t_p1p0.y() + factor_ra * orth_p1p0.y()));
202
203    // calculate angles for addArc
204    orth_p1p0 = FloatPoint(-orth_p1p0.x(), -orth_p1p0.y());
205    float sa = acos(orth_p1p0.x() / orth_p1p0_length);
206    if (orth_p1p0.y() < 0.f)
207        sa = 2 * piDouble - sa;
208
209    // anticlockwise logic
210    bool anticlockwise = false;
211
212    float factor_p1p2 = tangent / p1p2_length;
213    FloatPoint t_p1p2((p1.x() + factor_p1p2 * p1p2.x()), (p1.y() + factor_p1p2 * p1p2.y()));
214    FloatPoint orth_p1p2((t_p1p2.x() - p.x()),(t_p1p2.y() - p.y()));
215    float orth_p1p2_length = sqrtf(orth_p1p2.x() * orth_p1p2.x() + orth_p1p2.y() * orth_p1p2.y());
216    float ea = acos(orth_p1p2.x() / orth_p1p2_length);
217    if (orth_p1p2.y() < 0)
218        ea = 2 * piDouble - ea;
219    if ((sa > ea) && ((sa - ea) < piDouble))
220        anticlockwise = true;
221    if ((sa < ea) && ((ea - sa) > piDouble))
222        anticlockwise = true;
223
224    cairo_line_to(cr, t_p1p0.x(), t_p1p0.y());
225
226    addArc(p, radius, sa, ea, anticlockwise);
227}
228
229void Path::addEllipse(const FloatRect& rect)
230{
231    cairo_t* cr = platformPath()->m_cr;
232    cairo_save(cr);
233    float yRadius = .5 * rect.height();
234    float xRadius = .5 * rect.width();
235    cairo_translate(cr, rect.x() + xRadius, rect.y() + yRadius);
236    cairo_scale(cr, xRadius, yRadius);
237    cairo_arc(cr, 0., 0., 1., 0., 2 * piDouble);
238    cairo_restore(cr);
239}
240
241void Path::closeSubpath()
242{
243    cairo_t* cr = platformPath()->m_cr;
244    cairo_close_path(cr);
245}
246
247FloatRect Path::boundingRect() const
248{
249    cairo_t* cr = platformPath()->m_cr;
250    double x0, x1, y0, y1;
251    cairo_path_extents(cr, &x0, &y0, &x1, &y1);
252    return FloatRect(x0, y0, x1 - x0, y1 - y0);
253}
254
255FloatRect Path::strokeBoundingRect(StrokeStyleApplier* applier)
256{
257    cairo_t* cr = platformPath()->m_cr;
258    if (applier) {
259        GraphicsContext gc(cr);
260        applier->strokeStyle(&gc);
261    }
262
263    double x0, x1, y0, y1;
264    cairo_stroke_extents(cr, &x0, &y0, &x1, &y1);
265    return FloatRect(x0, y0, x1 - x0, y1 - y0);
266}
267
268bool Path::contains(const FloatPoint& point, WindRule rule) const
269{
270    if (!boundingRect().contains(point))
271        return false;
272
273    cairo_t* cr = platformPath()->m_cr;
274    cairo_fill_rule_t cur = cairo_get_fill_rule(cr);
275    cairo_set_fill_rule(cr, rule == RULE_EVENODD ? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING);
276    bool contains = cairo_in_fill(cr, point.x(), point.y());
277    cairo_set_fill_rule(cr, cur);
278    return contains;
279}
280
281bool Path::strokeContains(StrokeStyleApplier* applier, const FloatPoint& point) const
282{
283    ASSERT(applier);
284    cairo_t* cr = platformPath()->m_cr;
285    GraphicsContext gc(cr);
286    applier->strokeStyle(&gc);
287
288    return cairo_in_stroke(cr, point.x(), point.y());
289}
290
291void Path::apply(void* info, PathApplierFunction function) const
292{
293    cairo_t* cr = platformPath()->m_cr;
294    cairo_path_t* path = cairo_copy_path(cr);
295    cairo_path_data_t* data;
296    PathElement pelement;
297    FloatPoint points[3];
298    pelement.points = points;
299
300    for (int i = 0; i < path->num_data; i += path->data[i].header.length) {
301        data = &path->data[i];
302        switch (data->header.type) {
303        case CAIRO_PATH_MOVE_TO:
304            pelement.type = PathElementMoveToPoint;
305            pelement.points[0] = FloatPoint(data[1].point.x,data[1].point.y);
306            function(info, &pelement);
307            break;
308        case CAIRO_PATH_LINE_TO:
309            pelement.type = PathElementAddLineToPoint;
310            pelement.points[0] = FloatPoint(data[1].point.x,data[1].point.y);
311            function(info, &pelement);
312            break;
313        case CAIRO_PATH_CURVE_TO:
314            pelement.type = PathElementAddCurveToPoint;
315            pelement.points[0] = FloatPoint(data[1].point.x,data[1].point.y);
316            pelement.points[1] = FloatPoint(data[2].point.x,data[2].point.y);
317            pelement.points[2] = FloatPoint(data[3].point.x,data[3].point.y);
318            function(info, &pelement);
319            break;
320        case CAIRO_PATH_CLOSE_PATH:
321            pelement.type = PathElementCloseSubpath;
322            function(info, &pelement);
323            break;
324        }
325    }
326    cairo_path_destroy(path);
327}
328
329void Path::transform(const AffineTransform& trans)
330{
331    cairo_t* m_cr = platformPath()->m_cr;
332    cairo_matrix_t c_matrix = cairo_matrix_t(trans);
333    cairo_matrix_invert(&c_matrix);
334    cairo_transform(m_cr, &c_matrix);
335}
336
337String Path::debugString() const
338{
339    if (isEmpty())
340        return String();
341
342    String pathString;
343    cairo_path_t* path = cairo_copy_path(platformPath()->m_cr);
344    cairo_path_data_t* data;
345
346    for (int i = 0; i < path->num_data; i += path->data[i].header.length) {
347        data = &path->data[i];
348        switch (data->header.type) {
349        case CAIRO_PATH_MOVE_TO:
350            if (i < (path->num_data - path->data[i].header.length))
351                pathString += String::format("M%.2f,%.2f ",
352                                      data[1].point.x, data[1].point.y);
353            break;
354        case CAIRO_PATH_LINE_TO:
355            pathString += String::format("L%.2f,%.2f ",
356                                      data[1].point.x, data[1].point.y);
357            break;
358        case CAIRO_PATH_CURVE_TO:
359            pathString += String::format("C%.2f,%.2f,%.2f,%.2f,%.2f,%.2f ",
360                                      data[1].point.x, data[1].point.y,
361                                      data[2].point.x, data[2].point.y,
362                                      data[3].point.x, data[3].point.y);
363            break;
364        case CAIRO_PATH_CLOSE_PATH:
365            pathString += "Z ";
366            break;
367        }
368    }
369
370    cairo_path_destroy(path);
371    return pathString.simplifyWhiteSpace();
372}
373
374} // namespace WebCore
375