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