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
46QT_BEGIN_NAMESPACE
47
48// #define QPP_STROKE_DEBUG
49
50class QSubpathForwardIterator
51{
52public:
53 QSubpathForwardIterator(const QDataBuffer<QStrokerOps::Element> *path)
54 : m_path(path), m_pos(0) { }
55 inline int position() const { return m_pos; }
56 inline bool hasNext() const { return m_pos < m_path->size(); }
57 inline QStrokerOps::Element next() { Q_ASSERT(hasNext()); return m_path->at(i: m_pos++); }
58
59private:
60 const QDataBuffer<QStrokerOps::Element> *m_path;
61 int m_pos;
62};
63
64class QSubpathBackwardIterator
65{
66public:
67 QSubpathBackwardIterator(const QDataBuffer<QStrokerOps::Element> *path)
68 : m_path(path), m_pos(path->size() - 1) { }
69
70 inline int position() const { return m_pos; }
71
72 inline bool hasNext() const { return m_pos >= 0; }
73
74 inline QStrokerOps::Element next()
75 {
76 Q_ASSERT(hasNext());
77
78 QStrokerOps::Element ce = m_path->at(i: 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(i: 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(msg: "QSubpathReverseIterator::next: Case %d unhandled", ce.type);
105 break;
106 }
107 --m_pos;
108
109 return ce;
110 }
111
112private:
113 const QDataBuffer<QStrokerOps::Element> *m_path;
114 int m_pos;
115};
116
117class QSubpathFlatIterator
118{
119public:
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
123 inline bool hasNext() const { return m_curve_index >= 0 || m_pos < m_path->size(); }
124
125 QStrokerOps::Element next()
126 {
127 Q_ASSERT(hasNext());
128
129 if (m_curve_index >= 0) {
130 QStrokerOps::Element e = { .type: 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(i: 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(p1: 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 p2: QPointF(qt_fixed_to_real(e.x),
148 qt_fixed_to_real(e.y)),
149 p3: 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 p4: 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(bezier_flattening_threshold: m_curve_threshold);
153 m_curve_index = 1;
154 e.type = QPainterPath::LineToElement;
155 e.x = m_curve.at(i: 0).x();
156 e.y = m_curve.at(i: 0).y();
157 m_pos += 2;
158 }
159 Q_ASSERT(e.isLineTo() || e.isMoveTo());
160 ++m_pos;
161 return e;
162 }
163
164private:
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
172template <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 */
179static inline qreal adapted_angle_on_x(const QLineF &line)
180{
181 return QLineF(0, 0, 1, 0).angleTo(l: line);
182}
183
184QStrokerOps::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
195QStrokerOps::~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*/
207void 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*/
218void 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*/
236void 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: ++i);
257 const QPainterPath::Element &ep = path.elementAt(i: ++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: ++i)) * matrix;
281 QPointF ep = ((QPointF) path.elementAt(i: ++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
307void 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(customData: 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*/
340void 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, startAngle: 0, sweepLength: -360, controlPoints: pts, point_count: &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(customData: 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
366QStroker::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
376QStroker::~QStroker()
377{
378}
379
380Qt::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
387QStroker::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
394Qt::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
402QStroker::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*/
415void 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(it: &fwit, stroker: this, capFirst: false, startTangent: &fwStartTangent);
427 bool bwclosed = qt_stroke_side(it: &bwit, stroker: this, capFirst: !fwclosed, startTangent: &bwStartTangent);
428
429 if (!bwclosed && !fwStartTangent.isNull())
430 joinPoints(x: m_elements.at(i: 0).x, y: m_elements.at(i: 0).y, nextLine: fwStartTangent, join: m_capStyle);
431}
432
433
434/*!
435 \internal
436*/
437void 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(p1: m_back1X, p2: nextLine.x1()) && qFuzzyCompare(p1: m_back1Y, p2: 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(l: nextLine, intersectionPoint: &isect);
460
461 if (join == FlatJoin) {
462 QLineF shortCut(prevLine.p2(), nextLine.p1());
463 qreal angle = shortCut.angleTo(l: prevLine);
464 if (type == QLineF::BoundedIntersection || (angle > 90 && !qFuzzyCompare(p1: angle, p2: (qreal)90))) {
465 emitLineTo(x: focal_x, y: 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(l: prevLine);
479 if (type == QLineF::BoundedIntersection || (angle > 90 && !qFuzzyCompare(p1: angle, p2: (qreal)90))) {
480 emitLineTo(x: focal_x, y: 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(adx: prevLine.dx(), ady: prevLine.dy());
490
491 QLineF l2(nextLine);
492 l2.setLength(appliedMiterLimit);
493 l2.translate(adx: -l2.dx(), ady: -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(p1: QPointF(prevLine.dx(), prevLine.dy()), p2: 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(adx: l1.dx(), ady: l1.dy());
512 l1.setLength(qt_fixed_to_real(offset));
513 QLineF l2(nextLine.p2(), nextLine.p1());
514 l2.translate(adx: l2.dx(), ady: 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(l: prevLine);
525 if ((type == QLineF::BoundedIntersection || (angle > qreal(90.01))) && nextLine.length() > offset) {
526 emitLineTo(x: focal_x, y: 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(line: prevLine);
531 qreal l2_on_x = adapted_angle_on_x(line: nextLine);
532
533 qreal sweepLength = qAbs(t: 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(rect: 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 startAngle: l1_on_x + 90, sweepLength: -sweepLength,
544 controlPoints: curves, point_count: &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(p1: QPointF(prevLine.dx(), prevLine.dy()), p2: 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(adx: l1.dx(), ady: 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(adx: -l2.dy(), ady: 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(adx: nextLine.x1() - l1.x1(), ady: 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(l: prevLine);
604 if (type == QLineF::BoundedIntersection || (angle > 90 && !qFuzzyCompare(p1: angle, p2: (qreal)90))) {
605 emitLineTo(x: focal_x, y: 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*/
632template <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(adx: normal.dx(), ady: 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(focal_x: prev.x, focal_y: prev.y, nextLine: line, join: 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(focal_x: prev.x, focal_y: prev.y, nextLine: line, join: 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(p1: QPointF(qt_fixed_to_real(prev.x), qt_fixed_to_real(prev.y)),
705 p2: QPointF(qt_fixed_to_real(e.x), qt_fixed_to_real(e.y)),
706 p3: QPointF(qt_fixed_to_real(cp2.x), qt_fixed_to_real(cp2.y)),
707 p4: QPointF(qt_fixed_to_real(ep.x), qt_fixed_to_real(ep.y)));
708 int count = bezier.shifted(curveSegments: offsetCurves,
709 maxSegmets: MAX_OFFSET,
710 offset,
711 threshold: 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(point: offsetCurves[0].pt1() - bezier.pt1());
717 if (first) {
718 QPointF pt = offsetCurves[0].pt1();
719 if (capFirst) {
720 stroker->joinPoints(focal_x: prev.x, focal_y: prev.y,
721 nextLine: tangent,
722 join: 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(focal_x: prev.x, focal_y: prev.y,
731 nextLine: tangent,
732 join: 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(focal_x: prev.x, focal_y: prev.y, nextLine: *startTangent, join: 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
792qreal qt_t_for_arc_angle(qreal angle)
793{
794 if (qFuzzyIsNull(d: angle))
795 return 0;
796
797 if (qFuzzyCompare(p1: angle, p2: qreal(90)))
798 return 1;
799
800 qreal radians = qDegreesToRadians(degrees: angle);
801 qreal cosAngle = qCos(v: radians);
802 qreal sinAngle = qSin(v: 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
837Q_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*/
852QPointF 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(d: rect.x()) || qt_is_nan(d: rect.y()) || qt_is_nan(d: rect.width()) || qt_is_nan(d: rect.height())
860 || qt_is_nan(d: startAngle) || qt_is_nan(d: sweepLength)) {
861 qWarning(msg: "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(v: startAngle / 90));
923 int endSegment = int(qFloor(v: (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(d: startT - qreal(1))) {
936 startT = 0;
937 startSegment += delta;
938 }
939
940 // avoid empty end segment
941 if (qFuzzyIsNull(d: endT)) {
942 endT = 1;
943 endSegment -= delta;
944 }
945
946 startT = qt_t_for_arc_angle(angle: startT * 90);
947 endT = qt_t_for_arc_angle(angle: endT * 90);
948
949 const bool splitAtStart = !qFuzzyIsNull(d: startT);
950 const bool splitAtEnd = !qFuzzyIsNull(d: 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(r: rect, angle: startAngle, length: sweepLength, startPoint: &startPoint, endPoint: &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(p1: points[j + 3], p2: points[j + 2], p3: points[j + 1], p4: points[j]);
971 else
972 b = QBezier::fromPoints(p1: points[j], p2: points[j + 1], p3: points[j + 2], p4: points[j + 3]);
973
974 // empty arc?
975 if (startSegment == endSegment && qFuzzyCompare(p1: startT, p2: endT))
976 return startPoint;
977
978 if (i == startSegment) {
979 if (i == endSegment && splitAtEnd)
980 b = b.bezierOnInterval(t0: startT, t1: endT);
981 else if (splitAtStart)
982 b = b.bezierOnInterval(t0: startT, t1: 1);
983 } else if (i == endSegment && splitAtEnd) {
984 b = b.bezierOnInterval(t0: 0, t1: 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
1000static inline void qdashstroker_moveTo(qfixed x, qfixed y, void *data) {
1001 ((QStroker *) data)->moveTo(x, y);
1002}
1003
1004static inline void qdashstroker_lineTo(qfixed x, qfixed y, void *data) {
1005 ((QStroker *) data)->lineTo(x, y);
1006}
1007
1008static 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 */
1017QDashStroker::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
1027QDashStroker::~QDashStroker()
1028{
1029}
1030
1031QVector<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
1059static 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.
1066static 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(value1&: p1, value2&: p2); // make p1 above p2
1075 qfixed2d u;
1076 qfixed2d v;
1077 qfixed2d w = {.x: p2.x - p1.x, .y: 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
1101void QDashStroker::processCurrentSubpath()
1102{
1103 int dashCount = qMin(a: m_dashPattern.size(), b: 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(a: m_dashPattern.at(i), b: qreal(0)) * m_stroke_width;
1116 sumLength += dashes[i];
1117 if (dashes[i] > longestLength)
1118 longestLength = dashes[i];
1119 }
1120
1121 if (qFuzzyIsNull(d: 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(v: 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 // Check if the entire line should be clipped away or simplified
1183 bool clipIt = clipping && !lineIntersectsRect(p1: prev, p2: e, tl: clip_tl, br: clip_br);
1184 bool skipDashing = elen * invSumLength > repetitionLimit();
1185 int maxDashes = dashCount;
1186 if (skipDashing || clipIt) {
1187 // Cut away full dash sequences.
1188 elen -= std::floor(x: elen * invSumLength) * sumLength;
1189 // Update dash offset.
1190 while (!done) {
1191 qreal dpos = pos + dashes[idash] - doffset - estart;
1192
1193 Q_ASSERT(dpos >= 0);
1194
1195 if (dpos > elen) { // dash extends this line
1196 doffset = dashes[idash] - (dpos - elen); // subtract the part already used
1197 pos = estop; // move pos to next path element
1198 done = true;
1199 } else { // Dash is on this line
1200 pos = --maxDashes > 0 ? dpos + estart : estop;
1201 done = pos >= estop;
1202 if (++idash >= dashCount)
1203 idash = 0;
1204 doffset = 0; // full segment so no offset on next.
1205 }
1206 }
1207 if (clipIt) {
1208 hasMoveTo = false;
1209 } else {
1210 // skip costly dashing, just draw solid line
1211 if (!hasMoveTo) {
1212 emitMoveTo(x: move_to_pos.x, y: move_to_pos.y);
1213 hasMoveTo = true;
1214 }
1215 emitLineTo(x: e.x, y: e.y);
1216 }
1217 move_to_pos = e;
1218 }
1219
1220 // Dash away...
1221 while (!done) {
1222 QPointF p2;
1223
1224 bool has_offset = doffset > 0;
1225 bool evenDash = (idash & 1) == 0;
1226 qreal dpos = pos + dashes[idash] - doffset - estart;
1227
1228 Q_ASSERT(dpos >= 0);
1229
1230 if (dpos > elen) { // dash extends this line
1231 doffset = dashes[idash] - (dpos - elen); // subtract the part already used
1232 pos = estop; // move pos to next path element
1233 done = true;
1234 p2 = cline.p2();
1235 } else { // Dash is on this line
1236 p2 = cline.pointAt(t: dpos/elen);
1237 pos = dpos + estart;
1238 done = pos >= estop;
1239 if (++idash >= dashCount)
1240 idash = 0;
1241 doffset = 0; // full segment so no offset on next.
1242 }
1243
1244 if (evenDash) {
1245 line_to_pos.x = qt_real_to_fixed(p2.x());
1246 line_to_pos.y = qt_real_to_fixed(p2.y());
1247
1248 if (!clipping
1249 || lineRectIntersectsRect(p1: move_to_pos, p2: line_to_pos, tl: clip_tl, br: clip_br))
1250 {
1251 // If we have an offset, we're continuing a dash
1252 // from a previous element and should only
1253 // continue the current dash, without starting a
1254 // new subpath.
1255 if (!has_offset || !hasMoveTo) {
1256 emitMoveTo(x: move_to_pos.x, y: move_to_pos.y);
1257 hasMoveTo = true;
1258 }
1259
1260 emitLineTo(x: line_to_pos.x, y: line_to_pos.y);
1261 } else {
1262 hasMoveTo = false;
1263 }
1264 move_to_pos = line_to_pos;
1265 } else {
1266 move_to_pos.x = qt_real_to_fixed(p2.x());
1267 move_to_pos.y = qt_real_to_fixed(p2.y());
1268 }
1269 }
1270
1271 // Shuffle to the next cycle...
1272 estart = estop;
1273 prev = e;
1274 }
1275
1276}
1277
1278QT_END_NAMESPACE
1279

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