src/gui/painting/qstroker.cpp
changeset 30 5dc02b23752f
parent 22 79de32ba3296
--- a/src/gui/painting/qstroker.cpp	Wed Jun 23 19:07:03 2010 +0300
+++ b/src/gui/painting/qstroker.cpp	Tue Jul 06 15:10:48 2010 +0300
@@ -120,8 +120,8 @@
 class QSubpathFlatIterator
 {
 public:
-    QSubpathFlatIterator(const QDataBuffer<QStrokerOps::Element> *path)
-        : m_path(path), m_pos(0), m_curve_index(-1) { }
+    QSubpathFlatIterator(const QDataBuffer<QStrokerOps::Element> *path, qreal threshold)
+        : m_path(path), m_pos(0), m_curve_index(-1), m_curve_threshold(threshold) { }
 
     inline bool hasNext() const { return m_curve_index >= 0 || m_pos < m_path->size(); }
 
@@ -152,7 +152,7 @@
                                           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();
+                                                  qt_fixed_to_real(m_path->at(m_pos+2).y))).toPolygon(m_curve_threshold);
             m_curve_index = 1;
             e.type = QPainterPath::LineToElement;
             e.x = m_curve.at(0).x();
@@ -169,6 +169,7 @@
     int m_pos;
     QPolygonF m_curve;
     int m_curve_index;
+    qreal m_curve_threshold;
 };
 
 template <class Iterator> bool qt_stroke_side(Iterator *it, QStroker *stroker,
@@ -187,7 +188,12 @@
 }
 
 QStrokerOps::QStrokerOps()
-    : m_customData(0), m_moveTo(0), m_lineTo(0), m_cubicTo(0)
+    : m_elements(0)
+    , m_curveThreshold(qt_real_to_fixed(0.25))
+    , m_customData(0)
+    , m_moveTo(0)
+    , m_lineTo(0)
+    , m_cubicTo(0)
 {
 }
 
@@ -195,7 +201,6 @@
 {
 }
 
-
 /*!
     Prepares the stroker. Call this function once before starting a
     stroke by calling moveTo, lineTo or cubicTo.
@@ -238,6 +243,7 @@
     if (path.isEmpty())
         return;
 
+    setCurveThresholdFromTransform(matrix);
     begin(customData);
     int count = path.elementCount();
     if (matrix.isIdentity()) {
@@ -308,6 +314,8 @@
 {
     if (!pointCount)
         return;
+
+    setCurveThresholdFromTransform(matrix);
     begin(data);
     if (matrix.isIdentity()) {
         moveTo(qt_real_to_fixed(points[0].x()), qt_real_to_fixed(points[0].y()));
@@ -348,6 +356,7 @@
         }
     }
 
+    setCurveThresholdFromTransform(matrix);
     begin(data);
     moveTo(qt_real_to_fixed(start.x()), qt_real_to_fixed(start.y()));
     for (int i=0; i<12; i+=3) {
@@ -366,12 +375,10 @@
 {
     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)
@@ -1043,6 +1050,47 @@
     return pattern;
 }
 
+static inline bool lineRectIntersectsRect(qfixed2d p1, qfixed2d p2, const qfixed2d &tl, const qfixed2d &br)
+{
+    return ((p1.x > tl.x || p2.x > tl.x) && (p1.x < br.x || p2.x < br.x)
+        && (p1.y > tl.y || p2.y > tl.y) && (p1.y < br.y || p2.y < br.y));
+}
+
+// If the line intersects the rectangle, this function will return true.
+static bool lineIntersectsRect(qfixed2d p1, qfixed2d p2, const qfixed2d &tl, const qfixed2d &br)
+{
+    if (!lineRectIntersectsRect(p1, p2, tl, br))
+        return false;
+    if (p1.x == p2.x || p1.y == p2.y)
+        return true;
+
+    if (p1.y > p2.y)
+        qSwap(p1, p2); // make p1 above p2
+    qfixed2d u;
+    qfixed2d v;
+    qfixed2d w = {p2.x - p1.x, p2.y - p1.y};
+    if (p1.x < p2.x) {
+        // backslash
+        u.x = tl.x - p1.x; u.y = br.y - p1.y;
+        v.x = br.x - p1.x; v.y = tl.y - p1.y;
+    } else {
+        // slash
+        u.x = tl.x - p1.x; u.y = tl.y - p1.y;
+        v.x = br.x - p1.x; v.y = br.y - p1.y;
+    }
+#if defined(QFIXED_IS_26_6) || defined(QFIXED_IS_16_16)
+    qint64 val1 = qint64(u.x) * qint64(w.y) - qint64(u.y) * qint64(w.x);
+    qint64 val2 = qint64(v.x) * qint64(w.y) - qint64(v.y) * qint64(w.x);
+    return (val1 < 0 && val2 > 0) || (val1 > 0 && val2 < 0);
+#elif defined(QFIXED_IS_32_32)
+    // Cannot do proper test because it may overflow.
+    return true;
+#else
+    qreal val1 = u.x * w.y - u.y * w.x;
+    qreal val2 = v.x * w.y - v.y * w.x;
+    return (val1 < 0 && val2 > 0) || (val1 > 0 && val2 < 0);
+#endif
+}
 
 void QDashStroker::processCurrentSubpath()
 {
@@ -1067,9 +1115,11 @@
     if (qFuzzyIsNull(sumLength))
         return;
 
+    qreal invSumLength = qreal(1) / sumLength;
+
     Q_ASSERT(dashCount > 0);
 
-    dashCount = (dashCount / 2) * 2; // Round down to even number
+    dashCount = dashCount & -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
@@ -1077,11 +1127,12 @@
     qreal doffset = m_dashOffset * m_stroke_width;
 
     // make sure doffset is in range [0..sumLength)
-    doffset -= qFloor(doffset / sumLength) * sumLength;
+    doffset -= qFloor(doffset * invSumLength) * sumLength;
 
     while (doffset >= dashes[idash]) {
         doffset -= dashes[idash];
-        idash = (idash + 1) % dashCount;
+        if (++idash >= dashCount)
+            idash = 0;
     }
 
     qreal estart = 0; // The elements starting position
@@ -1091,7 +1142,7 @@
 
     QPainterPath dashPath;
 
-    QSubpathFlatIterator it(&m_elements);
+    QSubpathFlatIterator it(&m_elements, m_curveThreshold);
     qfixed2d prev = it.next();
 
     bool clipping = !m_clip_rect.isEmpty();
@@ -1119,12 +1170,41 @@
         estop = estart + elen;
 
         bool done = pos >= estop;
+
+        if (clipping) {
+            // Check if the entire line can be clipped away.
+            if (!lineIntersectsRect(prev, e, clip_tl, clip_br)) {
+                // Cut away full dash sequences.
+                elen -= qFloor(elen * invSumLength) * sumLength;
+                // Update dash offset.
+                while (!done) {
+                    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;
+                    } else { // Dash is on this line
+                        pos = dpos + estart;
+                        done = pos >= estop;
+                        if (++idash >= dashCount)
+                            idash = 0;
+                        doffset = 0; // full segment so no offset on next.
+                    }
+                }
+                hasMoveTo = false;
+                move_to_pos = e;
+            }
+        }
+
         // Dash away...
         while (!done) {
             QPointF p2;
 
-            int idash_incr = 0;
             bool has_offset = doffset > 0;
+            bool evenDash = (idash & 1) == 0;
             qreal dpos = pos + dashes[idash] - doffset - estart;
 
             Q_ASSERT(dpos >= 0);
@@ -1138,39 +1218,36 @@
                 p2 = cline.pointAt(dpos/elen);
                 pos = dpos + estart;
                 done = pos >= estop;
-                idash_incr = 1;
+                if (++idash >= dashCount)
+                    idash = 0;
                 doffset = 0; // full segment so no offset on next.
             }
 
-            if (idash % 2 == 0) {
+            if (evenDash) {
                 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) {
-                    emitMoveTo(move_to_pos.x, move_to_pos.y);
-                    hasMoveTo = true;
-                }
+                if (!clipping
+                    || lineRectIntersectsRect(move_to_pos, line_to_pos, clip_tl, clip_br))
+                {
+                    // 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) {
+                        emitMoveTo(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))
-                {
                     emitLineTo(line_to_pos.x, line_to_pos.y);
+                } else {
+                    hasMoveTo = false;
                 }
+                move_to_pos = line_to_pos;
             } 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...