Coverage Report

Created: 2026-01-25 07:18

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
1.15k
        : m_path(path), m_pos(0) { }
19
0
    inline int position() const { return m_pos; }
20
22.4k
    inline bool hasNext() const { return m_pos < m_path->size(); }
21
14.0k
    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
1.15k
        : m_path(path), m_pos(path->size() - 1) { }
33
34
0
    inline int position() const { return m_pos; }
35
36
22.4k
    inline bool hasNext() const { return m_pos >= 0; }
37
38
    inline QStrokerOps::Element next()
39
14.0k
    {
40
14.0k
        Q_ASSERT(hasNext());
41
42
14.0k
        QStrokerOps::Element ce = m_path->at(m_pos);   // current element
43
44
14.0k
        if (m_pos == m_path->size() - 1) {
45
1.15k
            --m_pos;
46
1.15k
            ce.type = QPainterPath::MoveToElement;
47
1.15k
            return ce;
48
1.15k
        }
49
50
12.9k
        const QStrokerOps::Element &pe = m_path->at(m_pos + 1); // previous element
51
52
12.9k
        switch (pe.type) {
53
2.53k
        case QPainterPath::LineToElement:
54
2.53k
            ce.type = QPainterPath::LineToElement;
55
2.53k
            break;
56
6.93k
        case QPainterPath::CurveToDataElement:
57
            // First control point?
58
6.93k
            if (ce.type == QPainterPath::CurveToElement) {
59
3.46k
                ce.type = QPainterPath::CurveToDataElement;
60
3.46k
            } else { // Second control point then
61
3.46k
                ce.type = QPainterPath::CurveToElement;
62
3.46k
            }
63
6.93k
            break;
64
3.46k
        case QPainterPath::CurveToElement:
65
3.46k
            ce.type = QPainterPath::CurveToDataElement;
66
3.46k
            break;
67
0
        default:
68
0
            qWarning("QSubpathReverseIterator::next: Case %d unhandled", ce.type);
69
0
            break;
70
12.9k
        }
71
12.9k
        --m_pos;
72
73
12.9k
        return ce;
74
12.9k
    }
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
319k
    : m_elements(0)
155
319k
    , m_curveThreshold(qt_real_to_fixed(0.25))
156
319k
    , m_dashThreshold(qt_real_to_fixed(0.25))
157
319k
    , m_customData(nullptr)
158
319k
    , m_moveTo(nullptr)
159
319k
    , m_lineTo(nullptr)
160
319k
    , m_cubicTo(nullptr)
161
319k
{
162
319k
}
163
164
QStrokerOps::~QStrokerOps()
165
319k
{
166
319k
}
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
258
{
178
258
    m_customData = customData;
179
258
    m_elements.reset();
180
258
}
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
258
{
189
258
    if (m_elements.size() > 1)
190
258
        processCurrentSubpath();
191
258
    m_customData = nullptr;
192
258
}
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
129
{
207
129
    if (path.isEmpty())
208
0
        return;
209
210
129
    setCurveThresholdFromTransform(QTransform());
211
129
    begin(customData);
212
129
    int count = path.elementCount();
213
129
    if (matrix.isIdentity()) {
214
3.70k
        for (int i=0; i<count; ++i) {
215
3.57k
            const QPainterPath::Element &e = path.elementAt(i);
216
3.57k
            switch (e.type) {
217
579
            case QPainterPath::MoveToElement:
218
579
                moveTo(qt_real_to_fixed(e.x), qt_real_to_fixed(e.y));
219
579
                break;
220
1.26k
            case QPainterPath::LineToElement:
221
1.26k
                lineTo(qt_real_to_fixed(e.x), qt_real_to_fixed(e.y));
222
1.26k
                break;
223
1.73k
            case QPainterPath::CurveToElement:
224
1.73k
                {
225
1.73k
                    const QPainterPath::Element &cp2 = path.elementAt(++i);
226
1.73k
                    const QPainterPath::Element &ep = path.elementAt(++i);
227
1.73k
                    cubicTo(qt_real_to_fixed(e.x), qt_real_to_fixed(e.y),
228
1.73k
                            qt_real_to_fixed(cp2.x), qt_real_to_fixed(cp2.y),
229
1.73k
                            qt_real_to_fixed(ep.x), qt_real_to_fixed(ep.y));
230
1.73k
                }
231
1.73k
                break;
232
0
            default:
233
0
                break;
234
3.57k
            }
235
3.57k
        }
236
129
    } 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
129
    end();
262
129
}
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
213k
    : m_capStyle(SquareJoin), m_joinStyle(FlatJoin),
337
213k
      m_back1X(0), m_back1Y(0),
338
213k
      m_back2X(0), m_back2Y(0),
339
213k
      m_forceOpen(false)
340
213k
{
341
213k
    m_strokeWidth = qt_real_to_fixed(1);
342
213k
    m_miterLimit = qt_real_to_fixed(2);
343
213k
}
344
345
QStroker::~QStroker()
346
{
347
}
348
349
Qt::PenCapStyle QStroker::capForJoinMode(LineJoinMode mode)
350
129
{
351
129
    if (mode == FlatJoin) return Qt::FlatCap;
352
0
    else if (mode == SquareJoin) return Qt::SquareCap;
353
0
    else return Qt::RoundCap;
354
129
}
355
356
QStroker::LineJoinMode QStroker::joinModeForCap(Qt::PenCapStyle style)
357
105k
{
358
105k
    if (style == Qt::FlatCap) return FlatJoin;
359
105k
    else if (style == Qt::SquareCap) return SquareJoin;
360
0
    else return RoundCap;
361
105k
}
362
363
Qt::PenJoinStyle QStroker::joinForJoinMode(LineJoinMode mode)
364
129
{
365
129
    if (mode == FlatJoin) return Qt::BevelJoin;
366
129
    else if (mode == MiterJoin) return Qt::MiterJoin;
367
129
    else if (mode == SvgMiterJoin) return Qt::SvgMiterJoin;
368
0
    else return Qt::RoundJoin;
369
129
}
370
371
QStroker::LineJoinMode QStroker::joinModeForJoin(Qt::PenJoinStyle joinStyle)
372
105k
{
373
105k
    if (joinStyle == Qt::BevelJoin) return FlatJoin;
374
258
    else if (joinStyle == Qt::MiterJoin) return MiterJoin;
375
258
    else if (joinStyle == Qt::SvgMiterJoin) return SvgMiterJoin;
376
0
    else return RoundJoin;
377
105k
}
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
1.15k
{
387
1.15k
    Q_ASSERT(!m_elements.isEmpty());
388
1.15k
    Q_ASSERT(m_elements.first().type == QPainterPath::MoveToElement);
389
1.15k
    Q_ASSERT(m_elements.size() > 1);
390
391
1.15k
    QSubpathForwardIterator fwit(&m_elements);
392
1.15k
    QSubpathBackwardIterator bwit(&m_elements);
393
394
1.15k
    QLineF fwStartTangent, bwStartTangent;
395
396
1.15k
    bool fwclosed = qt_stroke_side(&fwit, this, false, &fwStartTangent);
397
1.15k
    bool bwclosed = qt_stroke_side(&bwit, this, !fwclosed, &bwStartTangent);
398
399
1.15k
    if (!bwclosed && !fwStartTangent.isNull())
400
324
        joinPoints(m_elements.at(0).x, m_elements.at(0).y, fwStartTangent, m_capStyle);
401
1.15k
}
402
403
404
/*!
405
    \internal
406
*/
407
void QStroker::joinPoints(qfixed focal_x, qfixed focal_y, const QLineF &nextLine, LineJoinMode join)
408
12.0k
{
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
12.0k
#if !defined (QFIXED_26_6) && !defined (Q_FIXED_32_32)
418
12.0k
    if (qFuzzyCompare(m_back1X, nextLine.x1()) && qFuzzyCompare(m_back1Y, nextLine.y1()))
419
3.24k
        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
8.76k
    QLineF prevLine(qt_fixed_to_real(m_back2X), qt_fixed_to_real(m_back2Y),
427
8.76k
                    qt_fixed_to_real(m_back1X), qt_fixed_to_real(m_back1Y));
428
8.76k
    QPointF isect;
429
8.76k
    QLineF::IntersectionType type = prevLine.intersects(nextLine, &isect);
430
431
8.76k
    if (join == FlatJoin) {
432
4.38k
        QLineF shortCut(prevLine.p2(), nextLine.p1());
433
4.38k
        qreal angle = shortCut.angleTo(prevLine);
434
4.38k
        if (type == QLineF::BoundedIntersection || (angle > 90 && !qFuzzyCompare(angle, (qreal)90))) {
435
2.17k
            emitLineTo(focal_x, focal_y);
436
2.17k
            emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1()));
437
2.17k
            return;
438
2.17k
        }
439
2.20k
        emitLineTo(qt_real_to_fixed(nextLine.x1()),
440
2.20k
                   qt_real_to_fixed(nextLine.y1()));
441
442
4.38k
    } else {
443
4.38k
        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
4.38k
        } else if (join == SquareJoin) {
474
324
            qfixed offset = m_strokeWidth / 2;
475
476
324
            QLineF l1(prevLine);
477
324
            qreal dp = QPointF::dotProduct(QPointF(prevLine.dx(), prevLine.dy()), QPointF(nextLine.dx(), nextLine.dy()));
478
324
            if (dp > 0)  // same direction, means that prevLine is from a bezier that has been "reversed" by shifting
479
15
                l1 = QLineF(prevLine.p2(), prevLine.p1());
480
309
            else
481
309
                l1.translate(l1.dx(), l1.dy());
482
324
            l1.setLength(qt_fixed_to_real(offset));
483
324
            QLineF l2(nextLine.p2(), nextLine.p1());
484
324
            l2.translate(l2.dx(), l2.dy());
485
324
            l2.setLength(qt_fixed_to_real(offset));
486
324
            emitLineTo(qt_real_to_fixed(l1.x2()), qt_real_to_fixed(l1.y2()));
487
324
            emitLineTo(qt_real_to_fixed(l2.x2()), qt_real_to_fixed(l2.y2()));
488
324
            emitLineTo(qt_real_to_fixed(l2.x1()), qt_real_to_fixed(l2.y1()));
489
490
4.05k
        } 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
4.05k
        } 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
4.05k
        } else if (join == SvgMiterJoin) {
572
4.05k
            QLineF shortCut(prevLine.p2(), nextLine.p1());
573
4.05k
            qreal angle = shortCut.angleTo(prevLine);
574
4.05k
            if (type == QLineF::BoundedIntersection || (angle > 90 && !qFuzzyCompare(angle, (qreal)90))) {
575
2.16k
                emitLineTo(focal_x, focal_y);
576
2.16k
                emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1()));
577
2.16k
                return;
578
2.16k
            }
579
1.89k
            QLineF miterLine(QPointF(qt_fixed_to_real(focal_x),
580
1.89k
                                     qt_fixed_to_real(focal_y)), isect);
581
1.89k
            if (type == QLineF::NoIntersection || miterLine.length() > qt_fixed_to_real(m_strokeWidth * m_miterLimit) / 2) {
582
294
                emitLineTo(qt_real_to_fixed(nextLine.x1()),
583
294
                           qt_real_to_fixed(nextLine.y1()));
584
1.60k
            } else {
585
1.60k
                emitLineTo(qt_real_to_fixed(isect.x()), qt_real_to_fixed(isect.y()));
586
1.60k
                emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1()));
587
1.60k
            }
588
1.89k
        } else {
589
0
            Q_ASSERT(!"QStroker::joinPoints(), bad join style...");
590
0
        }
591
4.38k
    }
592
8.76k
}
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
2.31k
{
607
    // Used in CurveToElement section below.
608
2.31k
    const int MAX_OFFSET = 16;
609
2.31k
    QBezier offsetCurves[MAX_OFFSET];
610
611
2.31k
    Q_ASSERT(it->hasNext()); // The initaial move to
612
2.31k
    QStrokerOps::Element first_element = it->next();
613
2.31k
    Q_ASSERT(first_element.isMoveTo());
614
615
2.31k
    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
2.31k
    qfixed2d prev = start;
624
625
2.31k
    bool first = true;
626
627
2.31k
    qfixed offset = stroker->strokeWidth() / 2;
628
629
14.3k
    while (it->hasNext()) {
630
12.0k
        QStrokerOps::Element e = it->next();
631
632
        // LineToElement
633
12.0k
        if (e.isLineTo()) {
634
#ifdef QPP_STROKE_DEBUG
635
            qDebug("\n ---> (side) lineto [%.2f, %.2f]", e.x, e.y);
636
#endif
637
5.06k
            QLineF line(qt_fixed_to_real(prev.x), qt_fixed_to_real(prev.y),
638
5.06k
                        qt_fixed_to_real(e.x), qt_fixed_to_real(e.y));
639
5.06k
            if (line.p1() != line.p2()) {
640
5.06k
                QLineF normal = line.normalVector();
641
5.06k
                normal.setLength(offset);
642
5.06k
                line.translate(normal.dx(), normal.dy());
643
644
                // If we are starting a new subpath, move to correct starting point.
645
5.06k
                if (first) {
646
780
                    if (capFirst)
647
12
                        stroker->joinPoints(prev.x, prev.y, line, stroker->capStyleMode());
648
768
                    else
649
768
                        stroker->emitMoveTo(qt_real_to_fixed(line.x1()), qt_real_to_fixed(line.y1()));
650
780
                    *startTangent = line;
651
780
                    first = false;
652
4.28k
                } else {
653
4.28k
                    stroker->joinPoints(prev.x, prev.y, line, stroker->joinStyleMode());
654
4.28k
                }
655
656
                // Add the stroke for this line.
657
5.06k
                stroker->emitLineTo(qt_real_to_fixed(line.x2()),
658
5.06k
                                    qt_real_to_fixed(line.y2()));
659
5.06k
                prev = e;
660
5.06k
            }
661
662
        // CurveToElement
663
6.93k
        } else if (e.isCurveTo()) {
664
6.93k
            QStrokerOps::Element cp2 = it->next(); // control point 2
665
6.93k
            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
6.93k
            QBezier bezier =
674
6.93k
                QBezier::fromPoints(QPointF(qt_fixed_to_real(prev.x), qt_fixed_to_real(prev.y)),
675
6.93k
                                    QPointF(qt_fixed_to_real(e.x), qt_fixed_to_real(e.y)),
676
6.93k
                                    QPointF(qt_fixed_to_real(cp2.x), qt_fixed_to_real(cp2.y)),
677
6.93k
                                    QPointF(qt_fixed_to_real(ep.x), qt_fixed_to_real(ep.y)));
678
6.93k
            int count = bezier.shifted(offsetCurves,
679
6.93k
                                       MAX_OFFSET,
680
6.93k
                                       offset,
681
6.93k
                                       stroker->curveThreshold());
682
683
6.93k
            if (count) {
684
                // If we are starting a new subpath, move to correct starting point
685
6.93k
                QLineF tangent = bezier.startTangent();
686
6.93k
                tangent.translate(offsetCurves[0].pt1() - bezier.pt1());
687
6.93k
                if (first) {
688
1.53k
                    QPointF pt = offsetCurves[0].pt1();
689
1.53k
                    if (capFirst) {
690
312
                        stroker->joinPoints(prev.x, prev.y,
691
312
                                            tangent,
692
312
                                            stroker->capStyleMode());
693
1.22k
                    } else {
694
1.22k
                        stroker->emitMoveTo(qt_real_to_fixed(pt.x()),
695
1.22k
                                            qt_real_to_fixed(pt.y()));
696
1.22k
                    }
697
1.53k
                    *startTangent = tangent;
698
1.53k
                    first = false;
699
5.40k
                } else {
700
5.40k
                    stroker->joinPoints(prev.x, prev.y,
701
5.40k
                                        tangent,
702
5.40k
                                        stroker->joinStyleMode());
703
5.40k
                }
704
705
                // Add these beziers
706
17.9k
                for (int i=0; i<count; ++i) {
707
10.9k
                    QPointF cp1 = offsetCurves[i].pt2();
708
10.9k
                    QPointF cp2 = offsetCurves[i].pt3();
709
10.9k
                    QPointF ep = offsetCurves[i].pt4();
710
10.9k
                    stroker->emitCubicTo(qt_real_to_fixed(cp1.x()), qt_real_to_fixed(cp1.y()),
711
10.9k
                                         qt_real_to_fixed(cp2.x()), qt_real_to_fixed(cp2.y()),
712
10.9k
                                         qt_real_to_fixed(ep.x()), qt_real_to_fixed(ep.y()));
713
10.9k
                }
714
6.93k
            }
715
716
6.93k
            prev = ep;
717
6.93k
        }
718
12.0k
    }
719
720
2.31k
    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
1.66k
        if (!first)
727
1.66k
            stroker->joinPoints(prev.x, prev.y, *startTangent, stroker->joinStyleMode());
728
1.66k
        return true;
729
1.66k
    } else {
730
#ifdef QPP_STROKE_DEBUG
731
        qDebug("\n ---> (side) open subpath");
732
#endif
733
648
        return false;
734
648
    }
735
2.31k
}
bool qt_stroke_side<QSubpathForwardIterator>(QSubpathForwardIterator*, QStroker*, bool, QLineF*)
Line
Count
Source
606
1.15k
{
607
    // Used in CurveToElement section below.
608
1.15k
    const int MAX_OFFSET = 16;
609
1.15k
    QBezier offsetCurves[MAX_OFFSET];
610
611
1.15k
    Q_ASSERT(it->hasNext()); // The initaial move to
612
1.15k
    QStrokerOps::Element first_element = it->next();
613
1.15k
    Q_ASSERT(first_element.isMoveTo());
614
615
1.15k
    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
1.15k
    qfixed2d prev = start;
624
625
1.15k
    bool first = true;
626
627
1.15k
    qfixed offset = stroker->strokeWidth() / 2;
628
629
7.15k
    while (it->hasNext()) {
630
6.00k
        QStrokerOps::Element e = it->next();
631
632
        // LineToElement
633
6.00k
        if (e.isLineTo()) {
634
#ifdef QPP_STROKE_DEBUG
635
            qDebug("\n ---> (side) lineto [%.2f, %.2f]", e.x, e.y);
636
#endif
637
2.53k
            QLineF line(qt_fixed_to_real(prev.x), qt_fixed_to_real(prev.y),
638
2.53k
                        qt_fixed_to_real(e.x), qt_fixed_to_real(e.y));
639
2.53k
            if (line.p1() != line.p2()) {
640
2.53k
                QLineF normal = line.normalVector();
641
2.53k
                normal.setLength(offset);
642
2.53k
                line.translate(normal.dx(), normal.dy());
643
644
                // If we are starting a new subpath, move to correct starting point.
645
2.53k
                if (first) {
646
24
                    if (capFirst)
647
0
                        stroker->joinPoints(prev.x, prev.y, line, stroker->capStyleMode());
648
24
                    else
649
24
                        stroker->emitMoveTo(qt_real_to_fixed(line.x1()), qt_real_to_fixed(line.y1()));
650
24
                    *startTangent = line;
651
24
                    first = false;
652
2.50k
                } else {
653
2.50k
                    stroker->joinPoints(prev.x, prev.y, line, stroker->joinStyleMode());
654
2.50k
                }
655
656
                // Add the stroke for this line.
657
2.53k
                stroker->emitLineTo(qt_real_to_fixed(line.x2()),
658
2.53k
                                    qt_real_to_fixed(line.y2()));
659
2.53k
                prev = e;
660
2.53k
            }
661
662
        // CurveToElement
663
3.46k
        } else if (e.isCurveTo()) {
664
3.46k
            QStrokerOps::Element cp2 = it->next(); // control point 2
665
3.46k
            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
3.46k
            QBezier bezier =
674
3.46k
                QBezier::fromPoints(QPointF(qt_fixed_to_real(prev.x), qt_fixed_to_real(prev.y)),
675
3.46k
                                    QPointF(qt_fixed_to_real(e.x), qt_fixed_to_real(e.y)),
676
3.46k
                                    QPointF(qt_fixed_to_real(cp2.x), qt_fixed_to_real(cp2.y)),
677
3.46k
                                    QPointF(qt_fixed_to_real(ep.x), qt_fixed_to_real(ep.y)));
678
3.46k
            int count = bezier.shifted(offsetCurves,
679
3.46k
                                       MAX_OFFSET,
680
3.46k
                                       offset,
681
3.46k
                                       stroker->curveThreshold());
682
683
3.46k
            if (count) {
684
                // If we are starting a new subpath, move to correct starting point
685
3.46k
                QLineF tangent = bezier.startTangent();
686
3.46k
                tangent.translate(offsetCurves[0].pt1() - bezier.pt1());
687
3.46k
                if (first) {
688
1.13k
                    QPointF pt = offsetCurves[0].pt1();
689
1.13k
                    if (capFirst) {
690
0
                        stroker->joinPoints(prev.x, prev.y,
691
0
                                            tangent,
692
0
                                            stroker->capStyleMode());
693
1.13k
                    } else {
694
1.13k
                        stroker->emitMoveTo(qt_real_to_fixed(pt.x()),
695
1.13k
                                            qt_real_to_fixed(pt.y()));
696
1.13k
                    }
697
1.13k
                    *startTangent = tangent;
698
1.13k
                    first = false;
699
2.33k
                } else {
700
2.33k
                    stroker->joinPoints(prev.x, prev.y,
701
2.33k
                                        tangent,
702
2.33k
                                        stroker->joinStyleMode());
703
2.33k
                }
704
705
                // Add these beziers
706
8.96k
                for (int i=0; i<count; ++i) {
707
5.49k
                    QPointF cp1 = offsetCurves[i].pt2();
708
5.49k
                    QPointF cp2 = offsetCurves[i].pt3();
709
5.49k
                    QPointF ep = offsetCurves[i].pt4();
710
5.49k
                    stroker->emitCubicTo(qt_real_to_fixed(cp1.x()), qt_real_to_fixed(cp1.y()),
711
5.49k
                                         qt_real_to_fixed(cp2.x()), qt_real_to_fixed(cp2.y()),
712
5.49k
                                         qt_real_to_fixed(ep.x()), qt_real_to_fixed(ep.y()));
713
5.49k
                }
714
3.46k
            }
715
716
3.46k
            prev = ep;
717
3.46k
        }
718
6.00k
    }
719
720
1.15k
    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
834
        if (!first)
727
834
            stroker->joinPoints(prev.x, prev.y, *startTangent, stroker->joinStyleMode());
728
834
        return true;
729
834
    } else {
730
#ifdef QPP_STROKE_DEBUG
731
        qDebug("\n ---> (side) open subpath");
732
#endif
733
324
        return false;
734
324
    }
735
1.15k
}
bool qt_stroke_side<QSubpathBackwardIterator>(QSubpathBackwardIterator*, QStroker*, bool, QLineF*)
Line
Count
Source
606
1.15k
{
607
    // Used in CurveToElement section below.
608
1.15k
    const int MAX_OFFSET = 16;
609
1.15k
    QBezier offsetCurves[MAX_OFFSET];
610
611
1.15k
    Q_ASSERT(it->hasNext()); // The initaial move to
612
1.15k
    QStrokerOps::Element first_element = it->next();
613
1.15k
    Q_ASSERT(first_element.isMoveTo());
614
615
1.15k
    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
1.15k
    qfixed2d prev = start;
624
625
1.15k
    bool first = true;
626
627
1.15k
    qfixed offset = stroker->strokeWidth() / 2;
628
629
7.15k
    while (it->hasNext()) {
630
6.00k
        QStrokerOps::Element e = it->next();
631
632
        // LineToElement
633
6.00k
        if (e.isLineTo()) {
634
#ifdef QPP_STROKE_DEBUG
635
            qDebug("\n ---> (side) lineto [%.2f, %.2f]", e.x, e.y);
636
#endif
637
2.53k
            QLineF line(qt_fixed_to_real(prev.x), qt_fixed_to_real(prev.y),
638
2.53k
                        qt_fixed_to_real(e.x), qt_fixed_to_real(e.y));
639
2.53k
            if (line.p1() != line.p2()) {
640
2.53k
                QLineF normal = line.normalVector();
641
2.53k
                normal.setLength(offset);
642
2.53k
                line.translate(normal.dx(), normal.dy());
643
644
                // If we are starting a new subpath, move to correct starting point.
645
2.53k
                if (first) {
646
756
                    if (capFirst)
647
12
                        stroker->joinPoints(prev.x, prev.y, line, stroker->capStyleMode());
648
744
                    else
649
744
                        stroker->emitMoveTo(qt_real_to_fixed(line.x1()), qt_real_to_fixed(line.y1()));
650
756
                    *startTangent = line;
651
756
                    first = false;
652
1.77k
                } else {
653
1.77k
                    stroker->joinPoints(prev.x, prev.y, line, stroker->joinStyleMode());
654
1.77k
                }
655
656
                // Add the stroke for this line.
657
2.53k
                stroker->emitLineTo(qt_real_to_fixed(line.x2()),
658
2.53k
                                    qt_real_to_fixed(line.y2()));
659
2.53k
                prev = e;
660
2.53k
            }
661
662
        // CurveToElement
663
3.46k
        } else if (e.isCurveTo()) {
664
3.46k
            QStrokerOps::Element cp2 = it->next(); // control point 2
665
3.46k
            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
3.46k
            QBezier bezier =
674
3.46k
                QBezier::fromPoints(QPointF(qt_fixed_to_real(prev.x), qt_fixed_to_real(prev.y)),
675
3.46k
                                    QPointF(qt_fixed_to_real(e.x), qt_fixed_to_real(e.y)),
676
3.46k
                                    QPointF(qt_fixed_to_real(cp2.x), qt_fixed_to_real(cp2.y)),
677
3.46k
                                    QPointF(qt_fixed_to_real(ep.x), qt_fixed_to_real(ep.y)));
678
3.46k
            int count = bezier.shifted(offsetCurves,
679
3.46k
                                       MAX_OFFSET,
680
3.46k
                                       offset,
681
3.46k
                                       stroker->curveThreshold());
682
683
3.46k
            if (count) {
684
                // If we are starting a new subpath, move to correct starting point
685
3.46k
                QLineF tangent = bezier.startTangent();
686
3.46k
                tangent.translate(offsetCurves[0].pt1() - bezier.pt1());
687
3.46k
                if (first) {
688
402
                    QPointF pt = offsetCurves[0].pt1();
689
402
                    if (capFirst) {
690
312
                        stroker->joinPoints(prev.x, prev.y,
691
312
                                            tangent,
692
312
                                            stroker->capStyleMode());
693
312
                    } else {
694
90
                        stroker->emitMoveTo(qt_real_to_fixed(pt.x()),
695
90
                                            qt_real_to_fixed(pt.y()));
696
90
                    }
697
402
                    *startTangent = tangent;
698
402
                    first = false;
699
3.06k
                } else {
700
3.06k
                    stroker->joinPoints(prev.x, prev.y,
701
3.06k
                                        tangent,
702
3.06k
                                        stroker->joinStyleMode());
703
3.06k
                }
704
705
                // Add these beziers
706
8.96k
                for (int i=0; i<count; ++i) {
707
5.49k
                    QPointF cp1 = offsetCurves[i].pt2();
708
5.49k
                    QPointF cp2 = offsetCurves[i].pt3();
709
5.49k
                    QPointF ep = offsetCurves[i].pt4();
710
5.49k
                    stroker->emitCubicTo(qt_real_to_fixed(cp1.x()), qt_real_to_fixed(cp1.y()),
711
5.49k
                                         qt_real_to_fixed(cp2.x()), qt_real_to_fixed(cp2.y()),
712
5.49k
                                         qt_real_to_fixed(ep.x()), qt_real_to_fixed(ep.y()));
713
5.49k
                }
714
3.46k
            }
715
716
3.46k
            prev = ep;
717
3.46k
        }
718
6.00k
    }
719
720
1.15k
    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
834
        if (!first)
727
834
            stroker->joinPoints(prev.x, prev.y, *startTangent, stroker->joinStyleMode());
728
834
        return true;
729
834
    } else {
730
#ifdef QPP_STROKE_DEBUG
731
        qDebug("\n ---> (side) open subpath");
732
#endif
733
324
        return false;
734
324
    }
735
1.15k
}
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
106k
    : m_stroker(stroker), m_dashOffset(0), m_stroke_width(1), m_miter_limit(1)
989
106k
{
990
106k
    if (m_stroker) {
991
106k
        setMoveToHook(qdashstroker_moveTo);
992
106k
        setLineToHook(qdashstroker_lineTo);
993
106k
        setCubicToHook(qdashstroker_cubicTo);
994
106k
    }
995
106k
}
996
997
QDashStroker::~QDashStroker()
998
106k
{
999
106k
}
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