Coverage Report

Created: 2026-03-12 07:14

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