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 "qsvgstructure_p.h"
#include "qsvggraphics_p.h"
#include "qsvgstyle_p.h"
#include "qsvgdocument_p.h"
#include "qsvggraphics_p.h"
#include "qsvgstyle_p.h"
#include "qsvgfilter_p.h"
#include "qpainter.h"
#include "qlocale.h"
#include "qdebug.h"
#include <QLoggingCategory>
#include <qscopedvaluerollback.h>
#include <QtGui/qimageiohandler.h>
#include <QtCore/qlatin1stringview.h>
#include <QtCore/qstringview.h>
#include <QtCore/private/qoffsetstringarray_p.h>
#include <limits>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
QSvgG::QSvgG(QSvgNode *parent)
: QSvgStructureNode(parent)
{
}
QSvgG::~QSvgG()
= default;
void QSvgG::drawCommand(QPainter *p, QSvgExtraStates &states)
{
for (const auto &node : renderers()) {
if ((node->isVisible()) && (node->displayMode() != QSvgNode::NoneMode))
node->draw(p, states);
}
}
bool QSvgG::shouldDrawNode(QPainter *, QSvgExtraStates &) const
{
return true;
}
QSvgNode::Type QSvgG::type() const
{
return Group;
}
bool QSvgG::requiresGroupRendering() const
{
return m_renderers.size() > 1;
}
QSvgStructureNode::QSvgStructureNode(QSvgNode *parent)
:QSvgNode(parent)
{
}
QSvgStructureNode::~QSvgStructureNode()
= default;
void QSvgStructureNode::addChild(QSvgDocument *doc, std::unique_ptr<QSvgNode> child,
const QString &id)
{
if (!id.isEmpty())
doc->addNamedNode(id, child.get());
m_renderers.push_back(std::move(child));
}
QSvgDefs::QSvgDefs(QSvgNode *parent)
: QSvgStructureNode(parent)
{
}
QSvgDefs::~QSvgDefs()
= default;
bool QSvgDefs::shouldDrawNode(QPainter *, QSvgExtraStates &) const
{
return false;
}
QSvgNode::Type QSvgDefs::type() const
{
return Defs;
}
QSvgSymbolLike::QSvgSymbolLike(QSvgNode *parent, QRectF bounds, QRectF viewBox, QPointF refP,
QSvgSymbolLike::PreserveAspectRatios pAspectRatios, QSvgSymbolLike::Overflow overflow)
: QSvgStructureNode(parent)
, m_rect(bounds)
, m_viewBox(viewBox)
, m_refP(refP)
, m_pAspectRatios(pAspectRatios)
, m_overflow(overflow)
{
}
QSvgSymbolLike::~QSvgSymbolLike()
= default;
QRectF QSvgSymbolLike::decoratedInternalBounds(QPainter *p, QSvgExtraStates &states) const
{
p->save();
setPainterToRectAndAdjustment(p);
QRectF rect = internalBounds(p, states);
p->restore();
return rect;
}
bool QSvgSymbolLike::requiresGroupRendering() const
{
return m_renderers.size() > 1;
}
QRectF QSvgSymbolLike::clipRect() const
{
if (m_overflow != Overflow::Hidden || !m_viewBox.isValid())
return QRectF{};
qreal scaleX = 1.0;
if (m_rect.width() > 0 && m_viewBox.width() > 0)
scaleX = m_rect.width() / m_viewBox.width();
qreal scaleY = 1.0;
if (m_rect.height() > 0 && m_viewBox.height() > 0)
scaleY = m_rect.height() / m_viewBox.height();
QTransform t;
t.translate(- m_refP.x() * scaleX - m_rect.left() - m_viewBox.left() * scaleX,
- m_refP.y() * scaleY - m_rect.top() - m_viewBox.top() * scaleY);
t.scale(scaleX, scaleY);
return t.mapRect(m_viewBox);
}
QTransform QSvgSymbolLike::aspectRatioTransform() const
{
QTransform xform;
qreal offsetX = 0.0;
qreal offsetY = 0.0;
qreal scaleX = 1.0;
if (m_rect.width() > 0 && m_viewBox.width() > 0)
scaleX = m_rect.width() / m_viewBox.width();
qreal scaleY = 1.0;
if (m_rect.height() > 0 && m_viewBox.height() > 0)
scaleY = m_rect.height() / m_viewBox.height();
if (!qFuzzyCompare(scaleX, scaleY) &&
m_pAspectRatios.testAnyFlag(PreserveAspectRatio::xyMask)) {
if (m_pAspectRatios.testAnyFlag(PreserveAspectRatio::meet))
scaleX = scaleY = qMin(scaleX, scaleY);
else
scaleX = scaleY = qMax(scaleX, scaleY);
qreal xOverflow = scaleX * m_viewBox.width() - m_rect.width();
qreal yOverflow = scaleY * m_viewBox.height() - m_rect.height();
if ((m_pAspectRatios & PreserveAspectRatio::xMask) == PreserveAspectRatio::xMid)
offsetX -= xOverflow / 2.;
else if ((m_pAspectRatios & PreserveAspectRatio::xMask) == PreserveAspectRatio::xMax)
offsetX -= xOverflow;
if ((m_pAspectRatios & PreserveAspectRatio::yMask) == PreserveAspectRatio::yMid)
offsetY -= yOverflow / 2.;
else if ((m_pAspectRatios & PreserveAspectRatio::yMask) == PreserveAspectRatio::yMax)
offsetY -= yOverflow;
}
xform.translate(offsetX - m_refP.x() * scaleX, offsetY - m_refP.y() * scaleY);
xform.scale(scaleX, scaleY);
return xform;
}
void QSvgSymbolLike::setPainterToRectAndAdjustment(QPainter *p) const
{
QRectF clip = clipRect();
if (!clip.isEmpty())
p->setClipRect(clip);
p->setTransform(aspectRatioTransform(), true);
}
QSvgSymbol::QSvgSymbol(QSvgNode *parent, QRectF bounds, QRectF viewBox, QPointF refP,
QSvgSymbol::PreserveAspectRatios pAspectRatios,
QSvgSymbol::Overflow overflow)
: QSvgSymbolLike(parent, bounds, viewBox, refP, pAspectRatios, overflow)
{
}
QSvgSymbol::~QSvgSymbol()
= default;
void QSvgSymbol::drawCommand(QPainter *p, QSvgExtraStates &states)
{
if (!states.inUse) //Symbol is only drawn when within a use node.
return;
p->save();
setPainterToRectAndAdjustment(p);
for (const auto &node : renderers()) {
if ((node->isVisible()) && (node->displayMode() != QSvgNode::NoneMode))
node->draw(p, states);
}
p->restore();
}
QSvgNode::Type QSvgSymbol::type() const
{
return Symbol;
}
QSvgMarker::QSvgMarker(QSvgNode *parent, QRectF bounds, QRectF viewBox, QPointF refP,
QSvgSymbol::PreserveAspectRatios pAspectRatios, QSvgSymbol::Overflow overflow,
Orientation orientation, qreal orientationAngle, MarkerUnits markerUnits)
: QSvgSymbolLike(parent, bounds, viewBox, refP, pAspectRatios, overflow)
, m_orientation(orientation)
, m_orientationAngle(orientationAngle)
, m_markerUnits(markerUnits)
{
// apply the svg standard style
QSvgFillStylePtr fillProp = std::make_unique<QSvgFillStyle>();
fillProp->setBrush(Qt::black);
appendStyleProperty(std::move(fillProp));
QSvgStrokeStylePtr strokeProp = std::make_unique<QSvgStrokeStyle>();
strokeProp->setMiterLimit(4);
strokeProp->setWidth(1);
strokeProp->setLineCap(Qt::FlatCap);
strokeProp->setLineJoin(Qt::SvgMiterJoin);
strokeProp->setStroke(Qt::NoBrush);
appendStyleProperty(std::move(strokeProp));
}
QSvgMarker::~QSvgMarker()
= default;
QSvgFilterContainer::QSvgFilterContainer(QSvgNode *parent, const QSvgRectF &bounds,
QtSvg::UnitTypes filterUnits, QtSvg::UnitTypes primitiveUnits)
: QSvgStructureNode(parent)
, m_rect(bounds)
, m_filterUnits(filterUnits)
, m_primitiveUnits(primitiveUnits)
, m_supported(true)
{
}
QSvgFilterContainer::~QSvgFilterContainer()
= default;
bool QSvgFilterContainer::shouldDrawNode(QPainter *, QSvgExtraStates &) const
{
return false;
}
void QSvgMarker::drawCommand(QPainter *p, QSvgExtraStates &states)
{
if (!states.inUse) //Symbol is only drawn in combination with another node.
return;
if (Q_UNLIKELY(m_recursing))
return;
QScopedValueRollback<bool> recursingGuard(m_recursing, true);
p->save();
setPainterToRectAndAdjustment(p);
for (const auto &node : renderers()) {
if ((node->isVisible()) && (node->displayMode() != QSvgNode::NoneMode))
node->draw(p, states);
}
p->restore();
}
namespace {
struct PositionMarkerPair {
qreal x{0.0};
qreal y{0.0};
qreal angle{0.0};
QString markerId;
bool isStartNode{false};
};
QList<PositionMarkerPair> markersForNode(const QSvgNode *node)
{
if (!node->hasAnyMarker())
return {};
auto getMeanAngle = [](QPointF p0, QPointF p1, QPointF p2) -> qreal {
QPointF t1 = p1 - p0;
QPointF t2 = p2 - p1;
qreal hyp1 = hypot(t1.x(), t1.y());
if (hyp1 > 0)
t1 /= hyp1;
else
return 0.;
qreal hyp2 = hypot(t2.x(), t2.y());
if (hyp2 > 0)
t2 /= hyp2;
else
return 0.;
QPointF tangent = t1 + t2;
return -atan2(tangent.y(), tangent.x()) / M_PI * 180.;
};
QList<PositionMarkerPair> markers;
switch (node->type()) {
case QSvgNode::Line: {
const QSvgLine *line = static_cast<const QSvgLine*>(node);
if (node->hasMarkerStart())
markers << PositionMarkerPair { line->line().p1().x(), line->line().p1().y(),
line->line().angle(), line->markerStartId(),
true};
if (node->hasMarkerEnd())
markers << PositionMarkerPair { line->line().p2().x(), line->line().p2().y(),
line->line().angle(), line->markerEndId() };
break;
}
case QSvgNode::Polyline:
case QSvgNode::Polygon: {
const QPolygonF &polyData = (node->type() == QSvgNode::Polyline)
? static_cast<const QSvgPolyline*>(node)->polygon()
: static_cast<const QSvgPolygon*>(node)->polygon();
if (node->hasMarkerStart() && polyData.size() > 1) {
QLineF line(polyData.at(0), polyData.at(1));
markers << PositionMarkerPair { line.p1().x(),
line.p1().y(),
line.angle(),
node->markerStartId(),
true };
}
if (node->hasMarkerMid()) {
for (int i = 1; i < polyData.size() - 1; i++) {
QPointF p0 = polyData.at(i - 1);
QPointF p1 = polyData.at(i);
QPointF p2 = polyData.at(i + 1);
markers << PositionMarkerPair { p1.x(),
p1.y(),
getMeanAngle(p0, p1, p2),
node->markerStartId() };
}
}
if (node->hasMarkerEnd() && polyData.size() > 1) {
QLineF line(polyData.at(polyData.size() - 1), polyData.last());
markers << PositionMarkerPair { line.p2().x(),
line.p2().y(),
line.angle(),
node->markerEndId() };
}
break;
}
case QSvgNode::Path: {
const QSvgPath *path = static_cast<const QSvgPath*>(node);
if (node->hasMarkerStart())
markers << PositionMarkerPair { path->path().pointAtPercent(0.).x(),
path->path().pointAtPercent(0.).y(),
path->path().angleAtPercent(0.),
path->markerStartId(),
true };
if (node->hasMarkerMid()) {
for (int i = 1; i < path->path().elementCount() - 1; i++) {
if (path->path().elementAt(i).type == QPainterPath::MoveToElement)
continue;
if (path->path().elementAt(i).type == QPainterPath::CurveToElement)
continue;
if (( path->path().elementAt(i).type == QPainterPath::CurveToDataElement &&
path->path().elementAt(i + 1).type != QPainterPath::CurveToDataElement ) ||
path->path().elementAt(i).type == QPainterPath::LineToElement) {
QPointF p0(path->path().elementAt(i - 1).x, path->path().elementAt(i - 1).y);
QPointF p1(path->path().elementAt(i).x, path->path().elementAt(i).y);
QPointF p2(path->path().elementAt(i + 1).x, path->path().elementAt(i + 1).y);
markers << PositionMarkerPair { p1.x(),
p1.y(),
getMeanAngle(p0, p1, p2),
path->markerMidId() };
}
}
}
if (node->hasMarkerEnd())
markers << PositionMarkerPair { path->path().pointAtPercent(1.).x(),
path->path().pointAtPercent(1.).y(),
path->path().angleAtPercent(1.),
path->markerEndId() };
break;
}
default:
Q_UNREACHABLE();
break;
}
return markers;
}
} // anonymous namespace
void QSvgMarker::drawMarkersForNode(QSvgNode *node, QPainter *p, QSvgExtraStates &states)
{
drawHelper(node, p, states);
}
QRectF QSvgMarker::markersBoundsForNode(const QSvgNode *node, QPainter *p, QSvgExtraStates &states)
{
QRectF bounds;
drawHelper(node, p, states, &bounds);
return bounds;
}
QSvgNode::Type QSvgMarker::type() const
{
return Marker;
}
void QSvgMarker::drawHelper(const QSvgNode *node, QPainter *p,
QSvgExtraStates &states, QRectF *boundingRect)
{
QScopedValueRollback<bool> inUseGuard(states.inUse, true);
const bool isPainting = (boundingRect == nullptr);
const auto markers = markersForNode(node);
for (auto &i : markers) {
QSvgNode *referencedNode = states.doc()->namedNode(i.markerId);
if (!referencedNode || referencedNode->type() != QSvgNode::Marker)
continue;
QSvgMarker *markNode = static_cast<QSvgMarker *>(referencedNode);
p->save();
p->translate(i.x, i.y);
if (markNode->orientation() == QSvgMarker::Orientation::Value) {
p->rotate(markNode->orientationAngle());
} else {
p->rotate(-i.angle);
if (i.isStartNode && markNode->orientation()
== QSvgMarker::Orientation::AutoStartReverse) {
p->scale(-1, -1);
}
}
QRectF oldRect = markNode->m_rect;
if (markNode->markerUnits() == QSvgMarker::MarkerUnits::StrokeWidth) {
markNode->m_rect.setWidth(markNode->m_rect.width() * p->pen().widthF());
markNode->m_rect.setHeight(markNode->m_rect.height() * p->pen().widthF());
}
if (isPainting)
markNode->draw(p, states);
if (boundingRect) {
QTransform xf = p->transform();
p->resetTransform();
*boundingRect |= xf.mapRect(markNode->decoratedInternalBounds(p, states));
}
markNode->m_rect = oldRect;
p->restore();
}
}
QImage QSvgFilterContainer::applyFilter(const QImage &buffer, QPainter *p, const QRectF &bounds) const
{
QRectF localFilterRegion = m_rect.resolveRelativeLengths(bounds, m_filterUnits);
QRect globalFilterRegion = p->transform().mapRect(localFilterRegion).toRect();
QRect globalFilterRegionRel = globalFilterRegion.translated(-buffer.offset());
if (globalFilterRegionRel.isEmpty())
return buffer;
QImage proxy;
if (!QImageIOHandler::allocateImage(globalFilterRegionRel.size(), buffer.format(), &proxy)) {
qCWarning(lcSvgDraw) << "The requested filter is too big, ignoring";
return buffer;
}
proxy = buffer.copy(globalFilterRegionRel);
proxy.setOffset(globalFilterRegion.topLeft());
if (proxy.isNull())
return buffer;
QMap<QString, QImage> buffers;
buffers[QStringLiteral("")] = proxy;
buffers[QStringLiteral("SourceGraphic")] = proxy;
bool requiresSourceAlpha = false;
for (const auto &node : renderers()) {
const QSvgFeFilterPrimitive *filter = QSvgFeFilterPrimitive::castToFilterPrimitive(node.get());
if (filter && filter->requiresSourceAlpha()) {
requiresSourceAlpha = true;
break;
}
}
if (requiresSourceAlpha) {
QImage proxyAlpha = proxy.convertedTo(QImage::Format_Alpha8).convertedTo(proxy.format());
proxyAlpha.setOffset(proxy.offset());
if (proxyAlpha.isNull())
return buffer;
buffers[QStringLiteral("SourceAlpha")] = std::move(proxyAlpha);
}
QImage result;
for (const auto &node : renderers()) {
const QSvgFeFilterPrimitive *filter = QSvgFeFilterPrimitive::castToFilterPrimitive(node.get());
if (filter) {
result = filter->apply(buffers, p, bounds, localFilterRegion, m_primitiveUnits, m_filterUnits);
if (!result.isNull()) {
buffers[QStringLiteral("")] = result;
buffers[filter->result()] = result;
}
}
}
return result;
}
void QSvgFilterContainer::setSupported(bool supported)
{
m_supported = supported;
}
bool QSvgFilterContainer::supported() const
{
return m_supported;
}
QRectF QSvgFilterContainer::filterRegion(const QRectF &itemBounds) const
{
return m_rect.resolveRelativeLengths(itemBounds, m_filterUnits);
}
QSvgNode::Type QSvgFilterContainer::type() const
{
return Filter;
}
inline static bool isSupportedSvgFeature(QStringView str)
{
if (str.startsWith(prefix_1_2)) {
const auto suffix = str.sliced(prefix_1_2.size());
constexpr auto features = qOffsetStringArray(
"Text",
"Shape",
"SVG",
"Structure",
"SolidColor",
"Hyperlinking",
"CoreAttribute",
"XlinkAttribute",
"SVG-static",
"OpacityAttribute",
"Gradient",
"Font",
"Image",
"ConditionalProcessing",
"Extensibility",
"GraphicsAttribute",
"Prefetch",
"PaintAttribute",
"ConditionalProcessingAttribute",
"ExternalResourcesRequiredAttribute"
);
// This is
// return features.contains(suffix);
// but QOffsetStringArray does't support heterogeneous contains()
for (int i = 0; i < features.count(); ++i)
if (suffix == QLatin1StringView{features.at(i)})
return true;
} // 1.2
return false;
}
static inline bool isSupportedSvgExtension(const QString &)
{
return false;
}
QSvgSwitch::QSvgSwitch(QSvgNode *parent)
: QSvgStructureNode(parent)
{
init();
}
QSvgSwitch::~QSvgSwitch()
= default;
QSvgNode *QSvgSwitch::childToRender() const
{
for (const auto &node : renderers()) {
if (node->isVisible() && (node->displayMode() != QSvgNode::NoneMode)) {
const QStringList &features = node->requiredFeatures();
const QStringList &extensions = node->requiredExtensions();
const QStringList &languages = node->requiredLanguages();
const QStringList &formats = node->requiredFormats();
const QStringList &fonts = node->requiredFonts();
bool okToRender = true;
if (!features.isEmpty()) {
QStringList::const_iterator sitr = features.constBegin();
for (; sitr != features.constEnd(); ++sitr) {
if (!isSupportedSvgFeature(*sitr)) {
okToRender = false;
break;
}
}
}
if (okToRender && !extensions.isEmpty()) {
QStringList::const_iterator sitr = extensions.constBegin();
for (; sitr != extensions.constEnd(); ++sitr) {
if (!isSupportedSvgExtension(*sitr)) {
okToRender = false;
break;
}
}
}
if (okToRender && !languages.isEmpty()) {
QStringList::const_iterator sitr = languages.constBegin();
okToRender = false;
for (; sitr != languages.constEnd(); ++sitr) {
if ((*sitr).startsWith(m_systemLanguagePrefix)) {
okToRender = true;
break;
}
}
}
if (okToRender && !formats.isEmpty())
okToRender = false;
if (okToRender && !fonts.isEmpty())
okToRender = false;
if (okToRender)
return node.get();
}
}
return nullptr;
}
void QSvgSwitch::drawCommand(QPainter *p, QSvgExtraStates &states)
{
QSvgNode *node = childToRender();
if (node != nullptr)
node->draw(p, states);
}
QSvgNode::Type QSvgSwitch::type() const
{
return Switch;
}
void QSvgSwitch::init()
{
QLocale locale;
m_systemLanguage = locale.name().replace(QLatin1Char('_'), QLatin1Char('-'));
int idx = m_systemLanguage.indexOf(QLatin1Char('-'));
m_systemLanguagePrefix = m_systemLanguage.mid(0, idx);
}
QRectF QSvgStructureNode::internalBounds(QPainter *p, QSvgExtraStates &states) const
{
QRectF bounds;
if (!m_recursing) {
QScopedValueRollback<bool> guard(m_recursing, true);
for (const auto &node : renderers())
bounds |= node->bounds(p, states);
}
return bounds;
}
QRectF QSvgStructureNode::decoratedInternalBounds(QPainter *p, QSvgExtraStates &states) const
{
QRectF bounds;
if (!m_recursing) {
QScopedValueRollback<bool> guard(m_recursing, true);
for (const auto &node : renderers())
bounds |= node->decoratedBounds(p, states);
}
return bounds;
}
QSvgNode* QSvgStructureNode::previousSiblingNode(QSvgNode *n) const
{
QSvgNode *prev = nullptr;
for (const auto &node : renderers()) {
if (node.get() == n)
return prev;
prev = node.get();
}
return prev;
}
void QSvgStructureNode::releaseDescendants()
{
// This function will release the descendants of a QSvgStructureNode from bottom to top.
// Destructors are never called recursively in this case and stack overflow will not
// happen in deeply nested trees.
// This function does not allocate any memory at the cost of sacrificing some performance to
// make it safe to be called from a destructor.
QSvgNode *currentParent = this;
while (currentParent) {
switch (currentParent->type()) {
case QSvgNode::Doc:
case QSvgNode::Defs:
case QSvgNode::Group:
case QSvgNode::Mask:
case QSvgNode::Pattern:
case QSvgNode::Symbol:
case QSvgNode::Switch:
case QSvgNode::Filter:
{
QSvgStructureNode *currentParentSN = static_cast<QSvgStructureNode *>(currentParent);
if (currentParentSN->m_renderers.empty()) {
currentParent = currentParent->parent();
if (currentParent)
static_cast<QSvgStructureNode *>(currentParent)->m_renderers.pop_front();
} else {
Q_ASSERT(currentParentSN->m_renderers.front().get()->parent() == currentParent);
currentParent = currentParentSN->m_renderers.front().get();
}
}
break;
default:
currentParent = currentParent->parent();
if (currentParent)
static_cast<QSvgStructureNode *>(currentParent)->m_renderers.pop_front();
break;
}
}
Q_ASSERT(this->m_renderers.empty());
}
QSvgMask::QSvgMask(QSvgNode *parent, QSvgRectF bounds,
QtSvg::UnitTypes contentUnits)
: QSvgStructureNode(parent)
, m_rect(bounds)
, m_contentUnits(contentUnits)
{
}
QSvgMask::~QSvgMask()
= default;
bool QSvgMask::shouldDrawNode(QPainter *, QSvgExtraStates &) const
{
return false;
}
QImage QSvgMask::createMask(QPainter *p, QSvgExtraStates &states, QSvgNode *targetNode, QRectF *globalRect) const
{
QTransform t = p->transform();
p->resetTransform();
QRectF basicRect = targetNode->internalBounds(p, states);
*globalRect = t.mapRect(basicRect);
p->setTransform(t);
return createMask(p, states, basicRect, globalRect);
}
QImage QSvgMask::createMask(QPainter *p, QSvgExtraStates &states, const QRectF &localRect, QRectF *globalRect) const
{
QRect imageBound = globalRect->toAlignedRect();
*globalRect = imageBound.toRectF();
QImage mask;
if (!QImageIOHandler::allocateImage(imageBound.size(), QImage::Format_RGBA8888, &mask)) {
qCWarning(lcSvgDraw) << "The requested mask size is too big, ignoring";
return mask;
}
if (Q_UNLIKELY(m_recursing))
return mask;
QScopedValueRollback<bool> recursingGuard(m_recursing, true);
// Chrome seems to return the mask of the mask if a mask is set on the mask
if (this->hasMask()) {
QSvgNode *referencedNode = states.doc()->namedNode(this->maskId());
if (referencedNode && referencedNode->type() == QSvgNode::Mask) {
QSvgMask *maskNode = static_cast<QSvgMask *>(referencedNode);
QRectF boundsRect;
return maskNode->createMask(p, states, localRect, &boundsRect);
}
}
// The mask is created with other elements during rendering.
// Black pixels are masked out, white pixels are not masked.
// The strategy is to draw the elements in a buffer (QImage) and to map
// the white-black image into a transparent-white image that can be used
// with QPainters composition mode to set the mask.
mask.fill(Qt::transparent);
QPainter painter(&mask);
initPainter(&painter);
QSvgExtraStates maskNodeStates(states.doc());
maskNodeStates.trustedSource = states.trustedSource;
maskNodeStates.remainingNestedNodes = states.remainingNestedNodes;
applyStyleRecursive(&painter, maskNodeStates);
// The transformation of the mask node is not relevant. What matters are the contentUnits
// and the position/scale of the node that the mask is applied to.
painter.resetTransform();
painter.translate(-imageBound.topLeft());
painter.setTransform(p->transform(), true);
QTransform oldT = painter.transform();
if (m_contentUnits == QtSvg::UnitTypes::objectBoundingBox){
painter.translate(localRect.topLeft());
painter.scale(localRect.width(), localRect.height());
}
// Draw all content items of the mask to generate the mask
for (const auto &node : renderers())
{
if ((node->isVisible()) && (node->displayMode() != QSvgNode::NoneMode))
node->draw(&painter, maskNodeStates);
}
for (int i=0; i < mask.height(); i++) {
QRgb *line = reinterpret_cast<QRgb *>(mask.scanLine(i));
for (int j=0; j < mask.width(); j++) {
const qreal rC = 0.2125, gC = 0.7154, bC = 0.0721; //luminanceToAlpha times alpha following SVG 1.1
int alpha = 255 - (qRed(line[j]) * rC + qGreen(line[j]) * gC + qBlue(line[j]) * bC) * qAlpha(line[j])/255.;
line[j] = qRgba(0, 0, 0, alpha);
}
}
// Make a path out of the clipRectangle and draw it inverted - black over all content items.
// This is required to apply a clip rectangle with transformations.
// painter.setClipRect(clipRect) sounds like the obvious thing to do but
// created artifacts due to antialiasing.
QRectF clipRect = m_rect.resolveRelativeLengths(localRect);
QPainterPath clipPath;
clipPath.setFillRule(Qt::OddEvenFill);
clipPath.addRect(mask.rect().adjusted(-10, -10, 20, 20));
clipPath.addPolygon(oldT.map(QPolygonF(clipRect)));
painter.resetTransform();
painter.fillPath(clipPath, Qt::black);
revertStyleRecursive(&painter, maskNodeStates);
return mask;
}
QSvgNode::Type QSvgMask::type() const
{
return Mask;
}
QSvgPattern::QSvgPattern(QSvgNode *parent, QSvgRectF bounds, QRectF viewBox,
QtSvg::UnitTypes contentUnits, QTransform transform)
: QSvgStructureNode(parent),
m_rect(bounds),
m_viewBox(viewBox),
m_contentUnits(contentUnits),
m_isRendering(false),
m_transform(transform)
{
}
QSvgPattern::~QSvgPattern()
= default;
bool QSvgPattern::shouldDrawNode(QPainter *, QSvgExtraStates &) const
{
return false;
}
static QImage& defaultPattern()
{
static QImage checkerPattern;
if (checkerPattern.isNull()) {
checkerPattern = QImage(QSize(8, 8), QImage::Format_ARGB32);
QPainter p(&checkerPattern);
p.fillRect(QRect(0, 0, 4, 4), QColorConstants::Svg::white);
p.fillRect(QRect(4, 0, 4, 4), QColorConstants::Svg::black);
p.fillRect(QRect(0, 4, 4, 4), QColorConstants::Svg::black);
p.fillRect(QRect(4, 4, 4, 4), QColorConstants::Svg::white);
}
return checkerPattern;
}
QImage QSvgPattern::patternImage(QPainter *p, QSvgExtraStates &states, const QSvgNode *patternElement)
{
// pe stands for Pattern Element
QRectF peBoundingBox;
QRectF peWorldBoundingBox;
QTransform t = p->transform();
p->resetTransform();
peBoundingBox = patternElement->internalBounds(p, states);
peWorldBoundingBox = t.mapRect(peBoundingBox);
p->setTransform(t);
// This function renders the pattern into an Image, so we need to apply the correct
// scaling values when we draw the pattern. The scaling is affected by two factors :
// - The "patternTransform" attribute which itself might contain a scaling
// - The scaling applied globally.
// The first is obtained from m11 and m22 matrix elements,
// while the second is calculated by dividing the patternElement global size
// by its local size.
qreal contentScaleFactorX = m_transform.m11();
qreal contentScaleFactorY = m_transform.m22();
if (m_contentUnits == QtSvg::UnitTypes::userSpaceOnUse) {
contentScaleFactorX *= t.m11();
contentScaleFactorY *= t.m22();
} else {
contentScaleFactorX *= peWorldBoundingBox.width();
contentScaleFactorY *= peWorldBoundingBox.height();
}
// Calculate the pattern bounding box depending on the used UnitTypes
QRectF patternBoundingBox = m_rect.resolveRelativeLengths(peBoundingBox);
QSize imageSize;
auto patternWidth = patternBoundingBox.width() * t.m11() * m_transform.m11();
auto patternHeight = patternBoundingBox.height() * t.m22() * m_transform.m22();
constexpr auto maxVal = std::numeric_limits<int>::max();
if (patternWidth > maxVal) {
qCWarning(lcSvgDraw) << "The requested pattern width is too big, clamping to int::max";
patternWidth = maxVal;
}
if (patternHeight > maxVal) {
qCWarning(lcSvgDraw) << "The requested pattern height is too big, clamping to int::max";
patternHeight = maxVal;
}
imageSize.setWidth(qCeil(patternWidth));
imageSize.setHeight(qCeil(patternHeight));
if (imageSize.isEmpty())
return QImage(); // Avoid division by zero in calculateAppliedTransform()
calculateAppliedTransform(t, peBoundingBox, imageSize);
if (states.doc()->isCalculatingImplicitViewBox()) {
QImage pattern; // allocate dummy image to avoid endless recursion, only size matters
if (!QImageIOHandler::allocateImage(imageSize, QImage::Format_ARGB32, &pattern))
qCWarning(lcSvgDraw) << "The requested pattern size is too big, ignoring";
return pattern;
} else {
return renderPattern(imageSize, std::make_pair(contentScaleFactorX, contentScaleFactorY), states);
}
}
QSvgNode::Type QSvgPattern::type() const
{
return Pattern;
}
QImage QSvgPattern::renderPattern(QSize size, std::pair<qreal, qreal> scale, const QSvgExtraStates &states)
{
if (size.isEmpty() || !qIsFinite(scale.first) || !qIsFinite(scale.second))
return defaultPattern();
// Allocate a QImage to draw the pattern in with the calculated size.
QImage pattern;
if (!QImageIOHandler::allocateImage(size, QImage::Format_ARGB32, &pattern)) {
qCWarning(lcSvgDraw) << "The requested pattern size is too big, ignoring";
return defaultPattern();
}
pattern.fill(Qt::transparent);
if (m_isRendering) {
qCWarning(lcSvgDraw) << "The pattern is trying to render itself recursively. "
"Returning a transparent QImage of the right size.";
return pattern;
}
QScopedValueRollback<bool> guard(m_isRendering, true);
// Draw the pattern using our QPainter.
QPainter patternPainter(&pattern);
QSvgExtraStates patternStates(states.doc());
patternStates.trustedSource = states.trustedSource;
patternStates.remainingNestedNodes = states.remainingNestedNodes;
initPainter(&patternPainter);
applyStyleRecursive(&patternPainter, patternStates);
patternPainter.resetTransform();
// According to the <pattern> definition, if viewBox exists then patternContentUnits
// is ignored
if (m_viewBox.isNull())
patternPainter.scale(scale.first, scale.second);
else
patternPainter.setWindow(m_viewBox.toRect());
// Draw all this Pattern children nodes with our QPainter,
for (const auto &node : renderers())
node->draw(&patternPainter, patternStates);
revertStyleRecursive(&patternPainter, patternStates);
return pattern;
}
void QSvgPattern::calculateAppliedTransform(QTransform &worldTransform, QRectF peLocalBB, QSize imageSize)
{
// Calculate the required transform to be applied to the QBrush used for correct
// pattern drawing with the object being rendered.
// Scale : Apply inverse the scale used above because QBrush uses the transform used
// by the QPainter and this function has already rendered the QImage with the
// correct size. Moreover, take into account the difference between the required
// ideal image size in float and the QSize given to image as an integer value.
//
// Translate : Apply translation depending on the calculated x and y values so that the
// drawn pattern can be shifted inside the object.
// Pattern Transform : Apply the transform in the "patternTransform" attribute. This
// transform contains everything except scaling, because it is
// already applied above on the QImage and the QPainter while
// drawing the pattern tile.
m_appliedTransform.reset();
qreal imageDownScaleFactorX = 1 / worldTransform.m11();
qreal imageDownScaleFactorY = 1 / worldTransform.m22();
m_appliedTransform.scale(qIsFinite(imageDownScaleFactorX) ? imageDownScaleFactorX : 1.0,
qIsFinite(imageDownScaleFactorY) ? imageDownScaleFactorY : 1.0);
QRectF p = m_rect.resolveRelativeLengths(peLocalBB);
m_appliedTransform.scale((p.width() * worldTransform.m11() * m_transform.m11()) / imageSize.width(),
(p.height() * worldTransform.m22() * m_transform.m22()) / imageSize.height());
QPointF translation = m_rect.translationRelativeToBoundingBox(peLocalBB);
m_appliedTransform.translate(translation.x() * worldTransform.m11(), translation.y() * worldTransform.m22());
QTransform scalelessTransform = m_transform;
scalelessTransform.scale(1 / m_transform.m11(), 1 / m_transform.m22());
m_appliedTransform = m_appliedTransform * scalelessTransform;
}
QT_END_NAMESPACE