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 "qsvgnode_p.h"
++ #include "qsvgdocument_p.h"
++ #include "qsvggraphics_p.h"
++ #include <QtSvg/private/qsvgpaintserver_p.h>
++
++ #include <QLoggingCategory>
++ #include<QElapsedTimer>
++ #include <QtGui/qimageiohandler.h>
++
++ #include "qdebug.h"
++
++ #include <QtGui/private/qoutlinemapper_p.h>
++
++ #include <vector>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace Qt::StringLiterals;
++
++ #ifndef QT_NO_DEBUG
++ Q_STATIC_LOGGING_CATEGORY(lcSvgTiming, "qt.svg.timing")
++ #endif
++
++ #if !defined(QT_SVG_SIZE_LIMIT)
++ # define QT_SVG_SIZE_LIMIT QT_RASTER_COORD_LIMIT
++ #endif
++
++ QSvgNode::QSvgNode(QSvgNode *parent)
++ : m_parent(parent)
++ {
++ }
++
++ QSvgNode::~QSvgNode()
++ {
++
++ }
++
++ void QSvgNode::draw(QPainter *p, QSvgExtraStates &states)
++ {
++ #ifndef QT_NO_DEBUG
++ QElapsedTimer qtSvgTimer; qtSvgTimer.start();
++ #endif
++
++ if (shouldDrawNode(p, states)) {
++ quint8 remainingDepth = states.trustedSource ? states.remainingNestedNodes
++ : states.remainingNestedNodes - 1;
++ QScopedValueRollback<quint8> nestedNodesGuard(states.remainingNestedNodes, remainingDepth);
++ if (states.remainingNestedNodes == 0) {
++ qCWarning(lcSvgDraw).noquote() << "Too many nested nodes at" << typeName()
++ << "exceeding max nested limit of" << QtSvg::renderingMaxNestedNodes << "."
++ << "Enable AssumeTrustedSource in QSvgHandler or set QT_SVG_DEFAULT_OPTIONS=2 to disable this check.";
++ return;
++ }
++
++ applyStyle(p, states);
++ applyAnimatedStyle(p, states);
++ QSvgNode *maskNode = this->hasMask() ? states.doc()->namedNode(this->maskId()) : nullptr;
++ QSvgFilterContainer *filterNode = this->hasFilter()
++ ? static_cast<QSvgFilterContainer *>(states.doc()->namedNode(this->filterId()))
++ : nullptr;
++ if (filterNode && filterNode->type() == QSvgNode::Filter && filterNode->supported()) {
++ QTransform xf = p->transform();
++ p->resetTransform();
++ QRectF localRect = internalBounds(p, states);
++ p->setTransform(xf);
++ QRectF boundsRect = xf.mapRect(filterNode->filterRegion(localRect));
++ QImage proxy = drawIntoBuffer(p, states, boundsRect.toRect());
++ proxy = filterNode->applyFilter(proxy, p, localRect);
++ if (maskNode && maskNode->type() == QSvgNode::Mask) {
++ boundsRect = QRectF(proxy.offset(), proxy.size());
++ localRect = p->transform().inverted().mapRect(boundsRect);
++ QImage mask = static_cast<QSvgMask*>(maskNode)->createMask(p, states, localRect, &boundsRect);
++ applyMaskToBuffer(&proxy, mask);
++ }
++ applyBufferToCanvas(p, proxy);
++
++ } else if (maskNode && maskNode->type() == QSvgNode::Mask) {
++ QRectF boundsRect;
++ QImage mask = static_cast<QSvgMask*>(maskNode)->createMask(p, states, this, &boundsRect);
++ drawWithMask(p, states, mask, boundsRect.toRect());
++ } else if (!qFuzzyCompare(p->opacity(), qreal(1.0)) && requiresGroupRendering()) {
++ QTransform xf = p->transform();
++ p->resetTransform();
++
++ QRectF localRect = decoratedInternalBounds(p, states);
++ // adding safety border needed because of the antialiazing effects
++ QRectF boundsRect = xf.mapRect(localRect);
++ const int deltaX = boundsRect.width() * 0.1;
++ const int deltaY = boundsRect.height() * 0.1;
++ boundsRect = boundsRect.adjusted(-deltaX, -deltaY, deltaX, deltaY);
++
++ p->setTransform(xf);
++
++ QImage proxy = drawIntoBuffer(p, states, boundsRect.toAlignedRect());
++ applyBufferToCanvas(p, proxy);
++ } else {
++ if (separateFillStroke(p, states))
++ fillThenStroke(p, states);
++ else
++ drawCommand(p, states);
++
++ }
++ revertAnimatedStyle(p ,states);
++ revertStyle(p, states);
++ }
++
++ #ifndef QT_NO_DEBUG
++ if (Q_UNLIKELY(lcSvgTiming().isDebugEnabled()))
++ qCDebug(lcSvgTiming) << "Drawing" << typeName() << "took" << (qtSvgTimer.nsecsElapsed() / 1000000.0f) << "ms";
++ #endif
++ }
++
++ void QSvgNode::fillThenStroke(QPainter *p, QSvgExtraStates &states)
++ {
++ qreal oldOpacity = p->opacity();
++ if (p->brush().style() != Qt::NoBrush) {
++ QPen oldPen = p->pen();
++ p->setPen(Qt::NoPen);
++ p->setOpacity(oldOpacity * states.fillOpacity);
++
++ drawCommand(p, states);
++
++ p->setPen(oldPen);
++ }
++ if (p->pen() != Qt::NoPen && p->pen().brush() != Qt::NoBrush && p->pen().widthF() != 0) {
++ QBrush oldBrush = p->brush();
++ p->setOpacity(oldOpacity * states.strokeOpacity);
++ p->setBrush(Qt::NoBrush);
++
++ drawCommand(p, states);
++
++ p->setBrush(oldBrush);
++ }
++ p->setOpacity(oldOpacity);
++ }
++
++ void QSvgNode::drawWithMask(QPainter *p, QSvgExtraStates &states, const QImage &mask, const QRect &boundsRect)
++ {
++ QImage proxy = drawIntoBuffer(p, states, boundsRect);
++ if (proxy.isNull())
++ return;
++ applyMaskToBuffer(&proxy, mask);
++
++ p->save();
++ p->resetTransform();
++ p->drawImage(boundsRect, proxy);
++ p->restore();
++ }
++
++ QImage QSvgNode::drawIntoBuffer(QPainter *p, QSvgExtraStates &states, const QRect &boundsRect)
++ {
++ QImage proxy;
++ if (!QImageIOHandler::allocateImage(boundsRect.size(), QImage::Format_ARGB32_Premultiplied, &proxy)) {
++ qCWarning(lcSvgDraw) << "The requested buffer size is too big, ignoring";
++ return proxy;
++ }
++ proxy.setOffset(boundsRect.topLeft());
++ proxy.fill(Qt::transparent);
++ QPainter proxyPainter(&proxy);
++ proxyPainter.setPen(p->pen());
++ proxyPainter.setBrush(p->brush());
++ proxyPainter.setFont(p->font());
++ proxyPainter.translate(-boundsRect.topLeft());
++ proxyPainter.setTransform(p->transform(), true);
++ proxyPainter.setRenderHints(p->renderHints());
++ if (separateFillStroke(p, states))
++ fillThenStroke(&proxyPainter, states);
++ else
++ drawCommand(&proxyPainter, states);
++ return proxy;
++ }
++
++ void QSvgNode::applyMaskToBuffer(QImage *proxy, const QImage &mask) const
++ {
++ QPainter proxyPainter(proxy);
++ proxyPainter.setCompositionMode(QPainter::CompositionMode_DestinationOut);
++ proxyPainter.resetTransform();
++ proxyPainter.drawImage(QRect(0, 0, mask.width(), mask.height()), mask);
++ }
++
++ void QSvgNode::applyBufferToCanvas(QPainter *p, const QImage &proxy) const
++ {
++ QTransform xf = p->transform();
++ p->resetTransform();
++ p->drawImage(QRect(proxy.offset(), proxy.size()), proxy);
++ p->setTransform(xf);
++ }
++
++ bool QSvgNode::isDescendantOf(const QSvgNode *parent) const
++ {
++ const QSvgNode *n = this;
++ while (n) {
++ if (n == parent)
++ return true;
++ n = n->m_parent;
++ }
++ return false;
++ }
++
++ void QSvgNode::appendStyleProperty(QSvgStylePropertyPtr prop)
++ {
++ m_style.appendProperty(std::move(prop));
++ }
++
++ void QSvgNode::applyStyle(QPainter *p, QSvgExtraStates &states) const
++ {
++ m_style.apply(p, this, states);
++ }
++
++ /*!
++ \internal
++
++ Apply the styles of all parents to the painter and the states.
++ The styles are applied from the top level node to the current node.
++ This function can be used to set the correct style for a node
++ if it's draw function is triggered out of the ordinary draw context,
++ for example the mask node, that is cross-referenced.
++ */
++ void QSvgNode::applyStyleRecursive(QPainter *p, QSvgExtraStates &states) const
++ {
++ std::vector<const QSvgNode *> parents;
++
++ const QSvgNode *current = this;
++ do {
++ parents.push_back(current);
++ current = current->parent();
++ } while (current);
++
++ for (auto i = parents.crbegin(); i != parents.crend(); ++i)
++ (*i)->applyStyle(p, states);
++ }
++
++ void QSvgNode::revertStyle(QPainter *p, QSvgExtraStates &states) const
++ {
++ m_style.revert(p, states);
++ }
++
++ void QSvgNode::revertStyleRecursive(QPainter *p, QSvgExtraStates &states) const
++ {
++ const QSvgNode *current = this;
++ do {
++ current->revertStyle(p, states);
++ current = current->parent();
++ } while (current);
++ }
++
++ void QSvgNode::applyAnimatedStyle(QPainter *p, QSvgExtraStates &states) const
++ {
++ if (states.doc()->animated())
++ m_animatedStyle.apply(p, this, states);
++ }
++
++ void QSvgNode::revertAnimatedStyle(QPainter *p, QSvgExtraStates &states) const
++ {
++ if (states.doc()->animated())
++ m_animatedStyle.revert(p, states);
++ }
++
++ QSvgStyleProperty *QSvgNode::styleProperty(QSvgStyleProperty::Type type) const
++ {
++ const QSvgNode *node = this;
++ while (node) {
++ if (QSvgStyleProperty *prop = node->m_style.property(type))
++ return prop;
++
++ node = node->parent();
++ }
++
++ return nullptr;
++ }
++
++ QRectF QSvgNode::internalFastBounds(QPainter *p, QSvgExtraStates &states) const
++ {
++ return internalBounds(p, states);
++ }
++
++ QRectF QSvgNode::internalBounds(QPainter *, QSvgExtraStates &) const
++ {
++ return QRectF(0, 0, 0, 0);
++ }
++
++ QRectF QSvgNode::bounds(const QSvgDocument *doc) const
++ {
++ if (!m_cachedBounds.isEmpty())
++ return m_cachedBounds;
++
++ QImage dummy(1, 1, QImage::Format_RGB32);
++ QPainter p(&dummy);
++ initPainter(&p);
++ QSvgExtraStates states(doc);
++
++ if (parent())
++ parent()->applyStyleRecursive(&p, states);
++ p.setWorldTransform(QTransform());
++ m_cachedBounds = bounds(&p, states);
++ if (parent()) // always revert the style to not store old transformations
++ parent()->revertStyleRecursive(&p, states);
++ return m_cachedBounds;
++ }
++
++ QLatin1StringView QSvgNode::typeName() const
++ {
++ switch (type()) {
++ case Doc: return "svg"_L1;
++ case Group: return "g"_L1;
++ case Defs: return "defs"_L1;
++ case Switch: return "switch"_L1;
++ case AnimateColor: return "animateColor"_L1;
++ case AnimateTransform: return "animateTransform"_L1;
++ case Circle: return "circle"_L1;
++ case Ellipse: return "ellipse"_L1;
++ case Image: return "image"_L1;
++ case Line: return "line"_L1;
++ case Path: return "path"_L1;
++ case Polygon: return "polygon"_L1;
++ case Polyline: return "polyline"_L1;
++ case Rect: return "rect"_L1;
++ case Text: return "text"_L1;
++ case Textarea: return "textarea"_L1;
++ case Tspan: return "tspan"_L1;
++ case Use: return "use"_L1;
++ case Video: return "video"_L1;
++ case Mask: return "mask"_L1;
++ case Symbol: return "symbol"_L1;
++ case Marker: return "marker"_L1;
++ case Pattern: return "pattern"_L1;
++ case Filter: return "filter"_L1;
++ case FeMerge: return "feMerge"_L1;
++ case FeMergenode: return "feMergeNode"_L1;
++ case FeColormatrix: return "feColorMatrix"_L1;
++ case FeGaussianblur: return "feGaussianBlur"_L1;
++ case FeOffset: return "feOffset"_L1;
++ case FeComposite: return "feComposite"_L1;
++ case FeFlood: return "feFlood"_L1;
++ case FeBlend: return "feBlend"_L1;
++ case FeUnsupported: return "feUnsupported"_L1;
++ case Font: return "font"_L1;
++ }
++ return "unknown"_L1;
++ }
++
++ void QSvgNode::setRequiredFeatures(const QStringList &lst)
++ {
++ m_requiredFeatures = lst;
++ }
++
++ const QStringList & QSvgNode::requiredFeatures() const
++ {
++ return m_requiredFeatures;
++ }
++
++ void QSvgNode::setRequiredExtensions(const QStringList &lst)
++ {
++ m_requiredExtensions = lst;
++ }
++
++ const QStringList & QSvgNode::requiredExtensions() const
++ {
++ return m_requiredExtensions;
++ }
++
++ void QSvgNode::setRequiredLanguages(const QStringList &lst)
++ {
++ m_requiredLanguages = lst;
++ }
++
++ const QStringList & QSvgNode::requiredLanguages() const
++ {
++ return m_requiredLanguages;
++ }
++
++ void QSvgNode::setRequiredFormats(const QStringList &lst)
++ {
++ m_requiredFormats = lst;
++ }
++
++ const QStringList & QSvgNode::requiredFormats() const
++ {
++ return m_requiredFormats;
++ }
++
++ void QSvgNode::setRequiredFonts(const QStringList &lst)
++ {
++ m_requiredFonts = lst;
++ }
++
++ const QStringList & QSvgNode::requiredFonts() const
++ {
++ return m_requiredFonts;
++ }
++
++ void QSvgNode::setVisible(bool visible)
++ {
++ //propagate visibility change of true to the parent
++ //not propagating false is just a small performance
++ //degradation since we'll iterate over children without
++ //drawing any of them
++ if (m_parent && visible && !m_parent->isVisible())
++ m_parent->setVisible(true);
++
++ m_visible = visible;
++ }
++
++ QRectF QSvgNode::bounds(QPainter *p, QSvgExtraStates &states) const
++ {
++ applyStyle(p, states);
++ QRectF rect = internalBounds(p, states);
++ revertStyle(p, states);
++ return rect;
++ }
++
++ QRectF QSvgNode::decoratedInternalBounds(QPainter *p, QSvgExtraStates &states) const
++ {
++ return filterRegion(states.doc(), internalBounds(p, states));
++ }
++
++ QRectF QSvgNode::decoratedBounds(QPainter *p, QSvgExtraStates &states) const
++ {
++ applyStyle(p, states);
++ QRectF rect = decoratedInternalBounds(p, states);
++ revertStyle(p, states);
++ return rect;
++ }
++
++ void QSvgNode::setNodeId(const QString &i)
++ {
++ m_id = i;
++ }
++
++ void QSvgNode::setXmlClass(const QString &str)
++ {
++ m_class = str;
++ }
++
++ QString QSvgNode::maskId() const
++ {
++ return m_maskId;
++ }
++
++ void QSvgNode::setMaskId(const QString &str)
++ {
++ m_maskId = str;
++ }
++
++ bool QSvgNode::hasMask() const
++ {
++ return !m_maskId.isEmpty();
++ }
++
++ QString QSvgNode::filterId() const
++ {
++ return m_filterId;
++ }
++
++ void QSvgNode::setFilterId(const QString &str)
++ {
++ m_filterId = str;
++ }
++
++ bool QSvgNode::hasFilter() const
++ {
++ return !m_filterId.isEmpty();
++ }
++
++ QString QSvgNode::markerStartId() const
++ {
++ return m_markerStartId;
++ }
++
++ void QSvgNode::setMarkerStartId(const QString &str)
++ {
++ m_markerStartId = str;
++ }
++
++ bool QSvgNode::hasMarkerStart() const
++ {
++ return !m_markerStartId.isEmpty();
++ }
++
++ QString QSvgNode::markerMidId() const
++ {
++ return m_markerMidId;
++ }
++
++ void QSvgNode::setMarkerMidId(const QString &str)
++ {
++ m_markerMidId = str;
++ }
++
++ bool QSvgNode::hasMarkerMid() const
++ {
++ return !m_markerMidId.isEmpty();
++ }
++
++ QString QSvgNode::markerEndId() const
++ {
++ return m_markerEndId;
++ }
++
++ void QSvgNode::setMarkerEndId(const QString &str)
++ {
++ m_markerEndId = str;
++ }
++
++ bool QSvgNode::hasMarkerEnd() const
++ {
++ return !m_markerEndId.isEmpty();
++ }
++
++ bool QSvgNode::hasAnyMarker() const
++ {
++ return hasMarkerStart() || hasMarkerMid() || hasMarkerEnd();
++ }
++
++ bool QSvgNode::requiresGroupRendering() const
++ {
++ return false;
++ }
++
++ void QSvgNode::setDisplayMode(DisplayMode mode)
++ {
++ m_displayMode = mode;
++ }
++
++ QSvgNode::DisplayMode QSvgNode::displayMode() const
++ {
++ return m_displayMode;
++ }
++
++ qreal QSvgNode::strokeWidth(QPainter *p)
++ {
++ const QPen &pen = p->pen();
++ if (pen.style() == Qt::NoPen || pen.brush().style() == Qt::NoBrush || pen.isCosmetic())
++ return 0;
++ return pen.widthF();
++ }
++
++ void QSvgNode::initPainter(QPainter *p)
++ {
++ QPen pen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin);
++ pen.setMiterLimit(4);
++ p->setPen(pen);
++ p->setBrush(Qt::black);
++ p->setRenderHint(QPainter::Antialiasing);
++ p->setRenderHint(QPainter::SmoothPixmapTransform);
++ QFont font(p->font());
++ if (font.pointSize() < 0 && font.pixelSize() > 0) {
++ font.setPointSizeF(font.pixelSize() * 72.0 / p->device()->logicalDpiY());
++ p->setFont(font);
++ }
++ }
++
++ QRectF QSvgNode::boundsOnStroke(QPainter *p, const QPainterPath &path,
++ qreal width, BoundsMode mode)
++ {
++ QPainterPathStroker stroker;
++ stroker.setWidth(width);
++ if (mode == BoundsMode::IncludeMiterLimit) {
++ stroker.setJoinStyle(p->pen().joinStyle());
++ stroker.setMiterLimit(p->pen().miterLimit());
++ }
++ QPainterPath stroke = stroker.createStroke(path);
++ return p->transform().map(stroke).boundingRect();
++ }
++
++ bool QSvgNode::shouldDrawNode(QPainter *p, QSvgExtraStates &states) const
++ {
++ if (m_displayMode == DisplayMode::NoneMode)
++ return false;
++
++ if (states.doc() && states.trustedSource)
++ return true;
++
++ QRectF brect = internalFastBounds(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 << ")."
++ << "Enable AssumeTrustedSource in QSvgHandler or set QT_SVG_DEFAULT_OPTIONS=2 to disable this check.";
++ return false;
++ }
++ }
++
++ QRectF QSvgNode::filterRegion(const QSvgDocument *doc, QRectF bounds) const
++ {
++ QSvgFilterContainer *filterNode =
++ hasFilter() ? static_cast<QSvgFilterContainer *>(doc->namedNode(filterId())) : nullptr;
++
++ if (filterNode && filterNode->type() == QSvgNode::Filter && filterNode->supported())
++ return filterNode->filterRegion(bounds);
++
++ return bounds;
++ }
++
++ QT_END_NAMESPACE