summaryrefslogtreecommitdiffstats
path: root/src/gui/painting/qstroker.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/painting/qstroker.cpp')
-rw-r--r--src/gui/painting/qstroker.cpp1136
1 files changed, 1136 insertions, 0 deletions
diff --git a/src/gui/painting/qstroker.cpp b/src/gui/painting/qstroker.cpp
new file mode 100644
index 0000000..b894c62
--- /dev/null
+++ b/src/gui/painting/qstroker.cpp
@@ -0,0 +1,1136 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "private/qstroker_p.h"
+#include "private/qbezier_p.h"
+#include "private/qmath_p.h"
+#include "qline.h"
+#include "qtransform.h"
+#include <qmath.h>
+
+QT_BEGIN_NAMESPACE
+
+// #define QPP_STROKE_DEBUG
+
+class QSubpathForwardIterator
+{
+public:
+ QSubpathForwardIterator(const QDataBuffer<QStrokerOps::Element> *path)
+ : m_path(path), m_pos(0) { }
+ inline int position() const { return m_pos; }
+ inline bool hasNext() const { return m_pos < m_path->size(); }
+ inline QStrokerOps::Element next() { Q_ASSERT(hasNext()); return m_path->at(m_pos++); }
+
+private:
+ const QDataBuffer<QStrokerOps::Element> *m_path;
+ int m_pos;
+};
+
+class QSubpathBackwardIterator
+{
+public:
+ QSubpathBackwardIterator(const QDataBuffer<QStrokerOps::Element> *path)
+ : m_path(path), m_pos(path->size() - 1) { }
+
+ inline int position() const { return m_pos; }
+
+ inline bool hasNext() const { return m_pos >= 0; }
+
+ inline QStrokerOps::Element next()
+ {
+ Q_ASSERT(hasNext());
+
+ QStrokerOps::Element ce = m_path->at(m_pos); // current element
+
+ if (m_pos == m_path->size() - 1) {
+ --m_pos;
+ ce.type = QPainterPath::MoveToElement;
+ return ce;
+ }
+
+ const QStrokerOps::Element &pe = m_path->at(m_pos + 1); // previous element
+
+ switch (pe.type) {
+ case QPainterPath::LineToElement:
+ ce.type = QPainterPath::LineToElement;
+ break;
+ case QPainterPath::CurveToDataElement:
+ // First control point?
+ if (ce.type == QPainterPath::CurveToElement) {
+ ce.type = QPainterPath::CurveToDataElement;
+ } else { // Second control point then
+ ce.type = QPainterPath::CurveToElement;
+ }
+ break;
+ case QPainterPath::CurveToElement:
+ ce.type = QPainterPath::CurveToDataElement;
+ break;
+ default:
+ qWarning("QSubpathReverseIterator::next: Case %d unhandled", ce.type);
+ break;
+ }
+ --m_pos;
+
+ return ce;
+ }
+
+private:
+ const QDataBuffer<QStrokerOps::Element> *m_path;
+ int m_pos;
+};
+
+class QSubpathFlatIterator
+{
+public:
+ QSubpathFlatIterator(const QDataBuffer<QStrokerOps::Element> *path)
+ : m_path(path), m_pos(0), m_curve_index(-1) { }
+
+ inline bool hasNext() const { return m_curve_index >= 0 || m_pos < m_path->size(); }
+
+ QStrokerOps::Element next()
+ {
+ Q_ASSERT(hasNext());
+
+ if (m_curve_index >= 0) {
+ QStrokerOps::Element e = { QPainterPath::LineToElement,
+ qt_real_to_fixed(m_curve.at(m_curve_index).x()),
+ qt_real_to_fixed(m_curve.at(m_curve_index).y())
+ };
+ ++m_curve_index;
+ if (m_curve_index >= m_curve.size())
+ m_curve_index = -1;
+ return e;
+ }
+
+ QStrokerOps::Element e = m_path->at(m_pos);
+ if (e.isCurveTo()) {
+ Q_ASSERT(m_pos > 0);
+ Q_ASSERT(m_pos < m_path->size());
+
+ m_curve = QBezier::fromPoints(QPointF(qt_fixed_to_real(m_path->at(m_pos-1).x),
+ qt_fixed_to_real(m_path->at(m_pos-1).y)),
+ QPointF(qt_fixed_to_real(e.x),
+ qt_fixed_to_real(e.y)),
+ QPointF(qt_fixed_to_real(m_path->at(m_pos+1).x),
+ qt_fixed_to_real(m_path->at(m_pos+1).y)),
+ QPointF(qt_fixed_to_real(m_path->at(m_pos+2).x),
+ qt_fixed_to_real(m_path->at(m_pos+2).y))).toPolygon();
+ m_curve_index = 1;
+ e.type = QPainterPath::LineToElement;
+ e.x = m_curve.at(0).x();
+ e.y = m_curve.at(0).y();
+ m_pos += 2;
+ }
+ Q_ASSERT(e.isLineTo() || e.isMoveTo());
+ ++m_pos;
+ return e;
+ }
+
+private:
+ const QDataBuffer<QStrokerOps::Element> *m_path;
+ int m_pos;
+ QPolygonF m_curve;
+ int m_curve_index;
+};
+
+template <class Iterator> bool qt_stroke_side(Iterator *it, QStroker *stroker,
+ bool capFirst, QLineF *startTangent);
+
+/*******************************************************************************
+ * QLineF::angle gives us the smalles angle between two lines. Here we
+ * want to identify the line's angle direction on the unit circle.
+ */
+static inline qreal adapted_angle_on_x(const QLineF &line)
+{
+ qreal angle = line.angle(QLineF(0, 0, 1, 0));
+ if (line.dy() > 0)
+ angle = 360 - angle;
+ return angle;
+}
+
+QStrokerOps::QStrokerOps()
+ : m_customData(0), m_moveTo(0), m_lineTo(0), m_cubicTo(0)
+{
+}
+
+QStrokerOps::~QStrokerOps()
+{
+}
+
+
+/*!
+ Prepares the stroker. Call this function once before starting a
+ stroke by calling moveTo, lineTo or cubicTo.
+
+ The \a customData is passed back through that callback functions
+ and can be used by the user to for instance maintain state
+ information.
+*/
+void QStrokerOps::begin(void *customData)
+{
+ m_customData = customData;
+ m_elements.reset();
+}
+
+
+/*!
+ Finishes the stroke. Call this function once when an entire
+ primitive has been stroked.
+*/
+void QStrokerOps::end()
+{
+ if (m_elements.size() > 1)
+ processCurrentSubpath();
+ m_customData = 0;
+}
+
+/*!
+ Convenience function that decomposes \a path into begin(),
+ moveTo(), lineTo(), curevTo() and end() calls.
+
+ The \a customData parameter is used in the callback functions
+
+ The \a matrix is used to transform the points before input to the
+ stroker.
+
+ \sa begin()
+*/
+void QStrokerOps::strokePath(const QPainterPath &path, void *customData, const QTransform &matrix)
+{
+ if (path.isEmpty())
+ return;
+
+ begin(customData);
+ int count = path.elementCount();
+ if (matrix.isIdentity()) {
+ for (int i=0; i<count; ++i) {
+ const QPainterPath::Element &e = path.elementAt(i);
+ switch (e.type) {
+ case QPainterPath::MoveToElement:
+ moveTo(qt_real_to_fixed(e.x), qt_real_to_fixed(e.y));
+ break;
+ case QPainterPath::LineToElement:
+ lineTo(qt_real_to_fixed(e.x), qt_real_to_fixed(e.y));
+ break;
+ case QPainterPath::CurveToElement:
+ {
+ const QPainterPath::Element &cp2 = path.elementAt(++i);
+ const QPainterPath::Element &ep = path.elementAt(++i);
+ cubicTo(qt_real_to_fixed(e.x), qt_real_to_fixed(e.y),
+ qt_real_to_fixed(cp2.x), qt_real_to_fixed(cp2.y),
+ qt_real_to_fixed(ep.x), qt_real_to_fixed(ep.y));
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ } else {
+ for (int i=0; i<count; ++i) {
+ const QPainterPath::Element &e = path.elementAt(i);
+ QPointF pt = QPointF(e.x, e.y) * matrix;
+ switch (e.type) {
+ case QPainterPath::MoveToElement:
+ moveTo(qt_real_to_fixed(pt.x()), qt_real_to_fixed(pt.y()));
+ break;
+ case QPainterPath::LineToElement:
+ lineTo(qt_real_to_fixed(pt.x()), qt_real_to_fixed(pt.y()));
+ break;
+ case QPainterPath::CurveToElement:
+ {
+ QPointF cp2 = ((QPointF) path.elementAt(++i)) * matrix;
+ QPointF ep = ((QPointF) path.elementAt(++i)) * matrix;
+ cubicTo(qt_real_to_fixed(pt.x()), qt_real_to_fixed(pt.y()),
+ qt_real_to_fixed(cp2.x()), qt_real_to_fixed(cp2.y()),
+ qt_real_to_fixed(ep.x()), qt_real_to_fixed(ep.y()));
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ end();
+}
+
+/*!
+ Convenience function for stroking a polygon of the \a pointCount
+ first points in \a points. If \a implicit_close is set to true a
+ line is implictly drawn between the first and last point in the
+ polygon. Typically true for polygons and false for polylines.
+
+ The \a matrix is used to transform the points before they enter the
+ stroker.
+
+ \sa begin()
+*/
+
+void QStrokerOps::strokePolygon(const QPointF *points, int pointCount, bool implicit_close,
+ void *data, const QTransform &matrix)
+{
+ if (!pointCount)
+ return;
+ begin(data);
+ if (matrix.isIdentity()) {
+ moveTo(qt_real_to_fixed(points[0].x()), qt_real_to_fixed(points[0].y()));
+ for (int i=1; i<pointCount; ++i)
+ lineTo(qt_real_to_fixed(points[i].x()),
+ qt_real_to_fixed(points[i].y()));
+ if (implicit_close)
+ lineTo(qt_real_to_fixed(points[0].x()), qt_real_to_fixed(points[0].y()));
+ } else {
+ QPointF start = points[0] * matrix;
+ moveTo(qt_real_to_fixed(start.x()), qt_real_to_fixed(start.y()));
+ for (int i=1; i<pointCount; ++i) {
+ QPointF pt = points[i] * matrix;
+ lineTo(qt_real_to_fixed(pt.x()), qt_real_to_fixed(pt.y()));
+ }
+ if (implicit_close)
+ lineTo(qt_real_to_fixed(start.x()), qt_real_to_fixed(start.y()));
+ }
+ end();
+}
+
+/*!
+ Convenience function for stroking an ellipse with bounding rect \a
+ rect. The \a matrix is used to transform the coordinates before
+ they enter the stroker.
+*/
+void QStrokerOps::strokeEllipse(const QRectF &rect, void *data, const QTransform &matrix)
+{
+ int count = 0;
+ QPointF pts[12];
+ QPointF start = qt_curves_for_arc(rect, 0, -360, pts, &count);
+ Q_ASSERT(count == 12); // a perfect circle..
+
+ if (!matrix.isIdentity()) {
+ start = start * matrix;
+ for (int i=0; i<12; ++i) {
+ pts[i] = pts[i] * matrix;
+ }
+ }
+
+ begin(data);
+ moveTo(qt_real_to_fixed(start.x()), qt_real_to_fixed(start.y()));
+ for (int i=0; i<12; i+=3) {
+ cubicTo(qt_real_to_fixed(pts[i].x()), qt_real_to_fixed(pts[i].y()),
+ qt_real_to_fixed(pts[i+1].x()), qt_real_to_fixed(pts[i+1].y()),
+ qt_real_to_fixed(pts[i+2].x()), qt_real_to_fixed(pts[i+2].y()));
+ }
+ end();
+}
+
+
+QStroker::QStroker()
+ : m_capStyle(SquareJoin), m_joinStyle(FlatJoin),
+ m_back1X(0), m_back1Y(0),
+ m_back2X(0), m_back2Y(0)
+{
+ m_strokeWidth = qt_real_to_fixed(1);
+ m_miterLimit = qt_real_to_fixed(2);
+ m_curveThreshold = qt_real_to_fixed(0.25);
+}
+
+QStroker::~QStroker()
+{
+
+}
+
+Qt::PenCapStyle QStroker::capForJoinMode(LineJoinMode mode)
+{
+ if (mode == FlatJoin) return Qt::FlatCap;
+ else if (mode == SquareJoin) return Qt::SquareCap;
+ else return Qt::RoundCap;
+}
+
+QStroker::LineJoinMode QStroker::joinModeForCap(Qt::PenCapStyle style)
+{
+ if (style == Qt::FlatCap) return FlatJoin;
+ else if (style == Qt::SquareCap) return SquareJoin;
+ else return RoundCap;
+}
+
+Qt::PenJoinStyle QStroker::joinForJoinMode(LineJoinMode mode)
+{
+ if (mode == FlatJoin) return Qt::BevelJoin;
+ else if (mode == MiterJoin) return Qt::MiterJoin;
+ else if (mode == SvgMiterJoin) return Qt::SvgMiterJoin;
+ else return Qt::RoundJoin;
+}
+
+QStroker::LineJoinMode QStroker::joinModeForJoin(Qt::PenJoinStyle joinStyle)
+{
+ if (joinStyle == Qt::BevelJoin) return FlatJoin;
+ else if (joinStyle == Qt::MiterJoin) return MiterJoin;
+ else if (joinStyle == Qt::SvgMiterJoin) return SvgMiterJoin;
+ else return RoundJoin;
+}
+
+
+/*!
+ This function is called to stroke the currently built up
+ subpath. The subpath is cleared when the function completes.
+*/
+void QStroker::processCurrentSubpath()
+{
+ Q_ASSERT(!m_elements.isEmpty());
+ Q_ASSERT(m_elements.first().type == QPainterPath::MoveToElement);
+ Q_ASSERT(m_elements.size() > 1);
+
+ QSubpathForwardIterator fwit(&m_elements);
+ QSubpathBackwardIterator bwit(&m_elements);
+
+ QLineF fwStartTangent, bwStartTangent;
+
+ bool fwclosed = qt_stroke_side(&fwit, this, false, &fwStartTangent);
+ bool bwclosed = qt_stroke_side(&bwit, this, !fwclosed, &bwStartTangent);
+
+ if (!bwclosed)
+ joinPoints(m_elements.at(0).x, m_elements.at(0).y, fwStartTangent, m_capStyle);
+}
+
+
+/*!
+ \internal
+*/
+void QStroker::joinPoints(qfixed focal_x, qfixed focal_y, const QLineF &nextLine, LineJoinMode join)
+{
+#ifdef QPP_STROKE_DEBUG
+ printf(" -----> joinPoints: around=(%.0f, %.0f), next_p1=(%.0f, %.f) next_p2=(%.0f, %.f)\n",
+ qt_fixed_to_real(focal_x),
+ qt_fixed_to_real(focal_y),
+ nextLine.x1(), nextLine.y1(), nextLine.x2(), nextLine.y2());
+#endif
+ // points connected already, don't join
+
+#if !defined (QFIXED_26_6) && !defined (Q_FIXED_32_32)
+ if (qFuzzyCompare(m_back1X, nextLine.x1()) && qFuzzyCompare(m_back1Y, nextLine.y1()))
+ return;
+#else
+ if (m_back1X == qt_real_to_fixed(nextLine.x1())
+ && m_back1Y == qt_real_to_fixed(nextLine.y1())) {
+ return;
+ }
+#endif
+
+ if (join == FlatJoin) {
+ emitLineTo(qt_real_to_fixed(nextLine.x1()),
+ qt_real_to_fixed(nextLine.y1()));
+
+ } else {
+ QLineF prevLine(qt_fixed_to_real(m_back2X), qt_fixed_to_real(m_back2Y),
+ qt_fixed_to_real(m_back1X), qt_fixed_to_real(m_back1Y));
+
+ QPointF isect;
+ QLineF::IntersectType type = prevLine.intersect(nextLine, &isect);
+
+ if (join == MiterJoin) {
+ qreal appliedMiterLimit = qt_fixed_to_real(m_strokeWidth * m_miterLimit);
+
+ // If we are on the inside, do the short cut...
+ QLineF shortCut(prevLine.p2(), nextLine.p1());
+ qreal angle = shortCut.angleTo(prevLine);
+
+ if (type == QLineF::BoundedIntersection || (angle > 90 && !qFuzzyCompare(angle, (qreal)90))) {
+ emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1()));
+ return;
+ }
+ QLineF miterLine(QPointF(qt_fixed_to_real(m_back1X),
+ qt_fixed_to_real(m_back1Y)), isect);
+ if (type == QLineF::NoIntersection || miterLine.length() > appliedMiterLimit) {
+ QLineF l1(prevLine);
+ l1.setLength(appliedMiterLimit);
+ l1.translate(prevLine.dx(), prevLine.dy());
+
+ QLineF l2(nextLine);
+ l2.setLength(appliedMiterLimit);
+ l2.translate(-l2.dx(), -l2.dy());
+
+ emitLineTo(qt_real_to_fixed(l1.x2()), qt_real_to_fixed(l1.y2()));
+ emitLineTo(qt_real_to_fixed(l2.x1()), qt_real_to_fixed(l2.y1()));
+ emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1()));
+ } else {
+ emitLineTo(qt_real_to_fixed(isect.x()), qt_real_to_fixed(isect.y()));
+ emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1()));
+ }
+
+ } else if (join == SquareJoin) {
+ qfixed offset = m_strokeWidth / 2;
+
+ QLineF l1(prevLine);
+ l1.translate(l1.dx(), l1.dy());
+ l1.setLength(qt_fixed_to_real(offset));
+ QLineF l2(nextLine.p2(), nextLine.p1());
+ l2.translate(l2.dx(), l2.dy());
+ l2.setLength(qt_fixed_to_real(offset));
+ emitLineTo(qt_real_to_fixed(l1.x2()), qt_real_to_fixed(l1.y2()));
+ emitLineTo(qt_real_to_fixed(l2.x2()), qt_real_to_fixed(l2.y2()));
+ emitLineTo(qt_real_to_fixed(l2.x1()), qt_real_to_fixed(l2.y1()));
+
+ } else if (join == RoundJoin) {
+ qfixed offset = m_strokeWidth / 2;
+
+ QLineF shortCut(prevLine.p2(), nextLine.p1());
+ qreal angle = prevLine.angle(shortCut);
+ if (type == QLineF::BoundedIntersection || (angle > 90 && !qFuzzyCompare(angle, (qreal)90))) {
+ emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1()));
+ return;
+ }
+ qreal l1_on_x = adapted_angle_on_x(prevLine);
+ qreal l2_on_x = adapted_angle_on_x(nextLine);
+
+ qreal sweepLength = qAbs(l2_on_x - l1_on_x);
+
+ int point_count;
+ QPointF curves[15];
+
+ QPointF curve_start =
+ qt_curves_for_arc(QRectF(qt_fixed_to_real(focal_x - offset),
+ qt_fixed_to_real(focal_y - offset),
+ qt_fixed_to_real(offset * 2),
+ qt_fixed_to_real(offset * 2)),
+ l1_on_x + 90, -sweepLength,
+ curves, &point_count);
+
+// // line to the beginning of the arc segment, (should not be needed).
+// emitLineTo(qt_real_to_fixed(curve_start.x()), qt_real_to_fixed(curve_start.y()));
+
+ for (int i=0; i<point_count; i+=3) {
+ emitCubicTo(qt_real_to_fixed(curves[i].x()),
+ qt_real_to_fixed(curves[i].y()),
+ qt_real_to_fixed(curves[i+1].x()),
+ qt_real_to_fixed(curves[i+1].y()),
+ qt_real_to_fixed(curves[i+2].x()),
+ qt_real_to_fixed(curves[i+2].y()));
+ }
+
+ // line to the end of the arc segment, (should also not be needed).
+ emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1()));
+
+ // Same as round join except we know its 180 degrees. Can also optimize this
+ // later based on the addEllipse logic
+ } else if (join == RoundCap) {
+ qfixed offset = m_strokeWidth / 2;
+
+ // first control line
+ QLineF l1 = prevLine;
+ l1.translate(l1.dx(), l1.dy());
+ l1.setLength(QT_PATH_KAPPA * offset);
+
+ // second control line, find through normal between prevLine and focal.
+ QLineF l2(qt_fixed_to_real(focal_x), qt_fixed_to_real(focal_y),
+ prevLine.x2(), prevLine.y2());
+ l2.translate(-l2.dy(), l2.dx());
+ l2.setLength(QT_PATH_KAPPA * offset);
+
+ emitCubicTo(qt_real_to_fixed(l1.x2()),
+ qt_real_to_fixed(l1.y2()),
+ qt_real_to_fixed(l2.x2()),
+ qt_real_to_fixed(l2.y2()),
+ qt_real_to_fixed(l2.x1()),
+ qt_real_to_fixed(l2.y1()));
+
+ // move so that it matches
+ l2 = QLineF(l2.x1(), l2.y1(), l2.x1()-l2.dx(), l2.y1()-l2.dy());
+
+ // last line is parallel to l1 so just shift it down.
+ l1.translate(nextLine.x1() - l1.x1(), nextLine.y1() - l1.y1());
+
+ emitCubicTo(qt_real_to_fixed(l2.x2()),
+ qt_real_to_fixed(l2.y2()),
+ qt_real_to_fixed(l1.x2()),
+ qt_real_to_fixed(l1.y2()),
+ qt_real_to_fixed(l1.x1()),
+ qt_real_to_fixed(l1.y1()));
+ } else if (join == SvgMiterJoin) {
+ QLineF miterLine(QPointF(qt_fixed_to_real(focal_x),
+ qt_fixed_to_real(focal_y)), isect);
+ if (miterLine.length() > qt_fixed_to_real(m_strokeWidth * m_miterLimit) / 2) {
+ emitLineTo(qt_real_to_fixed(nextLine.x1()),
+ qt_real_to_fixed(nextLine.y1()));
+ } else {
+ emitLineTo(qt_real_to_fixed(isect.x()), qt_real_to_fixed(isect.y()));
+ emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1()));
+ }
+ } else {
+ Q_ASSERT(!"QStroker::joinPoints(), bad join style...");
+ }
+ }
+}
+
+
+/*
+ Strokes a subpath side using the \a it as source. Results are put into
+ \a stroke. The function returns true if the subpath side was closed.
+ If \a capFirst is true, we will use capPoints instead of joinPoints to
+ connect the first segment, other segments will be joined using joinPoints.
+ This is to put capping in order...
+*/
+template <class Iterator> bool qt_stroke_side(Iterator *it,
+ QStroker *stroker,
+ bool capFirst,
+ QLineF *startTangent)
+{
+ // Used in CurveToElement section below.
+ const int MAX_OFFSET = 16;
+ QBezier offsetCurves[MAX_OFFSET];
+
+ Q_ASSERT(it->hasNext()); // The initaial move to
+ QStrokerOps::Element first_element = it->next();
+ Q_ASSERT(first_element.isMoveTo());
+
+ qfixed2d start = first_element;
+
+#ifdef QPP_STROKE_DEBUG
+ qDebug(" -> (side) [%.2f, %.2f], startPos=%d",
+ qt_fixed_to_real(start.x),
+ qt_fixed_to_real(start.y));
+#endif
+
+ qfixed2d prev = start;
+
+ bool first = true;
+
+ qfixed offset = stroker->strokeWidth() / 2;
+
+ while (it->hasNext()) {
+ QStrokerOps::Element e = it->next();
+
+ // LineToElement
+ if (e.isLineTo()) {
+#ifdef QPP_STROKE_DEBUG
+ qDebug("\n ---> (side) lineto [%.2f, %.2f]", e.x, e.y);
+#endif
+ QLineF line(qt_fixed_to_real(prev.x), qt_fixed_to_real(prev.y),
+ qt_fixed_to_real(e.x), qt_fixed_to_real(e.y));
+ QLineF normal = line.normalVector();
+ normal.setLength(offset);
+ line.translate(normal.dx(), normal.dy());
+
+ // If we are starting a new subpath, move to correct starting point.
+ if (first) {
+ if (capFirst)
+ stroker->joinPoints(prev.x, prev.y, line, stroker->capStyleMode());
+ else
+ stroker->emitMoveTo(qt_real_to_fixed(line.x1()), qt_real_to_fixed(line.y1()));
+ *startTangent = line;
+ first = false;
+ } else {
+ stroker->joinPoints(prev.x, prev.y, line, stroker->joinStyleMode());
+ }
+
+ // Add the stroke for this line.
+ stroker->emitLineTo(qt_real_to_fixed(line.x2()),
+ qt_real_to_fixed(line.y2()));
+ prev = e;
+
+ // CurveToElement
+ } else if (e.isCurveTo()) {
+ QStrokerOps::Element cp2 = it->next(); // control point 2
+ QStrokerOps::Element ep = it->next(); // end point
+
+#ifdef QPP_STROKE_DEBUG
+ qDebug("\n ---> (side) cubicTo [%.2f, %.2f]",
+ qt_fixed_to_real(ep.x),
+ qt_fixed_to_real(ep.y));
+#endif
+
+ QBezier bezier =
+ QBezier::fromPoints(QPointF(qt_fixed_to_real(prev.x), qt_fixed_to_real(prev.y)),
+ QPointF(qt_fixed_to_real(e.x), qt_fixed_to_real(e.y)),
+ QPointF(qt_fixed_to_real(cp2.x), qt_fixed_to_real(cp2.y)),
+ QPointF(qt_fixed_to_real(ep.x), qt_fixed_to_real(ep.y)));
+
+ int count = bezier.shifted(offsetCurves,
+ MAX_OFFSET,
+ offset,
+ stroker->curveThreshold());
+
+ if (count) {
+ // If we are starting a new subpath, move to correct starting point
+ QLineF tangent = bezier.startTangent();
+ tangent.translate(offsetCurves[0].pt1() - bezier.pt1());
+ if (first) {
+ QPointF pt = offsetCurves[0].pt1();
+ if (capFirst) {
+ stroker->joinPoints(prev.x, prev.y,
+ tangent,
+ stroker->capStyleMode());
+ } else {
+ stroker->emitMoveTo(qt_real_to_fixed(pt.x()),
+ qt_real_to_fixed(pt.y()));
+ }
+ *startTangent = tangent;
+ first = false;
+ } else {
+ stroker->joinPoints(prev.x, prev.y,
+ tangent,
+ stroker->joinStyleMode());
+ }
+
+ // Add these beziers
+ for (int i=0; i<count; ++i) {
+ QPointF cp1 = offsetCurves[i].pt2();
+ QPointF cp2 = offsetCurves[i].pt3();
+ QPointF ep = offsetCurves[i].pt4();
+ stroker->emitCubicTo(qt_real_to_fixed(cp1.x()), qt_real_to_fixed(cp1.y()),
+ qt_real_to_fixed(cp2.x()), qt_real_to_fixed(cp2.y()),
+ qt_real_to_fixed(ep.x()), qt_real_to_fixed(ep.y()));
+ }
+ }
+
+ prev = ep;
+ }
+ }
+
+ if (start == prev) {
+ // closed subpath, join first and last point
+#ifdef QPP_STROKE_DEBUG
+ qDebug("\n ---> (side) closed subpath");
+#endif
+ stroker->joinPoints(prev.x, prev.y, *startTangent, stroker->joinStyleMode());
+ return true;
+ } else {
+#ifdef QPP_STROKE_DEBUG
+ qDebug("\n ---> (side) open subpath");
+#endif
+ return false;
+ }
+}
+
+/*!
+ \internal
+
+ For a given angle in the range [0 .. 90], finds the corresponding parameter t
+ of the prototype cubic bezier arc segment
+ b = fromPoints(QPointF(1, 0), QPointF(1, KAPPA), QPointF(KAPPA, 1), QPointF(0, 1));
+
+ From the bezier equation:
+ b.pointAt(t).x() = (1-t)^3 + t*(1-t)^2 + t^2*(1-t)*KAPPA
+ b.pointAt(t).y() = t*(1-t)^2 * KAPPA + t^2*(1-t) + t^3
+
+ Third degree coefficients:
+ b.pointAt(t).x() = at^3 + bt^2 + ct + d
+ where a = 2-3*KAPPA, b = 3*(KAPPA-1), c = 0, d = 1
+
+ b.pointAt(t).y() = at^3 + bt^2 + ct + d
+ where a = 3*KAPPA-2, b = 6*KAPPA+3, c = 3*KAPPA, d = 0
+
+ Newton's method to find the zero of a function:
+ given a function f(x) and initial guess x_0
+ x_1 = f(x_0) / f'(x_0)
+ x_2 = f(x_1) / f'(x_1)
+ etc...
+*/
+
+qreal qt_t_for_arc_angle(qreal angle)
+{
+ if (qFuzzyCompare(angle + 1, qreal(1)))
+ return 0;
+
+ if (qFuzzyCompare(angle, qreal(90)))
+ return 1;
+
+ qreal radians = Q_PI * angle / 180;
+ qreal cosAngle = qCos(radians);
+ qreal sinAngle = qSin(radians);
+
+ // initial guess
+ qreal tc = angle / 90;
+ // do some iterations of newton's method to approximate cosAngle
+ // finds the zero of the function b.pointAt(tc).x() - cosAngle
+ tc -= ((((2-3*QT_PATH_KAPPA) * tc + 3*(QT_PATH_KAPPA-1)) * tc) * tc + 1 - cosAngle) // value
+ / (((6-9*QT_PATH_KAPPA) * tc + 6*(QT_PATH_KAPPA-1)) * tc); // derivative
+ tc -= ((((2-3*QT_PATH_KAPPA) * tc + 3*(QT_PATH_KAPPA-1)) * tc) * tc + 1 - cosAngle) // value
+ / (((6-9*QT_PATH_KAPPA) * tc + 6*(QT_PATH_KAPPA-1)) * tc); // derivative
+
+ // initial guess
+ qreal ts = tc;
+ // do some iterations of newton's method to approximate sinAngle
+ // finds the zero of the function b.pointAt(tc).y() - sinAngle
+ ts -= ((((3*QT_PATH_KAPPA-2) * ts - 6*QT_PATH_KAPPA + 3) * ts + 3*QT_PATH_KAPPA) * ts - sinAngle)
+ / (((9*QT_PATH_KAPPA-6) * ts + 12*QT_PATH_KAPPA - 6) * ts + 3*QT_PATH_KAPPA);
+ ts -= ((((3*QT_PATH_KAPPA-2) * ts - 6*QT_PATH_KAPPA + 3) * ts + 3*QT_PATH_KAPPA) * ts - sinAngle)
+ / (((9*QT_PATH_KAPPA-6) * ts + 12*QT_PATH_KAPPA - 6) * ts + 3*QT_PATH_KAPPA);
+
+ // use the average of the t that best approximates cosAngle
+ // and the t that best approximates sinAngle
+ qreal t = 0.5 * (tc + ts);
+
+#if 0
+ printf("angle: %f, t: %f\n", angle, t);
+ qreal a, b, c, d;
+ bezierCoefficients(t, a, b, c, d);
+ printf("cosAngle: %.10f, value: %.10f\n", cosAngle, a + b + c * QT_PATH_KAPPA);
+ printf("sinAngle: %.10f, value: %.10f\n", sinAngle, b * QT_PATH_KAPPA + c + d);
+#endif
+
+ return t;
+}
+
+void qt_find_ellipse_coords(const QRectF &r, qreal angle, qreal length,
+ QPointF* startPoint, QPointF *endPoint);
+
+/*!
+ \internal
+
+ Creates a number of curves for a given arc definition. The arc is
+ defined an arc along the ellipses that fits into \a rect starting
+ at \a startAngle and an arc length of \a sweepLength.
+
+ The function has three out parameters. The return value is the
+ starting point of the arc. The \a curves array represents the list
+ of cubicTo elements up to a maximum of \a point_count. There are of course
+ 3 points pr curve.
+*/
+QPointF qt_curves_for_arc(const QRectF &rect, qreal startAngle, qreal sweepLength,
+ QPointF *curves, int *point_count)
+{
+ Q_ASSERT(point_count);
+ Q_ASSERT(curves);
+
+ *point_count = 0;
+ if (qt_is_nan(rect.x()) || qt_is_nan(rect.y()) || qt_is_nan(rect.width()) || qt_is_nan(rect.height())
+ || qt_is_nan(startAngle) || qt_is_nan(sweepLength)) {
+ qWarning("QPainterPath::arcTo: Adding arc where a parameter is NaN, results are undefined");
+ return QPointF();
+ }
+
+ if (rect.isNull()) {
+ return QPointF();
+ }
+
+ qreal x = rect.x();
+ qreal y = rect.y();
+
+ qreal w = rect.width();
+ qreal w2 = rect.width() / 2;
+ qreal w2k = w2 * QT_PATH_KAPPA;
+
+ qreal h = rect.height();
+ qreal h2 = rect.height() / 2;
+ qreal h2k = h2 * QT_PATH_KAPPA;
+
+ QPointF points[16] =
+ {
+ // start point
+ QPointF(x + w, y + h2),
+
+ // 0 -> 270 degrees
+ QPointF(x + w, y + h2 + h2k),
+ QPointF(x + w2 + w2k, y + h),
+ QPointF(x + w2, y + h),
+
+ // 270 -> 180 degrees
+ QPointF(x + w2 - w2k, y + h),
+ QPointF(x, y + h2 + h2k),
+ QPointF(x, y + h2),
+
+ // 180 -> 90 degrees
+ QPointF(x, y + h2 - h2k),
+ QPointF(x + w2 - w2k, y),
+ QPointF(x + w2, y),
+
+ // 90 -> 0 degrees
+ QPointF(x + w2 + w2k, y),
+ QPointF(x + w, y + h2 - h2k),
+ QPointF(x + w, y + h2)
+ };
+
+ if (sweepLength > 360) sweepLength = 360;
+ else if (sweepLength < -360) sweepLength = -360;
+
+ // Special case fast paths
+ if (startAngle == 0.0) {
+ if (sweepLength == 360.0) {
+ for (int i = 11; i >= 0; --i)
+ curves[(*point_count)++] = points[i];
+ return points[12];
+ } else if (sweepLength == -360.0) {
+ for (int i = 1; i <= 12; ++i)
+ curves[(*point_count)++] = points[i];
+ return points[0];
+ }
+ }
+
+ int startSegment = int(floor(startAngle / 90));
+ int endSegment = int(floor((startAngle + sweepLength) / 90));
+
+ qreal startT = (startAngle - startSegment * 90) / 90;
+ qreal endT = (startAngle + sweepLength - endSegment * 90) / 90;
+
+ int delta = sweepLength > 0 ? 1 : -1;
+ if (delta < 0) {
+ startT = 1 - startT;
+ endT = 1 - endT;
+ }
+
+ // avoid empty start segment
+ if (qFuzzyCompare(startT, qreal(1))) {
+ startT = 0;
+ startSegment += delta;
+ }
+
+ // avoid empty end segment
+ if (qFuzzyCompare(endT + 1, qreal(1))) {
+ endT = 1;
+ endSegment -= delta;
+ }
+
+ startT = qt_t_for_arc_angle(startT * 90);
+ endT = qt_t_for_arc_angle(endT * 90);
+
+ const bool splitAtStart = !qFuzzyCompare(startT + 1, qreal(1));
+ const bool splitAtEnd = !qFuzzyCompare(endT, qreal(1));
+
+ const int end = endSegment + delta;
+
+ // empty arc?
+ if (startSegment == end) {
+ const int quadrant = 3 - ((startSegment % 4) + 4) % 4;
+ const int j = 3 * quadrant;
+ return delta > 0 ? points[j + 3] : points[j];
+ }
+
+ QPointF startPoint, endPoint;
+ qt_find_ellipse_coords(rect, startAngle, sweepLength, &startPoint, &endPoint);
+
+ for (int i = startSegment; i != end; i += delta) {
+ const int quadrant = 3 - ((i % 4) + 4) % 4;
+ const int j = 3 * quadrant;
+
+ QBezier b;
+ if (delta > 0)
+ b = QBezier::fromPoints(points[j + 3], points[j + 2], points[j + 1], points[j]);
+ else
+ b = QBezier::fromPoints(points[j], points[j + 1], points[j + 2], points[j + 3]);
+
+ // empty arc?
+ if (startSegment == endSegment && qFuzzyCompare(startT, endT))
+ return startPoint;
+
+ if (i == startSegment) {
+ if (i == endSegment && splitAtEnd)
+ b = b.bezierOnInterval(startT, endT);
+ else if (splitAtStart)
+ b = b.bezierOnInterval(startT, 1);
+ } else if (i == endSegment && splitAtEnd) {
+ b = b.bezierOnInterval(0, endT);
+ }
+
+ // push control points
+ curves[(*point_count)++] = b.pt2();
+ curves[(*point_count)++] = b.pt3();
+ curves[(*point_count)++] = b.pt4();
+ }
+
+ Q_ASSERT(*point_count > 0);
+ curves[*(point_count)-1] = endPoint;
+
+ return startPoint;
+}
+
+
+/*******************************************************************************
+ * QDashStroker members
+ */
+QDashStroker::QDashStroker(QStroker *stroker)
+ : m_stroker(stroker), m_dashOffset(0)
+{
+
+}
+
+QVector<qfixed> QDashStroker::patternForStyle(Qt::PenStyle style)
+{
+ const qfixed space = 2;
+ const qfixed dot = 1;
+ const qfixed dash = 4;
+
+ QVector<qfixed> pattern;
+
+ switch (style) {
+ case Qt::DashLine:
+ pattern << dash << space;
+ break;
+ case Qt::DotLine:
+ pattern << dot << space;
+ break;
+ case Qt::DashDotLine:
+ pattern << dash << space << dot << space;
+ break;
+ case Qt::DashDotDotLine:
+ pattern << dash << space << dot << space << dot << space;
+ break;
+ default:
+ break;
+ }
+
+ return pattern;
+}
+
+
+void QDashStroker::processCurrentSubpath()
+{
+ int dashCount = qMin(m_dashPattern.size(), 32);
+ qfixed dashes[32];
+
+ qreal sumLength = 0;
+ for (int i=0; i<dashCount; ++i) {
+ dashes[i] = qMax(m_dashPattern.at(i), qreal(0)) * m_stroker->strokeWidth();
+ sumLength += dashes[i];
+ }
+
+ if (qFuzzyCompare(sumLength + 1, qreal(1)))
+ return;
+
+ Q_ASSERT(dashCount > 0);
+
+ dashCount = (dashCount / 2) * 2; // Round down to even number
+
+ int idash = 0; // Index to current dash
+ qreal pos = 0; // The position on the curve, 0 <= pos <= path.length
+ qreal elen = 0; // element length
+ qreal doffset = m_dashOffset * m_stroker->strokeWidth();
+
+ // make sure doffset is in range [0..sumLength)
+ doffset -= qFloor(doffset / sumLength) * sumLength;
+
+ while (doffset >= dashes[idash]) {
+ doffset -= dashes[idash];
+ idash = (idash + 1) % dashCount;
+ }
+
+ qreal estart = 0; // The elements starting position
+ qreal estop = 0; // The element stop position
+
+ QLineF cline;
+
+ QPainterPath dashPath;
+
+ QSubpathFlatIterator it(&m_elements);
+ qfixed2d prev = it.next();
+
+ bool clipping = !m_clip_rect.isEmpty();
+ qfixed2d move_to_pos = prev;
+ qfixed2d line_to_pos;
+
+ // Pad to avoid clipping the borders of thick pens.
+ qfixed padding = qMax(m_stroker->strokeWidth(), m_stroker->miterLimit());
+ qfixed2d clip_tl = { qt_real_to_fixed(m_clip_rect.left()) - padding,
+ qt_real_to_fixed(m_clip_rect.top()) - padding };
+ qfixed2d clip_br = { qt_real_to_fixed(m_clip_rect.right()) + padding ,
+ qt_real_to_fixed(m_clip_rect.bottom()) + padding };
+
+ bool hasMoveTo = false;
+ while (it.hasNext()) {
+ QStrokerOps::Element e = it.next();
+
+ Q_ASSERT(e.isLineTo());
+ cline = QLineF(qt_fixed_to_real(prev.x),
+ qt_fixed_to_real(prev.y),
+ qt_fixed_to_real(e.x),
+ qt_fixed_to_real(e.y));
+ elen = cline.length();
+
+ estop = estart + elen;
+
+ bool done = pos >= estop;
+ // Dash away...
+ while (!done) {
+ QPointF p2;
+
+ int idash_incr = 0;
+ bool has_offset = doffset > 0;
+ qreal dpos = pos + dashes[idash] - doffset - estart;
+
+ Q_ASSERT(dpos >= 0);
+
+ if (dpos > elen) { // dash extends this line
+ doffset = dashes[idash] - (dpos - elen); // subtract the part already used
+ pos = estop; // move pos to next path element
+ done = true;
+ p2 = cline.p2();
+ } else { // Dash is on this line
+ p2 = cline.pointAt(dpos/elen);
+ pos = dpos + estart;
+ done = pos >= estop;
+ idash_incr = 1;
+ doffset = 0; // full segment so no offset on next.
+ }
+
+ if (idash % 2 == 0) {
+ line_to_pos.x = qt_real_to_fixed(p2.x());
+ line_to_pos.y = qt_real_to_fixed(p2.y());
+
+ // If we have an offset, we're continuing a dash
+ // from a previous element and should only
+ // continue the current dash, without starting a
+ // new subpath.
+ if (!has_offset || !hasMoveTo) {
+ m_stroker->moveTo(move_to_pos.x, move_to_pos.y);
+ hasMoveTo = true;
+ }
+
+ if (!clipping
+ // if move_to is inside...
+ || (move_to_pos.x > clip_tl.x && move_to_pos.x < clip_br.x
+ && move_to_pos.y > clip_tl.y && move_to_pos.y < clip_br.y)
+ // Or if line_to is inside...
+ || (line_to_pos.x > clip_tl.x && line_to_pos.x < clip_br.x
+ && line_to_pos.y > clip_tl.y && line_to_pos.y < clip_br.y))
+ {
+ m_stroker->lineTo(line_to_pos.x, line_to_pos.y);
+ }
+ } else {
+ move_to_pos.x = qt_real_to_fixed(p2.x());
+ move_to_pos.y = qt_real_to_fixed(p2.y());
+ }
+
+ idash = (idash + idash_incr) % dashCount;
+ }
+
+ // Shuffle to the next cycle...
+ estart = estop;
+ prev = e;
+ }
+}
+
+QT_END_NAMESPACE