Revision control
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++
++ #include "qsvggraphics_p.h"
++ #include "qsvgstructure_p.h"
++ #include "qsvgfont_p.h"
++
++ #include <qabstracttextdocumentlayout.h>
++ #include <qdebug.h>
++ #include <qloggingcategory.h>
++ #include <qpainter.h>
++ #include <qscopedvaluerollback.h>
++ #include <qtextcursor.h>
++ #include <qtextdocument.h>
++ #include <private/qfixed_p.h>
++
++ #include <QElapsedTimer>
++ #include <QLoggingCategory>
++
++ #include <math.h>
++
++ QT_BEGIN_NAMESPACE
++
++ Q_LOGGING_CATEGORY(lcSvgDraw, "qt.svg.draw")
++
++ #ifndef QT_SVG_MAX_LAYOUT_SIZE
++ #define QT_SVG_MAX_LAYOUT_SIZE (qint64(QFIXED_MAX / 2))
++ #endif
++
++ QSvgDummyNode::~QSvgDummyNode()
++ = default;
++
++ void QSvgDummyNode::drawCommand(QPainter *, QSvgExtraStates &)
++ {
++ qWarning("Dummy node not meant to be drawn");
++ }
++
++ QSvgEllipse::QSvgEllipse(QSvgNode *parent, const QRectF &rect)
++ : QSvgNode(parent), m_bounds(rect)
++ {
++ }
++
++ QSvgEllipse::~QSvgEllipse()
++ = default;
++
++ QRectF QSvgEllipse::internalFastBounds(QPainter *p, QSvgExtraStates &) const
++ {
++ return p->transform().mapRect(m_bounds);
++ }
++
++ QRectF QSvgEllipse::internalBounds(QPainter *p, QSvgExtraStates &) const
++ {
++ QPainterPath path;
++ path.addEllipse(m_bounds);
++ qreal sw = strokeWidth(p);
++ return qFuzzyIsNull(sw) ? p->transform().map(path).boundingRect()
++ : boundsOnStroke(p, path, sw, BoundsMode::Simplistic);
++ }
++
++ QRectF QSvgEllipse::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s) const
++ {
++ QPainterPath path;
++ path.addEllipse(m_bounds);
++ qreal sw = strokeWidth(p);
++ QRectF rect = qFuzzyIsNull(sw) ? p->transform().map(path).boundingRect()
++ : boundsOnStroke(p, path, sw, BoundsMode::IncludeMiterLimit);
++ return filterRegion(s.doc(), rect);
++ }
++
++ void QSvgEllipse::drawCommand(QPainter *p, QSvgExtraStates &)
++ {
++ p->drawEllipse(m_bounds);
++ }
++
++ bool QSvgEllipse::separateFillStroke(const QPainter *, const QSvgExtraStates &) const
++ {
++ return true;
++ }
++
++ QSvgImage::QSvgImage(QSvgNode *parent,
++ const QImage &image,
++ const QString &filename,
++ const QRectF &bounds)
++ : QSvgNode(parent)
++ , m_filename(filename)
++ , m_image(image)
++ , m_bounds(bounds)
++ {
++ if (m_bounds.width() == 0.0)
++ m_bounds.setWidth(static_cast<qreal>(m_image.width()));
++ if (m_bounds.height() == 0.0)
++ m_bounds.setHeight(static_cast<qreal>(m_image.height()));
++ }
++
++ QSvgImage::~QSvgImage()
++ = default;
++
++ void QSvgImage::drawCommand(QPainter *p, QSvgExtraStates &)
++ {
++ p->drawImage(m_bounds, m_image);
++ }
++
++ QSvgLine::QSvgLine(QSvgNode *parent, const QLineF &line)
++ : QSvgNode(parent), m_line(line)
++ {
++ }
++
++ QSvgLine::~QSvgLine()
++ = default;
++
++ void QSvgLine::drawCommand(QPainter *p, QSvgExtraStates &states)
++ {
++ if (p->pen().widthF() != 0) {
++ qreal oldOpacity = p->opacity();
++ p->setOpacity(oldOpacity * states.strokeOpacity);
++ if (m_line.isNull() && p->pen().capStyle() != Qt::FlatCap)
++ p->drawPoint(m_line.p1());
++ else
++ p->drawLine(m_line);
++ p->setOpacity(oldOpacity);
++ }
++ QSvgMarker::drawMarkersForNode(this, p, states);
++ }
++
++ QSvgPath::QSvgPath(QSvgNode *parent, const QPainterPath &qpath)
++ : QSvgNode(parent), m_path(qpath)
++ {
++ }
++
++ QSvgPath::~QSvgPath()
++ = default;
++
++ void QSvgPath::drawCommand(QPainter *p, QSvgExtraStates &states)
++ {
++ const qreal oldOpacity = p->opacity();
++ const bool drawingInOnePass = !separateFillStroke(p, states);
++ if (drawingInOnePass)
++ p->setOpacity(oldOpacity * states.fillOpacity);
++ m_path.setFillRule(states.fillRule);
++ if (m_path.boundingRect().isNull() && p->pen().capStyle() != Qt::FlatCap)
++ p->drawPoint(m_path.boundingRect().topLeft());
++ else
++ p->drawPath(m_path);
++ if (!path().isEmpty())
++ QSvgMarker::drawMarkersForNode(this, p, states);
++ if (drawingInOnePass)
++ p->setOpacity(oldOpacity);
++ }
++
++ bool QSvgPath::separateFillStroke(const QPainter *p, const QSvgExtraStates &s) const
++ {
++ return !qFuzzyCompare(s.fillOpacity, s.strokeOpacity)
++ || qFuzzyIsNull(p->pen().widthF());
++ }
++
++ QRectF QSvgPath::internalFastBounds(QPainter *p, QSvgExtraStates &) const
++ {
++ return p->transform().mapRect(m_path.controlPointRect());
++ }
++
++ QRectF QSvgPath::internalBounds(QPainter *p, QSvgExtraStates &) const
++ {
++ qreal sw = strokeWidth(p);
++ return qFuzzyIsNull(sw) ? p->transform().map(m_path).boundingRect()
++ : boundsOnStroke(p, m_path, sw, BoundsMode::Simplistic);
++ }
++
++ QRectF QSvgPath::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s) const
++ {
++ qreal sw = strokeWidth(p);
++ QRectF rect = qFuzzyIsNull(sw) ? p->transform().map(m_path).boundingRect()
++ : boundsOnStroke(p, m_path, sw, BoundsMode::IncludeMiterLimit);
++ rect |= QSvgMarker::markersBoundsForNode(this, p, s);
++ return filterRegion(s.doc(), rect);
++ }
++
++ bool QSvgPath::requiresGroupRendering() const
++ {
++ return hasAnyMarker();
++ }
++
++ QSvgPolygon::QSvgPolygon(QSvgNode *parent, const QPolygonF &poly)
++ : QSvgNode(parent), m_poly(poly)
++ {
++ }
++
++ QSvgPolygon::~QSvgPolygon()
++ = default;
++
++ QRectF QSvgPolygon::internalFastBounds(QPainter *p, QSvgExtraStates &) const
++ {
++ return p->transform().mapRect(m_poly.boundingRect());
++ }
++
++ QRectF QSvgPolygon::internalBounds(QPainter *p, QSvgExtraStates &s) const
++ {
++ return internalBounds(p, s, BoundsMode::Simplistic);
++ }
++
++ QRectF QSvgPolygon::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s) const
++ {
++ QRectF rect = internalBounds(p, s, BoundsMode::IncludeMiterLimit);
++ rect |= QSvgMarker::markersBoundsForNode(this, p, s);
++ return filterRegion(s.doc(), rect);
++ }
++
++ bool QSvgPolygon::requiresGroupRendering() const
++ {
++ return hasAnyMarker();
++ }
++
++ QRectF QSvgPolygon::internalBounds(QPainter *p, QSvgExtraStates &, BoundsMode mode) const
++ {
++ qreal sw = strokeWidth(p);
++ if (qFuzzyIsNull(sw)) {
++ return p->transform().map(m_poly).boundingRect();
++ } else {
++ QPainterPath path;
++ path.addPolygon(m_poly);
++ return boundsOnStroke(p, path, sw, mode);
++ }
++ }
++
++ void QSvgPolygon::drawCommand(QPainter *p, QSvgExtraStates &states)
++ {
++ if (m_poly.boundingRect().isNull() && p->pen().capStyle() != Qt::FlatCap)
++ p->drawPoint(m_poly.first());
++ else
++ p->drawPolygon(m_poly, states.fillRule);
++ QSvgMarker::drawMarkersForNode(this, p, states);
++ }
++
++ bool QSvgPolygon::separateFillStroke(const QPainter *, const QSvgExtraStates &) const
++ {
++ return true;
++ }
++
++ QSvgPolyline::QSvgPolyline(QSvgNode *parent, const QPolygonF &poly)
++ : QSvgNode(parent), m_poly(poly)
++ {
++
++ }
++
++ QSvgPolyline::~QSvgPolyline()
++ = default;
++
++ void QSvgPolyline::drawCommand(QPainter *p, QSvgExtraStates &states)
++ {
++ if (p->brush().style() != Qt::NoBrush) {
++ p->drawPolygon(m_poly, states.fillRule);
++ } else {
++ if (m_poly.boundingRect().isNull() && p->pen().capStyle() != Qt::FlatCap)
++ p->drawPoint(m_poly.first());
++ else
++ p->drawPolyline(m_poly);
++ QSvgMarker::drawMarkersForNode(this, p, states);
++ }
++ }
++
++ bool QSvgPolyline::separateFillStroke(const QPainter *, const QSvgExtraStates &) const
++ {
++ return true;
++ }
++
++ QSvgRect::QSvgRect(QSvgNode *node, const QRectF &rect, qreal rx, qreal ry)
++ : QSvgNode(node),
++ m_rect(rect), m_rx(rx), m_ry(ry)
++ {
++ }
++
++ QSvgRect::~QSvgRect()
++ = default;
++
++ QRectF QSvgRect::internalFastBounds(QPainter *p, QSvgExtraStates &) const
++ {
++ return p->transform().mapRect(m_rect);
++ }
++
++ QRectF QSvgRect::internalBounds(QPainter *p, QSvgExtraStates &s) const
++ {
++ return internalBounds(p, s, BoundsMode::Simplistic);
++ }
++
++ QRectF QSvgRect::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s) const
++ {
++ return filterRegion(s.doc(), internalBounds(p, s, BoundsMode::IncludeMiterLimit));
++ }
++
++ QRectF QSvgRect::internalBounds(QPainter *p, QSvgExtraStates &, BoundsMode mode) const
++ {
++ qreal sw = strokeWidth(p);
++ if (qFuzzyIsNull(sw)) {
++ return p->transform().mapRect(m_rect);
++ } else {
++ QPainterPath path;
++ path.addRect(m_rect);
++ return boundsOnStroke(p, path, sw, mode);
++ }
++ }
++
++ void QSvgRect::drawCommand(QPainter *p, QSvgExtraStates &)
++ {
++ if (m_rx || m_ry)
++ p->drawRoundedRect(m_rect, m_rx, m_ry, Qt::RelativeSize);
++ else
++ p->drawRect(m_rect);
++ }
++
++ bool QSvgRect::separateFillStroke(const QPainter *, const QSvgExtraStates &) const
++ {
++ return true;
++ }
++
++ QSvgTspan * const QSvgText::LINEBREAK = 0;
++
++ QSvgText::QSvgText(QSvgNode *parent, const QPointF &coord)
++ : QSvgNode(parent)
++ , m_coord(coord)
++ , m_size(0, 0)
++ , m_type(Text)
++ , m_mode(Default)
++ {
++ }
++
++ QSvgText::~QSvgText()
++ {
++ for (int i = 0; i < m_tspans.size(); ++i) {
++ if (m_tspans[i] != LINEBREAK)
++ delete m_tspans[i];
++ }
++ }
++
++ void QSvgText::setTextArea(const QSizeF &size)
++ {
++ m_size = size;
++ m_type = Textarea;
++ }
++
++ QRectF QSvgText::internalFastBounds(QPainter *p, QSvgExtraStates &) const
++ {
++ QSvgFontStyle *fontProperty = static_cast<QSvgFontStyle *>(m_style.property(QSvgStyleProperty::Font));
++ QFont font = fontProperty ? fontProperty->qfont() : p->font();
++ QFontMetricsF fm(font);
++
++ int charCount = 0;
++ for (int i = 0; i < m_tspans.size(); ++i) {
++ if (m_tspans.at(i) != LINEBREAK)
++ charCount += m_tspans.at(i)->text().size();
++ }
++
++ QRectF approxMaximumBrect(m_coord.x(),
++ m_coord.y(),
++ charCount * fm.averageCharWidth(),
++ -m_tspans.size() * fm.height());
++ return p->transform().mapRect(approxMaximumBrect);
++ }
++
++ QRectF QSvgText::internalBounds(QPainter *p, QSvgExtraStates &states) const
++ {
++ QRectF boundingRect;
++ if (shouldDrawNode(p, states))
++ draw_helper(p, states, &boundingRect);
++ return p->transform().mapRect(boundingRect);
++ }
++
++ void QSvgText::drawCommand(QPainter *p, QSvgExtraStates &states)
++ {
++ draw_helper(p, states);
++ }
++
++ bool QSvgText::shouldDrawNode(QPainter *p, QSvgExtraStates &) const
++ {
++ qsizetype numChars = 0;
++ qreal originalFontSize = p->font().pointSizeF();
++ qreal maxFontSize = originalFontSize;
++ for (const QSvgTspan *span : std::as_const(m_tspans)) {
++ if (span == LINEBREAK)
++ continue;
++
++ numChars += span->text().size();
++
++ QSvgFontStyle *style = static_cast<QSvgFontStyle *>(span->styleProperty(QSvgStyleProperty::Font));
++ if (style != nullptr && style->qfont().pointSizeF() > maxFontSize)
++ maxFontSize = style->qfont().pointSizeF();
++ }
++
++ QFont font = p->font();
++ font.setPixelSize(maxFontSize);
++ QFontMetricsF fm(font);
++ if (m_tspans.size() * fm.height() >= QT_SVG_MAX_LAYOUT_SIZE) {
++ qCWarning(lcSvgDraw) << "Text element too high to lay out, ignoring";
++ return false;
++ }
++
++ if (numChars * fm.maxWidth() >= QT_SVG_MAX_LAYOUT_SIZE) {
++ qCWarning(lcSvgDraw) << "Text element too wide to lay out, ignoring";
++ return false;
++ }
++
++ return true;
++ }
++
++ bool QSvgText::separateFillStroke(const QPainter *, const QSvgExtraStates &) const
++ {
++ return true;
++ }
++
++ void QSvgText::draw_helper(QPainter *p, QSvgExtraStates &states, QRectF *boundingRect) const
++ {
++ const bool isPainting = (boundingRect == nullptr);
++ if (!isPainting || shouldDrawNode(p, states)) {
++ QFont font = p->font();
++ Qt::Alignment alignment = states.textAnchor;
++
++ qreal y = 0;
++ bool initial = true;
++ qreal px = m_coord.x();
++ qreal py = m_coord.y();
++
++ if (m_type == Textarea) {
++ if (alignment == Qt::AlignHCenter)
++ px += m_size.width() / 2;
++ else if (alignment == Qt::AlignRight)
++ px += m_size.width();
++ }
++
++ QRectF bounds;
++ if (m_size.height() != 0)
++ bounds = QRectF(0, py, 1, m_size.height()); // x and width are not used.
++
++ bool appendSpace = false;
++ QStringList paragraphs;
++ QList<QList<QTextLayout::FormatRange> > formatRanges(1);
++ // Parallel to 'paragraphs': the absolute position of a paragraph whose leading
++ // tspan carried x/y, or nullopt for a paragraph that flows from the previous one.
++ QList<std::optional<QPointF>> paragraphPos;
++ paragraphs.push_back(QString());
++ paragraphPos.push_back(std::nullopt);
++
++ for (int i = 0; i < m_tspans.size(); ++i) {
++ if (m_tspans[i] == LINEBREAK) {
++ // A line break starts a new paragraph. For a text area, an empty paragraph
++ // must be padded with a space so the blank line is preserved;
++ // plain text (e.g. multi-line tspans) just needs a fresh paragraph.
++ if (m_type == Textarea && paragraphs.back().isEmpty()) {
++ font.setPixelSize(font.pointSizeF());
++ font.setHintingPreference(QFont::PreferNoHinting);
++
++ QTextLayout::FormatRange range;
++ range.start = 0;
++ range.length = 1;
++ range.format.setFont(font);
++ formatRanges.back().append(range);
++
++ paragraphs.back().append(QLatin1Char(' '));;
++ }
++ appendSpace = false;
++ paragraphs.push_back(QString());
++ formatRanges.resize(formatRanges.size() + 1);
++ paragraphPos.push_back(std::nullopt);
++ } else {
++ // A tspan carrying an absolute x/y starts a new positioned run: a fresh
++ // paragraph anchored at that user-space coordinate. An unset axis falls
++ // back to the enclosing <text> element's origin.
++ if (m_tspans[i]->isTspan() && m_tspans[i]->hasPosition()) {
++ if (!paragraphs.back().isEmpty()) {
++ paragraphs.push_back(QString());
++ formatRanges.resize(formatRanges.size() + 1);
++ paragraphPos.push_back(std::nullopt);
++ }
++ appendSpace = false;
++ paragraphPos.back() = QPointF(m_tspans[i]->hasX() ? m_tspans[i]->x() : m_coord.x(),
++ m_tspans[i]->hasY() ? m_tspans[i]->y() : m_coord.y());
++ }
++ WhitespaceMode mode = m_tspans[i]->whitespaceMode();
++ m_tspans[i]->applyStyle(p, states);
++
++ font = p->font();
++ font.setPixelSize(font.pointSizeF());
++ font.setHintingPreference(QFont::PreferNoHinting);
++
++ QString newText(m_tspans[i]->text());
++ newText.replace(QLatin1Char('\t'), QLatin1Char(' '));
++ newText.replace(QLatin1Char('\n'), QLatin1Char(' '));
++
++ bool prependSpace = !appendSpace && !m_tspans[i]->isTspan() && (mode == Default) && !paragraphs.back().isEmpty() && newText.startsWith(QLatin1Char(' '));
++ if (appendSpace || prependSpace)
++ paragraphs.back().append(QLatin1Char(' '));
++
++ bool appendSpaceNext = (!m_tspans[i]->isTspan() && (mode == Default) && newText.endsWith(QLatin1Char(' ')));
++
++ if (mode == Default) {
++ newText = newText.simplified();
++ if (newText.isEmpty())
++ appendSpaceNext = false;
++ }
++
++ QTextLayout::FormatRange range;
++ range.start = paragraphs.back().size();
++ range.length = newText.size();
++ range.format.setFont(font);
++ if (p->pen().style() != Qt::NoPen && p->pen().brush() != Qt::NoBrush)
++ range.format.setTextOutline(p->pen());
++ // work around QTBUG-136696: NoBrush fills the foreground with the outline's pen
++ range.format.setForeground(p->brush().style() == Qt::NoBrush
++ ? QColor(Qt::transparent) : p->brush());
++
++ if (appendSpace) {
++ Q_ASSERT(!formatRanges.back().isEmpty());
++ ++formatRanges.back().back().length;
++ } else if (prependSpace) {
++ --range.start;
++ ++range.length;
++ }
++ formatRanges.back().append(range);
++
++ appendSpace = appendSpaceNext;
++ paragraphs.back() += newText;
++
++ m_tspans[i]->revertStyle(p, states);
++ }
++ }
++
++ if (states.svgFont) {
++ // SVG fonts not fully supported...
++ if (!m_glyphsToDraw)
++ m_glyphsToDraw = states.svgFont->toGlyphs(paragraphs.join(QLatin1Char('\n')));
++ if (isPainting) {
++ states.svgFont->draw(p, m_coord, m_glyphsToDraw.value(),
++ p->font().pointSizeF(), states.textAnchor);
++ } else {
++ *boundingRect = states.svgFont->boundingRect(p, m_coord, m_glyphsToDraw.value(),
++ p->font().pointSizeF(), states.textAnchor);
++ }
++ } else {
++ QRectF brect;
++ for (int i = 0; i < paragraphs.size(); ++i) {
++ QTextLayout tl(paragraphs[i]);
++ QTextOption op = tl.textOption();
++ op.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
++ tl.setTextOption(op);
++ tl.setFormats(formatRanges[i]);
++ tl.beginLayout();
++
++ forever {
++ QTextLine line = tl.createLine();
++ if (!line.isValid())
++ break;
++ if (m_size.width() != 0)
++ line.setLineWidth(m_size.width());
++ }
++ tl.endLayout();
++
++ // A paragraph with an explicit position is drawn at that absolute user-space
++ // coordinate; otherwise it flows below the previous one from the text origin.
++ const bool positioned = paragraphPos[i].has_value();
++ const qreal originX = positioned ? paragraphPos[i]->x() : px;
++ const qreal originY = positioned ? paragraphPos[i]->y() : py;
++ qreal localY = 0; // baseline offset within a positioned paragraph
++
++ bool endOfBoundsReached = false;
++ for (int j = 0; j < tl.lineCount(); ++j) {
++ QTextLine line = tl.lineAt(j);
++
++ qreal x = 0;
++ if (alignment == Qt::AlignHCenter)
++ x -= 0.5 * line.naturalTextWidth();
++ else if (alignment == Qt::AlignRight)
++ x -= line.naturalTextWidth();
++
++ if (positioned) {
++ // The tspan's y is a baseline; QTextLine is placed by its top-left,
++ // so offset the first line up by its ascent.
++ if (j == 0)
++ localY -= line.ascent();
++ line.setPosition(QPointF(x, localY));
++ brect |= line.naturalTextRect().translated(originX, originY);
++ localY += 1.1 * line.height();
++ continue;
++ }
++
++ if (initial && m_type == Text)
++ y -= line.ascent();
++ initial = false;
++
++ line.setPosition(QPointF(x, y));
++ brect |= line.naturalTextRect().translated(originX, originY);
++
++ // Check if the current line fits into the bounding rectangle.
++ if ((m_size.width() != 0 && line.naturalTextWidth() > m_size.width())
++ || (m_size.height() != 0 && y + line.height() > m_size.height())) {
++ // I need to set the bounds height to 'y-epsilon' to avoid drawing the current
++ // line. Since the font is scaled to 100 units, 1 should be a safe epsilon.
++ bounds.setHeight(y - 1);
++ endOfBoundsReached = true;
++ break;
++ }
++
++ y += 1.1 * line.height();
++ }
++ if (isPainting)
++ tl.draw(p, QPointF(originX, originY), QList<QTextLayout::FormatRange>(), bounds);
++
++ if (endOfBoundsReached)
++ break;
++ }
++ if (boundingRect) {
++ if (bounds.height() > 0)
++ brect.setBottom(qMin(brect.bottom(), bounds.bottom()));
++ *boundingRect = brect;
++ }
++ }
++ }
++ }
++
++ void QSvgText::addText(QStringView text)
++ {
++ m_tspans.append(new QSvgTspan(this, false));
++ m_tspans.back()->setWhitespaceMode(m_mode);
++ m_tspans.back()->addText(text);
++ }
++
++ QSvgTspan::~QSvgTspan()
++ = default;
++
++ QSvgUse::QSvgUse(const QPointF &start, QSvgNode *parent, QSvgNode *node)
++ : QSvgNode(parent), m_link(node), m_start(start), m_recursing(false)
++ {
++
++ }
++
++ QSvgUse::~QSvgUse()
++ = default;
++
++ void QSvgUse::drawCommand(QPainter *p, QSvgExtraStates &states)
++ {
++ if (Q_UNLIKELY(!m_link || isDescendantOf(m_link) || m_recursing))
++ return;
++
++ Q_ASSERT(states.nestedUseCount == 0 || states.nestedUseLevel > 0);
++ if (states.nestedUseLevel > 3 && states.nestedUseCount > (256 + states.nestedUseLevel * 2)) {
++ qCDebug(lcSvgDraw, "Too many nested use nodes at #%s!", qPrintable(m_linkId));
++ return;
++ }
++
++ QScopedValueRollback<bool> inUseGuard(states.inUse, true);
++
++ if (!m_start.isNull()) {
++ p->translate(m_start);
++ }
++ if (states.nestedUseLevel > 0)
++ ++states.nestedUseCount;
++ {
++ QScopedValueRollback<int> useLevelGuard(states.nestedUseLevel, states.nestedUseLevel + 1);
++ QScopedValueRollback<bool> recursingGuard(m_recursing, true);
++ m_link->draw(p, states);
++ }
++ if (states.nestedUseLevel == 0)
++ states.nestedUseCount = 0;
++
++ if (!m_start.isNull()) {
++ p->translate(-m_start);
++ }
++ }
++
++ QSvgNode::Type QSvgDummyNode::type() const
++ {
++ return FeUnsupported;
++ }
++
++
++ QSvgCircle::~QSvgCircle()
++ = default;
++
++ QSvgNode::Type QSvgCircle::type() const
++ {
++ return Circle;
++ }
++
++ QSvgNode::Type QSvgEllipse::type() const
++ {
++ return Ellipse;
++ }
++
++ QSvgNode::Type QSvgImage::type() const
++ {
++ return Image;
++ }
++
++ QSvgNode::Type QSvgLine::type() const
++ {
++ return Line;
++ }
++
++ QSvgNode::Type QSvgPath::type() const
++ {
++ return Path;
++ }
++
++ QSvgNode::Type QSvgPolygon::type() const
++ {
++ return Polygon;
++ }
++
++ QSvgNode::Type QSvgPolyline::type() const
++ {
++ return Polyline;
++ }
++
++ QSvgNode::Type QSvgRect::type() const
++ {
++ return Rect;
++ }
++
++ QSvgNode::Type QSvgText::type() const
++ {
++ return m_type;
++ }
++
++ QSvgNode::Type QSvgUse::type() const
++ {
++ return Use;
++ }
++
++ QSvgVideo::~QSvgVideo()
++ = default;
++
++ QSvgNode::Type QSvgVideo::type() const
++ {
++ return Video;
++ }
++
++ QRectF QSvgUse::internalBounds(QPainter *p, QSvgExtraStates &states) const
++ {
++ QRectF bounds;
++ if (Q_LIKELY(m_link && !isDescendantOf(m_link) && !m_recursing)) {
++ QScopedValueRollback<bool> guard(m_recursing, true);
++ p->translate(m_start);
++ bounds = m_link->bounds(p, states);
++ p->translate(-m_start);
++ }
++ return bounds;
++ }
++
++ QRectF QSvgUse::decoratedInternalBounds(QPainter *p, QSvgExtraStates &states) const
++ {
++ QRectF bounds;
++ if (Q_LIKELY(m_link && !isDescendantOf(m_link) && !m_recursing)) {
++ QScopedValueRollback<bool> guard(m_recursing, true);
++ p->translate(m_start);
++ bounds = m_link->decoratedBounds(p, states);
++ p->translate(-m_start);
++ }
++ return bounds;
++ }
++
++ QRectF QSvgPolyline::internalFastBounds(QPainter *p, QSvgExtraStates &) const
++ {
++ return p->transform().mapRect(m_poly.boundingRect());
++ }
++
++ QRectF QSvgPolyline::internalBounds(QPainter *p, QSvgExtraStates &s) const
++ {
++ return internalBounds(p, s, BoundsMode::Simplistic);
++ }
++
++ QRectF QSvgPolyline::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s) const
++ {
++ QRectF rect = internalBounds(p, s, BoundsMode::IncludeMiterLimit);
++ rect |= QSvgMarker::markersBoundsForNode(this, p, s);
++ return filterRegion(s.doc(), rect);
++ }
++
++ bool QSvgPolyline::requiresGroupRendering() const
++ {
++ return hasAnyMarker();
++ }
++
++ QRectF QSvgPolyline::internalBounds(QPainter *p, QSvgExtraStates &, BoundsMode mode) const
++ {
++ qreal sw = strokeWidth(p);
++ if (qFuzzyIsNull(sw)) {
++ return p->transform().map(m_poly).boundingRect();
++ } else {
++ QPainterPath path;
++ path.addPolygon(m_poly);
++ return boundsOnStroke(p, path, sw, mode);
++ }
++ }
++
++ QRectF QSvgImage::internalBounds(QPainter *p, QSvgExtraStates &) const
++ {
++ return p->transform().mapRect(m_bounds);
++ }
++
++ QRectF QSvgLine::internalFastBounds(QPainter *p, QSvgExtraStates &) const
++ {
++ QPointF p1 = p->transform().map(m_line.p1());
++ QPointF p2 = p->transform().map(m_line.p2());
++ qreal minX = qMin(p1.x(), p2.x());
++ qreal minY = qMin(p1.y(), p2.y());
++ qreal maxX = qMax(p1.x(), p2.x());
++ qreal maxY = qMax(p1.y(), p2.y());
++ return QRectF(minX, minY, maxX - minX, maxY - minY);
++ }
++
++ QRectF QSvgLine::internalBounds(QPainter *p, QSvgExtraStates &s) const
++ {
++ return internalBounds(p, s, BoundsMode::Simplistic);
++ }
++
++ QRectF QSvgLine::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s) const
++ {
++ QRectF rect = internalBounds(p, s, BoundsMode::IncludeMiterLimit);
++ rect |= QSvgMarker::markersBoundsForNode(this, p, s);
++ return filterRegion(s.doc(), rect);
++ }
++
++ bool QSvgLine::requiresGroupRendering() const
++ {
++ return hasAnyMarker();
++ }
++
++ QRectF QSvgLine::internalBounds(QPainter *p, QSvgExtraStates &s, BoundsMode mode) const
++ {
++ qreal sw = strokeWidth(p);
++ if (qFuzzyIsNull(sw)) {
++ return internalFastBounds(p, s);
++ } else {
++ QPainterPath path;
++ path.moveTo(m_line.p1());
++ path.lineTo(m_line.p2());
++ return boundsOnStroke(p, path, sw, mode);
++ }
++ }
++
++ QT_END_NAMESPACE