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