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