source: trunk/src/gui/painting/qstroker.cpp@ 900

Last change on this file since 900 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

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