Coverage Report

Created: 2026-08-17 07:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qtsvg/src/svg/qsvggraphics.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
6
#include "qsvggraphics_p.h"
7
#include "qsvgstructure_p.h"
8
#include "qsvgfont_p.h"
9
10
#include <qabstracttextdocumentlayout.h>
11
#include <qdebug.h>
12
#include <qloggingcategory.h>
13
#include <qpainter.h>
14
#include <qscopedvaluerollback.h>
15
#include <qtextcursor.h>
16
#include <qtextdocument.h>
17
#include <private/qfixed_p.h>
18
19
#include <QElapsedTimer>
20
#include <QLoggingCategory>
21
22
#include <math.h>
23
24
QT_BEGIN_NAMESPACE
25
26
Q_LOGGING_CATEGORY(lcSvgDraw, "qt.svg.draw")
27
28
#ifndef QT_SVG_MAX_LAYOUT_SIZE
29
0
#define QT_SVG_MAX_LAYOUT_SIZE (qint64(QFIXED_MAX / 2))
30
#endif
31
32
0
QSvgDummyNode::~QSvgDummyNode()
33
    = default;
34
35
void QSvgDummyNode::drawCommand(QPainter *, QSvgExtraStates &)
36
0
{
37
0
    qWarning("Dummy node not meant to be drawn");
38
0
}
39
40
QSvgEllipse::QSvgEllipse(QSvgNode *parent, const QRectF &rect)
41
0
    : QSvgNode(parent), m_bounds(rect)
42
0
{
43
0
}
44
45
0
QSvgEllipse::~QSvgEllipse()
46
    = default;
47
48
QRectF QSvgEllipse::internalFastBounds(QPainter *p, QSvgExtraStates &) const
49
0
{
50
0
    return p->transform().mapRect(m_bounds);
51
0
}
52
53
QRectF QSvgEllipse::internalBounds(QPainter *p, QSvgExtraStates &) const
54
0
{
55
0
    QPainterPath path;
56
0
    path.addEllipse(m_bounds);
57
0
    qreal sw = strokeWidth(p);
58
0
    return qFuzzyIsNull(sw) ? p->transform().map(path).boundingRect()
59
0
                            : boundsOnStroke(p, path, sw, BoundsMode::Simplistic);
60
0
}
61
62
QRectF QSvgEllipse::decoratedInternalBounds(QPainter *p, QSvgExtraStates &) const
63
0
{
64
0
    QPainterPath path;
65
0
    path.addEllipse(m_bounds);
66
0
    qreal sw = strokeWidth(p);
67
0
    QRectF rect = qFuzzyIsNull(sw) ? p->transform().map(path).boundingRect()
68
0
                                   : boundsOnStroke(p, path, sw, BoundsMode::IncludeMiterLimit);
69
0
    return filterRegion(rect);
70
0
}
71
72
void QSvgEllipse::drawCommand(QPainter *p, QSvgExtraStates &)
73
0
{
74
0
    p->drawEllipse(m_bounds);
75
0
}
76
77
bool QSvgEllipse::separateFillStroke(const QPainter *, const QSvgExtraStates &) const
78
0
{
79
0
    return true;
80
0
}
81
82
QSvgImage::QSvgImage(QSvgNode *parent,
83
                     const QImage &image,
84
                     const QString &filename,
85
                     const QRectF &bounds)
86
0
    : QSvgNode(parent)
87
0
    , m_filename(filename)
88
0
    , m_image(image)
89
0
    , m_bounds(bounds)
90
0
{
91
0
    if (m_bounds.width() == 0.0)
92
0
        m_bounds.setWidth(static_cast<qreal>(m_image.width()));
93
0
    if (m_bounds.height() == 0.0)
94
0
        m_bounds.setHeight(static_cast<qreal>(m_image.height()));
95
0
}
96
97
0
QSvgImage::~QSvgImage()
98
    = default;
99
100
void QSvgImage::drawCommand(QPainter *p, QSvgExtraStates &)
101
0
{
102
0
    p->drawImage(m_bounds, m_image);
103
0
}
104
105
QSvgLine::QSvgLine(QSvgNode *parent, const QLineF &line)
106
32
    : QSvgNode(parent), m_line(line)
107
32
{
108
32
}
109
110
32
QSvgLine::~QSvgLine()
111
    = default;
112
113
void QSvgLine::drawCommand(QPainter *p, QSvgExtraStates &states)
114
0
{
115
0
    if (p->pen().widthF() != 0) {
116
0
        qreal oldOpacity = p->opacity();
117
0
        p->setOpacity(oldOpacity * states.strokeOpacity);
118
0
        if (m_line.isNull() && p->pen().capStyle() != Qt::FlatCap)
119
0
            p->drawPoint(m_line.p1());
120
0
        else
121
0
            p->drawLine(m_line);
122
0
        p->setOpacity(oldOpacity);
123
0
    }
124
0
    QSvgMarker::drawMarkersForNode(this, p, states);
125
0
}
126
127
QSvgPath::QSvgPath(QSvgNode *parent, const QPainterPath &qpath)
128
155k
    : QSvgNode(parent), m_path(qpath)
129
155k
{
130
155k
}
131
132
155k
QSvgPath::~QSvgPath()
133
    = default;
134
135
void QSvgPath::drawCommand(QPainter *p, QSvgExtraStates &states)
136
116k
{
137
116k
    const qreal oldOpacity = p->opacity();
138
116k
    const bool drawingInOnePass = !separateFillStroke(p, states);
139
116k
    if (drawingInOnePass)
140
42.2k
        p->setOpacity(oldOpacity * states.fillOpacity);
141
116k
    m_path.setFillRule(states.fillRule);
142
116k
    if (m_path.boundingRect().isNull() && p->pen().capStyle() != Qt::FlatCap)
143
3.50k
        p->drawPoint(m_path.boundingRect().topLeft());
144
112k
    else
145
112k
        p->drawPath(m_path);
146
116k
    if (!path().isEmpty())
147
106k
        QSvgMarker::drawMarkersForNode(this, p, states);
148
116k
    if (drawingInOnePass)
149
42.2k
        p->setOpacity(oldOpacity);
150
116k
}
151
152
bool QSvgPath::separateFillStroke(const QPainter *p, const QSvgExtraStates &s) const
153
199k
{
154
199k
    return !qFuzzyCompare(s.fillOpacity, s.strokeOpacity)
155
84.4k
            || qFuzzyIsNull(p->pen().widthF());
156
199k
}
157
158
QRectF QSvgPath::internalFastBounds(QPainter *p, QSvgExtraStates &) const
159
91.7k
{
160
91.7k
    return p->transform().mapRect(m_path.controlPointRect());
161
91.7k
}
162
163
QRectF QSvgPath::internalBounds(QPainter *p, QSvgExtraStates &) const
164
54.5k
{
165
54.5k
    qreal sw = strokeWidth(p);
166
54.5k
    return qFuzzyIsNull(sw) ? p->transform().map(m_path).boundingRect()
167
54.5k
                            : boundsOnStroke(p, m_path, sw, BoundsMode::Simplistic);
168
54.5k
}
169
170
QRectF QSvgPath::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s) const
171
0
{
172
0
    qreal sw = strokeWidth(p);
173
0
    QRectF rect = qFuzzyIsNull(sw) ? p->transform().map(m_path).boundingRect()
174
0
                                   : boundsOnStroke(p, m_path, sw, BoundsMode::IncludeMiterLimit);
175
0
    rect |= QSvgMarker::markersBoundsForNode(this, p, s);
176
0
    return filterRegion(rect);
177
0
}
178
179
bool QSvgPath::requiresGroupRendering() const
180
778
{
181
778
    return hasAnyMarker();
182
778
}
183
184
QSvgPolygon::QSvgPolygon(QSvgNode *parent, const QPolygonF &poly)
185
0
    : QSvgNode(parent), m_poly(poly)
186
0
{
187
0
}
188
189
0
QSvgPolygon::~QSvgPolygon()
190
    = default;
191
192
QRectF QSvgPolygon::internalFastBounds(QPainter *p, QSvgExtraStates &) const
193
0
{
194
0
    return p->transform().mapRect(m_poly.boundingRect());
195
0
}
196
197
QRectF QSvgPolygon::internalBounds(QPainter *p, QSvgExtraStates &s) const
198
0
{
199
0
    return internalBounds(p, s, BoundsMode::Simplistic);
200
0
}
201
202
QRectF QSvgPolygon::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s) const
203
0
{
204
0
    QRectF rect = internalBounds(p, s, BoundsMode::IncludeMiterLimit);
205
0
    rect |= QSvgMarker::markersBoundsForNode(this, p, s);
206
0
    return filterRegion(rect);
207
0
}
208
209
bool QSvgPolygon::requiresGroupRendering() const
210
0
{
211
0
    return hasAnyMarker();
212
0
}
213
214
QRectF QSvgPolygon::internalBounds(QPainter *p, QSvgExtraStates &, BoundsMode mode) const
215
0
{
216
0
    qreal sw = strokeWidth(p);
217
0
    if (qFuzzyIsNull(sw)) {
218
0
        return p->transform().map(m_poly).boundingRect();
219
0
    } else {
220
0
        QPainterPath path;
221
0
        path.addPolygon(m_poly);
222
0
        return boundsOnStroke(p, path, sw, mode);
223
0
    }
224
0
}
225
226
void QSvgPolygon::drawCommand(QPainter *p, QSvgExtraStates &states)
227
0
{
228
0
    if (m_poly.boundingRect().isNull() && p->pen().capStyle() != Qt::FlatCap)
229
0
        p->drawPoint(m_poly.first());
230
0
    else
231
0
        p->drawPolygon(m_poly, states.fillRule);
232
0
    QSvgMarker::drawMarkersForNode(this, p, states);
233
0
}
234
235
bool QSvgPolygon::separateFillStroke(const QPainter *, const QSvgExtraStates &) const
236
0
{
237
0
    return true;
238
0
}
239
240
QSvgPolyline::QSvgPolyline(QSvgNode *parent, const QPolygonF &poly)
241
0
    : QSvgNode(parent), m_poly(poly)
242
0
{
243
244
0
}
245
246
0
QSvgPolyline::~QSvgPolyline()
247
    = default;
248
249
void QSvgPolyline::drawCommand(QPainter *p, QSvgExtraStates &states)
250
0
{
251
0
    if (p->brush().style() != Qt::NoBrush) {
252
0
        p->drawPolygon(m_poly, states.fillRule);
253
0
    } else {
254
0
        if (m_poly.boundingRect().isNull() && p->pen().capStyle() != Qt::FlatCap)
255
0
            p->drawPoint(m_poly.first());
256
0
        else
257
0
            p->drawPolyline(m_poly);
258
0
        QSvgMarker::drawMarkersForNode(this, p, states);
259
0
    }
260
0
}
261
262
bool QSvgPolyline::separateFillStroke(const QPainter *, const QSvgExtraStates &) const
263
0
{
264
0
    return true;
265
0
}
266
267
QSvgRect::QSvgRect(QSvgNode *node, const QRectF &rect, qreal rx, qreal ry)
268
0
    : QSvgNode(node),
269
0
      m_rect(rect), m_rx(rx), m_ry(ry)
270
0
{
271
0
}
272
273
0
QSvgRect::~QSvgRect()
274
    = default;
275
276
QRectF QSvgRect::internalFastBounds(QPainter *p, QSvgExtraStates &) const
277
0
{
278
0
    return p->transform().mapRect(m_rect);
279
0
}
280
281
QRectF QSvgRect::internalBounds(QPainter *p, QSvgExtraStates &s) const
282
0
{
283
0
    return internalBounds(p, s, BoundsMode::Simplistic);
284
0
}
285
286
QRectF QSvgRect::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s) const
287
0
{
288
0
    return filterRegion(internalBounds(p, s, BoundsMode::IncludeMiterLimit));
289
0
}
290
291
QRectF QSvgRect::internalBounds(QPainter *p, QSvgExtraStates &, BoundsMode mode) const
292
0
{
293
0
    qreal sw = strokeWidth(p);
294
0
    if (qFuzzyIsNull(sw)) {
295
0
        return p->transform().mapRect(m_rect);
296
0
    } else {
297
0
        QPainterPath path;
298
0
        path.addRect(m_rect);
299
0
        return boundsOnStroke(p, path, sw, mode);
300
0
    }
301
0
}
302
303
void QSvgRect::drawCommand(QPainter *p, QSvgExtraStates &)
304
0
{
305
0
    if (m_rx || m_ry)
306
0
        p->drawRoundedRect(m_rect, m_rx, m_ry, Qt::RelativeSize);
307
0
    else
308
0
        p->drawRect(m_rect);
309
0
}
310
311
bool QSvgRect::separateFillStroke(const QPainter *, const QSvgExtraStates &) const
312
0
{
313
0
    return true;
314
0
}
315
316
QSvgTspan * const QSvgText::LINEBREAK = 0;
317
318
QSvgText::QSvgText(QSvgNode *parent, const QPointF &coord)
319
21.6k
    : QSvgNode(parent)
320
21.6k
    , m_coord(coord)
321
21.6k
    , m_size(0, 0)
322
21.6k
    , m_type(Text)
323
21.6k
    , m_mode(Default)
324
21.6k
{
325
21.6k
}
326
327
QSvgText::~QSvgText()
328
21.6k
{
329
33.1k
    for (int i = 0; i < m_tspans.size(); ++i) {
330
11.4k
        if (m_tspans[i] != LINEBREAK)
331
11.4k
            delete m_tspans[i];
332
11.4k
    }
333
21.6k
}
334
335
void QSvgText::setTextArea(const QSizeF &size)
336
0
{
337
0
    m_size = size;
338
0
    m_type = Textarea;
339
0
}
340
341
QRectF QSvgText::internalFastBounds(QPainter *p, QSvgExtraStates &) const
342
0
{
343
0
    QSvgFontStyle *fontProperty = static_cast<QSvgFontStyle *>(m_style.property(QSvgStyleProperty::Font));
344
0
    QFont font = fontProperty ? fontProperty->qfont() : p->font();
345
0
    QFontMetricsF fm(font);
346
347
0
    int charCount = 0;
348
0
    for (int i = 0; i < m_tspans.size(); ++i) {
349
0
        if (m_tspans.at(i) != LINEBREAK)
350
0
            charCount += m_tspans.at(i)->text().size();
351
0
    }
352
353
0
    QRectF approxMaximumBrect(m_coord.x(),
354
0
                              m_coord.y(),
355
0
                              charCount * fm.averageCharWidth(),
356
0
                              -m_tspans.size() * fm.height());
357
0
    return p->transform().mapRect(approxMaximumBrect);
358
0
}
359
360
QRectF QSvgText::internalBounds(QPainter *p, QSvgExtraStates &states) const
361
0
{
362
0
    QRectF boundingRect;
363
0
    if (shouldDrawNode(p, states))
364
0
        draw_helper(p, states, &boundingRect);
365
0
    return p->transform().mapRect(boundingRect);
366
0
}
367
368
void QSvgText::drawCommand(QPainter *p, QSvgExtraStates &states)
369
0
{
370
0
    draw_helper(p, states);
371
0
}
372
373
bool QSvgText::shouldDrawNode(QPainter *p, QSvgExtraStates &) const
374
0
{
375
0
    qsizetype numChars = 0;
376
0
    qreal originalFontSize = p->font().pointSizeF();
377
0
    qreal maxFontSize = originalFontSize;
378
0
    for (const QSvgTspan *span : std::as_const(m_tspans)) {
379
0
        if (span == LINEBREAK)
380
0
            continue;
381
382
0
        numChars += span->text().size();
383
384
0
        QSvgFontStyle *style = static_cast<QSvgFontStyle *>(span->styleProperty(QSvgStyleProperty::Font));
385
0
        if (style != nullptr && style->qfont().pointSizeF() > maxFontSize)
386
0
            maxFontSize = style->qfont().pointSizeF();
387
0
    }
388
389
0
    QFont font = p->font();
390
0
    font.setPixelSize(maxFontSize);
391
0
    QFontMetricsF fm(font);
392
0
    if (m_tspans.size() * fm.height() >= QT_SVG_MAX_LAYOUT_SIZE) {
393
0
        qCWarning(lcSvgDraw) << "Text element too high to lay out, ignoring";
394
0
        return false;
395
0
    }
396
397
0
    if (numChars * fm.maxWidth() >= QT_SVG_MAX_LAYOUT_SIZE) {
398
0
        qCWarning(lcSvgDraw) << "Text element too wide to lay out, ignoring";
399
0
        return false;
400
0
    }
401
402
0
    return true;
403
0
}
404
405
bool QSvgText::separateFillStroke(const QPainter *, const QSvgExtraStates &) const
406
0
{
407
0
    return true;
408
0
}
409
410
void QSvgText::draw_helper(QPainter *p, QSvgExtraStates &states, QRectF *boundingRect) const
411
0
{
412
0
    const bool isPainting = (boundingRect == nullptr);
413
0
    if (!isPainting || shouldDrawNode(p, states)) {
414
0
        QFont font = p->font();
415
0
        Qt::Alignment alignment = states.textAnchor;
416
417
0
        qreal y = 0;
418
0
        bool initial = true;
419
0
        qreal px = m_coord.x();
420
0
        qreal py = m_coord.y();
421
422
0
        if (m_type == Textarea) {
423
0
            if (alignment == Qt::AlignHCenter)
424
0
                px += m_size.width() / 2;
425
0
            else if (alignment == Qt::AlignRight)
426
0
                px += m_size.width();
427
0
        }
428
429
0
        QRectF bounds;
430
0
        if (m_size.height() != 0)
431
0
            bounds = QRectF(0, py, 1, m_size.height()); // x and width are not used.
432
433
0
        bool appendSpace = false;
434
0
        QStringList paragraphs;
435
0
        QList<QList<QTextLayout::FormatRange> > formatRanges(1);
436
0
        paragraphs.push_back(QString());
437
438
0
        for (int i = 0; i < m_tspans.size(); ++i) {
439
0
            if (m_tspans[i] == LINEBREAK) {
440
0
                if (m_type == Textarea) {
441
0
                    if (paragraphs.back().isEmpty()) {
442
0
                        font.setPixelSize(font.pointSizeF());
443
0
                        font.setHintingPreference(QFont::PreferNoHinting);
444
445
0
                        QTextLayout::FormatRange range;
446
0
                        range.start = 0;
447
0
                        range.length = 1;
448
0
                        range.format.setFont(font);
449
0
                        formatRanges.back().append(range);
450
451
0
                        paragraphs.back().append(QLatin1Char(' '));;
452
0
                    }
453
0
                    appendSpace = false;
454
0
                    paragraphs.push_back(QString());
455
0
                    formatRanges.resize(formatRanges.size() + 1);
456
0
                }
457
0
            } else {
458
0
                WhitespaceMode mode = m_tspans[i]->whitespaceMode();
459
0
                m_tspans[i]->applyStyle(p, states);
460
461
0
                font = p->font();
462
0
                font.setPixelSize(font.pointSizeF());
463
0
                font.setHintingPreference(QFont::PreferNoHinting);
464
465
0
                QString newText(m_tspans[i]->text());
466
0
                newText.replace(QLatin1Char('\t'), QLatin1Char(' '));
467
0
                newText.replace(QLatin1Char('\n'), QLatin1Char(' '));
468
469
0
                bool prependSpace = !appendSpace && !m_tspans[i]->isTspan() && (mode == Default) && !paragraphs.back().isEmpty() && newText.startsWith(QLatin1Char(' '));
470
0
                if (appendSpace || prependSpace)
471
0
                    paragraphs.back().append(QLatin1Char(' '));
472
473
0
                bool appendSpaceNext = (!m_tspans[i]->isTspan() && (mode == Default) && newText.endsWith(QLatin1Char(' ')));
474
475
0
                if (mode == Default) {
476
0
                    newText = newText.simplified();
477
0
                    if (newText.isEmpty())
478
0
                        appendSpaceNext = false;
479
0
                }
480
481
0
                QTextLayout::FormatRange range;
482
0
                range.start = paragraphs.back().size();
483
0
                range.length = newText.size();
484
0
                range.format.setFont(font);
485
0
                if (p->pen().style() != Qt::NoPen && p->pen().brush() != Qt::NoBrush)
486
0
                    range.format.setTextOutline(p->pen());
487
                // work around QTBUG-136696: NoBrush fills the foreground with the outline's pen
488
0
                range.format.setForeground(p->brush().style() == Qt::NoBrush
489
0
                                           ? QColor(Qt::transparent) : p->brush());
490
491
0
                if (appendSpace) {
492
0
                    Q_ASSERT(!formatRanges.back().isEmpty());
493
0
                    ++formatRanges.back().back().length;
494
0
                } else if (prependSpace) {
495
0
                    --range.start;
496
0
                    ++range.length;
497
0
                }
498
0
                formatRanges.back().append(range);
499
500
0
                appendSpace = appendSpaceNext;
501
0
                paragraphs.back() += newText;
502
503
0
                m_tspans[i]->revertStyle(p, states);
504
0
            }
505
0
        }
506
507
0
        if (states.svgFont) {
508
            // SVG fonts not fully supported...
509
0
            if (!m_glyphsToDraw)
510
0
                m_glyphsToDraw = states.svgFont->toGlyphs(paragraphs.join(QLatin1Char('\n')));
511
0
            if (isPainting) {
512
0
                states.svgFont->draw(p, m_coord, m_glyphsToDraw.value(),
513
0
                                     p->font().pointSizeF(), states.textAnchor);
514
0
            } else {
515
0
                *boundingRect = states.svgFont->boundingRect(p, m_coord, m_glyphsToDraw.value(),
516
0
                                                             p->font().pointSizeF(), states.textAnchor);
517
0
            }
518
0
        } else {
519
0
            QRectF brect;
520
0
            for (int i = 0; i < paragraphs.size(); ++i) {
521
0
                QTextLayout tl(paragraphs[i]);
522
0
                QTextOption op = tl.textOption();
523
0
                op.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
524
0
                tl.setTextOption(op);
525
0
                tl.setFormats(formatRanges[i]);
526
0
                tl.beginLayout();
527
528
0
                forever {
529
0
                    QTextLine line = tl.createLine();
530
0
                    if (!line.isValid())
531
0
                        break;
532
0
                    if (m_size.width() != 0)
533
0
                        line.setLineWidth(m_size.width());
534
0
                }
535
0
                tl.endLayout();
536
537
0
                bool endOfBoundsReached = false;
538
0
                for (int i = 0; i < tl.lineCount(); ++i) {
539
0
                    QTextLine line = tl.lineAt(i);
540
541
0
                    qreal x = 0;
542
0
                    if (alignment == Qt::AlignHCenter)
543
0
                        x -= 0.5 * line.naturalTextWidth();
544
0
                    else if (alignment == Qt::AlignRight)
545
0
                        x -= line.naturalTextWidth();
546
547
0
                    if (initial && m_type == Text)
548
0
                        y -= line.ascent();
549
0
                    initial = false;
550
551
0
                    line.setPosition(QPointF(x, y));
552
0
                    brect |= line.naturalTextRect();
553
554
                    // Check if the current line fits into the bounding rectangle.
555
0
                    if ((m_size.width() != 0 && line.naturalTextWidth() > m_size.width())
556
0
                        || (m_size.height() != 0 && y + line.height() > m_size.height())) {
557
                        // I need to set the bounds height to 'y-epsilon' to avoid drawing the current
558
                        // line. Since the font is scaled to 100 units, 1 should be a safe epsilon.
559
0
                        bounds.setHeight(y - 1);
560
0
                        endOfBoundsReached = true;
561
0
                        break;
562
0
                    }
563
564
0
                    y += 1.1 * line.height();
565
0
                }
566
0
                if (isPainting)
567
0
                    tl.draw(p, QPointF(px, py), QList<QTextLayout::FormatRange>(), bounds);
568
569
0
                if (endOfBoundsReached)
570
0
                    break;
571
0
            }
572
0
            if (boundingRect) {
573
0
                brect.translate(m_coord);
574
0
                if (bounds.height() > 0)
575
0
                    brect.setBottom(qMin(brect.bottom(), bounds.bottom()));
576
0
                *boundingRect = brect;
577
0
            }
578
0
        }
579
0
    }
580
0
}
581
582
void QSvgText::addText(QStringView text)
583
11.4k
{
584
11.4k
    m_tspans.append(new QSvgTspan(this, false));
585
11.4k
    m_tspans.back()->setWhitespaceMode(m_mode);
586
11.4k
    m_tspans.back()->addText(text);
587
11.4k
}
588
589
11.4k
QSvgTspan::~QSvgTspan()
590
    = default;
591
592
QSvgUse::QSvgUse(const QPointF &start, QSvgNode *parent, QSvgNode *node)
593
44.2k
    : QSvgNode(parent), m_link(node), m_start(start), m_recursing(false)
594
44.2k
{
595
596
44.2k
}
597
598
44.2k
QSvgUse::~QSvgUse()
599
    = default;
600
601
void QSvgUse::drawCommand(QPainter *p, QSvgExtraStates &states)
602
0
{
603
0
    if (Q_UNLIKELY(!m_link || isDescendantOf(m_link) || m_recursing))
604
0
        return;
605
606
0
    Q_ASSERT(states.nestedUseCount == 0 || states.nestedUseLevel > 0);
607
0
    if (states.nestedUseLevel > 3 && states.nestedUseCount > (256 + states.nestedUseLevel * 2)) {
608
0
        qCDebug(lcSvgDraw, "Too many nested use nodes at #%s!", qPrintable(m_linkId));
609
0
        return;
610
0
    }
611
612
0
    QScopedValueRollback<bool> inUseGuard(states.inUse, true);
613
614
0
    if (!m_start.isNull()) {
615
0
        p->translate(m_start);
616
0
    }
617
0
    if (states.nestedUseLevel > 0)
618
0
        ++states.nestedUseCount;
619
0
    {
620
0
        QScopedValueRollback<int> useLevelGuard(states.nestedUseLevel, states.nestedUseLevel + 1);
621
0
        QScopedValueRollback<bool> recursingGuard(m_recursing, true);
622
0
        m_link->draw(p, states);
623
0
    }
624
0
    if (states.nestedUseLevel == 0)
625
0
        states.nestedUseCount = 0;
626
627
0
    if (!m_start.isNull()) {
628
0
        p->translate(-m_start);
629
0
    }
630
0
}
631
632
QSvgNode::Type QSvgDummyNode::type() const
633
0
{
634
0
    return FeUnsupported;
635
0
}
636
637
638
QSvgCircle::~QSvgCircle()
639
    = default;
640
641
QSvgNode::Type QSvgCircle::type() const
642
0
{
643
0
    return Circle;
644
0
}
645
646
QSvgNode::Type QSvgEllipse::type() const
647
0
{
648
0
    return Ellipse;
649
0
}
650
651
QSvgNode::Type QSvgImage::type() const
652
0
{
653
0
    return Image;
654
0
}
655
656
QSvgNode::Type QSvgLine::type() const
657
479
{
658
479
    return Line;
659
479
}
660
661
QSvgNode::Type QSvgPath::type() const
662
1.21M
{
663
1.21M
    return Path;
664
1.21M
}
665
666
QSvgNode::Type QSvgPolygon::type() const
667
0
{
668
0
    return Polygon;
669
0
}
670
671
QSvgNode::Type QSvgPolyline::type() const
672
0
{
673
0
    return Polyline;
674
0
}
675
676
QSvgNode::Type QSvgRect::type() const
677
0
{
678
0
    return Rect;
679
0
}
680
681
QSvgNode::Type QSvgText::type() const
682
92.9k
{
683
92.9k
    return m_type;
684
92.9k
}
685
686
QSvgNode::Type QSvgUse::type() const
687
1.09M
{
688
1.09M
    return Use;
689
1.09M
}
690
691
0
QSvgVideo::~QSvgVideo()
692
    = default;
693
694
QSvgNode::Type QSvgVideo::type() const
695
0
{
696
0
    return Video;
697
0
}
698
699
QRectF QSvgUse::internalBounds(QPainter *p, QSvgExtraStates &states) const
700
868k
{
701
868k
    QRectF bounds;
702
868k
    if (Q_LIKELY(m_link && !isDescendantOf(m_link) && !m_recursing)) {
703
413k
        QScopedValueRollback<bool> guard(m_recursing, true);
704
413k
        p->translate(m_start);
705
413k
        bounds = m_link->bounds(p, states);
706
413k
        p->translate(-m_start);
707
413k
    }
708
868k
    return bounds;
709
868k
}
710
711
QRectF QSvgUse::decoratedInternalBounds(QPainter *p, QSvgExtraStates &states) const
712
0
{
713
0
    QRectF bounds;
714
0
    if (Q_LIKELY(m_link && !isDescendantOf(m_link) && !m_recursing)) {
715
0
        QScopedValueRollback<bool> guard(m_recursing, true);
716
0
        p->translate(m_start);
717
0
        bounds = m_link->decoratedBounds(p, states);
718
0
        p->translate(-m_start);
719
0
    }
720
0
    return bounds;
721
0
}
722
723
QRectF QSvgPolyline::internalFastBounds(QPainter *p, QSvgExtraStates &) const
724
0
{
725
0
    return p->transform().mapRect(m_poly.boundingRect());
726
0
}
727
728
QRectF QSvgPolyline::internalBounds(QPainter *p, QSvgExtraStates &s) const
729
0
{
730
0
    return internalBounds(p, s, BoundsMode::Simplistic);
731
0
}
732
733
QRectF QSvgPolyline::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s) const
734
0
{
735
0
    QRectF rect = internalBounds(p, s, BoundsMode::IncludeMiterLimit);
736
0
    rect |= QSvgMarker::markersBoundsForNode(this, p, s);
737
0
    return filterRegion(rect);
738
0
}
739
740
bool QSvgPolyline::requiresGroupRendering() const
741
0
{
742
0
    return hasAnyMarker();
743
0
}
744
745
QRectF QSvgPolyline::internalBounds(QPainter *p, QSvgExtraStates &, BoundsMode mode) const
746
0
{
747
0
    qreal sw = strokeWidth(p);
748
0
    if (qFuzzyIsNull(sw)) {
749
0
        return p->transform().map(m_poly).boundingRect();
750
0
    } else {
751
0
        QPainterPath path;
752
0
        path.addPolygon(m_poly);
753
0
        return boundsOnStroke(p, path, sw, mode);
754
0
    }
755
0
}
756
757
QRectF QSvgImage::internalBounds(QPainter *p, QSvgExtraStates &) const
758
0
{
759
0
    return p->transform().mapRect(m_bounds);
760
0
}
761
762
QRectF QSvgLine::internalFastBounds(QPainter *p, QSvgExtraStates &) const
763
0
{
764
0
    QPointF p1 = p->transform().map(m_line.p1());
765
0
    QPointF p2 = p->transform().map(m_line.p2());
766
0
    qreal minX = qMin(p1.x(), p2.x());
767
0
    qreal minY = qMin(p1.y(), p2.y());
768
0
    qreal maxX = qMax(p1.x(), p2.x());
769
0
    qreal maxY = qMax(p1.y(), p2.y());
770
0
    return QRectF(minX, minY, maxX - minX, maxY - minY);
771
0
}
772
773
QRectF QSvgLine::internalBounds(QPainter *p, QSvgExtraStates &s) const
774
0
{
775
0
    return  internalBounds(p, s, BoundsMode::Simplistic);
776
0
}
777
778
QRectF QSvgLine::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s) const
779
0
{
780
0
    QRectF rect = internalBounds(p, s, BoundsMode::IncludeMiterLimit);
781
0
    rect |= QSvgMarker::markersBoundsForNode(this, p, s);
782
0
    return filterRegion(rect);
783
0
}
784
785
bool QSvgLine::requiresGroupRendering() const
786
0
{
787
0
    return hasAnyMarker();
788
0
}
789
790
QRectF QSvgLine::internalBounds(QPainter *p, QSvgExtraStates &s, BoundsMode mode) const
791
0
{
792
0
    qreal sw = strokeWidth(p);
793
0
    if (qFuzzyIsNull(sw)) {
794
0
        return internalFastBounds(p, s);
795
0
    } else {
796
0
        QPainterPath path;
797
0
        path.moveTo(m_line.p1());
798
0
        path.lineTo(m_line.p2());
799
0
        return boundsOnStroke(p, path, sw, mode);
800
0
    }
801
0
}
802
803
QT_END_NAMESPACE