1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtGui module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "private/qstroker_p.h"
43 #include "private/qbezier_p.h"
44 #include "private/qmath_p.h"
46 #include "qtransform.h"
51 // #define QPP_STROKE_DEBUG
53 class QSubpathForwardIterator
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++); }
63 const QDataBuffer<QStrokerOps::Element> *m_path;
67 class QSubpathBackwardIterator
70 QSubpathBackwardIterator(const QDataBuffer<QStrokerOps::Element> *path)
71 : m_path(path), m_pos(path->size() - 1) { }
73 inline int position() const { return m_pos; }
75 inline bool hasNext() const { return m_pos >= 0; }
77 inline QStrokerOps::Element next()
81 QStrokerOps::Element ce = m_path->at(m_pos); // current element
83 if (m_pos == m_path->size() - 1) {
85 ce.type = QPainterPath::MoveToElement;
89 const QStrokerOps::Element &pe = m_path->at(m_pos + 1); // previous element
92 case QPainterPath::LineToElement:
93 ce.type = QPainterPath::LineToElement;
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;
103 case QPainterPath::CurveToElement:
104 ce.type = QPainterPath::CurveToDataElement;
107 qWarning("QSubpathReverseIterator::next: Case %d unhandled", ce.type);
116 const QDataBuffer<QStrokerOps::Element> *m_path;
120 class QSubpathFlatIterator
123 QSubpathFlatIterator(const QDataBuffer<QStrokerOps::Element> *path, qreal threshold)
124 : m_path(path), m_pos(0), m_curve_index(-1), m_curve_threshold(threshold) { }
126 inline bool hasNext() const { return m_curve_index >= 0 || m_pos < m_path->size(); }
128 QStrokerOps::Element next()
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())
138 if (m_curve_index >= m_curve.size())
143 QStrokerOps::Element e = m_path->at(m_pos);
146 Q_ASSERT(m_pos < m_path->size());
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);
157 e.type = QPainterPath::LineToElement;
158 e.x = m_curve.at(0).x();
159 e.y = m_curve.at(0).y();
162 Q_ASSERT(e.isLineTo() || e.isMoveTo());
168 const QDataBuffer<QStrokerOps::Element> *m_path;
172 qreal m_curve_threshold;
175 template <class Iterator> bool qt_stroke_side(Iterator *it, QStroker *stroker,
176 bool capFirst, QLineF *startTangent);
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.
182 static inline qreal adapted_angle_on_x(const QLineF &line)
184 qreal angle = line.angle(QLineF(0, 0, 1, 0));
190 QStrokerOps::QStrokerOps()
192 , m_curveThreshold(qt_real_to_fixed(0.25))
193 , m_dashThreshold(qt_real_to_fixed(0.25))
201 QStrokerOps::~QStrokerOps()
206 Prepares the stroker. Call this function once before starting a
207 stroke by calling moveTo, lineTo or cubicTo.
209 The \a customData is passed back through that callback functions
210 and can be used by the user to for instance maintain state
213 void QStrokerOps::begin(void *customData)
215 m_customData = customData;
221 Finishes the stroke. Call this function once when an entire
222 primitive has been stroked.
224 void QStrokerOps::end()
226 if (m_elements.size() > 1)
227 processCurrentSubpath();
232 Convenience function that decomposes \a path into begin(),
233 moveTo(), lineTo(), curevTo() and end() calls.
235 The \a customData parameter is used in the callback functions
237 The \a matrix is used to transform the points before input to the
242 void QStrokerOps::strokePath(const QPainterPath &path, void *customData, const QTransform &matrix)
247 setCurveThresholdFromTransform(QTransform());
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);
254 case QPainterPath::MoveToElement:
255 moveTo(qt_real_to_fixed(e.x), qt_real_to_fixed(e.y));
257 case QPainterPath::LineToElement:
258 lineTo(qt_real_to_fixed(e.x), qt_real_to_fixed(e.y));
260 case QPainterPath::CurveToElement:
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));
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;
278 case QPainterPath::MoveToElement:
279 moveTo(qt_real_to_fixed(pt.x()), qt_real_to_fixed(pt.y()));
281 case QPainterPath::LineToElement:
282 lineTo(qt_real_to_fixed(pt.x()), qt_real_to_fixed(pt.y()));
284 case QPainterPath::CurveToElement:
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()));
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.
307 The \a matrix is used to transform the points before they enter the
313 void QStrokerOps::strokePolygon(const QPointF *points, int pointCount, bool implicit_close,
314 void *data, const QTransform &matrix)
319 setCurveThresholdFromTransform(QTransform());
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()));
327 lineTo(qt_real_to_fixed(points[0].x()), qt_real_to_fixed(points[0].y()));
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()));
336 lineTo(qt_real_to_fixed(start.x()), qt_real_to_fixed(start.y()));
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.
346 void QStrokerOps::strokeEllipse(const QRectF &rect, void *data, const QTransform &matrix)
350 QPointF start = qt_curves_for_arc(rect, 0, -360, pts, &count);
351 Q_ASSERT(count == 12); // a perfect circle..
353 if (!matrix.isIdentity()) {
354 start = start * matrix;
355 for (int i=0; i<12; ++i) {
356 pts[i] = pts[i] * matrix;
360 setCurveThresholdFromTransform(QTransform());
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()));
373 : m_capStyle(SquareJoin), m_joinStyle(FlatJoin),
374 m_back1X(0), m_back1Y(0),
375 m_back2X(0), m_back2Y(0)
377 m_strokeWidth = qt_real_to_fixed(1);
378 m_miterLimit = qt_real_to_fixed(2);
381 QStroker::~QStroker()
385 Qt::PenCapStyle QStroker::capForJoinMode(LineJoinMode mode)
387 if (mode == FlatJoin) return Qt::FlatCap;
388 else if (mode == SquareJoin) return Qt::SquareCap;
389 else return Qt::RoundCap;
392 QStroker::LineJoinMode QStroker::joinModeForCap(Qt::PenCapStyle style)
394 if (style == Qt::FlatCap) return FlatJoin;
395 else if (style == Qt::SquareCap) return SquareJoin;
396 else return RoundCap;
399 Qt::PenJoinStyle QStroker::joinForJoinMode(LineJoinMode mode)
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;
407 QStroker::LineJoinMode QStroker::joinModeForJoin(Qt::PenJoinStyle joinStyle)
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;
417 This function is called to stroke the currently built up
418 subpath. The subpath is cleared when the function completes.
420 void QStroker::processCurrentSubpath()
422 Q_ASSERT(!m_elements.isEmpty());
423 Q_ASSERT(m_elements.first().type == QPainterPath::MoveToElement);
424 Q_ASSERT(m_elements.size() > 1);
426 QSubpathForwardIterator fwit(&m_elements);
427 QSubpathBackwardIterator bwit(&m_elements);
429 QLineF fwStartTangent, bwStartTangent;
431 bool fwclosed = qt_stroke_side(&fwit, this, false, &fwStartTangent);
432 bool bwclosed = qt_stroke_side(&bwit, this, !fwclosed, &bwStartTangent);
435 joinPoints(m_elements.at(0).x, m_elements.at(0).y, fwStartTangent, m_capStyle);
442 void QStroker::joinPoints(qfixed focal_x, qfixed focal_y, const QLineF &nextLine, LineJoinMode join)
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());
450 // points connected already, don't join
452 #if !defined (QFIXED_26_6) && !defined (Q_FIXED_32_32)
453 if (qFuzzyCompare(m_back1X, nextLine.x1()) && qFuzzyCompare(m_back1Y, nextLine.y1()))
456 if (m_back1X == qt_real_to_fixed(nextLine.x1())
457 && m_back1Y == qt_real_to_fixed(nextLine.y1())) {
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));
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()));
474 emitLineTo(qt_real_to_fixed(nextLine.x1()),
475 qt_real_to_fixed(nextLine.y1()));
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));
482 QLineF::IntersectType type = prevLine.intersect(nextLine, &isect);
484 if (join == MiterJoin) {
485 qreal appliedMiterLimit = qt_fixed_to_real(m_strokeWidth * m_miterLimit);
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()));
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) {
499 l1.setLength(appliedMiterLimit);
500 l1.translate(prevLine.dx(), prevLine.dy());
503 l2.setLength(appliedMiterLimit);
504 l2.translate(-l2.dx(), -l2.dy());
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()));
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()));
514 } else if (join == SquareJoin) {
515 qfixed offset = m_strokeWidth / 2;
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()));
527 } else if (join == RoundJoin) {
528 qfixed offset = m_strokeWidth / 2;
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()));
537 qreal l1_on_x = adapted_angle_on_x(prevLine);
538 qreal l2_on_x = adapted_angle_on_x(nextLine);
540 qreal sweepLength = qAbs(l2_on_x - l1_on_x);
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);
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 Q_UNUSED(curve_start);
557 for (int i=0; i<point_count; i+=3) {
558 emitCubicTo(qt_real_to_fixed(curves[i].x()),
559 qt_real_to_fixed(curves[i].y()),
560 qt_real_to_fixed(curves[i+1].x()),
561 qt_real_to_fixed(curves[i+1].y()),
562 qt_real_to_fixed(curves[i+2].x()),
563 qt_real_to_fixed(curves[i+2].y()));
566 // line to the end of the arc segment, (should also not be needed).
567 emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1()));
569 // Same as round join except we know its 180 degrees. Can also optimize this
570 // later based on the addEllipse logic
571 } else if (join == RoundCap) {
572 qfixed offset = m_strokeWidth / 2;
574 // first control line
575 QLineF l1 = prevLine;
576 l1.translate(l1.dx(), l1.dy());
577 l1.setLength(QT_PATH_KAPPA * offset);
579 // second control line, find through normal between prevLine and focal.
580 QLineF l2(qt_fixed_to_real(focal_x), qt_fixed_to_real(focal_y),
581 prevLine.x2(), prevLine.y2());
582 l2.translate(-l2.dy(), l2.dx());
583 l2.setLength(QT_PATH_KAPPA * offset);
585 emitCubicTo(qt_real_to_fixed(l1.x2()),
586 qt_real_to_fixed(l1.y2()),
587 qt_real_to_fixed(l2.x2()),
588 qt_real_to_fixed(l2.y2()),
589 qt_real_to_fixed(l2.x1()),
590 qt_real_to_fixed(l2.y1()));
592 // move so that it matches
593 l2 = QLineF(l2.x1(), l2.y1(), l2.x1()-l2.dx(), l2.y1()-l2.dy());
595 // last line is parallel to l1 so just shift it down.
596 l1.translate(nextLine.x1() - l1.x1(), nextLine.y1() - l1.y1());
598 emitCubicTo(qt_real_to_fixed(l2.x2()),
599 qt_real_to_fixed(l2.y2()),
600 qt_real_to_fixed(l1.x2()),
601 qt_real_to_fixed(l1.y2()),
602 qt_real_to_fixed(l1.x1()),
603 qt_real_to_fixed(l1.y1()));
604 } else if (join == SvgMiterJoin) {
605 QLineF shortCut(prevLine.p2(), nextLine.p1());
606 qreal angle = shortCut.angleTo(prevLine);
607 if (type == QLineF::BoundedIntersection || (angle > 90 && !qFuzzyCompare(angle, (qreal)90))) {
608 emitLineTo(focal_x, focal_y);
609 emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1()));
612 QLineF miterLine(QPointF(qt_fixed_to_real(focal_x),
613 qt_fixed_to_real(focal_y)), isect);
614 if (type == QLineF::NoIntersection || miterLine.length() > qt_fixed_to_real(m_strokeWidth * m_miterLimit) / 2) {
615 emitLineTo(qt_real_to_fixed(nextLine.x1()),
616 qt_real_to_fixed(nextLine.y1()));
618 emitLineTo(qt_real_to_fixed(isect.x()), qt_real_to_fixed(isect.y()));
619 emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1()));
622 Q_ASSERT(!"QStroker::joinPoints(), bad join style...");
629 Strokes a subpath side using the \a it as source. Results are put into
630 \a stroke. The function returns true if the subpath side was closed.
631 If \a capFirst is true, we will use capPoints instead of joinPoints to
632 connect the first segment, other segments will be joined using joinPoints.
633 This is to put capping in order...
635 template <class Iterator> bool qt_stroke_side(Iterator *it,
638 QLineF *startTangent)
640 // Used in CurveToElement section below.
641 const int MAX_OFFSET = 16;
642 QBezier offsetCurves[MAX_OFFSET];
644 Q_ASSERT(it->hasNext()); // The initaial move to
645 QStrokerOps::Element first_element = it->next();
646 Q_ASSERT(first_element.isMoveTo());
648 qfixed2d start = first_element;
650 #ifdef QPP_STROKE_DEBUG
651 qDebug(" -> (side) [%.2f, %.2f], startPos=%d",
652 qt_fixed_to_real(start.x),
653 qt_fixed_to_real(start.y));
656 qfixed2d prev = start;
660 qfixed offset = stroker->strokeWidth() / 2;
662 while (it->hasNext()) {
663 QStrokerOps::Element e = it->next();
667 #ifdef QPP_STROKE_DEBUG
668 qDebug("\n ---> (side) lineto [%.2f, %.2f]", e.x, e.y);
670 QLineF line(qt_fixed_to_real(prev.x), qt_fixed_to_real(prev.y),
671 qt_fixed_to_real(e.x), qt_fixed_to_real(e.y));
672 if (line.p1() != line.p2()) {
673 QLineF normal = line.normalVector();
674 normal.setLength(offset);
675 line.translate(normal.dx(), normal.dy());
677 // If we are starting a new subpath, move to correct starting point.
680 stroker->joinPoints(prev.x, prev.y, line, stroker->capStyleMode());
682 stroker->emitMoveTo(qt_real_to_fixed(line.x1()), qt_real_to_fixed(line.y1()));
683 *startTangent = line;
686 stroker->joinPoints(prev.x, prev.y, line, stroker->joinStyleMode());
689 // Add the stroke for this line.
690 stroker->emitLineTo(qt_real_to_fixed(line.x2()),
691 qt_real_to_fixed(line.y2()));
696 } else if (e.isCurveTo()) {
697 QStrokerOps::Element cp2 = it->next(); // control point 2
698 QStrokerOps::Element ep = it->next(); // end point
700 #ifdef QPP_STROKE_DEBUG
701 qDebug("\n ---> (side) cubicTo [%.2f, %.2f]",
702 qt_fixed_to_real(ep.x),
703 qt_fixed_to_real(ep.y));
707 QBezier::fromPoints(QPointF(qt_fixed_to_real(prev.x), qt_fixed_to_real(prev.y)),
708 QPointF(qt_fixed_to_real(e.x), qt_fixed_to_real(e.y)),
709 QPointF(qt_fixed_to_real(cp2.x), qt_fixed_to_real(cp2.y)),
710 QPointF(qt_fixed_to_real(ep.x), qt_fixed_to_real(ep.y)));
712 int count = bezier.shifted(offsetCurves,
715 stroker->curveThreshold());
718 // If we are starting a new subpath, move to correct starting point
719 QLineF tangent = bezier.startTangent();
720 tangent.translate(offsetCurves[0].pt1() - bezier.pt1());
722 QPointF pt = offsetCurves[0].pt1();
724 stroker->joinPoints(prev.x, prev.y,
726 stroker->capStyleMode());
728 stroker->emitMoveTo(qt_real_to_fixed(pt.x()),
729 qt_real_to_fixed(pt.y()));
731 *startTangent = tangent;
734 stroker->joinPoints(prev.x, prev.y,
736 stroker->joinStyleMode());
740 for (int i=0; i<count; ++i) {
741 QPointF cp1 = offsetCurves[i].pt2();
742 QPointF cp2 = offsetCurves[i].pt3();
743 QPointF ep = offsetCurves[i].pt4();
744 stroker->emitCubicTo(qt_real_to_fixed(cp1.x()), qt_real_to_fixed(cp1.y()),
745 qt_real_to_fixed(cp2.x()), qt_real_to_fixed(cp2.y()),
746 qt_real_to_fixed(ep.x()), qt_real_to_fixed(ep.y()));
755 // closed subpath, join first and last point
756 #ifdef QPP_STROKE_DEBUG
757 qDebug("\n ---> (side) closed subpath");
759 stroker->joinPoints(prev.x, prev.y, *startTangent, stroker->joinStyleMode());
762 #ifdef QPP_STROKE_DEBUG
763 qDebug("\n ---> (side) open subpath");
772 For a given angle in the range [0 .. 90], finds the corresponding parameter t
773 of the prototype cubic bezier arc segment
774 b = fromPoints(QPointF(1, 0), QPointF(1, KAPPA), QPointF(KAPPA, 1), QPointF(0, 1));
776 From the bezier equation:
777 b.pointAt(t).x() = (1-t)^3 + t*(1-t)^2 + t^2*(1-t)*KAPPA
778 b.pointAt(t).y() = t*(1-t)^2 * KAPPA + t^2*(1-t) + t^3
780 Third degree coefficients:
781 b.pointAt(t).x() = at^3 + bt^2 + ct + d
782 where a = 2-3*KAPPA, b = 3*(KAPPA-1), c = 0, d = 1
784 b.pointAt(t).y() = at^3 + bt^2 + ct + d
785 where a = 3*KAPPA-2, b = 6*KAPPA+3, c = 3*KAPPA, d = 0
787 Newton's method to find the zero of a function:
788 given a function f(x) and initial guess x_0
789 x_1 = f(x_0) / f'(x_0)
790 x_2 = f(x_1) / f'(x_1)
794 qreal qt_t_for_arc_angle(qreal angle)
796 if (qFuzzyIsNull(angle))
799 if (qFuzzyCompare(angle, qreal(90)))
802 qreal radians = Q_PI * angle / 180;
803 qreal cosAngle = qCos(radians);
804 qreal sinAngle = qSin(radians);
807 qreal tc = angle / 90;
808 // do some iterations of newton's method to approximate cosAngle
809 // finds the zero of the function b.pointAt(tc).x() - cosAngle
810 tc -= ((((2-3*QT_PATH_KAPPA) * tc + 3*(QT_PATH_KAPPA-1)) * tc) * tc + 1 - cosAngle) // value
811 / (((6-9*QT_PATH_KAPPA) * tc + 6*(QT_PATH_KAPPA-1)) * tc); // derivative
812 tc -= ((((2-3*QT_PATH_KAPPA) * tc + 3*(QT_PATH_KAPPA-1)) * tc) * tc + 1 - cosAngle) // value
813 / (((6-9*QT_PATH_KAPPA) * tc + 6*(QT_PATH_KAPPA-1)) * tc); // derivative
817 // do some iterations of newton's method to approximate sinAngle
818 // finds the zero of the function b.pointAt(tc).y() - sinAngle
819 ts -= ((((3*QT_PATH_KAPPA-2) * ts - 6*QT_PATH_KAPPA + 3) * ts + 3*QT_PATH_KAPPA) * ts - sinAngle)
820 / (((9*QT_PATH_KAPPA-6) * ts + 12*QT_PATH_KAPPA - 6) * ts + 3*QT_PATH_KAPPA);
821 ts -= ((((3*QT_PATH_KAPPA-2) * ts - 6*QT_PATH_KAPPA + 3) * ts + 3*QT_PATH_KAPPA) * ts - sinAngle)
822 / (((9*QT_PATH_KAPPA-6) * ts + 12*QT_PATH_KAPPA - 6) * ts + 3*QT_PATH_KAPPA);
824 // use the average of the t that best approximates cosAngle
825 // and the t that best approximates sinAngle
826 qreal t = 0.5 * (tc + ts);
829 printf("angle: %f, t: %f\n", angle, t);
831 bezierCoefficients(t, a, b, c, d);
832 printf("cosAngle: %.10f, value: %.10f\n", cosAngle, a + b + c * QT_PATH_KAPPA);
833 printf("sinAngle: %.10f, value: %.10f\n", sinAngle, b * QT_PATH_KAPPA + c + d);
839 Q_GUI_EXPORT void qt_find_ellipse_coords(const QRectF &r, qreal angle, qreal length,
840 QPointF* startPoint, QPointF *endPoint);
845 Creates a number of curves for a given arc definition. The arc is
846 defined an arc along the ellipses that fits into \a rect starting
847 at \a startAngle and an arc length of \a sweepLength.
849 The function has three out parameters. The return value is the
850 starting point of the arc. The \a curves array represents the list
851 of cubicTo elements up to a maximum of \a point_count. There are of course
854 QPointF qt_curves_for_arc(const QRectF &rect, qreal startAngle, qreal sweepLength,
855 QPointF *curves, int *point_count)
857 Q_ASSERT(point_count);
861 if (qt_is_nan(rect.x()) || qt_is_nan(rect.y()) || qt_is_nan(rect.width()) || qt_is_nan(rect.height())
862 || qt_is_nan(startAngle) || qt_is_nan(sweepLength)) {
863 qWarning("QPainterPath::arcTo: Adding arc where a parameter is NaN, results are undefined");
874 qreal w = rect.width();
875 qreal w2 = rect.width() / 2;
876 qreal w2k = w2 * QT_PATH_KAPPA;
878 qreal h = rect.height();
879 qreal h2 = rect.height() / 2;
880 qreal h2k = h2 * QT_PATH_KAPPA;
885 QPointF(x + w, y + h2),
888 QPointF(x + w, y + h2 + h2k),
889 QPointF(x + w2 + w2k, y + h),
890 QPointF(x + w2, y + h),
892 // 270 -> 180 degrees
893 QPointF(x + w2 - w2k, y + h),
894 QPointF(x, y + h2 + h2k),
898 QPointF(x, y + h2 - h2k),
899 QPointF(x + w2 - w2k, y),
903 QPointF(x + w2 + w2k, y),
904 QPointF(x + w, y + h2 - h2k),
905 QPointF(x + w, y + h2)
908 if (sweepLength > 360) sweepLength = 360;
909 else if (sweepLength < -360) sweepLength = -360;
911 // Special case fast paths
912 if (startAngle == 0.0) {
913 if (sweepLength == 360.0) {
914 for (int i = 11; i >= 0; --i)
915 curves[(*point_count)++] = points[i];
917 } else if (sweepLength == -360.0) {
918 for (int i = 1; i <= 12; ++i)
919 curves[(*point_count)++] = points[i];
924 int startSegment = int(qFloor(startAngle / 90));
925 int endSegment = int(qFloor((startAngle + sweepLength) / 90));
927 qreal startT = (startAngle - startSegment * 90) / 90;
928 qreal endT = (startAngle + sweepLength - endSegment * 90) / 90;
930 int delta = sweepLength > 0 ? 1 : -1;
936 // avoid empty start segment
937 if (qFuzzyIsNull(startT - qreal(1))) {
939 startSegment += delta;
942 // avoid empty end segment
943 if (qFuzzyIsNull(endT)) {
948 startT = qt_t_for_arc_angle(startT * 90);
949 endT = qt_t_for_arc_angle(endT * 90);
951 const bool splitAtStart = !qFuzzyIsNull(startT);
952 const bool splitAtEnd = !qFuzzyIsNull(endT - qreal(1));
954 const int end = endSegment + delta;
957 if (startSegment == end) {
958 const int quadrant = 3 - ((startSegment % 4) + 4) % 4;
959 const int j = 3 * quadrant;
960 return delta > 0 ? points[j + 3] : points[j];
963 QPointF startPoint, endPoint;
964 qt_find_ellipse_coords(rect, startAngle, sweepLength, &startPoint, &endPoint);
966 for (int i = startSegment; i != end; i += delta) {
967 const int quadrant = 3 - ((i % 4) + 4) % 4;
968 const int j = 3 * quadrant;
972 b = QBezier::fromPoints(points[j + 3], points[j + 2], points[j + 1], points[j]);
974 b = QBezier::fromPoints(points[j], points[j + 1], points[j + 2], points[j + 3]);
977 if (startSegment == endSegment && qFuzzyCompare(startT, endT))
980 if (i == startSegment) {
981 if (i == endSegment && splitAtEnd)
982 b = b.bezierOnInterval(startT, endT);
983 else if (splitAtStart)
984 b = b.bezierOnInterval(startT, 1);
985 } else if (i == endSegment && splitAtEnd) {
986 b = b.bezierOnInterval(0, endT);
989 // push control points
990 curves[(*point_count)++] = b.pt2();
991 curves[(*point_count)++] = b.pt3();
992 curves[(*point_count)++] = b.pt4();
995 Q_ASSERT(*point_count > 0);
996 curves[*(point_count)-1] = endPoint;
1002 static inline void qdashstroker_moveTo(qfixed x, qfixed y, void *data) {
1003 ((QStroker *) data)->moveTo(x, y);
1006 static inline void qdashstroker_lineTo(qfixed x, qfixed y, void *data) {
1007 ((QStroker *) data)->lineTo(x, y);
1010 static inline void qdashstroker_cubicTo(qfixed, qfixed, qfixed, qfixed, qfixed, qfixed, void *) {
1012 // ((QStroker *) data)->cubicTo(c1x, c1y, c2x, c2y, ex, ey);
1016 /*******************************************************************************
1017 * QDashStroker members
1019 QDashStroker::QDashStroker(QStroker *stroker)
1020 : m_stroker(stroker), m_dashOffset(0), m_stroke_width(1), m_miter_limit(1)
1023 setMoveToHook(qdashstroker_moveTo);
1024 setLineToHook(qdashstroker_lineTo);
1025 setCubicToHook(qdashstroker_cubicTo);
1029 QVector<qfixed> QDashStroker::patternForStyle(Qt::PenStyle style)
1031 const qfixed space = 2;
1032 const qfixed dot = 1;
1033 const qfixed dash = 4;
1035 QVector<qfixed> pattern;
1039 pattern << dash << space;
1042 pattern << dot << space;
1044 case Qt::DashDotLine:
1045 pattern << dash << space << dot << space;
1047 case Qt::DashDotDotLine:
1048 pattern << dash << space << dot << space << dot << space;
1057 static inline bool lineRectIntersectsRect(qfixed2d p1, qfixed2d p2, const qfixed2d &tl, const qfixed2d &br)
1059 return ((p1.x > tl.x || p2.x > tl.x) && (p1.x < br.x || p2.x < br.x)
1060 && (p1.y > tl.y || p2.y > tl.y) && (p1.y < br.y || p2.y < br.y));
1063 // If the line intersects the rectangle, this function will return true.
1064 static bool lineIntersectsRect(qfixed2d p1, qfixed2d p2, const qfixed2d &tl, const qfixed2d &br)
1066 if (!lineRectIntersectsRect(p1, p2, tl, br))
1068 if (p1.x == p2.x || p1.y == p2.y)
1072 qSwap(p1, p2); // make p1 above p2
1075 qfixed2d w = {p2.x - p1.x, p2.y - p1.y};
1078 u.x = tl.x - p1.x; u.y = br.y - p1.y;
1079 v.x = br.x - p1.x; v.y = tl.y - p1.y;
1082 u.x = tl.x - p1.x; u.y = tl.y - p1.y;
1083 v.x = br.x - p1.x; v.y = br.y - p1.y;
1085 #if defined(QFIXED_IS_26_6) || defined(QFIXED_IS_16_16)
1086 qint64 val1 = qint64(u.x) * qint64(w.y) - qint64(u.y) * qint64(w.x);
1087 qint64 val2 = qint64(v.x) * qint64(w.y) - qint64(v.y) * qint64(w.x);
1088 return (val1 < 0 && val2 > 0) || (val1 > 0 && val2 < 0);
1089 #elif defined(QFIXED_IS_32_32)
1090 // Cannot do proper test because it may overflow.
1093 qreal val1 = u.x * w.y - u.y * w.x;
1094 qreal val2 = v.x * w.y - v.y * w.x;
1095 return (val1 < 0 && val2 > 0) || (val1 > 0 && val2 < 0);
1099 void QDashStroker::processCurrentSubpath()
1101 int dashCount = qMin(m_dashPattern.size(), 32);
1105 m_customData = m_stroker;
1106 m_stroke_width = m_stroker->strokeWidth();
1107 m_miter_limit = m_stroker->miterLimit();
1110 qreal longestLength = 0;
1111 qreal sumLength = 0;
1112 for (int i=0; i<dashCount; ++i) {
1113 dashes[i] = qMax(m_dashPattern.at(i), qreal(0)) * m_stroke_width;
1114 sumLength += dashes[i];
1115 if (dashes[i] > longestLength)
1116 longestLength = dashes[i];
1119 if (qFuzzyIsNull(sumLength))
1122 qreal invSumLength = qreal(1) / sumLength;
1124 Q_ASSERT(dashCount > 0);
1126 dashCount = dashCount & -2; // Round down to even number
1128 int idash = 0; // Index to current dash
1129 qreal pos = 0; // The position on the curve, 0 <= pos <= path.length
1130 qreal elen = 0; // element length
1131 qreal doffset = m_dashOffset * m_stroke_width;
1133 // make sure doffset is in range [0..sumLength)
1134 doffset -= qFloor(doffset * invSumLength) * sumLength;
1136 while (doffset >= dashes[idash]) {
1137 doffset -= dashes[idash];
1138 if (++idash >= dashCount)
1142 qreal estart = 0; // The elements starting position
1143 qreal estop = 0; // The element stop position
1147 QPainterPath dashPath;
1149 QSubpathFlatIterator it(&m_elements, m_dashThreshold);
1150 qfixed2d prev = it.next();
1152 bool clipping = !m_clip_rect.isEmpty();
1153 qfixed2d move_to_pos = prev;
1154 qfixed2d line_to_pos;
1156 // Pad to avoid clipping the borders of thick pens.
1157 qfixed padding = qt_real_to_fixed(qMax(m_stroke_width, m_miter_limit) * longestLength);
1158 qfixed2d clip_tl = { qt_real_to_fixed(m_clip_rect.left()) - padding,
1159 qt_real_to_fixed(m_clip_rect.top()) - padding };
1160 qfixed2d clip_br = { qt_real_to_fixed(m_clip_rect.right()) + padding ,
1161 qt_real_to_fixed(m_clip_rect.bottom()) + padding };
1163 bool hasMoveTo = false;
1164 while (it.hasNext()) {
1165 QStrokerOps::Element e = it.next();
1167 Q_ASSERT(e.isLineTo());
1168 cline = QLineF(qt_fixed_to_real(prev.x),
1169 qt_fixed_to_real(prev.y),
1170 qt_fixed_to_real(e.x),
1171 qt_fixed_to_real(e.y));
1172 elen = cline.length();
1174 estop = estart + elen;
1176 bool done = pos >= estop;
1179 // Check if the entire line can be clipped away.
1180 if (!lineIntersectsRect(prev, e, clip_tl, clip_br)) {
1181 // Cut away full dash sequences.
1182 elen -= qFloor(elen * invSumLength) * sumLength;
1183 // Update dash offset.
1185 qreal dpos = pos + dashes[idash] - doffset - estart;
1187 Q_ASSERT(dpos >= 0);
1189 if (dpos > elen) { // dash extends this line
1190 doffset = dashes[idash] - (dpos - elen); // subtract the part already used
1191 pos = estop; // move pos to next path element
1193 } else { // Dash is on this line
1194 pos = dpos + estart;
1195 done = pos >= estop;
1196 if (++idash >= dashCount)
1198 doffset = 0; // full segment so no offset on next.
1210 bool has_offset = doffset > 0;
1211 bool evenDash = (idash & 1) == 0;
1212 qreal dpos = pos + dashes[idash] - doffset - estart;
1214 Q_ASSERT(dpos >= 0);
1216 if (dpos > elen) { // dash extends this line
1217 doffset = dashes[idash] - (dpos - elen); // subtract the part already used
1218 pos = estop; // move pos to next path element
1221 } else { // Dash is on this line
1222 p2 = cline.pointAt(dpos/elen);
1223 pos = dpos + estart;
1224 done = pos >= estop;
1225 if (++idash >= dashCount)
1227 doffset = 0; // full segment so no offset on next.
1231 line_to_pos.x = qt_real_to_fixed(p2.x());
1232 line_to_pos.y = qt_real_to_fixed(p2.y());
1235 || lineRectIntersectsRect(move_to_pos, line_to_pos, clip_tl, clip_br))
1237 // If we have an offset, we're continuing a dash
1238 // from a previous element and should only
1239 // continue the current dash, without starting a
1241 if (!has_offset || !hasMoveTo) {
1242 emitMoveTo(move_to_pos.x, move_to_pos.y);
1246 emitLineTo(line_to_pos.x, line_to_pos.y);
1250 move_to_pos = line_to_pos;
1252 move_to_pos.x = qt_real_to_fixed(p2.x());
1253 move_to_pos.y = qt_real_to_fixed(p2.y());
1257 // Shuffle to the next cycle...