2009-07-14 Dmitry Titov <dimich@chromium.org>
[WebKit-https.git] / WebCore / platform / graphics / qt / PathQt.cpp
1 /*
2  * Copyright (C) 2006 Zack Rusin   <zack@kde.org>
3  *               2006 Rob Buis     <buis@kde.org>
4  *               2009 Dirk Schulze <krit@webkit.org>
5  *
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
21  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
25  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include "config.h"
31 #include "Path.h"
32
33 #include "TransformationMatrix.h"
34 #include "FloatRect.h"
35 #include "GraphicsContext.h"
36 #include "ImageBuffer.h"
37 #include "PlatformString.h"
38 #include "StrokeStyleApplier.h"
39 #include <QPainterPath>
40 #include <QTransform>
41 #include <QString>
42 #include <wtf/OwnPtr.h>
43
44 #define _USE_MATH_DEFINES
45 #include <math.h>
46
47 #ifndef M_PI
48 #   define M_PI 3.14159265358979323846
49 #endif
50
51 namespace WebCore {
52
53 Path::Path()
54     : m_path(new QPainterPath())
55 {
56 }
57
58 Path::~Path()
59 {
60     delete m_path;
61 }
62
63 Path::Path(const Path& other)
64     : m_path(new QPainterPath(*other.platformPath()))
65 {
66 }
67
68 Path& Path::operator=(const Path& other)
69 {
70     if (&other != this) {
71         delete m_path;
72         m_path = new QPainterPath(*other.platformPath());
73     }
74
75     return *this;
76 }
77
78 bool Path::contains(const FloatPoint& point, WindRule rule) const
79 {
80     Qt::FillRule savedRule = m_path->fillRule();
81     m_path->setFillRule(rule == RULE_EVENODD ? Qt::OddEvenFill : Qt::WindingFill);
82
83     bool contains = m_path->contains(point);
84
85     m_path->setFillRule(savedRule);
86     return contains;
87 }
88
89 bool Path::strokeContains(StrokeStyleApplier* applier, const FloatPoint& point) const
90 {
91     ASSERT(applier);
92
93     // FIXME: We should try to use a 'shared Context' instead of creating a new ImageBuffer
94     // on each call.
95     OwnPtr<ImageBuffer> scratchImage = ImageBuffer::create(IntSize(1, 1), false);
96     GraphicsContext* gc = scratchImage->context();
97     QPainterPathStroker stroke;
98     applier->strokeStyle(gc);
99
100     QPen pen = gc->pen();
101     stroke.setWidth(pen.widthF());
102     stroke.setCapStyle(pen.capStyle());
103     stroke.setJoinStyle(pen.joinStyle());
104     stroke.setMiterLimit(pen.miterLimit());
105     stroke.setDashPattern(pen.dashPattern());
106     stroke.setDashOffset(pen.dashOffset());
107
108     return (stroke.createStroke(*platformPath())).contains(point);
109 }
110
111 void Path::translate(const FloatSize& size)
112 {
113     QTransform matrix;
114     matrix.translate(size.width(), size.height());
115     *m_path = (*m_path) * matrix;
116 }
117
118 FloatRect Path::boundingRect() const
119 {
120     return m_path->boundingRect();
121 }
122
123 FloatRect Path::strokeBoundingRect(StrokeStyleApplier* applier)
124 {
125     // FIXME: We should try to use a 'shared Context' instead of creating a new ImageBuffer
126     // on each call.
127     OwnPtr<ImageBuffer> scratchImage = ImageBuffer::create(IntSize(1, 1), false);
128     GraphicsContext* gc = scratchImage->context();
129     QPainterPathStroker stroke;
130     if (applier) {
131         applier->strokeStyle(gc);
132
133         QPen pen = gc->pen();
134         stroke.setWidth(pen.widthF());
135         stroke.setCapStyle(pen.capStyle());
136         stroke.setJoinStyle(pen.joinStyle());
137         stroke.setMiterLimit(pen.miterLimit());
138         stroke.setDashPattern(pen.dashPattern());
139         stroke.setDashOffset(pen.dashOffset());
140     }
141     return (stroke.createStroke(*platformPath())).boundingRect();
142 }
143
144 void Path::moveTo(const FloatPoint& point)
145 {
146     m_path->moveTo(point);
147 }
148
149 void Path::addLineTo(const FloatPoint& p)
150 {
151     m_path->lineTo(p);
152 }
153
154 void Path::addQuadCurveTo(const FloatPoint& cp, const FloatPoint& p)
155 {
156     m_path->quadTo(cp, p);
157 }
158
159 void Path::addBezierCurveTo(const FloatPoint& cp1, const FloatPoint& cp2, const FloatPoint& p)
160 {
161     m_path->cubicTo(cp1, cp2, p);
162 }
163
164 void Path::addArcTo(const FloatPoint& p1, const FloatPoint& p2, float radius)
165 {
166     FloatPoint p0(m_path->currentPosition());
167
168     if ((p1.x() == p0.x() && p1.y() == p0.y()) || (p1.x() == p2.x() && p1.y() == p2.y()) || radius == 0.f) {
169         m_path->lineTo(p1);
170         return;
171     }
172
173     FloatPoint p1p0((p0.x() - p1.x()),(p0.y() - p1.y()));
174     FloatPoint p1p2((p2.x() - p1.x()),(p2.y() - p1.y()));
175     float p1p0_length = sqrtf(p1p0.x() * p1p0.x() + p1p0.y() * p1p0.y());
176     float p1p2_length = sqrtf(p1p2.x() * p1p2.x() + p1p2.y() * p1p2.y());
177
178     double cos_phi = (p1p0.x() * p1p2.x() + p1p0.y() * p1p2.y()) / (p1p0_length * p1p2_length);
179     // all points on a line logic
180     if (cos_phi == -1) {
181         m_path->lineTo(p1);
182         return;
183     }
184     if (cos_phi == 1) {
185         // add infinite far away point
186         unsigned int max_length = 65535;
187         double factor_max = max_length / p1p0_length;
188         FloatPoint ep((p0.x() + factor_max * p1p0.x()), (p0.y() + factor_max * p1p0.y()));
189         m_path->lineTo(ep);
190         return;
191     }
192
193     float tangent = radius / tan(acos(cos_phi) / 2);
194     float factor_p1p0 = tangent / p1p0_length;
195     FloatPoint t_p1p0((p1.x() + factor_p1p0 * p1p0.x()), (p1.y() + factor_p1p0 * p1p0.y()));
196
197     FloatPoint orth_p1p0(p1p0.y(), -p1p0.x());
198     float orth_p1p0_length = sqrt(orth_p1p0.x() * orth_p1p0.x() + orth_p1p0.y() * orth_p1p0.y());
199     float factor_ra = radius / orth_p1p0_length;
200
201     // angle between orth_p1p0 and p1p2 to get the right vector orthographic to p1p0
202     double cos_alpha = (orth_p1p0.x() * p1p2.x() + orth_p1p0.y() * p1p2.y()) / (orth_p1p0_length * p1p2_length);
203     if (cos_alpha < 0.f)
204         orth_p1p0 = FloatPoint(-orth_p1p0.x(), -orth_p1p0.y());
205
206     FloatPoint p((t_p1p0.x() + factor_ra * orth_p1p0.x()), (t_p1p0.y() + factor_ra * orth_p1p0.y()));
207
208     // calculate angles for addArc
209     orth_p1p0 = FloatPoint(-orth_p1p0.x(), -orth_p1p0.y());
210     float sa = acos(orth_p1p0.x() / orth_p1p0_length);
211     if (orth_p1p0.y() < 0.f)
212         sa = 2 * piDouble - sa;
213
214     // anticlockwise logic
215     bool anticlockwise = false;
216
217     float factor_p1p2 = tangent / p1p2_length;
218     FloatPoint t_p1p2((p1.x() + factor_p1p2 * p1p2.x()), (p1.y() + factor_p1p2 * p1p2.y()));
219     FloatPoint orth_p1p2((t_p1p2.x() - p.x()),(t_p1p2.y() - p.y()));
220     float orth_p1p2_length = sqrtf(orth_p1p2.x() * orth_p1p2.x() + orth_p1p2.y() * orth_p1p2.y());
221     float ea = acos(orth_p1p2.x() / orth_p1p2_length);
222     if (orth_p1p2.y() < 0)
223         ea = 2 * piDouble - ea;
224     if ((sa > ea) && ((sa - ea) < piDouble))
225         anticlockwise = true;
226     if ((sa < ea) && ((ea - sa) > piDouble))
227         anticlockwise = true;
228
229     m_path->lineTo(t_p1p0);
230
231     addArc(p, radius, sa, ea, anticlockwise);
232 }
233
234 void Path::closeSubpath()
235 {
236     m_path->closeSubpath();
237 }
238
239 #define DEGREES(t) ((t) * 180.0 / M_PI)
240 void Path::addArc(const FloatPoint& p, float r, float sar, float ear, bool anticlockwise)
241 {
242     qreal xc = p.x();
243     qreal yc = p.y();
244     qreal radius = r;
245
246
247     //### HACK
248     // In Qt we don't switch the coordinate system for degrees
249     // and still use the 0,0 as bottom left for degrees so we need
250     // to switch
251     sar = -sar;
252     ear = -ear;
253     anticlockwise = !anticlockwise;
254     //end hack
255
256     float sa = DEGREES(sar);
257     float ea = DEGREES(ear);
258
259     double span = 0;
260
261     double xs = xc - radius;
262     double ys = yc - radius;
263     double width  = radius*2;
264     double height = radius*2;
265
266     if (!anticlockwise && (ea < sa))
267         span += 360;
268     else if (anticlockwise && (sa < ea))
269         span -= 360;
270
271     // this is also due to switched coordinate system
272     // we would end up with a 0 span instead of 360
273     if (!(qFuzzyCompare(span + (ea - sa) + 1, 1.0) &&
274           qFuzzyCompare(qAbs(span), 360.0))) {
275         span += ea - sa;
276     }
277
278     m_path->moveTo(QPointF(xc + radius  * cos(sar),
279                           yc - radius  * sin(sar)));
280
281     m_path->arcTo(xs, ys, width, height, sa, span);
282 }
283
284 void Path::addRect(const FloatRect& r)
285 {
286     m_path->addRect(r.x(), r.y(), r.width(), r.height());
287 }
288
289 void Path::addEllipse(const FloatRect& r)
290 {
291     m_path->addEllipse(r.x(), r.y(), r.width(), r.height());
292 }
293
294 void Path::clear()
295 {
296     *m_path = QPainterPath();
297 }
298
299 bool Path::isEmpty() const
300 {
301     // Don't use QPainterPath::isEmpty(), as that also returns true if there's only
302     // one initial MoveTo element in the path.
303     return m_path->elementCount() == 0;
304 }
305
306 bool Path::hasCurrentPoint() const
307 {
308     return !isEmpty();
309 }
310
311 String Path::debugString() const
312 {
313     QString ret;
314     for (int i = 0; i < m_path->elementCount(); ++i) {
315         const QPainterPath::Element &cur = m_path->elementAt(i);
316
317         switch (cur.type) {
318             case QPainterPath::MoveToElement:
319                 ret += QString(QLatin1String("M %1 %2")).arg(cur.x).arg(cur.y);
320                 break;
321             case QPainterPath::LineToElement:
322                 ret += QString(QLatin1String("L %1 %2")).arg(cur.x).arg(cur.y);
323                 break;
324             case QPainterPath::CurveToElement:
325             {
326                 const QPainterPath::Element &c1 = m_path->elementAt(i + 1);
327                 const QPainterPath::Element &c2 = m_path->elementAt(i + 2);
328
329                 Q_ASSERT(c1.type == QPainterPath::CurveToDataElement);
330                 Q_ASSERT(c2.type == QPainterPath::CurveToDataElement);
331
332                 ret += QString(QLatin1String("C %1 %2 %3 %4 %5 %6")).arg(cur.x).arg(cur.y).arg(c1.x).arg(c1.y).arg(c2.x).arg(c2.y);
333
334                 i += 2;
335                 break;
336             }
337             case QPainterPath::CurveToDataElement:
338                 Q_ASSERT(false);
339                 break;
340         }
341     }
342
343     return ret;
344 }
345
346 void Path::apply(void* info, PathApplierFunction function) const
347 {
348     PathElement pelement;
349     FloatPoint points[3];
350     pelement.points = points;
351     for (int i = 0; i < m_path->elementCount(); ++i) {
352         const QPainterPath::Element& cur = m_path->elementAt(i);
353
354         switch (cur.type) {
355             case QPainterPath::MoveToElement:
356                 pelement.type = PathElementMoveToPoint;
357                 pelement.points[0] = QPointF(cur);
358                 function(info, &pelement);
359                 break;
360             case QPainterPath::LineToElement:
361                 pelement.type = PathElementAddLineToPoint;
362                 pelement.points[0] = QPointF(cur);
363                 function(info, &pelement);
364                 break;
365             case QPainterPath::CurveToElement:
366             {
367                 const QPainterPath::Element& c1 = m_path->elementAt(i + 1);
368                 const QPainterPath::Element& c2 = m_path->elementAt(i + 2);
369
370                 Q_ASSERT(c1.type == QPainterPath::CurveToDataElement);
371                 Q_ASSERT(c2.type == QPainterPath::CurveToDataElement);
372
373                 pelement.type = PathElementAddCurveToPoint;
374                 pelement.points[0] = QPointF(cur);
375                 pelement.points[1] = QPointF(c1);
376                 pelement.points[2] = QPointF(c2);
377                 function(info, &pelement);
378
379                 i += 2;
380                 break;
381             }
382             case QPainterPath::CurveToDataElement:
383                 Q_ASSERT(false);
384         }
385     }
386 }
387
388 void Path::transform(const TransformationMatrix& transform)
389 {
390     if (m_path) {
391         QTransform mat = transform;
392         QPainterPath temp = mat.map(*m_path);
393         delete m_path;
394         m_path = new QPainterPath(temp);
395     }
396 }
397
398 }
399
400 // vim: ts=4 sw=4 et