1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtGui module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** 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 // don't join empty subpaths
761 stroker->joinPoints(prev.x, prev.y, *startTangent, stroker->joinStyleMode());
764 #ifdef QPP_STROKE_DEBUG
765 qDebug("\n ---> (side) open subpath");
774 For a given angle in the range [0 .. 90], finds the corresponding parameter t
775 of the prototype cubic bezier arc segment
776 b = fromPoints(QPointF(1, 0), QPointF(1, KAPPA), QPointF(KAPPA, 1), QPointF(0, 1));
778 From the bezier equation:
779 b.pointAt(t).x() = (1-t)^3 + t*(1-t)^2 + t^2*(1-t)*KAPPA
780 b.pointAt(t).y() = t*(1-t)^2 * KAPPA + t^2*(1-t) + t^3
782 Third degree coefficients:
783 b.pointAt(t).x() = at^3 + bt^2 + ct + d
784 where a = 2-3*KAPPA, b = 3*(KAPPA-1), c = 0, d = 1
786 b.pointAt(t).y() = at^3 + bt^2 + ct + d
787 where a = 3*KAPPA-2, b = 6*KAPPA+3, c = 3*KAPPA, d = 0
789 Newton's method to find the zero of a function:
790 given a function f(x) and initial guess x_0
791 x_1 = f(x_0) / f'(x_0)
792 x_2 = f(x_1) / f'(x_1)
796 qreal qt_t_for_arc_angle(qreal angle)
798 if (qFuzzyIsNull(angle))
801 if (qFuzzyCompare(angle, qreal(90)))
804 qreal radians = Q_PI * angle / 180;
805 qreal cosAngle = qCos(radians);
806 qreal sinAngle = qSin(radians);
809 qreal tc = angle / 90;
810 // do some iterations of newton's method to approximate cosAngle
811 // finds the zero of the function b.pointAt(tc).x() - cosAngle
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
814 tc -= ((((2-3*QT_PATH_KAPPA) * tc + 3*(QT_PATH_KAPPA-1)) * tc) * tc + 1 - cosAngle) // value
815 / (((6-9*QT_PATH_KAPPA) * tc + 6*(QT_PATH_KAPPA-1)) * tc); // derivative
819 // do some iterations of newton's method to approximate sinAngle
820 // finds the zero of the function b.pointAt(tc).y() - sinAngle
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);
823 ts -= ((((3*QT_PATH_KAPPA-2) * ts - 6*QT_PATH_KAPPA + 3) * ts + 3*QT_PATH_KAPPA) * ts - sinAngle)
824 / (((9*QT_PATH_KAPPA-6) * ts + 12*QT_PATH_KAPPA - 6) * ts + 3*QT_PATH_KAPPA);
826 // use the average of the t that best approximates cosAngle
827 // and the t that best approximates sinAngle
828 qreal t = 0.5 * (tc + ts);
831 printf("angle: %f, t: %f\n", angle, t);
833 bezierCoefficients(t, a, b, c, d);
834 printf("cosAngle: %.10f, value: %.10f\n", cosAngle, a + b + c * QT_PATH_KAPPA);
835 printf("sinAngle: %.10f, value: %.10f\n", sinAngle, b * QT_PATH_KAPPA + c + d);
841 Q_GUI_EXPORT void qt_find_ellipse_coords(const QRectF &r, qreal angle, qreal length,
842 QPointF* startPoint, QPointF *endPoint);
847 Creates a number of curves for a given arc definition. The arc is
848 defined an arc along the ellipses that fits into \a rect starting
849 at \a startAngle and an arc length of \a sweepLength.
851 The function has three out parameters. The return value is the
852 starting point of the arc. The \a curves array represents the list
853 of cubicTo elements up to a maximum of \a point_count. There are of course
856 QPointF qt_curves_for_arc(const QRectF &rect, qreal startAngle, qreal sweepLength,
857 QPointF *curves, int *point_count)
859 Q_ASSERT(point_count);
863 if (qt_is_nan(rect.x()) || qt_is_nan(rect.y()) || qt_is_nan(rect.width()) || qt_is_nan(rect.height())
864 || qt_is_nan(startAngle) || qt_is_nan(sweepLength)) {
865 qWarning("QPainterPath::arcTo: Adding arc where a parameter is NaN, results are undefined");
876 qreal w = rect.width();
877 qreal w2 = rect.width() / 2;
878 qreal w2k = w2 * QT_PATH_KAPPA;
880 qreal h = rect.height();
881 qreal h2 = rect.height() / 2;
882 qreal h2k = h2 * QT_PATH_KAPPA;
887 QPointF(x + w, y + h2),
890 QPointF(x + w, y + h2 + h2k),
891 QPointF(x + w2 + w2k, y + h),
892 QPointF(x + w2, y + h),
894 // 270 -> 180 degrees
895 QPointF(x + w2 - w2k, y + h),
896 QPointF(x, y + h2 + h2k),
900 QPointF(x, y + h2 - h2k),
901 QPointF(x + w2 - w2k, y),
905 QPointF(x + w2 + w2k, y),
906 QPointF(x + w, y + h2 - h2k),
907 QPointF(x + w, y + h2)
910 if (sweepLength > 360) sweepLength = 360;
911 else if (sweepLength < -360) sweepLength = -360;
913 // Special case fast paths
914 if (startAngle == 0.0) {
915 if (sweepLength == 360.0) {
916 for (int i = 11; i >= 0; --i)
917 curves[(*point_count)++] = points[i];
919 } else if (sweepLength == -360.0) {
920 for (int i = 1; i <= 12; ++i)
921 curves[(*point_count)++] = points[i];
926 int startSegment = int(qFloor(startAngle / 90));
927 int endSegment = int(qFloor((startAngle + sweepLength) / 90));
929 qreal startT = (startAngle - startSegment * 90) / 90;
930 qreal endT = (startAngle + sweepLength - endSegment * 90) / 90;
932 int delta = sweepLength > 0 ? 1 : -1;
938 // avoid empty start segment
939 if (qFuzzyIsNull(startT - qreal(1))) {
941 startSegment += delta;
944 // avoid empty end segment
945 if (qFuzzyIsNull(endT)) {
950 startT = qt_t_for_arc_angle(startT * 90);
951 endT = qt_t_for_arc_angle(endT * 90);
953 const bool splitAtStart = !qFuzzyIsNull(startT);
954 const bool splitAtEnd = !qFuzzyIsNull(endT - qreal(1));
956 const int end = endSegment + delta;
959 if (startSegment == end) {
960 const int quadrant = 3 - ((startSegment % 4) + 4) % 4;
961 const int j = 3 * quadrant;
962 return delta > 0 ? points[j + 3] : points[j];
965 QPointF startPoint, endPoint;
966 qt_find_ellipse_coords(rect, startAngle, sweepLength, &startPoint, &endPoint);
968 for (int i = startSegment; i != end; i += delta) {
969 const int quadrant = 3 - ((i % 4) + 4) % 4;
970 const int j = 3 * quadrant;
974 b = QBezier::fromPoints(points[j + 3], points[j + 2], points[j + 1], points[j]);
976 b = QBezier::fromPoints(points[j], points[j + 1], points[j + 2], points[j + 3]);
979 if (startSegment == endSegment && qFuzzyCompare(startT, endT))
982 if (i == startSegment) {
983 if (i == endSegment && splitAtEnd)
984 b = b.bezierOnInterval(startT, endT);
985 else if (splitAtStart)
986 b = b.bezierOnInterval(startT, 1);
987 } else if (i == endSegment && splitAtEnd) {
988 b = b.bezierOnInterval(0, endT);
991 // push control points
992 curves[(*point_count)++] = b.pt2();
993 curves[(*point_count)++] = b.pt3();
994 curves[(*point_count)++] = b.pt4();
997 Q_ASSERT(*point_count > 0);
998 curves[*(point_count)-1] = endPoint;
1004 static inline void qdashstroker_moveTo(qfixed x, qfixed y, void *data) {
1005 ((QStroker *) data)->moveTo(x, y);
1008 static inline void qdashstroker_lineTo(qfixed x, qfixed y, void *data) {
1009 ((QStroker *) data)->lineTo(x, y);
1012 static inline void qdashstroker_cubicTo(qfixed, qfixed, qfixed, qfixed, qfixed, qfixed, void *) {
1014 // ((QStroker *) data)->cubicTo(c1x, c1y, c2x, c2y, ex, ey);
1018 /*******************************************************************************
1019 * QDashStroker members
1021 QDashStroker::QDashStroker(QStroker *stroker)
1022 : m_stroker(stroker), m_dashOffset(0), m_stroke_width(1), m_miter_limit(1)
1025 setMoveToHook(qdashstroker_moveTo);
1026 setLineToHook(qdashstroker_lineTo);
1027 setCubicToHook(qdashstroker_cubicTo);
1031 QDashStroker::~QDashStroker()
1035 QVector<qfixed> QDashStroker::patternForStyle(Qt::PenStyle style)
1037 const qfixed space = 2;
1038 const qfixed dot = 1;
1039 const qfixed dash = 4;
1041 QVector<qfixed> pattern;
1045 pattern << dash << space;
1048 pattern << dot << space;
1050 case Qt::DashDotLine:
1051 pattern << dash << space << dot << space;
1053 case Qt::DashDotDotLine:
1054 pattern << dash << space << dot << space << dot << space;
1063 static inline bool lineRectIntersectsRect(qfixed2d p1, qfixed2d p2, const qfixed2d &tl, const qfixed2d &br)
1065 return ((p1.x > tl.x || p2.x > tl.x) && (p1.x < br.x || p2.x < br.x)
1066 && (p1.y > tl.y || p2.y > tl.y) && (p1.y < br.y || p2.y < br.y));
1069 // If the line intersects the rectangle, this function will return true.
1070 static bool lineIntersectsRect(qfixed2d p1, qfixed2d p2, const qfixed2d &tl, const qfixed2d &br)
1072 if (!lineRectIntersectsRect(p1, p2, tl, br))
1074 if (p1.x == p2.x || p1.y == p2.y)
1078 qSwap(p1, p2); // make p1 above p2
1081 qfixed2d w = {p2.x - p1.x, p2.y - p1.y};
1084 u.x = tl.x - p1.x; u.y = br.y - p1.y;
1085 v.x = br.x - p1.x; v.y = tl.y - p1.y;
1088 u.x = tl.x - p1.x; u.y = tl.y - p1.y;
1089 v.x = br.x - p1.x; v.y = br.y - p1.y;
1091 #if defined(QFIXED_IS_26_6) || defined(QFIXED_IS_16_16)
1092 qint64 val1 = qint64(u.x) * qint64(w.y) - qint64(u.y) * qint64(w.x);
1093 qint64 val2 = qint64(v.x) * qint64(w.y) - qint64(v.y) * qint64(w.x);
1094 return (val1 < 0 && val2 > 0) || (val1 > 0 && val2 < 0);
1095 #elif defined(QFIXED_IS_32_32)
1096 // Cannot do proper test because it may overflow.
1099 qreal val1 = u.x * w.y - u.y * w.x;
1100 qreal val2 = v.x * w.y - v.y * w.x;
1101 return (val1 < 0 && val2 > 0) || (val1 > 0 && val2 < 0);
1105 void QDashStroker::processCurrentSubpath()
1107 int dashCount = qMin(m_dashPattern.size(), 32);
1111 m_customData = m_stroker;
1112 m_stroke_width = m_stroker->strokeWidth();
1113 m_miter_limit = m_stroker->miterLimit();
1116 qreal longestLength = 0;
1117 qreal sumLength = 0;
1118 for (int i=0; i<dashCount; ++i) {
1119 dashes[i] = qMax(m_dashPattern.at(i), qreal(0)) * m_stroke_width;
1120 sumLength += dashes[i];
1121 if (dashes[i] > longestLength)
1122 longestLength = dashes[i];
1125 if (qFuzzyIsNull(sumLength))
1128 qreal invSumLength = qreal(1) / sumLength;
1130 Q_ASSERT(dashCount > 0);
1132 dashCount = dashCount & -2; // Round down to even number
1134 int idash = 0; // Index to current dash
1135 qreal pos = 0; // The position on the curve, 0 <= pos <= path.length
1136 qreal elen = 0; // element length
1137 qreal doffset = m_dashOffset * m_stroke_width;
1139 // make sure doffset is in range [0..sumLength)
1140 doffset -= qFloor(doffset * invSumLength) * sumLength;
1142 while (doffset >= dashes[idash]) {
1143 doffset -= dashes[idash];
1144 if (++idash >= dashCount)
1148 qreal estart = 0; // The elements starting position
1149 qreal estop = 0; // The element stop position
1153 QPainterPath dashPath;
1155 QSubpathFlatIterator it(&m_elements, m_dashThreshold);
1156 qfixed2d prev = it.next();
1158 bool clipping = !m_clip_rect.isEmpty();
1159 qfixed2d move_to_pos = prev;
1160 qfixed2d line_to_pos;
1162 // Pad to avoid clipping the borders of thick pens.
1163 qfixed padding = qt_real_to_fixed(qMax(m_stroke_width, m_miter_limit) * longestLength);
1164 qfixed2d clip_tl = { qt_real_to_fixed(m_clip_rect.left()) - padding,
1165 qt_real_to_fixed(m_clip_rect.top()) - padding };
1166 qfixed2d clip_br = { qt_real_to_fixed(m_clip_rect.right()) + padding ,
1167 qt_real_to_fixed(m_clip_rect.bottom()) + padding };
1169 bool hasMoveTo = false;
1170 while (it.hasNext()) {
1171 QStrokerOps::Element e = it.next();
1173 Q_ASSERT(e.isLineTo());
1174 cline = QLineF(qt_fixed_to_real(prev.x),
1175 qt_fixed_to_real(prev.y),
1176 qt_fixed_to_real(e.x),
1177 qt_fixed_to_real(e.y));
1178 elen = cline.length();
1180 estop = estart + elen;
1182 bool done = pos >= estop;
1185 // Check if the entire line can be clipped away.
1186 if (!lineIntersectsRect(prev, e, clip_tl, clip_br)) {
1187 // Cut away full dash sequences.
1188 elen -= qFloor(elen * invSumLength) * sumLength;
1189 // Update dash offset.
1191 qreal dpos = pos + dashes[idash] - doffset - estart;
1193 Q_ASSERT(dpos >= 0);
1195 if (dpos > elen) { // dash extends this line
1196 doffset = dashes[idash] - (dpos - elen); // subtract the part already used
1197 pos = estop; // move pos to next path element
1199 } else { // Dash is on this line
1200 pos = dpos + estart;
1201 done = pos >= estop;
1202 if (++idash >= dashCount)
1204 doffset = 0; // full segment so no offset on next.
1216 bool has_offset = doffset > 0;
1217 bool evenDash = (idash & 1) == 0;
1218 qreal dpos = pos + dashes[idash] - doffset - estart;
1220 Q_ASSERT(dpos >= 0);
1222 if (dpos > elen) { // dash extends this line
1223 doffset = dashes[idash] - (dpos - elen); // subtract the part already used
1224 pos = estop; // move pos to next path element
1227 } else { // Dash is on this line
1228 p2 = cline.pointAt(dpos/elen);
1229 pos = dpos + estart;
1230 done = pos >= estop;
1231 if (++idash >= dashCount)
1233 doffset = 0; // full segment so no offset on next.
1237 line_to_pos.x = qt_real_to_fixed(p2.x());
1238 line_to_pos.y = qt_real_to_fixed(p2.y());
1241 || lineRectIntersectsRect(move_to_pos, line_to_pos, clip_tl, clip_br))
1243 // If we have an offset, we're continuing a dash
1244 // from a previous element and should only
1245 // continue the current dash, without starting a
1247 if (!has_offset || !hasMoveTo) {
1248 emitMoveTo(move_to_pos.x, move_to_pos.y);
1252 emitLineTo(line_to_pos.x, line_to_pos.y);
1256 move_to_pos = line_to_pos;
1258 move_to_pos.x = qt_real_to_fixed(p2.x());
1259 move_to_pos.y = qt_real_to_fixed(p2.y());
1263 // Shuffle to the next cycle...