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