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