1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the QtGui module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file. Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "private/qstroker_p.h"
43 #include "private/qbezier_p.h"
44 #include "private/qmath_p.h"
45 #include "qline.h"
46 #include "qtransform.h"
47 #include <qmath.h>
48
49 QT_BEGIN_NAMESPACE
50
51 // #define QPP_STROKE_DEBUG
52
53 class QSubpathForwardIterator
54 {
55 public:
QSubpathForwardIterator(const QDataBuffer<QStrokerOps::Element> * path)56 QSubpathForwardIterator(const QDataBuffer<QStrokerOps::Element> *path)
57 : m_path(path), m_pos(0) { }
position() const58 inline int position() const { return m_pos; }
hasNext() const59 inline bool hasNext() const { return m_pos < m_path->size(); }
next()60 inline QStrokerOps::Element next() { Q_ASSERT(hasNext()); return m_path->at(m_pos++); }
61
62 private:
63 const QDataBuffer<QStrokerOps::Element> *m_path;
64 int m_pos;
65 };
66
67 class QSubpathBackwardIterator
68 {
69 public:
QSubpathBackwardIterator(const QDataBuffer<QStrokerOps::Element> * path)70 QSubpathBackwardIterator(const QDataBuffer<QStrokerOps::Element> *path)
71 : m_path(path), m_pos(path->size() - 1) { }
72
position() const73 inline int position() const { return m_pos; }
74
hasNext() const75 inline bool hasNext() const { return m_pos >= 0; }
76
next()77 inline QStrokerOps::Element next()
78 {
79 Q_ASSERT(hasNext());
80
81 QStrokerOps::Element ce = m_path->at(m_pos); // current element
82
83 if (m_pos == m_path->size() - 1) {
84 --m_pos;
85 ce.type = QPainterPath::MoveToElement;
86 return ce;
87 }
88
89 const QStrokerOps::Element &pe = m_path->at(m_pos + 1); // previous element
90
91 switch (pe.type) {
92 case QPainterPath::LineToElement:
93 ce.type = QPainterPath::LineToElement;
94 break;
95 case QPainterPath::CurveToDataElement:
96 // First control point?
97 if (ce.type == QPainterPath::CurveToElement) {
98 ce.type = QPainterPath::CurveToDataElement;
99 } else { // Second control point then
100 ce.type = QPainterPath::CurveToElement;
101 }
102 break;
103 case QPainterPath::CurveToElement:
104 ce.type = QPainterPath::CurveToDataElement;
105 break;
106 default:
107 qWarning("QSubpathReverseIterator::next: Case %d unhandled", ce.type);
108 break;
109 }
110 --m_pos;
111
112 return ce;
113 }
114
115 private:
116 const QDataBuffer<QStrokerOps::Element> *m_path;
117 int m_pos;
118 };
119
120 class QSubpathFlatIterator
121 {
122 public:
QSubpathFlatIterator(const QDataBuffer<QStrokerOps::Element> * path,qreal threshold)123 QSubpathFlatIterator(const QDataBuffer<QStrokerOps::Element> *path, qreal threshold)
124 : m_path(path), m_pos(0), m_curve_index(-1), m_curve_threshold(threshold) { }
125
hasNext() const126 inline bool hasNext() const { return m_curve_index >= 0 || m_pos < m_path->size(); }
127
next()128 QStrokerOps::Element next()
129 {
130 Q_ASSERT(hasNext());
131
132 if (m_curve_index >= 0) {
133 QStrokerOps::Element e = { QPainterPath::LineToElement,
134 qt_real_to_fixed(m_curve.at(m_curve_index).x()),
135 qt_real_to_fixed(m_curve.at(m_curve_index).y())
136 };
137 ++m_curve_index;
138 if (m_curve_index >= m_curve.size())
139 m_curve_index = -1;
140 return e;
141 }
142
143 QStrokerOps::Element e = m_path->at(m_pos);
144 if (e.isCurveTo()) {
145 Q_ASSERT(m_pos > 0);
146 Q_ASSERT(m_pos < m_path->size());
147
148 m_curve = QBezier::fromPoints(QPointF(qt_fixed_to_real(m_path->at(m_pos-1).x),
149 qt_fixed_to_real(m_path->at(m_pos-1).y)),
150 QPointF(qt_fixed_to_real(e.x),
151 qt_fixed_to_real(e.y)),
152 QPointF(qt_fixed_to_real(m_path->at(m_pos+1).x),
153 qt_fixed_to_real(m_path->at(m_pos+1).y)),
154 QPointF(qt_fixed_to_real(m_path->at(m_pos+2).x),
155 qt_fixed_to_real(m_path->at(m_pos+2).y))).toPolygon(m_curve_threshold);
156 m_curve_index = 1;
157 e.type = QPainterPath::LineToElement;
158 e.x = m_curve.at(0).x();
159 e.y = m_curve.at(0).y();
160 m_pos += 2;
161 }
162 Q_ASSERT(e.isLineTo() || e.isMoveTo());
163 ++m_pos;
164 return e;
165 }
166
167 private:
168 const QDataBuffer<QStrokerOps::Element> *m_path;
169 int m_pos;
170 QPolygonF m_curve;
171 int m_curve_index;
172 qreal m_curve_threshold;
173 };
174
175 template <class Iterator> bool qt_stroke_side(Iterator *it, QStroker *stroker,
176 bool capFirst, QLineF *startTangent);
177
178 /*******************************************************************************
179 * QLineF::angle gives us the smalles angle between two lines. Here we
180 * want to identify the line's angle direction on the unit circle.
181 */
adapted_angle_on_x(const QLineF & line)182 static inline qreal adapted_angle_on_x(const QLineF &line)
183 {
184 qreal angle = line.angle(QLineF(0, 0, 1, 0));
185 if (line.dy() > 0)
186 angle = 360 - angle;
187 return angle;
188 }
189
QStrokerOps()190 QStrokerOps::QStrokerOps()
191 : m_elements(0)
192 , m_curveThreshold(qt_real_to_fixed(0.25))
193 , m_dashThreshold(qt_real_to_fixed(0.25))
194 , m_customData(0)
195 , m_moveTo(0)
196 , m_lineTo(0)
197 , m_cubicTo(0)
198 {
199 }
200
~QStrokerOps()201 QStrokerOps::~QStrokerOps()
202 {
203 }
204
205 /*!
206 Prepares the stroker. Call this function once before starting a
207 stroke by calling moveTo, lineTo or cubicTo.
208
209 The \a customData is passed back through that callback functions
210 and can be used by the user to for instance maintain state
211 information.
212 */
begin(void * customData)213 void QStrokerOps::begin(void *customData)
214 {
215 m_customData = customData;
216 m_elements.reset();
217 }
218
219
220 /*!
221 Finishes the stroke. Call this function once when an entire
222 primitive has been stroked.
223 */
end()224 void QStrokerOps::end()
225 {
226 if (m_elements.size() > 1)
227 processCurrentSubpath();
228 m_customData = 0;
229 }
230
231 /*!
232 Convenience function that decomposes \a path into begin(),
233 moveTo(), lineTo(), curevTo() and end() calls.
234
235 The \a customData parameter is used in the callback functions
236
237 The \a matrix is used to transform the points before input to the
238 stroker.
239
240 \sa begin()
241 */
strokePath(const QPainterPath & path,void * customData,const QTransform & matrix)242 void QStrokerOps::strokePath(const QPainterPath &path, void *customData, const QTransform &matrix)
243 {
244 if (path.isEmpty())
245 return;
246
247 setCurveThresholdFromTransform(QTransform());
248 begin(customData);
249 int count = path.elementCount();
250 if (matrix.isIdentity()) {
251 for (int i=0; i<count; ++i) {
252 const QPainterPath::Element &e = path.elementAt(i);
253 switch (e.type) {
254 case QPainterPath::MoveToElement:
255 moveTo(qt_real_to_fixed(e.x), qt_real_to_fixed(e.y));
256 break;
257 case QPainterPath::LineToElement:
258 lineTo(qt_real_to_fixed(e.x), qt_real_to_fixed(e.y));
259 break;
260 case QPainterPath::CurveToElement:
261 {
262 const QPainterPath::Element &cp2 = path.elementAt(++i);
263 const QPainterPath::Element &ep = path.elementAt(++i);
264 cubicTo(qt_real_to_fixed(e.x), qt_real_to_fixed(e.y),
265 qt_real_to_fixed(cp2.x), qt_real_to_fixed(cp2.y),
266 qt_real_to_fixed(ep.x), qt_real_to_fixed(ep.y));
267 }
268 break;
269 default:
270 break;
271 }
272 }
273 } else {
274 for (int i=0; i<count; ++i) {
275 const QPainterPath::Element &e = path.elementAt(i);
276 QPointF pt = QPointF(e.x, e.y) * matrix;
277 switch (e.type) {
278 case QPainterPath::MoveToElement:
279 moveTo(qt_real_to_fixed(pt.x()), qt_real_to_fixed(pt.y()));
280 break;
281 case QPainterPath::LineToElement:
282 lineTo(qt_real_to_fixed(pt.x()), qt_real_to_fixed(pt.y()));
283 break;
284 case QPainterPath::CurveToElement:
285 {
286 QPointF cp2 = ((QPointF) path.elementAt(++i)) * matrix;
287 QPointF ep = ((QPointF) path.elementAt(++i)) * matrix;
288 cubicTo(qt_real_to_fixed(pt.x()), qt_real_to_fixed(pt.y()),
289 qt_real_to_fixed(cp2.x()), qt_real_to_fixed(cp2.y()),
290 qt_real_to_fixed(ep.x()), qt_real_to_fixed(ep.y()));
291 }
292 break;
293 default:
294 break;
295 }
296 }
297 }
298 end();
299 }
300
301 /*!
302 Convenience function for stroking a polygon of the \a pointCount
303 first points in \a points. If \a implicit_close is set to true a
304 line is implictly drawn between the first and last point in the
305 polygon. Typically true for polygons and false for polylines.
306
307 The \a matrix is used to transform the points before they enter the
308 stroker.
309
310 \sa begin()
311 */
312
strokePolygon(const QPointF * points,int pointCount,bool implicit_close,void * data,const QTransform & matrix)313 void QStrokerOps::strokePolygon(const QPointF *points, int pointCount, bool implicit_close,
314 void *data, const QTransform &matrix)
315 {
316 if (!pointCount)
317 return;
318
319 setCurveThresholdFromTransform(QTransform());
320 begin(data);
321 if (matrix.isIdentity()) {
322 moveTo(qt_real_to_fixed(points[0].x()), qt_real_to_fixed(points[0].y()));
323 for (int i=1; i<pointCount; ++i)
324 lineTo(qt_real_to_fixed(points[i].x()),
325 qt_real_to_fixed(points[i].y()));
326 if (implicit_close)
327 lineTo(qt_real_to_fixed(points[0].x()), qt_real_to_fixed(points[0].y()));
328 } else {
329 QPointF start = points[0] * matrix;
330 moveTo(qt_real_to_fixed(start.x()), qt_real_to_fixed(start.y()));
331 for (int i=1; i<pointCount; ++i) {
332 QPointF pt = points[i] * matrix;
333 lineTo(qt_real_to_fixed(pt.x()), qt_real_to_fixed(pt.y()));
334 }
335 if (implicit_close)
336 lineTo(qt_real_to_fixed(start.x()), qt_real_to_fixed(start.y()));
337 }
338 end();
339 }
340
341 /*!
342 Convenience function for stroking an ellipse with bounding rect \a
343 rect. The \a matrix is used to transform the coordinates before
344 they enter the stroker.
345 */
strokeEllipse(const QRectF & rect,void * data,const QTransform & matrix)346 void QStrokerOps::strokeEllipse(const QRectF &rect, void *data, const QTransform &matrix)
347 {
348 int count = 0;
349 QPointF pts[12];
350 QPointF start = qt_curves_for_arc(rect, 0, -360, pts, &count);
351 Q_ASSERT(count == 12); // a perfect circle..
352
353 if (!matrix.isIdentity()) {
354 start = start * matrix;
355 for (int i=0; i<12; ++i) {
356 pts[i] = pts[i] * matrix;
357 }
358 }
359
360 setCurveThresholdFromTransform(QTransform());
361 begin(data);
362 moveTo(qt_real_to_fixed(start.x()), qt_real_to_fixed(start.y()));
363 for (int i=0; i<12; i+=3) {
364 cubicTo(qt_real_to_fixed(pts[i].x()), qt_real_to_fixed(pts[i].y()),
365 qt_real_to_fixed(pts[i+1].x()), qt_real_to_fixed(pts[i+1].y()),
366 qt_real_to_fixed(pts[i+2].x()), qt_real_to_fixed(pts[i+2].y()));
367 }
368 end();
369 }
370
371
QStroker()372 QStroker::QStroker()
373 : m_capStyle(SquareJoin), m_joinStyle(FlatJoin),
374 m_back1X(0), m_back1Y(0),
375 m_back2X(0), m_back2Y(0)
376 {
377 m_strokeWidth = qt_real_to_fixed(1);
378 m_miterLimit = qt_real_to_fixed(2);
379 }
380
~QStroker()381 QStroker::~QStroker()
382 {
383 }
384
capForJoinMode(LineJoinMode mode)385 Qt::PenCapStyle QStroker::capForJoinMode(LineJoinMode mode)
386 {
387 if (mode == FlatJoin) return Qt::FlatCap;
388 else if (mode == SquareJoin) return Qt::SquareCap;
389 else return Qt::RoundCap;
390 }
391
joinModeForCap(Qt::PenCapStyle style)392 QStroker::LineJoinMode QStroker::joinModeForCap(Qt::PenCapStyle style)
393 {
394 if (style == Qt::FlatCap) return FlatJoin;
395 else if (style == Qt::SquareCap) return SquareJoin;
396 else return RoundCap;
397 }
398
joinForJoinMode(LineJoinMode mode)399 Qt::PenJoinStyle QStroker::joinForJoinMode(LineJoinMode mode)
400 {
401 if (mode == FlatJoin) return Qt::BevelJoin;
402 else if (mode == MiterJoin) return Qt::MiterJoin;
403 else if (mode == SvgMiterJoin) return Qt::SvgMiterJoin;
404 else return Qt::RoundJoin;
405 }
406
joinModeForJoin(Qt::PenJoinStyle joinStyle)407 QStroker::LineJoinMode QStroker::joinModeForJoin(Qt::PenJoinStyle joinStyle)
408 {
409 if (joinStyle == Qt::BevelJoin) return FlatJoin;
410 else if (joinStyle == Qt::MiterJoin) return MiterJoin;
411 else if (joinStyle == Qt::SvgMiterJoin) return SvgMiterJoin;
412 else return RoundJoin;
413 }
414
415
416 /*!
417 This function is called to stroke the currently built up
418 subpath. The subpath is cleared when the function completes.
419 */
processCurrentSubpath()420 void QStroker::processCurrentSubpath()
421 {
422 Q_ASSERT(!m_elements.isEmpty());
423 Q_ASSERT(m_elements.first().type == QPainterPath::MoveToElement);
424 Q_ASSERT(m_elements.size() > 1);
425
426 QSubpathForwardIterator fwit(&m_elements);
427 QSubpathBackwardIterator bwit(&m_elements);
428
429 QLineF fwStartTangent, bwStartTangent;
430
431 bool fwclosed = qt_stroke_side(&fwit, this, false, &fwStartTangent);
432 bool bwclosed = qt_stroke_side(&bwit, this, !fwclosed, &bwStartTangent);
433
434 if (!bwclosed)
435 joinPoints(m_elements.at(0).x, m_elements.at(0).y, fwStartTangent, m_capStyle);
436 }
437
438
439 /*!
440 \internal
441 */
joinPoints(qfixed focal_x,qfixed focal_y,const QLineF & nextLine,LineJoinMode join)442 void QStroker::joinPoints(qfixed focal_x, qfixed focal_y, const QLineF &nextLine, LineJoinMode join)
443 {
444 #ifdef QPP_STROKE_DEBUG
445 printf(" -----> joinPoints: around=(%.0f, %.0f), next_p1=(%.0f, %.f) next_p2=(%.0f, %.f)\n",
446 qt_fixed_to_real(focal_x),
447 qt_fixed_to_real(focal_y),
448 nextLine.x1(), nextLine.y1(), nextLine.x2(), nextLine.y2());
449 #endif
450 // points connected already, don't join
451
452 #if !defined (QFIXED_26_6) && !defined (Q_FIXED_32_32)
453 if (qFuzzyCompare(m_back1X, nextLine.x1()) && qFuzzyCompare(m_back1Y, nextLine.y1()))
454 return;
455 #else
456 if (m_back1X == qt_real_to_fixed(nextLine.x1())
457 && m_back1Y == qt_real_to_fixed(nextLine.y1())) {
458 return;
459 }
460 #endif
461
462 if (join == FlatJoin) {
463 QLineF prevLine(qt_fixed_to_real(m_back2X), qt_fixed_to_real(m_back2Y),
464 qt_fixed_to_real(m_back1X), qt_fixed_to_real(m_back1Y));
465 QPointF isect;
466 QLineF::IntersectType type = prevLine.intersect(nextLine, &isect);
467 QLineF shortCut(prevLine.p2(), nextLine.p1());
468 qreal angle = shortCut.angleTo(prevLine);
469 if (type == QLineF::BoundedIntersection || (angle > 90 && !qFuzzyCompare(angle, (qreal)90))) {
470 emitLineTo(focal_x, focal_y);
471 emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1()));
472 return;
473 }
474 emitLineTo(qt_real_to_fixed(nextLine.x1()),
475 qt_real_to_fixed(nextLine.y1()));
476
477 } else {
478 QLineF prevLine(qt_fixed_to_real(m_back2X), qt_fixed_to_real(m_back2Y),
479 qt_fixed_to_real(m_back1X), qt_fixed_to_real(m_back1Y));
480
481 QPointF isect;
482 QLineF::IntersectType type = prevLine.intersect(nextLine, &isect);
483
484 if (join == MiterJoin) {
485 qreal appliedMiterLimit = qt_fixed_to_real(m_strokeWidth * m_miterLimit);
486
487 // If we are on the inside, do the short cut...
488 QLineF shortCut(prevLine.p2(), nextLine.p1());
489 qreal angle = shortCut.angleTo(prevLine);
490 if (type == QLineF::BoundedIntersection || (angle > 90 && !qFuzzyCompare(angle, (qreal)90))) {
491 emitLineTo(focal_x, focal_y);
492 emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1()));
493 return;
494 }
495 QLineF miterLine(QPointF(qt_fixed_to_real(m_back1X),
496 qt_fixed_to_real(m_back1Y)), isect);
497 if (type == QLineF::NoIntersection || miterLine.length() > appliedMiterLimit) {
498 QLineF l1(prevLine);
499 l1.setLength(appliedMiterLimit);
500 l1.translate(prevLine.dx(), prevLine.dy());
501
502 QLineF l2(nextLine);
503 l2.setLength(appliedMiterLimit);
504 l2.translate(-l2.dx(), -l2.dy());
505
506 emitLineTo(qt_real_to_fixed(l1.x2()), qt_real_to_fixed(l1.y2()));
507 emitLineTo(qt_real_to_fixed(l2.x1()), qt_real_to_fixed(l2.y1()));
508 emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1()));
509 } else {
510 emitLineTo(qt_real_to_fixed(isect.x()), qt_real_to_fixed(isect.y()));
511 emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1()));
512 }
513
514 } else if (join == SquareJoin) {
515 qfixed offset = m_strokeWidth / 2;
516
517 QLineF l1(prevLine);
518 l1.translate(l1.dx(), l1.dy());
519 l1.setLength(qt_fixed_to_real(offset));
520 QLineF l2(nextLine.p2(), nextLine.p1());
521 l2.translate(l2.dx(), l2.dy());
522 l2.setLength(qt_fixed_to_real(offset));
523 emitLineTo(qt_real_to_fixed(l1.x2()), qt_real_to_fixed(l1.y2()));
524 emitLineTo(qt_real_to_fixed(l2.x2()), qt_real_to_fixed(l2.y2()));
525 emitLineTo(qt_real_to_fixed(l2.x1()), qt_real_to_fixed(l2.y1()));
526
527 } else if (join == RoundJoin) {
528 qfixed offset = m_strokeWidth / 2;
529
530 QLineF shortCut(prevLine.p2(), nextLine.p1());
531 qreal angle = shortCut.angleTo(prevLine);
532 if (type == QLineF::BoundedIntersection || (angle > 90 && !qFuzzyCompare(angle, (qreal)90))) {
533 emitLineTo(focal_x, focal_y);
534 emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1()));
535 return;
536 }
537 qreal l1_on_x = adapted_angle_on_x(prevLine);
538 qreal l2_on_x = adapted_angle_on_x(nextLine);
539
540 qreal sweepLength = qAbs(l2_on_x - l1_on_x);
541
542 int point_count;
543 QPointF curves[15];
544
545 QPointF curve_start =
546 qt_curves_for_arc(QRectF(qt_fixed_to_real(focal_x - offset),
547 qt_fixed_to_real(focal_y - offset),
548 qt_fixed_to_real(offset * 2),
549 qt_fixed_to_real(offset * 2)),
550 l1_on_x + 90, -sweepLength,
551 curves, &point_count);
552
553 // // line to the beginning of the arc segment, (should not be needed).
554 // emitLineTo(qt_real_to_fixed(curve_start.x()), qt_real_to_fixed(curve_start.y()));
555 Q_UNUSED(curve_start);
556
557 for (int i=0; i<point_count; i+=3) {
558 emitCubicTo(qt_real_to_fixed(curves[i].x()),
559 qt_real_to_fixed(curves[i].y()),
560 qt_real_to_fixed(curves[i+1].x()),
561 qt_real_to_fixed(curves[i+1].y()),
562 qt_real_to_fixed(curves[i+2].x()),
563 qt_real_to_fixed(curves[i+2].y()));
564 }
565
566 // line to the end of the arc segment, (should also not be needed).
567 emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1()));
568
569 // Same as round join except we know its 180 degrees. Can also optimize this
570 // later based on the addEllipse logic
571 } else if (join == RoundCap) {
572 qfixed offset = m_strokeWidth / 2;
573
574 // first control line
575 QLineF l1 = prevLine;
576 l1.translate(l1.dx(), l1.dy());
577 l1.setLength(QT_PATH_KAPPA * offset);
578
579 // second control line, find through normal between prevLine and focal.
580 QLineF l2(qt_fixed_to_real(focal_x), qt_fixed_to_real(focal_y),
581 prevLine.x2(), prevLine.y2());
582 l2.translate(-l2.dy(), l2.dx());
583 l2.setLength(QT_PATH_KAPPA * offset);
584
585 emitCubicTo(qt_real_to_fixed(l1.x2()),
586 qt_real_to_fixed(l1.y2()),
587 qt_real_to_fixed(l2.x2()),
588 qt_real_to_fixed(l2.y2()),
589 qt_real_to_fixed(l2.x1()),
590 qt_real_to_fixed(l2.y1()));
591
592 // move so that it matches
593 l2 = QLineF(l2.x1(), l2.y1(), l2.x1()-l2.dx(), l2.y1()-l2.dy());
594
595 // last line is parallel to l1 so just shift it down.
596 l1.translate(nextLine.x1() - l1.x1(), nextLine.y1() - l1.y1());
597
598 emitCubicTo(qt_real_to_fixed(l2.x2()),
599 qt_real_to_fixed(l2.y2()),
600 qt_real_to_fixed(l1.x2()),
601 qt_real_to_fixed(l1.y2()),
602 qt_real_to_fixed(l1.x1()),
603 qt_real_to_fixed(l1.y1()));
604 } else if (join == SvgMiterJoin) {
605 QLineF shortCut(prevLine.p2(), nextLine.p1());
606 qreal angle = shortCut.angleTo(prevLine);
607 if (type == QLineF::BoundedIntersection || (angle > 90 && !qFuzzyCompare(angle, (qreal)90))) {
608 emitLineTo(focal_x, focal_y);
609 emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1()));
610 return;
611 }
612 QLineF miterLine(QPointF(qt_fixed_to_real(focal_x),
613 qt_fixed_to_real(focal_y)), isect);
614 if (type == QLineF::NoIntersection || miterLine.length() > qt_fixed_to_real(m_strokeWidth * m_miterLimit) / 2) {
615 emitLineTo(qt_real_to_fixed(nextLine.x1()),
616 qt_real_to_fixed(nextLine.y1()));
617 } else {
618 emitLineTo(qt_real_to_fixed(isect.x()), qt_real_to_fixed(isect.y()));
619 emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1()));
620 }
621 } else {
622 Q_ASSERT(!"QStroker::joinPoints(), bad join style...");
623 }
624 }
625 }
626
627
628 /*
629 Strokes a subpath side using the \a it as source. Results are put into
630 \a stroke. The function returns true if the subpath side was closed.
631 If \a capFirst is true, we will use capPoints instead of joinPoints to
632 connect the first segment, other segments will be joined using joinPoints.
633 This is to put capping in order...
634 */
qt_stroke_side(Iterator * it,QStroker * stroker,bool capFirst,QLineF * startTangent)635 template <class Iterator> bool qt_stroke_side(Iterator *it,
636 QStroker *stroker,
637 bool capFirst,
638 QLineF *startTangent)
639 {
640 // Used in CurveToElement section below.
641 const int MAX_OFFSET = 16;
642 QBezier offsetCurves[MAX_OFFSET];
643
644 Q_ASSERT(it->hasNext()); // The initaial move to
645 QStrokerOps::Element first_element = it->next();
646 Q_ASSERT(first_element.isMoveTo());
647
648 qfixed2d start = first_element;
649
650 #ifdef QPP_STROKE_DEBUG
651 qDebug(" -> (side) [%.2f, %.2f], startPos=%d",
652 qt_fixed_to_real(start.x),
653 qt_fixed_to_real(start.y));
654 #endif
655
656 qfixed2d prev = start;
657
658 bool first = true;
659
660 qfixed offset = stroker->strokeWidth() / 2;
661
662 while (it->hasNext()) {
663 QStrokerOps::Element e = it->next();
664
665 // LineToElement
666 if (e.isLineTo()) {
667 #ifdef QPP_STROKE_DEBUG
668 qDebug("\n ---> (side) lineto [%.2f, %.2f]", e.x, e.y);
669 #endif
670 QLineF line(qt_fixed_to_real(prev.x), qt_fixed_to_real(prev.y),
671 qt_fixed_to_real(e.x), qt_fixed_to_real(e.y));
672 if (line.p1() != line.p2()) {
673 QLineF normal = line.normalVector();
674 normal.setLength(offset);
675 line.translate(normal.dx(), normal.dy());
676
677 // If we are starting a new subpath, move to correct starting point.
678 if (first) {
679 if (capFirst)
680 stroker->joinPoints(prev.x, prev.y, line, stroker->capStyleMode());
681 else
682 stroker->emitMoveTo(qt_real_to_fixed(line.x1()), qt_real_to_fixed(line.y1()));
683 *startTangent = line;
684 first = false;
685 } else {
686 stroker->joinPoints(prev.x, prev.y, line, stroker->joinStyleMode());
687 }
688
689 // Add the stroke for this line.
690 stroker->emitLineTo(qt_real_to_fixed(line.x2()),
691 qt_real_to_fixed(line.y2()));
692 prev = e;
693 }
694
695 // CurveToElement
696 } else if (e.isCurveTo()) {
697 QStrokerOps::Element cp2 = it->next(); // control point 2
698 QStrokerOps::Element ep = it->next(); // end point
699
700 #ifdef QPP_STROKE_DEBUG
701 qDebug("\n ---> (side) cubicTo [%.2f, %.2f]",
702 qt_fixed_to_real(ep.x),
703 qt_fixed_to_real(ep.y));
704 #endif
705
706 QBezier bezier =
707 QBezier::fromPoints(QPointF(qt_fixed_to_real(prev.x), qt_fixed_to_real(prev.y)),
708 QPointF(qt_fixed_to_real(e.x), qt_fixed_to_real(e.y)),
709 QPointF(qt_fixed_to_real(cp2.x), qt_fixed_to_real(cp2.y)),
710 QPointF(qt_fixed_to_real(ep.x), qt_fixed_to_real(ep.y)));
711
712 int count = bezier.shifted(offsetCurves,
713 MAX_OFFSET,
714 offset,
715 stroker->curveThreshold());
716
717 if (count) {
718 // If we are starting a new subpath, move to correct starting point
719 QLineF tangent = bezier.startTangent();
720 tangent.translate(offsetCurves[0].pt1() - bezier.pt1());
721 if (first) {
722 QPointF pt = offsetCurves[0].pt1();
723 if (capFirst) {
724 stroker->joinPoints(prev.x, prev.y,
725 tangent,
726 stroker->capStyleMode());
727 } else {
728 stroker->emitMoveTo(qt_real_to_fixed(pt.x()),
729 qt_real_to_fixed(pt.y()));
730 }
731 *startTangent = tangent;
732 first = false;
733 } else {
734 stroker->joinPoints(prev.x, prev.y,
735 tangent,
736 stroker->joinStyleMode());
737 }
738
739 // Add these beziers
740 for (int i=0; i<count; ++i) {
741 QPointF cp1 = offsetCurves[i].pt2();
742 QPointF cp2 = offsetCurves[i].pt3();
743 QPointF ep = offsetCurves[i].pt4();
744 stroker->emitCubicTo(qt_real_to_fixed(cp1.x()), qt_real_to_fixed(cp1.y()),
745 qt_real_to_fixed(cp2.x()), qt_real_to_fixed(cp2.y()),
746 qt_real_to_fixed(ep.x()), qt_real_to_fixed(ep.y()));
747 }
748 }
749
750 prev = ep;
751 }
752 }
753
754 if (start == prev) {
755 // closed subpath, join first and last point
756 #ifdef QPP_STROKE_DEBUG
757 qDebug("\n ---> (side) closed subpath");
758 #endif
759 // don't join empty subpaths
760 if (!first)
761 stroker->joinPoints(prev.x, prev.y, *startTangent, stroker->joinStyleMode());
762 return true;
763 } else {
764 #ifdef QPP_STROKE_DEBUG
765 qDebug("\n ---> (side) open subpath");
766 #endif
767 return false;
768 }
769 }
770
771 /*!
772 \internal
773
774 For a given angle in the range [0 .. 90], finds the corresponding parameter t
775 of the prototype cubic bezier arc segment
776 b = fromPoints(QPointF(1, 0), QPointF(1, KAPPA), QPointF(KAPPA, 1), QPointF(0, 1));
777
778 From the bezier equation:
779 b.pointAt(t).x() = (1-t)^3 + t*(1-t)^2 + t^2*(1-t)*KAPPA
780 b.pointAt(t).y() = t*(1-t)^2 * KAPPA + t^2*(1-t) + t^3
781
782 Third degree coefficients:
783 b.pointAt(t).x() = at^3 + bt^2 + ct + d
784 where a = 2-3*KAPPA, b = 3*(KAPPA-1), c = 0, d = 1
785
786 b.pointAt(t).y() = at^3 + bt^2 + ct + d
787 where a = 3*KAPPA-2, b = 6*KAPPA+3, c = 3*KAPPA, d = 0
788
789 Newton's method to find the zero of a function:
790 given a function f(x) and initial guess x_0
791 x_1 = f(x_0) / f'(x_0)
792 x_2 = f(x_1) / f'(x_1)
793 etc...
794 */
795
qt_t_for_arc_angle(qreal angle)796 qreal qt_t_for_arc_angle(qreal angle)
797 {
798 if (qFuzzyIsNull(angle))
799 return 0;
800
801 if (qFuzzyCompare(angle, qreal(90)))
802 return 1;
803
804 qreal radians = Q_PI * angle / 180;
805 qreal cosAngle = qCos(radians);
806 qreal sinAngle = qSin(radians);
807
808 // initial guess
809 qreal tc = angle / 90;
810 // do some iterations of newton's method to approximate cosAngle
811 // finds the zero of the function b.pointAt(tc).x() - cosAngle
812 tc -= ((((2-3*QT_PATH_KAPPA) * tc + 3*(QT_PATH_KAPPA-1)) * tc) * tc + 1 - cosAngle) // value
813 / (((6-9*QT_PATH_KAPPA) * tc + 6*(QT_PATH_KAPPA-1)) * tc); // derivative
814 tc -= ((((2-3*QT_PATH_KAPPA) * tc + 3*(QT_PATH_KAPPA-1)) * tc) * tc + 1 - cosAngle) // value
815 / (((6-9*QT_PATH_KAPPA) * tc + 6*(QT_PATH_KAPPA-1)) * tc); // derivative
816
817 // initial guess
818 qreal ts = tc;
819 // do some iterations of newton's method to approximate sinAngle
820 // finds the zero of the function b.pointAt(tc).y() - sinAngle
821 ts -= ((((3*QT_PATH_KAPPA-2) * ts - 6*QT_PATH_KAPPA + 3) * ts + 3*QT_PATH_KAPPA) * ts - sinAngle)
822 / (((9*QT_PATH_KAPPA-6) * ts + 12*QT_PATH_KAPPA - 6) * ts + 3*QT_PATH_KAPPA);
823 ts -= ((((3*QT_PATH_KAPPA-2) * ts - 6*QT_PATH_KAPPA + 3) * ts + 3*QT_PATH_KAPPA) * ts - sinAngle)
824 / (((9*QT_PATH_KAPPA-6) * ts + 12*QT_PATH_KAPPA - 6) * ts + 3*QT_PATH_KAPPA);
825
826 // use the average of the t that best approximates cosAngle
827 // and the t that best approximates sinAngle
828 qreal t = 0.5 * (tc + ts);
829
830 #if 0
831 printf("angle: %f, t: %f\n", angle, t);
832 qreal a, b, c, d;
833 bezierCoefficients(t, a, b, c, d);
834 printf("cosAngle: %.10f, value: %.10f\n", cosAngle, a + b + c * QT_PATH_KAPPA);
835 printf("sinAngle: %.10f, value: %.10f\n", sinAngle, b * QT_PATH_KAPPA + c + d);
836 #endif
837
838 return t;
839 }
840
841 Q_GUI_EXPORT void qt_find_ellipse_coords(const QRectF &r, qreal angle, qreal length,
842 QPointF* startPoint, QPointF *endPoint);
843
844 /*!
845 \internal
846
847 Creates a number of curves for a given arc definition. The arc is
848 defined an arc along the ellipses that fits into \a rect starting
849 at \a startAngle and an arc length of \a sweepLength.
850
851 The function has three out parameters. The return value is the
852 starting point of the arc. The \a curves array represents the list
853 of cubicTo elements up to a maximum of \a point_count. There are of course
854 3 points pr curve.
855 */
qt_curves_for_arc(const QRectF & rect,qreal startAngle,qreal sweepLength,QPointF * curves,int * point_count)856 QPointF qt_curves_for_arc(const QRectF &rect, qreal startAngle, qreal sweepLength,
857 QPointF *curves, int *point_count)
858 {
859 Q_ASSERT(point_count);
860 Q_ASSERT(curves);
861
862 *point_count = 0;
863 if (qt_is_nan(rect.x()) || qt_is_nan(rect.y()) || qt_is_nan(rect.width()) || qt_is_nan(rect.height())
864 || qt_is_nan(startAngle) || qt_is_nan(sweepLength)) {
865 qWarning("QPainterPath::arcTo: Adding arc where a parameter is NaN, results are undefined");
866 return QPointF();
867 }
868
869 if (rect.isNull()) {
870 return QPointF();
871 }
872
873 qreal x = rect.x();
874 qreal y = rect.y();
875
876 qreal w = rect.width();
877 qreal w2 = rect.width() / 2;
878 qreal w2k = w2 * QT_PATH_KAPPA;
879
880 qreal h = rect.height();
881 qreal h2 = rect.height() / 2;
882 qreal h2k = h2 * QT_PATH_KAPPA;
883
884 QPointF points[16] =
885 {
886 // start point
887 QPointF(x + w, y + h2),
888
889 // 0 -> 270 degrees
890 QPointF(x + w, y + h2 + h2k),
891 QPointF(x + w2 + w2k, y + h),
892 QPointF(x + w2, y + h),
893
894 // 270 -> 180 degrees
895 QPointF(x + w2 - w2k, y + h),
896 QPointF(x, y + h2 + h2k),
897 QPointF(x, y + h2),
898
899 // 180 -> 90 degrees
900 QPointF(x, y + h2 - h2k),
901 QPointF(x + w2 - w2k, y),
902 QPointF(x + w2, y),
903
904 // 90 -> 0 degrees
905 QPointF(x + w2 + w2k, y),
906 QPointF(x + w, y + h2 - h2k),
907 QPointF(x + w, y + h2)
908 };
909
910 if (sweepLength > 360) sweepLength = 360;
911 else if (sweepLength < -360) sweepLength = -360;
912
913 // Special case fast paths
914 if (startAngle == 0.0) {
915 if (sweepLength == 360.0) {
916 for (int i = 11; i >= 0; --i)
917 curves[(*point_count)++] = points[i];
918 return points[12];
919 } else if (sweepLength == -360.0) {
920 for (int i = 1; i <= 12; ++i)
921 curves[(*point_count)++] = points[i];
922 return points[0];
923 }
924 }
925
926 int startSegment = int(qFloor(startAngle / 90));
927 int endSegment = int(qFloor((startAngle + sweepLength) / 90));
928
929 qreal startT = (startAngle - startSegment * 90) / 90;
930 qreal endT = (startAngle + sweepLength - endSegment * 90) / 90;
931
932 int delta = sweepLength > 0 ? 1 : -1;
933 if (delta < 0) {
934 startT = 1 - startT;
935 endT = 1 - endT;
936 }
937
938 // avoid empty start segment
939 if (qFuzzyIsNull(startT - qreal(1))) {
940 startT = 0;
941 startSegment += delta;
942 }
943
944 // avoid empty end segment
945 if (qFuzzyIsNull(endT)) {
946 endT = 1;
947 endSegment -= delta;
948 }
949
950 startT = qt_t_for_arc_angle(startT * 90);
951 endT = qt_t_for_arc_angle(endT * 90);
952
953 const bool splitAtStart = !qFuzzyIsNull(startT);
954 const bool splitAtEnd = !qFuzzyIsNull(endT - qreal(1));
955
956 const int end = endSegment + delta;
957
958 // empty arc?
959 if (startSegment == end) {
960 const int quadrant = 3 - ((startSegment % 4) + 4) % 4;
961 const int j = 3 * quadrant;
962 return delta > 0 ? points[j + 3] : points[j];
963 }
964
965 QPointF startPoint, endPoint;
966 qt_find_ellipse_coords(rect, startAngle, sweepLength, &startPoint, &endPoint);
967
968 for (int i = startSegment; i != end; i += delta) {
969 const int quadrant = 3 - ((i % 4) + 4) % 4;
970 const int j = 3 * quadrant;
971
972 QBezier b;
973 if (delta > 0)
974 b = QBezier::fromPoints(points[j + 3], points[j + 2], points[j + 1], points[j]);
975 else
976 b = QBezier::fromPoints(points[j], points[j + 1], points[j + 2], points[j + 3]);
977
978 // empty arc?
979 if (startSegment == endSegment && qFuzzyCompare(startT, endT))
980 return startPoint;
981
982 if (i == startSegment) {
983 if (i == endSegment && splitAtEnd)
984 b = b.bezierOnInterval(startT, endT);
985 else if (splitAtStart)
986 b = b.bezierOnInterval(startT, 1);
987 } else if (i == endSegment && splitAtEnd) {
988 b = b.bezierOnInterval(0, endT);
989 }
990
991 // push control points
992 curves[(*point_count)++] = b.pt2();
993 curves[(*point_count)++] = b.pt3();
994 curves[(*point_count)++] = b.pt4();
995 }
996
997 Q_ASSERT(*point_count > 0);
998 curves[*(point_count)-1] = endPoint;
999
1000 return startPoint;
1001 }
1002
1003
qdashstroker_moveTo(qfixed x,qfixed y,void * data)1004 static inline void qdashstroker_moveTo(qfixed x, qfixed y, void *data) {
1005 ((QStroker *) data)->moveTo(x, y);
1006 }
1007
qdashstroker_lineTo(qfixed x,qfixed y,void * data)1008 static inline void qdashstroker_lineTo(qfixed x, qfixed y, void *data) {
1009 ((QStroker *) data)->lineTo(x, y);
1010 }
1011
qdashstroker_cubicTo(qfixed,qfixed,qfixed,qfixed,qfixed,qfixed,void *)1012 static inline void qdashstroker_cubicTo(qfixed, qfixed, qfixed, qfixed, qfixed, qfixed, void *) {
1013 Q_ASSERT(0);
1014 // ((QStroker *) data)->cubicTo(c1x, c1y, c2x, c2y, ex, ey);
1015 }
1016
1017
1018 /*******************************************************************************
1019 * QDashStroker members
1020 */
QDashStroker(QStroker * stroker)1021 QDashStroker::QDashStroker(QStroker *stroker)
1022 : m_stroker(stroker), m_dashOffset(0), m_stroke_width(1), m_miter_limit(1)
1023 {
1024 if (m_stroker) {
1025 setMoveToHook(qdashstroker_moveTo);
1026 setLineToHook(qdashstroker_lineTo);
1027 setCubicToHook(qdashstroker_cubicTo);
1028 }
1029 }
1030
patternForStyle(Qt::PenStyle style)1031 QVector<qfixed> QDashStroker::patternForStyle(Qt::PenStyle style)
1032 {
1033 const qfixed space = 2;
1034 const qfixed dot = 1;
1035 const qfixed dash = 4;
1036
1037 QVector<qfixed> pattern;
1038
1039 switch (style) {
1040 case Qt::DashLine:
1041 pattern << dash << space;
1042 break;
1043 case Qt::DotLine:
1044 pattern << dot << space;
1045 break;
1046 case Qt::DashDotLine:
1047 pattern << dash << space << dot << space;
1048 break;
1049 case Qt::DashDotDotLine:
1050 pattern << dash << space << dot << space << dot << space;
1051 break;
1052 default:
1053 break;
1054 }
1055
1056 return pattern;
1057 }
1058
lineRectIntersectsRect(qfixed2d p1,qfixed2d p2,const qfixed2d & tl,const qfixed2d & br)1059 static inline bool lineRectIntersectsRect(qfixed2d p1, qfixed2d p2, const qfixed2d &tl, const qfixed2d &br)
1060 {
1061 return ((p1.x > tl.x || p2.x > tl.x) && (p1.x < br.x || p2.x < br.x)
1062 && (p1.y > tl.y || p2.y > tl.y) && (p1.y < br.y || p2.y < br.y));
1063 }
1064
1065 // If the line intersects the rectangle, this function will return true.
lineIntersectsRect(qfixed2d p1,qfixed2d p2,const qfixed2d & tl,const qfixed2d & br)1066 static bool lineIntersectsRect(qfixed2d p1, qfixed2d p2, const qfixed2d &tl, const qfixed2d &br)
1067 {
1068 if (!lineRectIntersectsRect(p1, p2, tl, br))
1069 return false;
1070 if (p1.x == p2.x || p1.y == p2.y)
1071 return true;
1072
1073 if (p1.y > p2.y)
1074 qSwap(p1, p2); // make p1 above p2
1075 qfixed2d u;
1076 qfixed2d v;
1077 qfixed2d w = {p2.x - p1.x, p2.y - p1.y};
1078 if (p1.x < p2.x) {
1079 // backslash
1080 u.x = tl.x - p1.x; u.y = br.y - p1.y;
1081 v.x = br.x - p1.x; v.y = tl.y - p1.y;
1082 } else {
1083 // slash
1084 u.x = tl.x - p1.x; u.y = tl.y - p1.y;
1085 v.x = br.x - p1.x; v.y = br.y - p1.y;
1086 }
1087 #if defined(QFIXED_IS_26_6) || defined(QFIXED_IS_16_16)
1088 qint64 val1 = qint64(u.x) * qint64(w.y) - qint64(u.y) * qint64(w.x);
1089 qint64 val2 = qint64(v.x) * qint64(w.y) - qint64(v.y) * qint64(w.x);
1090 return (val1 < 0 && val2 > 0) || (val1 > 0 && val2 < 0);
1091 #elif defined(QFIXED_IS_32_32)
1092 // Cannot do proper test because it may overflow.
1093 return true;
1094 #else
1095 qreal val1 = u.x * w.y - u.y * w.x;
1096 qreal val2 = v.x * w.y - v.y * w.x;
1097 return (val1 < 0 && val2 > 0) || (val1 > 0 && val2 < 0);
1098 #endif
1099 }
1100
processCurrentSubpath()1101 void QDashStroker::processCurrentSubpath()
1102 {
1103 int dashCount = qMin(m_dashPattern.size(), 32);
1104 qfixed dashes[32];
1105
1106 if (m_stroker) {
1107 m_customData = m_stroker;
1108 m_stroke_width = m_stroker->strokeWidth();
1109 m_miter_limit = m_stroker->miterLimit();
1110 }
1111
1112 qreal longestLength = 0;
1113 qreal sumLength = 0;
1114 for (int i=0; i<dashCount; ++i) {
1115 dashes[i] = qMax(m_dashPattern.at(i), qreal(0)) * m_stroke_width;
1116 sumLength += dashes[i];
1117 if (dashes[i] > longestLength)
1118 longestLength = dashes[i];
1119 }
1120
1121 if (qFuzzyIsNull(sumLength))
1122 return;
1123
1124 qreal invSumLength = qreal(1) / sumLength;
1125
1126 Q_ASSERT(dashCount > 0);
1127
1128 dashCount = dashCount & -2; // Round down to even number
1129
1130 int idash = 0; // Index to current dash
1131 qreal pos = 0; // The position on the curve, 0 <= pos <= path.length
1132 qreal elen = 0; // element length
1133 qreal doffset = m_dashOffset * m_stroke_width;
1134
1135 // make sure doffset is in range [0..sumLength)
1136 doffset -= qFloor(doffset * invSumLength) * sumLength;
1137
1138 while (doffset >= dashes[idash]) {
1139 doffset -= dashes[idash];
1140 if (++idash >= dashCount)
1141 idash = 0;
1142 }
1143
1144 qreal estart = 0; // The elements starting position
1145 qreal estop = 0; // The element stop position
1146
1147 QLineF cline;
1148
1149 QPainterPath dashPath;
1150
1151 QSubpathFlatIterator it(&m_elements, m_dashThreshold);
1152 qfixed2d prev = it.next();
1153
1154 bool clipping = !m_clip_rect.isEmpty();
1155 qfixed2d move_to_pos = prev;
1156 qfixed2d line_to_pos;
1157
1158 // Pad to avoid clipping the borders of thick pens.
1159 qfixed padding = qt_real_to_fixed(qMax(m_stroke_width, m_miter_limit) * longestLength);
1160 qfixed2d clip_tl = { qt_real_to_fixed(m_clip_rect.left()) - padding,
1161 qt_real_to_fixed(m_clip_rect.top()) - padding };
1162 qfixed2d clip_br = { qt_real_to_fixed(m_clip_rect.right()) + padding ,
1163 qt_real_to_fixed(m_clip_rect.bottom()) + padding };
1164
1165 bool hasMoveTo = false;
1166 while (it.hasNext()) {
1167 QStrokerOps::Element e = it.next();
1168
1169 Q_ASSERT(e.isLineTo());
1170 cline = QLineF(qt_fixed_to_real(prev.x),
1171 qt_fixed_to_real(prev.y),
1172 qt_fixed_to_real(e.x),
1173 qt_fixed_to_real(e.y));
1174 elen = cline.length();
1175
1176 estop = estart + elen;
1177
1178 bool done = pos >= estop;
1179
1180 if (clipping) {
1181 // Check if the entire line can be clipped away.
1182 if (!lineIntersectsRect(prev, e, clip_tl, clip_br)) {
1183 // Cut away full dash sequences.
1184 elen -= qFloor(elen * invSumLength) * sumLength;
1185 // Update dash offset.
1186 while (!done) {
1187 qreal dpos = pos + dashes[idash] - doffset - estart;
1188
1189 Q_ASSERT(dpos >= 0);
1190
1191 if (dpos > elen) { // dash extends this line
1192 doffset = dashes[idash] - (dpos - elen); // subtract the part already used
1193 pos = estop; // move pos to next path element
1194 done = true;
1195 } else { // Dash is on this line
1196 pos = dpos + estart;
1197 done = pos >= estop;
1198 if (++idash >= dashCount)
1199 idash = 0;
1200 doffset = 0; // full segment so no offset on next.
1201 }
1202 }
1203 hasMoveTo = false;
1204 move_to_pos = e;
1205 }
1206 }
1207
1208 // Dash away...
1209 while (!done) {
1210 QPointF p2;
1211
1212 bool has_offset = doffset > 0;
1213 bool evenDash = (idash & 1) == 0;
1214 qreal dpos = pos + dashes[idash] - doffset - estart;
1215
1216 Q_ASSERT(dpos >= 0);
1217
1218 if (dpos > elen) { // dash extends this line
1219 doffset = dashes[idash] - (dpos - elen); // subtract the part already used
1220 pos = estop; // move pos to next path element
1221 done = true;
1222 p2 = cline.p2();
1223 } else { // Dash is on this line
1224 p2 = cline.pointAt(dpos/elen);
1225 pos = dpos + estart;
1226 done = pos >= estop;
1227 if (++idash >= dashCount)
1228 idash = 0;
1229 doffset = 0; // full segment so no offset on next.
1230 }
1231
1232 if (evenDash) {
1233 line_to_pos.x = qt_real_to_fixed(p2.x());
1234 line_to_pos.y = qt_real_to_fixed(p2.y());
1235
1236 if (!clipping
1237 || lineRectIntersectsRect(move_to_pos, line_to_pos, clip_tl, clip_br))
1238 {
1239 // If we have an offset, we're continuing a dash
1240 // from a previous element and should only
1241 // continue the current dash, without starting a
1242 // new subpath.
1243 if (!has_offset || !hasMoveTo) {
1244 emitMoveTo(move_to_pos.x, move_to_pos.y);
1245 hasMoveTo = true;
1246 }
1247
1248 emitLineTo(line_to_pos.x, line_to_pos.y);
1249 } else {
1250 hasMoveTo = false;
1251 }
1252 move_to_pos = line_to_pos;
1253 } else {
1254 move_to_pos.x = qt_real_to_fixed(p2.x());
1255 move_to_pos.y = qt_real_to_fixed(p2.y());
1256 }
1257 }
1258
1259 // Shuffle to the next cycle...
1260 estart = estop;
1261 prev = e;
1262 }
1263
1264 }
1265
1266 QT_END_NAMESPACE
1267