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