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

Last change on this file since 769 was 769, checked in by Dmitry A. Kuminov, 15 years ago

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

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