2 * Copyright (C) 2006 Zack Rusin <zack@kde.org>
3 * 2006 Rob Buis <buis@kde.org>
4 * 2009 Dirk Schulze <krit@webkit.org>
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
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.
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.
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>
42 #include <wtf/OwnPtr.h>
44 #define _USE_MATH_DEFINES
48 # define M_PI 3.14159265358979323846
54 : m_path(new QPainterPath())
63 Path::Path(const Path& other)
64 : m_path(new QPainterPath(*other.platformPath()))
68 Path& Path::operator=(const Path& other)
72 m_path = new QPainterPath(*other.platformPath());
78 bool Path::contains(const FloatPoint& point, WindRule rule) const
80 Qt::FillRule savedRule = m_path->fillRule();
81 m_path->setFillRule(rule == RULE_EVENODD ? Qt::OddEvenFill : Qt::WindingFill);
83 bool contains = m_path->contains(point);
85 m_path->setFillRule(savedRule);
89 bool Path::strokeContains(StrokeStyleApplier* applier, const FloatPoint& point) const
93 // FIXME: We should try to use a 'shared Context' instead of creating a new ImageBuffer
95 OwnPtr<ImageBuffer> scratchImage = ImageBuffer::create(IntSize(1, 1), false);
96 GraphicsContext* gc = scratchImage->context();
97 QPainterPathStroker stroke;
98 applier->strokeStyle(gc);
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());
108 return (stroke.createStroke(*platformPath())).contains(point);
111 void Path::translate(const FloatSize& size)
114 matrix.translate(size.width(), size.height());
115 *m_path = (*m_path) * matrix;
118 FloatRect Path::boundingRect() const
120 return m_path->boundingRect();
123 FloatRect Path::strokeBoundingRect(StrokeStyleApplier* applier)
125 // FIXME: We should try to use a 'shared Context' instead of creating a new ImageBuffer
127 OwnPtr<ImageBuffer> scratchImage = ImageBuffer::create(IntSize(1, 1), false);
128 GraphicsContext* gc = scratchImage->context();
129 QPainterPathStroker stroke;
131 applier->strokeStyle(gc);
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());
141 return (stroke.createStroke(*platformPath())).boundingRect();
144 void Path::moveTo(const FloatPoint& point)
146 m_path->moveTo(point);
149 void Path::addLineTo(const FloatPoint& p)
154 void Path::addQuadCurveTo(const FloatPoint& cp, const FloatPoint& p)
156 m_path->quadTo(cp, p);
159 void Path::addBezierCurveTo(const FloatPoint& cp1, const FloatPoint& cp2, const FloatPoint& p)
161 m_path->cubicTo(cp1, cp2, p);
164 void Path::addArcTo(const FloatPoint& p1, const FloatPoint& p2, float radius)
166 FloatPoint p0(m_path->currentPosition());
168 if ((p1.x() == p0.x() && p1.y() == p0.y()) || (p1.x() == p2.x() && p1.y() == p2.y()) || radius == 0.f) {
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());
178 double cos_phi = (p1p0.x() * p1p2.x() + p1p0.y() * p1p2.y()) / (p1p0_length * p1p2_length);
179 // all points on a line logic
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()));
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()));
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;
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);
204 orth_p1p0 = FloatPoint(-orth_p1p0.x(), -orth_p1p0.y());
206 FloatPoint p((t_p1p0.x() + factor_ra * orth_p1p0.x()), (t_p1p0.y() + factor_ra * orth_p1p0.y()));
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;
214 // anticlockwise logic
215 bool anticlockwise = false;
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;
229 m_path->lineTo(t_p1p0);
231 addArc(p, radius, sa, ea, anticlockwise);
234 void Path::closeSubpath()
236 m_path->closeSubpath();
239 #define DEGREES(t) ((t) * 180.0 / M_PI)
240 void Path::addArc(const FloatPoint& p, float r, float sar, float ear, bool anticlockwise)
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
253 anticlockwise = !anticlockwise;
256 float sa = DEGREES(sar);
257 float ea = DEGREES(ear);
261 double xs = xc - radius;
262 double ys = yc - radius;
263 double width = radius*2;
264 double height = radius*2;
266 if (!anticlockwise && (ea < sa))
268 else if (anticlockwise && (sa < ea))
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))) {
278 m_path->moveTo(QPointF(xc + radius * cos(sar),
279 yc - radius * sin(sar)));
281 m_path->arcTo(xs, ys, width, height, sa, span);
284 void Path::addRect(const FloatRect& r)
286 m_path->addRect(r.x(), r.y(), r.width(), r.height());
289 void Path::addEllipse(const FloatRect& r)
291 m_path->addEllipse(r.x(), r.y(), r.width(), r.height());
296 *m_path = QPainterPath();
299 bool Path::isEmpty() const
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;
306 bool Path::hasCurrentPoint() const
311 String Path::debugString() const
314 for (int i = 0; i < m_path->elementCount(); ++i) {
315 const QPainterPath::Element &cur = m_path->elementAt(i);
318 case QPainterPath::MoveToElement:
319 ret += QString(QLatin1String("M %1 %2")).arg(cur.x).arg(cur.y);
321 case QPainterPath::LineToElement:
322 ret += QString(QLatin1String("L %1 %2")).arg(cur.x).arg(cur.y);
324 case QPainterPath::CurveToElement:
326 const QPainterPath::Element &c1 = m_path->elementAt(i + 1);
327 const QPainterPath::Element &c2 = m_path->elementAt(i + 2);
329 Q_ASSERT(c1.type == QPainterPath::CurveToDataElement);
330 Q_ASSERT(c2.type == QPainterPath::CurveToDataElement);
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);
337 case QPainterPath::CurveToDataElement:
346 void Path::apply(void* info, PathApplierFunction function) const
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);
355 case QPainterPath::MoveToElement:
356 pelement.type = PathElementMoveToPoint;
357 pelement.points[0] = QPointF(cur);
358 function(info, &pelement);
360 case QPainterPath::LineToElement:
361 pelement.type = PathElementAddLineToPoint;
362 pelement.points[0] = QPointF(cur);
363 function(info, &pelement);
365 case QPainterPath::CurveToElement:
367 const QPainterPath::Element& c1 = m_path->elementAt(i + 1);
368 const QPainterPath::Element& c2 = m_path->elementAt(i + 2);
370 Q_ASSERT(c1.type == QPainterPath::CurveToDataElement);
371 Q_ASSERT(c2.type == QPainterPath::CurveToDataElement);
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);
382 case QPainterPath::CurveToDataElement:
388 void Path::transform(const TransformationMatrix& transform)
391 QTransform mat = transform;
392 QPainterPath temp = mat.map(*m_path);
394 m_path = new QPainterPath(temp);