Coverage Report

Created: 2026-10-27 06:57

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