Coverage Report

Created: 2025-07-23 08:13

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