From d130c74f4f54cddcd5baa7d8dc4c5b68772345c2 Mon Sep 17 00:00:00 2001 From: Eskil Abrahamsen Blomfeldt Date: Wed, 12 Apr 2023 13:21:54 +0200 Subject: Don't rasterize gigantic shapes We set a limit for the bounding rect of shapes to avoid stalling applications for several minutes while rendering unreasonably large shapes. The limit can be disabled using an environment variable. [ChangeLog][Performance] Unreasonably large shapes are now ignored in SVG files to avoid stalling the application. The check can be disabled by setting QT_SVG_DISABLE_SIZE_LIMIT=1 in the environment. Pick-to: 6.2 6.5 Fixes: QTBUG-111850 Change-Id: Id8154049c96a565aad237ee007da0ee879446448 Reviewed-by: Eirik Aavitsland --- src/svg/qsvggraphics.cpp | 454 +++++++++++++++++++++++++++-------------------- src/svg/qsvggraphics_p.h | 8 + src/svg/qsvgnode.cpp | 31 ++++ src/svg/qsvgnode_p.h | 3 + 4 files changed, 302 insertions(+), 194 deletions(-) (limited to 'src') diff --git a/src/svg/qsvggraphics.cpp b/src/svg/qsvggraphics.cpp index facb190..ac8051d 100644 --- a/src/svg/qsvggraphics.cpp +++ b/src/svg/qsvggraphics.cpp @@ -32,7 +32,7 @@ Q_LOGGING_CATEGORY(lcSvgTiming, "qt.svg.timing") qCDebug(lcSvgTiming) << "Drawing" << TYPE << "took" << (qtSvgTimer.nsecsElapsed() / 1000000.0f) << "ms"; #define QT_SVG_DRAW_SHAPE(command) \ - qreal oldOpacity = p->opacity(); \ + { qreal oldOpacity = p->opacity(); \ QBrush oldBrush = p->brush(); \ QPen oldPen = p->pen(); \ p->setPen(Qt::NoPen); \ @@ -45,7 +45,7 @@ Q_LOGGING_CATEGORY(lcSvgTiming, "qt.svg.timing") command; \ p->setBrush(oldBrush); \ } \ - p->setOpacity(oldOpacity); + p->setOpacity(oldOpacity); } void QSvgAnimation::draw(QPainter *, QSvgExtraStates &) @@ -66,6 +66,10 @@ QSvgEllipse::QSvgEllipse(QSvgNode *parent, const QRectF &rect) { } +QRectF QSvgEllipse::fastBounds(QPainter *p, QSvgExtraStates &) const +{ + return p->transform().mapRect(m_bounds); +} QRectF QSvgEllipse::bounds(QPainter *p, QSvgExtraStates &) const { @@ -79,7 +83,8 @@ void QSvgEllipse::draw(QPainter *p, QSvgExtraStates &states) { QT_SVG_TIMING_ENTER applyStyle(p, states); - QT_SVG_DRAW_SHAPE(p->drawEllipse(m_bounds)); + if (shouldDrawNode(p, states)) + QT_SVG_DRAW_SHAPE(p->drawEllipse(m_bounds)); revertStyle(p, states); QT_SVG_TIMING_EXIT("Ellipse") } @@ -93,11 +98,13 @@ void QSvgArc::draw(QPainter *p, QSvgExtraStates &states) { QT_SVG_TIMING_ENTER applyStyle(p, states); - if (p->pen().widthF() != 0) { - qreal oldOpacity = p->opacity(); - p->setOpacity(oldOpacity * states.strokeOpacity); - p->drawPath(m_path); - p->setOpacity(oldOpacity); + if (shouldDrawNode(p, states)) { + if (p->pen().widthF() != 0) { + qreal oldOpacity = p->opacity(); + p->setOpacity(oldOpacity * states.strokeOpacity); + p->drawPath(m_path); + p->setOpacity(oldOpacity); + } } revertStyle(p, states); QT_SVG_TIMING_EXIT("Arc") @@ -117,9 +124,11 @@ QSvgImage::QSvgImage(QSvgNode *parent, const QImage &image, void QSvgImage::draw(QPainter *p, QSvgExtraStates &states) { QT_SVG_TIMING_ENTER - applyStyle(p, states); - p->drawImage(m_bounds, m_image); - revertStyle(p, states); + if (shouldDrawNode(p, states)) { + applyStyle(p, states); + p->drawImage(m_bounds, m_image); + revertStyle(p, states); + } QT_SVG_TIMING_EXIT("Image") } @@ -134,11 +143,13 @@ void QSvgLine::draw(QPainter *p, QSvgExtraStates &states) { QT_SVG_TIMING_ENTER applyStyle(p, states); - if (p->pen().widthF() != 0) { - qreal oldOpacity = p->opacity(); - p->setOpacity(oldOpacity * states.strokeOpacity); - p->drawLine(m_line); - p->setOpacity(oldOpacity); + if (shouldDrawNode(p, states)) { + if (p->pen().widthF() != 0) { + qreal oldOpacity = p->opacity(); + p->setOpacity(oldOpacity * states.strokeOpacity); + p->drawLine(m_line); + p->setOpacity(oldOpacity); + } } revertStyle(p, states); QT_SVG_TIMING_EXIT("Line") @@ -153,12 +164,19 @@ void QSvgPath::draw(QPainter *p, QSvgExtraStates &states) { QT_SVG_TIMING_ENTER applyStyle(p, states); - m_path.setFillRule(states.fillRule); - QT_SVG_DRAW_SHAPE(p->drawPath(m_path)); + if (shouldDrawNode(p, states)) { + m_path.setFillRule(states.fillRule); + QT_SVG_DRAW_SHAPE(p->drawPath(m_path)); + } revertStyle(p, states); QT_SVG_TIMING_EXIT("Path") } +QRectF QSvgPath::fastBounds(QPainter *p, QSvgExtraStates &) const +{ + return p->transform().mapRect(m_path.controlPointRect()); +} + QRectF QSvgPath::bounds(QPainter *p, QSvgExtraStates &) const { qreal sw = strokeWidth(p); @@ -171,6 +189,11 @@ QSvgPolygon::QSvgPolygon(QSvgNode *parent, const QPolygonF &poly) { } +QRectF QSvgPolygon::fastBounds(QPainter *p, QSvgExtraStates &) const +{ + return p->transform().mapRect(m_poly.boundingRect()); +} + QRectF QSvgPolygon::bounds(QPainter *p, QSvgExtraStates &) const { qreal sw = strokeWidth(p); @@ -187,7 +210,8 @@ void QSvgPolygon::draw(QPainter *p, QSvgExtraStates &states) { QT_SVG_TIMING_ENTER applyStyle(p, states); - QT_SVG_DRAW_SHAPE(p->drawPolygon(m_poly, states.fillRule)); + if (shouldDrawNode(p, states)) + QT_SVG_DRAW_SHAPE(p->drawPolygon(m_poly, states.fillRule)); revertStyle(p, states); QT_SVG_TIMING_EXIT("Polygon") } @@ -203,19 +227,21 @@ void QSvgPolyline::draw(QPainter *p, QSvgExtraStates &states) { QT_SVG_TIMING_ENTER applyStyle(p, states); - qreal oldOpacity = p->opacity(); - if (p->brush().style() != Qt::NoBrush) { - QPen save = p->pen(); - p->setPen(QPen(Qt::NoPen)); - p->setOpacity(oldOpacity * states.fillOpacity); - p->drawPolygon(m_poly, states.fillRule); - p->setPen(save); - } - if (p->pen().widthF() != 0) { - p->setOpacity(oldOpacity * states.strokeOpacity); - p->drawPolyline(m_poly); + if (shouldDrawNode(p, states)) { + qreal oldOpacity = p->opacity(); + if (p->brush().style() != Qt::NoBrush) { + QPen save = p->pen(); + p->setPen(QPen(Qt::NoPen)); + p->setOpacity(oldOpacity * states.fillOpacity); + p->drawPolygon(m_poly, states.fillRule); + p->setPen(save); + } + if (p->pen().widthF() != 0) { + p->setOpacity(oldOpacity * states.strokeOpacity); + p->drawPolyline(m_poly); + } + p->setOpacity(oldOpacity); } - p->setOpacity(oldOpacity); revertStyle(p, states); QT_SVG_TIMING_EXIT("Polyline") } @@ -226,6 +252,11 @@ QSvgRect::QSvgRect(QSvgNode *node, const QRectF &rect, int rx, int ry) { } +QRectF QSvgRect::fastBounds(QPainter *p, QSvgExtraStates &) const +{ + return p->transform().mapRect(m_rect); +} + QRectF QSvgRect::bounds(QPainter *p, QSvgExtraStates &) const { qreal sw = strokeWidth(p); @@ -242,10 +273,12 @@ void QSvgRect::draw(QPainter *p, QSvgExtraStates &states) { QT_SVG_TIMING_ENTER applyStyle(p, states); - if (m_rx || m_ry) { - QT_SVG_DRAW_SHAPE(p->drawRoundedRect(m_rect, m_rx, m_ry, Qt::RelativeSize)); - } else { - QT_SVG_DRAW_SHAPE(p->drawRect(m_rect)); + if (shouldDrawNode(p, states)) { + if (m_rx || m_ry) { + QT_SVG_DRAW_SHAPE(p->drawRoundedRect(m_rect, m_rx, m_ry, Qt::RelativeSize)); + } else { + QT_SVG_DRAW_SHAPE(p->drawRect(m_rect)); + } } revertStyle(p, states); QT_SVG_TIMING_EXIT("Rect") @@ -276,6 +309,22 @@ void QSvgText::setTextArea(const QSizeF &size) m_type = TEXTAREA; } +QRectF QSvgText::fastBounds(QPainter *p, QSvgExtraStates &) const +{ + QFont font = p->font(); + QFontMetricsF fm(font); + + int charCount = 0; + for (int i = 0; i < m_tspans.size(); ++i) + 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::bounds(QPainter *p, QSvgExtraStates &states) const { QRectF boundingRect; @@ -294,176 +343,178 @@ void QSvgText::draw_helper(QPainter *p, QSvgExtraStates &states, QRectF *boundin { const bool isPainting = (boundingRect == nullptr); applyStyle(p, states); - qreal oldOpacity = p->opacity(); - p->setOpacity(oldOpacity * states.fillOpacity); - - // Force the font to have a size of 100 pixels to avoid truncation problems - // when the font is very small. - qreal scale = 100.0 / p->font().pointSizeF(); - Qt::Alignment alignment = states.textAnchor; - - QTransform oldTransform = p->worldTransform(); - p->scale(1 / scale, 1 / scale); - - qreal y = 0; - bool initial = true; - qreal px = m_coord.x() * scale; - qreal py = m_coord.y() * scale; - QSizeF scaledSize = m_size * scale; - - if (m_type == TEXTAREA) { - if (alignment == Qt::AlignHCenter) - px += scaledSize.width() / 2; - else if (alignment == Qt::AlignRight) - px += scaledSize.width(); - } - - QRectF bounds; - if (m_size.height() != 0) - bounds = QRectF(0, py, 1, scaledSize.height()); // x and width are not used. + if (!isPainting || shouldDrawNode(p, states)) { + qreal oldOpacity = p->opacity(); + p->setOpacity(oldOpacity * states.fillOpacity); - bool appendSpace = false; - QList paragraphs; - QList > formatRanges(1); - paragraphs.push_back(QString()); + // Force the font to have a size of 100 pixels to avoid truncation problems + // when the font is very small. + qreal scale = 100.0 / p->font().pointSizeF(); + Qt::Alignment alignment = states.textAnchor; + + QTransform oldTransform = p->worldTransform(); + p->scale(1 / scale, 1 / scale); + + qreal y = 0; + bool initial = true; + qreal px = m_coord.x() * scale; + qreal py = m_coord.y() * scale; + QSizeF scaledSize = m_size * scale; + + if (m_type == TEXTAREA) { + if (alignment == Qt::AlignHCenter) + px += scaledSize.width() / 2; + else if (alignment == Qt::AlignRight) + px += scaledSize.width(); + } - for (int i = 0; i < m_tspans.size(); ++i) { - if (m_tspans[i] == LINEBREAK) { - if (m_type == TEXTAREA) { - if (paragraphs.back().isEmpty()) { - QFont font = p->font(); - font.setPixelSize(font.pointSizeF() * scale); - - QTextLayout::FormatRange range; - range.start = 0; - range.length = 1; - range.format.setFont(font); - formatRanges.back().append(range); - - paragraphs.back().append(QLatin1Char(' '));; + QRectF bounds; + if (m_size.height() != 0) + bounds = QRectF(0, py, 1, scaledSize.height()); // x and width are not used. + + bool appendSpace = false; + QList paragraphs; + QList > formatRanges(1); + paragraphs.push_back(QString()); + + for (int i = 0; i < m_tspans.size(); ++i) { + if (m_tspans[i] == LINEBREAK) { + if (m_type == TEXTAREA) { + if (paragraphs.back().isEmpty()) { + QFont font = p->font(); + font.setPixelSize(font.pointSizeF() * scale); + + 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); } - appendSpace = false; - paragraphs.push_back(QString()); - formatRanges.resize(formatRanges.size() + 1); - } - } else { - WhitespaceMode mode = m_tspans[i]->whitespaceMode(); - m_tspans[i]->applyStyle(p, states); + } else { + WhitespaceMode mode = m_tspans[i]->whitespaceMode(); + m_tspans[i]->applyStyle(p, states); - QFont font = p->font(); - font.setPixelSize(font.pointSizeF() * scale); + QFont font = p->font(); + font.setPixelSize(font.pointSizeF() * scale); - QString newText(m_tspans[i]->text()); - newText.replace(QLatin1Char('\t'), QLatin1Char(' ')); - newText.replace(QLatin1Char('\n'), QLatin1Char(' ')); + 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 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(' '))); + bool appendSpaceNext = (!m_tspans[i]->isTspan() && (mode == Default) && newText.endsWith(QLatin1Char(' '))); - if (mode == Default) { - newText = newText.simplified(); - if (newText.isEmpty()) - appendSpaceNext = false; - } + 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); - range.format.setTextOutline(p->pen()); - range.format.setForeground(p->brush()); - - if (appendSpace) { - Q_ASSERT(!formatRanges.back().isEmpty()); - ++formatRanges.back().back().length; - } else if (prependSpace) { - --range.start; - ++range.length; - } - formatRanges.back().append(range); + QTextLayout::FormatRange range; + range.start = paragraphs.back().size(); + range.length = newText.size(); + range.format.setFont(font); + range.format.setTextOutline(p->pen()); + range.format.setForeground(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; + appendSpace = appendSpaceNext; + paragraphs.back() += newText; - m_tspans[i]->revertStyle(p, states); + m_tspans[i]->revertStyle(p, states); + } } - } - if (states.svgFont) { - // SVG fonts not fully supported... - QString text = paragraphs.front(); - for (int i = 1; i < paragraphs.size(); ++i) { - text.append(QLatin1Char('\n')); - text.append(paragraphs[i]); - } - states.svgFont->draw(p, m_coord * scale, text, p->font().pointSizeF() * scale, 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(scaledSize.width()); + if (states.svgFont) { + // SVG fonts not fully supported... + QString text = paragraphs.front(); + for (int i = 1; i < paragraphs.size(); ++i) { + text.append(QLatin1Char('\n')); + text.append(paragraphs[i]); } - tl.endLayout(); - - bool endOfBoundsReached = false; - for (int i = 0; i < tl.lineCount(); ++i) { - QTextLine line = tl.lineAt(i); - - qreal x = 0; - if (alignment == Qt::AlignHCenter) - x -= 0.5 * line.naturalTextWidth(); - else if (alignment == Qt::AlignRight) - x -= line.naturalTextWidth(); - - if (initial && m_type == TEXT) - y -= line.ascent(); - initial = false; - - line.setPosition(QPointF(x, y)); - brect |= line.naturalTextRect(); - - // Check if the current line fits into the bounding rectangle. - if ((m_size.width() != 0 && line.naturalTextWidth() > scaledSize.width()) - || (m_size.height() != 0 && y + line.height() > scaledSize.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; + states.svgFont->draw(p, m_coord * scale, text, p->font().pointSizeF() * scale, 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(scaledSize.width()); + } + tl.endLayout(); + + bool endOfBoundsReached = false; + for (int i = 0; i < tl.lineCount(); ++i) { + QTextLine line = tl.lineAt(i); + + qreal x = 0; + if (alignment == Qt::AlignHCenter) + x -= 0.5 * line.naturalTextWidth(); + else if (alignment == Qt::AlignRight) + x -= line.naturalTextWidth(); + + if (initial && m_type == TEXT) + y -= line.ascent(); + initial = false; + + line.setPosition(QPointF(x, y)); + brect |= line.naturalTextRect(); + + // Check if the current line fits into the bounding rectangle. + if ((m_size.width() != 0 && line.naturalTextWidth() > scaledSize.width()) + || (m_size.height() != 0 && y + line.height() > scaledSize.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(px, py), QList(), bounds); - y += 1.1 * line.height(); + if (endOfBoundsReached) + break; + } + if (boundingRect) { + brect.translate(m_coord * scale); + if (bounds.height() > 0) + brect.setBottom(qMin(brect.bottom(), bounds.bottom())); + *boundingRect = QTransform::fromScale(1 / scale, 1 / scale).mapRect(brect); } - if (isPainting) - tl.draw(p, QPointF(px, py), QList(), bounds); - - if (endOfBoundsReached) - break; - } - if (boundingRect) { - brect.translate(m_coord * scale); - if (bounds.height() > 0) - brect.setBottom(qMin(brect.bottom(), bounds.bottom())); - *boundingRect = QTransform::fromScale(1 / scale, 1 / scale).mapRect(brect); } - } - p->setWorldTransform(oldTransform, false); - p->setOpacity(oldOpacity); + p->setWorldTransform(oldTransform, false); + p->setOpacity(oldOpacity); + } revertStyle(p, states); } @@ -599,6 +650,11 @@ QRectF QSvgUse::bounds(QPainter *p, QSvgExtraStates &states) const return bounds; } +QRectF QSvgPolyline::fastBounds(QPainter *p, QSvgExtraStates &) const +{ + return p->transform().mapRect(m_poly.boundingRect()); +} + QRectF QSvgPolyline::bounds(QPainter *p, QSvgExtraStates &) const { qreal sw = strokeWidth(p); @@ -611,6 +667,11 @@ QRectF QSvgPolyline::bounds(QPainter *p, QSvgExtraStates &) const } } +QRectF QSvgArc::fastBounds(QPainter *p, QSvgExtraStates &) const +{ + return p->transform().mapRect(m_path.controlPointRect()); +} + QRectF QSvgArc::bounds(QPainter *p, QSvgExtraStates &) const { qreal sw = strokeWidth(p); @@ -623,17 +684,22 @@ QRectF QSvgImage::bounds(QPainter *p, QSvgExtraStates &) const return p->transform().mapRect(m_bounds); } -QRectF QSvgLine::bounds(QPainter *p, QSvgExtraStates &) const +QRectF QSvgLine::fastBounds(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::bounds(QPainter *p, QSvgExtraStates &s) const { qreal sw = strokeWidth(p); if (qFuzzyIsNull(sw)) { - 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); + return fastBounds(p, s); } else { QPainterPath path; path.moveTo(m_line.p1()); diff --git a/src/svg/qsvggraphics_p.h b/src/svg/qsvggraphics_p.h index 7c956e8..96be929 100644 --- a/src/svg/qsvggraphics_p.h +++ b/src/svg/qsvggraphics_p.h @@ -41,6 +41,7 @@ public: QSvgArc(QSvgNode *parent, const QPainterPath &path); void draw(QPainter *p, QSvgExtraStates &states) override; Type type() const override; + QRectF fastBounds(QPainter *p, QSvgExtraStates &states) const override; QRectF bounds(QPainter *p, QSvgExtraStates &states) const override; private: QPainterPath m_path; @@ -52,6 +53,7 @@ public: QSvgEllipse(QSvgNode *parent, const QRectF &rect); void draw(QPainter *p, QSvgExtraStates &states) override; Type type() const override; + QRectF fastBounds(QPainter *p, QSvgExtraStates &states) const override; QRectF bounds(QPainter *p, QSvgExtraStates &states) const override; private: QRectF m_bounds; @@ -83,6 +85,7 @@ public: QSvgLine(QSvgNode *parent, const QLineF &line); void draw(QPainter *p, QSvgExtraStates &states) override; Type type() const override; + QRectF fastBounds(QPainter *p, QSvgExtraStates &states) const override; QRectF bounds(QPainter *p, QSvgExtraStates &states) const override; private: QLineF m_line; @@ -94,6 +97,7 @@ public: QSvgPath(QSvgNode *parent, const QPainterPath &qpath); void draw(QPainter *p, QSvgExtraStates &states) override; Type type() const override; + QRectF fastBounds(QPainter *p, QSvgExtraStates &states) const override; QRectF bounds(QPainter *p, QSvgExtraStates &states) const override; QPainterPath *qpath() { @@ -109,6 +113,7 @@ public: QSvgPolygon(QSvgNode *parent, const QPolygonF &poly); void draw(QPainter *p, QSvgExtraStates &states) override; Type type() const override; + QRectF fastBounds(QPainter *p, QSvgExtraStates &states) const override; QRectF bounds(QPainter *p, QSvgExtraStates &states) const override; private: QPolygonF m_poly; @@ -120,6 +125,7 @@ public: QSvgPolyline(QSvgNode *parent, const QPolygonF &poly); void draw(QPainter *p, QSvgExtraStates &states) override; Type type() const override; + QRectF fastBounds(QPainter *p, QSvgExtraStates &states) const override; QRectF bounds(QPainter *p, QSvgExtraStates &states) const override; private: QPolygonF m_poly; @@ -131,6 +137,7 @@ public: QSvgRect(QSvgNode *paren, const QRectF &rect, int rx=0, int ry=0); Type type() const override; void draw(QPainter *p, QSvgExtraStates &states) override; + QRectF fastBounds(QPainter *p, QSvgExtraStates &states) const override; QRectF bounds(QPainter *p, QSvgExtraStates &states) const override; private: QRectF m_rect; @@ -160,6 +167,7 @@ public: void addLineBreak() {m_tspans.append(LINEBREAK);} void setWhitespaceMode(WhitespaceMode mode) {m_mode = mode;} + QRectF fastBounds(QPainter *p, QSvgExtraStates &states) const override; QRectF bounds(QPainter *p, QSvgExtraStates &states) const override; private: diff --git a/src/svg/qsvgnode.cpp b/src/svg/qsvgnode.cpp index 62c118e..31e5338 100644 --- a/src/svg/qsvgnode.cpp +++ b/src/svg/qsvgnode.cpp @@ -4,11 +4,21 @@ #include "qsvgnode_p.h" #include "qsvgtinydocument_p.h" +#include + #include "qdebug.h" #include "qstack.h" +#include + +#if !defined(QT_SVG_SIZE_LIMIT) +# define QT_SVG_SIZE_LIMIT QT_RASTER_COORD_LIMIT +#endif + QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(lcSvgDraw) + QSvgNode::QSvgNode(QSvgNode *parent) : m_parent(parent), m_visible(true), @@ -167,6 +177,11 @@ QSvgFillStyleProperty * QSvgNode::styleProperty(const QString &id) const return doc ? doc->namedStyle(rid) : 0; } +QRectF QSvgNode::fastBounds(QPainter *p, QSvgExtraStates &states) const +{ + return bounds(p, states); +} + QRectF QSvgNode::bounds(QPainter *, QSvgExtraStates &) const { return QRectF(0, 0, 0, 0); @@ -311,4 +326,20 @@ qreal QSvgNode::strokeWidth(QPainter *p) return pen.widthF(); } +bool QSvgNode::shouldDrawNode(QPainter *p, QSvgExtraStates &states) const +{ + static bool alwaysDraw = qEnvironmentVariableIntValue("QT_SVG_DISABLE_SIZE_LIMIT"); + if (alwaysDraw) + return true; + + QRectF brect = fastBounds(p, states); + if (brect.width() <= QT_SVG_SIZE_LIMIT && brect.height() <= QT_SVG_SIZE_LIMIT) { + return true; + } else { + qCWarning(lcSvgDraw) << "Shape of type" << type() << "ignored because it will take too long to rasterize (bounding rect=" << brect << ")." + << "Set QT_SVG_DISABLE_SIZE_LIMIT=1 to disable this check."; + return false; + } +} + QT_END_NAMESPACE diff --git a/src/svg/qsvgnode_p.h b/src/svg/qsvgnode_p.h index d8c1ccd..df9ef8a 100644 --- a/src/svg/qsvgnode_p.h +++ b/src/svg/qsvgnode_p.h @@ -88,6 +88,7 @@ public: QSvgTinyDocument *document() const; virtual Type type() const =0; + virtual QRectF fastBounds(QPainter *p, QSvgExtraStates &states) const; virtual QRectF bounds(QPainter *p, QSvgExtraStates &states) const; virtual QRectF transformedBounds(QPainter *p, QSvgExtraStates &states) const; QRectF transformedBounds() const; @@ -118,6 +119,8 @@ public: QString xmlClass() const; void setXmlClass(const QString &str); + + bool shouldDrawNode(QPainter *p, QSvgExtraStates &states) const; protected: mutable QSvgStyle m_style; -- cgit v1.2.1