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

source code of qtbase/src/gui/painting/qstroker.cpp