Revision control

Copy as Markdown

// 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