Coverage Report

Created: 2026-02-10 07:39

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