summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexandru Croitor <alexandru.croitor@qt.io>2019-06-17 13:28:57 +0200
committerAlexandru Croitor <alexandru.croitor@qt.io>2019-06-17 13:29:56 +0200
commitf33a29a8dbe03cbe9e59f9e5124f15e90b6336fc (patch)
tree09ba56a3338604358e53c7a33c35d29dc32860a6
parent3864601f76e0b212ec69ad64409d939fd021fbd2 (diff)
parent5c298ef5bea0930b68263ba84194250dbb9d26a9 (diff)
downloadqtsvg-f33a29a8dbe03cbe9e59f9e5124f15e90b6336fc.tar.gz
Merge remote-tracking branch 'origin/wip/qt6' into wip/cmake
Change-Id: Icb0da709c1cb8d2bdc2f33edfe656b7821cb148a
-rw-r--r--.qmake.conf2
-rw-r--r--CMakeLists.txt4
-rw-r--r--dist/changes-5.12.220
-rw-r--r--dist/changes-5.12.322
-rw-r--r--examples/svg/embedded/desktopservices/contenttab.cpp1
-rw-r--r--examples/svg/embedded/fluidlauncher/fluidlauncher.cpp4
-rw-r--r--examples/svg/embedded/weatherinfo/weatherinfo.cpp4
-rw-r--r--examples/svg/network/bearercloud/cloud.cpp2
-rw-r--r--examples/svg/opengl/framebufferobject/glwidget.cpp4
-rw-r--r--examples/svg/svgviewer/mainwindow.cpp14
-rw-r--r--examples/svg/svgviewer/mainwindow.h2
-rw-r--r--examples/svg/svgviewer/svgview.cpp8
-rw-r--r--examples/svg/svgviewer/svgview.h2
-rw-r--r--src/plugins/iconengines/svgiconengine/qsvgiconengine.cpp2
-rw-r--r--src/svg/qsvggenerator.cpp54
-rw-r--r--src/svg/qsvggraphics.cpp33
-rw-r--r--src/svg/qsvggraphics_p.h5
-rw-r--r--src/svg/qsvghandler.cpp57
-rw-r--r--src/svg/qsvgstyle.cpp11
-rw-r--r--src/svg/qsvgstyle_p.h1
-rw-r--r--src/svg/qsvgtinydocument.cpp50
-rw-r--r--src/svg/qsvgtinydocument_p.h5
-rw-r--r--tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp110
-rw-r--r--tests/libfuzzer/svg/qsvgrenderer/render/main.cpp42
-rw-r--r--tests/libfuzzer/svg/qsvgrenderer/render/render.pro3
-rw-r--r--tests/manual/rendertestsuite/.gitignore2
-rw-r--r--tests/manual/rendertestsuite/README32
-rw-r--r--tests/manual/rendertestsuite/main.cpp139
-rw-r--r--tests/manual/rendertestsuite/rendertestsuite.pro3
29 files changed, 517 insertions, 121 deletions
diff --git a/.qmake.conf b/.qmake.conf
index ffdb3bf..8b97f9b 100644
--- a/.qmake.conf
+++ b/.qmake.conf
@@ -3,4 +3,4 @@ load(qt_build_config)
CONFIG += warning_clean
DEFINES += QT_NO_FOREACH
-MODULE_VERSION = 5.13.0
+MODULE_VERSION = 6.0.0
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 50b2b08..8004d78 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,11 +1,11 @@
cmake_minimum_required(VERSION 3.14.0)
project(QtSvg
- VERSION 5.14.0
+ VERSION 6.0.0
DESCRIPTION "Qt SVG Libraries"
HOMEPAGE_URL "https://qt.io/"
LANGUAGES CXX C
)
-find_package(Qt5 ${PROJECT_VERSION} CONFIG REQUIRED COMPONENTS BuildInternals Core Gui Widgets)
+find_package(Qt6 ${PROJECT_VERSION} CONFIG REQUIRED COMPONENTS BuildInternals Core Gui Widgets)
qt_build_repo()
diff --git a/dist/changes-5.12.2 b/dist/changes-5.12.2
new file mode 100644
index 0000000..0c8bd85
--- /dev/null
+++ b/dist/changes-5.12.2
@@ -0,0 +1,20 @@
+Qt 5.12.2 is a bug-fix release. It maintains both forward and backward
+compatibility (source and binary) with Qt 5.12.0 through 5.12.1.
+
+For more details, refer to the online documentation included in this
+distribution. The documentation is also available online:
+
+https://doc.qt.io/qt-5/index.html
+
+The Qt version 5.12 series is binary compatible with the 5.11.x series.
+Applications compiled for 5.11 will continue to run with 5.12.
+
+Some of the changes listed in this file include issue tracking numbers
+corresponding to tasks in the Qt Bug Tracker:
+
+https://bugreports.qt.io/
+
+Each of these identifiers can be entered in the bug tracker to obtain more
+information about a particular change.
+
+ - This release contains only minor code improvements.
diff --git a/dist/changes-5.12.3 b/dist/changes-5.12.3
new file mode 100644
index 0000000..f5b447f
--- /dev/null
+++ b/dist/changes-5.12.3
@@ -0,0 +1,22 @@
+Qt 5.12.3 is a bug-fix release. It maintains both forward and backward
+compatibility (source and binary) with Qt 5.12.0 through 5.12.2.
+
+For more details, refer to the online documentation included in this
+distribution. The documentation is also available online:
+
+https://doc.qt.io/qt-5/index.html
+
+The Qt version 5.12 series is binary compatible with the 5.11.x series.
+Applications compiled for 5.11 will continue to run with 5.12.
+
+Some of the changes listed in this file include issue tracking numbers
+corresponding to tasks in the Qt Bug Tracker:
+
+https://bugreports.qt.io/
+
+Each of these identifiers can be entered in the bug tracker to obtain more
+information about a particular change.
+
+ - [QTBUG-74083] Fixed leak on parsing failure.
+ - [QTBUG-74129] Fixed possible heap overflow in path parsing.
+ - [QTBUG-74189] Fixed crash with recursive gradient references.
diff --git a/examples/svg/embedded/desktopservices/contenttab.cpp b/examples/svg/embedded/desktopservices/contenttab.cpp
index 8d467df..2dac351 100644
--- a/examples/svg/embedded/desktopservices/contenttab.cpp
+++ b/examples/svg/embedded/desktopservices/contenttab.cpp
@@ -124,6 +124,7 @@ void ContentTab::keyPressEvent(QKeyEvent *event)
switch (event->key()) {
case Qt::Key_Select:
openItem(currentItem());
+ Q_FALLTHROUGH();
default:
QListWidget::keyPressEvent(event);
break;
diff --git a/examples/svg/embedded/fluidlauncher/fluidlauncher.cpp b/examples/svg/embedded/fluidlauncher/fluidlauncher.cpp
index 23d569d..ae36595 100644
--- a/examples/svg/embedded/fluidlauncher/fluidlauncher.cpp
+++ b/examples/svg/embedded/fluidlauncher/fluidlauncher.cpp
@@ -48,11 +48,11 @@
**
****************************************************************************/
+#include <QScreen>
#include <QXmlStreamReader>
#include "fluidlauncher.h"
-
#define DEFAULT_INPUT_TIMEOUT 10000
#define SIZING_FACTOR_HEIGHT 6/10
#define SIZING_FACTOR_WIDTH 6/10
@@ -68,7 +68,7 @@ FluidLauncher::FluidLauncher(QStringList* args)
setCurrentWidget(pictureFlowWidget);
pictureFlowWidget->setFocus();
- QRect screen_size = QApplication::desktop()->screenGeometry();
+ QRect screen_size = QGuiApplication::primaryScreen()->geometry();
QObject::connect(pictureFlowWidget, SIGNAL(itemActivated(int)), this, SLOT(launchApplication(int)));
QObject::connect(pictureFlowWidget, SIGNAL(inputReceived()), this, SLOT(resetInputTimeout()));
diff --git a/examples/svg/embedded/weatherinfo/weatherinfo.cpp b/examples/svg/embedded/weatherinfo/weatherinfo.cpp
index b64d8ca..21aa986 100644
--- a/examples/svg/embedded/weatherinfo/weatherinfo.cpp
+++ b/examples/svg/embedded/weatherinfo/weatherinfo.cpp
@@ -147,17 +147,13 @@ private slots:
void animate(int frame) {
qreal progress = static_cast<qreal>(frame) / 100;
-#if QT_VERSION >= 0x040500
m_iconItem->setOpacity(progress);
-#endif
qreal hw = width() / 2.0;
m_statusItem->setPos(-hw + hw * progress, 0);
for (int i = 0; i < m_forecastItems.count(); ++i) {
qreal ofs = i * 0.5 / m_forecastItems.count();
qreal alpha = qBound(qreal(0), 2 * (progress - ofs), qreal(1));
-#if QT_VERSION >= 0x040500
m_conditionItems[i]->setOpacity(alpha);
-#endif
QPointF pos = m_forecastItems[i]->pos();
if (width() > height()) {
qreal fx = width() - width() * 0.4 * alpha;
diff --git a/examples/svg/network/bearercloud/cloud.cpp b/examples/svg/network/bearercloud/cloud.cpp
index 4e0313a..1117c94 100644
--- a/examples/svg/network/bearercloud/cloud.cpp
+++ b/examples/svg/network/bearercloud/cloud.cpp
@@ -76,9 +76,7 @@ Cloud::Cloud(const QNetworkConfiguration &config, QGraphicsItem *parent)
this, SLOT(stateChanged(QNetworkSession::State)));
setFlag(ItemIsMovable);
-#if (QT_VERSION >= QT_VERSION_CHECK(4, 6, 0))
setFlag(ItemSendsGeometryChanges);
-#endif
setZValue(1);
icon = new QGraphicsSvgItem(this);
diff --git a/examples/svg/opengl/framebufferobject/glwidget.cpp b/examples/svg/opengl/framebufferobject/glwidget.cpp
index a3c0d51..38ee5db 100644
--- a/examples/svg/opengl/framebufferobject/glwidget.cpp
+++ b/examples/svg/opengl/framebufferobject/glwidget.cpp
@@ -228,8 +228,8 @@ void GLWidget::draw()
const QString str1(tr("A simple OpenGL framebuffer object example."));
const QString str2(tr("Use the mouse wheel to zoom, press buttons and move mouse to rotate, double-click to flip."));
QFontMetrics fm(p.font());
- p.drawText(width()/2 - fm.width(str1)/2, 20, str1);
- p.drawText(width()/2 - fm.width(str2)/2, 20 + fm.lineSpacing(), str2);
+ p.drawText(width()/2 - fm.horizontalAdvance(str1)/2, 20, str1);
+ p.drawText(width()/2 - fm.horizontalAdvance(str2)/2, 20 + fm.lineSpacing(), str2);
}
void GLWidget::mousePressEvent(QMouseEvent *e)
diff --git a/examples/svg/svgviewer/mainwindow.cpp b/examples/svg/svgviewer/mainwindow.cpp
index 7dd240a..d095416 100644
--- a/examples/svg/svgviewer/mainwindow.cpp
+++ b/examples/svg/svgviewer/mainwindow.cpp
@@ -118,14 +118,10 @@ MainWindow::MainWindow()
m_imageAction->setData(int(SvgView::Image));
rendererMenu->addSeparator();
- m_highQualityAntialiasingAction = rendererMenu->addAction(tr("&High Quality Antialiasing"));
- m_highQualityAntialiasingAction->setEnabled(false);
- m_highQualityAntialiasingAction->setCheckable(true);
- m_highQualityAntialiasingAction->setChecked(false);
- connect(m_highQualityAntialiasingAction, &QAction::toggled, m_view, &SvgView::setHighQualityAntialiasing);
-#ifdef QT_NO_OPENGL
- m_highQualityAntialiasingAction->setVisible(false);
-#endif
+ m_antialiasingAction = rendererMenu->addAction(tr("&Antialiasing"));
+ m_antialiasingAction->setCheckable(true);
+ m_antialiasingAction->setChecked(false);
+ connect(m_antialiasingAction, &QAction::toggled, m_view, &SvgView::setAntialiasing);
QActionGroup *rendererGroup = new QActionGroup(this);
rendererGroup->addAction(m_nativeAction);
@@ -191,8 +187,6 @@ bool MainWindow::loadFile(const QString &fileName)
void MainWindow::setRenderer(int renderMode)
{
-
- m_highQualityAntialiasingAction->setEnabled(renderMode == SvgView::OpenGL);
m_view->setRenderer(static_cast<SvgView::RendererType>(renderMode));
}
diff --git a/examples/svg/svgviewer/mainwindow.h b/examples/svg/svgviewer/mainwindow.h
index 6c2af7e..a102eaa 100644
--- a/examples/svg/svgviewer/mainwindow.h
+++ b/examples/svg/svgviewer/mainwindow.h
@@ -85,7 +85,7 @@ private:
QAction *m_nativeAction;
QAction *m_glAction;
QAction *m_imageAction;
- QAction *m_highQualityAntialiasingAction;
+ QAction *m_antialiasingAction;
QAction *m_backgroundAction;
QAction *m_outlineAction;
diff --git a/examples/svg/svgviewer/svgview.cpp b/examples/svg/svgviewer/svgview.cpp
index ecc8409..50b622b 100644
--- a/examples/svg/svgviewer/svgview.cpp
+++ b/examples/svg/svgviewer/svgview.cpp
@@ -153,13 +153,9 @@ void SvgView::setRenderer(RendererType type)
}
}
-void SvgView::setHighQualityAntialiasing(bool highQualityAntialiasing)
+void SvgView::setAntialiasing(bool antialiasing)
{
-#ifndef QT_NO_OPENGL
- setRenderHint(QPainter::HighQualityAntialiasing, highQualityAntialiasing);
-#else
- Q_UNUSED(highQualityAntialiasing);
-#endif
+ setRenderHint(QPainter::Antialiasing, antialiasing);
}
void SvgView::setViewBackground(bool enable)
diff --git a/examples/svg/svgviewer/svgview.h b/examples/svg/svgviewer/svgview.h
index 1b6b33e..7d5f7d1 100644
--- a/examples/svg/svgviewer/svgview.h
+++ b/examples/svg/svgviewer/svgview.h
@@ -79,7 +79,7 @@ public:
qreal zoomFactor() const;
public slots:
- void setHighQualityAntialiasing(bool highQualityAntialiasing);
+ void setAntialiasing(bool antialiasing);
void setViewBackground(bool enable);
void setViewOutline(bool enable);
void zoomIn();
diff --git a/src/plugins/iconengines/svgiconengine/qsvgiconengine.cpp b/src/plugins/iconengines/svgiconengine/qsvgiconengine.cpp
index e23dd9a..09064e3 100644
--- a/src/plugins/iconengines/svgiconengine/qsvgiconengine.cpp
+++ b/src/plugins/iconengines/svgiconengine/qsvgiconengine.cpp
@@ -197,7 +197,7 @@ QPixmap QSvgIconEngine::pixmap(const QSize &size, QIcon::Mode mode,
QPixmap pm;
QString pmckey(d->pmcKey(size, mode, state));
- if (QPixmapCache::find(pmckey, pm))
+ if (QPixmapCache::find(pmckey, &pm))
return pm;
if (d->addedPixmaps) {
diff --git a/src/svg/qsvggenerator.cpp b/src/svg/qsvggenerator.cpp
index 07f8d74..360b02b 100644
--- a/src/svg/qsvggenerator.cpp
+++ b/src/svg/qsvggenerator.cpp
@@ -227,10 +227,10 @@ public:
QString rct(QStringLiteral("<rect x=\"%1\" y=\"%2\" width=\"%3\" height=\"%4\" />"));
QTextStream str(&d_func()->defs, QIODevice::Append);
str << "<mask id=\"" << maskId << "\" x=\"0\" y=\"0\" width=\"8\" height=\"8\" "
- << "stroke=\"none\" fill=\"#ffffff\" patternUnits=\"userSpaceOnUse\" >" << endl;
+ << "stroke=\"none\" fill=\"#ffffff\" patternUnits=\"userSpaceOnUse\" >" << Qt::endl;
for (QRect r : reg)
- str << rct.arg(r.x()).arg(r.y()).arg(r.width()).arg(r.height()) << endl;
- str << QStringLiteral("</mask>") << endl << endl;
+ str << rct.arg(r.x()).arg(r.y()).arg(r.width()).arg(r.height()) << Qt::endl;
+ str << QStringLiteral("</mask>") << Qt::endl << Qt::endl;
d_func()->savedPatternMasks.append(maskId);
}
return maskId;
@@ -243,9 +243,9 @@ public:
QString maskId = savePatternMask(brush.style());
QString geo(QStringLiteral("x=\"0\" y=\"0\" width=\"8\" height=\"8\""));
QTextStream str(&d_func()->defs, QIODevice::Append);
- str << QString(QStringLiteral("<pattern id=\"%1\" %2 patternUnits=\"userSpaceOnUse\" >")).arg(patternId, geo) << endl;
- str << QString(QStringLiteral("<rect %1 stroke=\"none\" fill=\"%2\" mask=\"url(#%3);\" />")).arg(geo, color, maskId) << endl;
- str << QStringLiteral("</pattern>") << endl << endl;
+ str << QString(QStringLiteral("<pattern id=\"%1\" %2 patternUnits=\"userSpaceOnUse\" >")).arg(patternId, geo) << Qt::endl;
+ str << QString(QStringLiteral("<rect %1 stroke=\"none\" fill=\"%2\" mask=\"url(#%3);\" />")).arg(geo, color, maskId) << Qt::endl;
+ str << QStringLiteral("</pattern>") << Qt::endl << Qt::endl;
d_func()->savedPatternBrushes.append(patternId);
}
return patternId;
@@ -266,7 +266,7 @@ public:
str << QLatin1String("id=\"") << d_func()->generateGradientName() << QLatin1String("\">\n");
saveGradientStops(str, g);
- str << QLatin1String("</linearGradient>") <<endl;
+ str << QLatin1String("</linearGradient>") <<Qt::endl;
}
void saveRadialGradientBrush(const QGradient *g)
{
@@ -283,7 +283,7 @@ public:
}
str << QLatin1String("id=\"") <<d_func()->generateGradientName()<< QLatin1String("\">\n");
saveGradientStops(str, g);
- str << QLatin1String("</radialGradient>") << endl;
+ str << QLatin1String("</radialGradient>") << Qt::endl;
}
void saveConicalGradientBrush(const QGradient *)
{
@@ -544,7 +544,7 @@ public:
"font-size=\"" << d->attributes.font_size << "\" "
"font-weight=\"" << d->attributes.font_weight << "\" "
"font-style=\"" << d->attributes.font_style << "\" "
- << endl;
+ << Qt::endl;
}
};
@@ -892,29 +892,29 @@ bool QSvgPaintEngine::begin(QPaintDevice *)
d->stream = new QTextStream(&d->header);
// stream out the header...
- *d->stream << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" << endl << "<svg";
+ *d->stream << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" << Qt::endl << "<svg";
if (d->size.isValid()) {
qreal wmm = d->size.width() * 25.4 / d->resolution;
qreal hmm = d->size.height() * 25.4 / d->resolution;
- *d->stream << " width=\"" << wmm << "mm\" height=\"" << hmm << "mm\"" << endl;
+ *d->stream << " width=\"" << wmm << "mm\" height=\"" << hmm << "mm\"" << Qt::endl;
}
if (d->viewBox.isValid()) {
*d->stream << " viewBox=\"" << d->viewBox.left() << ' ' << d->viewBox.top();
- *d->stream << ' ' << d->viewBox.width() << ' ' << d->viewBox.height() << '\"' << endl;
+ *d->stream << ' ' << d->viewBox.width() << ' ' << d->viewBox.height() << '\"' << Qt::endl;
}
*d->stream << " xmlns=\"http://www.w3.org/2000/svg\""
" xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
- " version=\"1.2\" baseProfile=\"tiny\">" << endl;
+ " version=\"1.2\" baseProfile=\"tiny\">" << Qt::endl;
if (!d->attributes.document_title.isEmpty()) {
- *d->stream << "<title>" << d->attributes.document_title << "</title>" << endl;
+ *d->stream << "<title>" << d->attributes.document_title << "</title>" << Qt::endl;
}
if (!d->attributes.document_description.isEmpty()) {
- *d->stream << "<desc>" << d->attributes.document_description << "</desc>" << endl;
+ *d->stream << "<desc>" << d->attributes.document_description << "</desc>" << Qt::endl;
}
d->stream->setString(&d->defs);
@@ -924,7 +924,7 @@ bool QSvgPaintEngine::begin(QPaintDevice *)
// Start the initial graphics state...
*d->stream << "<g ";
generateQtDefaults();
- *d->stream << endl;
+ *d->stream << Qt::endl;
return true;
}
@@ -945,10 +945,10 @@ bool QSvgPaintEngine::end()
*d->stream << d->defs;
*d->stream << d->body;
if (d->afterFirstUpdate)
- *d->stream << "</g>" << endl; // close the updateState
+ *d->stream << "</g>" << Qt::endl; // close the updateState
- *d->stream << "</g>" << endl // close the Qt defaults
- << "</svg>" << endl;
+ *d->stream << "</g>" << Qt::endl // close the Qt defaults
+ << "</svg>" << Qt::endl;
delete d->stream;
@@ -1009,13 +1009,13 @@ void QSvgPaintEngine::updateState(const QPaintEngineState &state)
}
if (flags & QPaintEngine::DirtyTransform) {
- d->matrix = state.matrix();
+ d->matrix = state.transform().toAffine();
*d->stream << "transform=\"matrix(" << d->matrix.m11() << ','
<< d->matrix.m12() << ','
<< d->matrix.m21() << ',' << d->matrix.m22() << ','
<< d->matrix.dx() << ',' << d->matrix.dy()
<< ")\""
- << endl;
+ << Qt::endl;
}
if (flags & QPaintEngine::DirtyFont) {
@@ -1027,7 +1027,7 @@ void QSvgPaintEngine::updateState(const QPaintEngineState &state)
stream() << "opacity=\""<<state.opacity()<<"\" ";
}
- *d->stream << '>' << endl;
+ *d->stream << '>' << Qt::endl;
d->afterFirstUpdate = true;
}
@@ -1046,7 +1046,7 @@ void QSvgPaintEngine::drawEllipse(const QRectF &r)
*d->stream << "\" r=\"" << r.width() / qreal(2.0);
else
*d->stream << "\" rx=\"" << r.width() / qreal(2.0) << "\" ry=\"" << r.height() / qreal(2.0);
- *d->stream << "\"/>" << endl;
+ *d->stream << "\"/>" << Qt::endl;
}
void QSvgPaintEngine::drawPath(const QPainterPath &p)
@@ -1090,7 +1090,7 @@ void QSvgPaintEngine::drawPath(const QPainterPath &p)
}
}
- *d->stream << "\"/>" << endl;
+ *d->stream << "\"/>" << Qt::endl;
}
void QSvgPaintEngine::drawPolygon(const QPointF *points, int pointCount,
@@ -1112,7 +1112,7 @@ void QSvgPaintEngine::drawPolygon(const QPointF *points, int pointCount,
const QPointF &pt = points[i];
stream() << pt.x() << ',' << pt.y() << ' ';
}
- stream() << "\" />" <<endl;
+ stream() << "\" />" <<Qt::endl;
} else {
path.closeSubpath();
drawPath(path);
@@ -1130,7 +1130,7 @@ void QSvgPaintEngine::drawRects(const QRectF *rects, int rectCount)
*d->stream << " vector-effect=\"non-scaling-stroke\"";
*d->stream << " x=\"" << rect.x() << "\" y=\"" << rect.y()
<< "\" width=\"" << rect.width() << "\" height=\"" << rect.height()
- << "\"/>" << endl;
+ << "\"/>" << Qt::endl;
}
}
@@ -1155,7 +1155,7 @@ void QSvgPaintEngine::drawTextItem(const QPointF &pt, const QTextItem &textItem)
*d->stream << " >"
<< s.toHtmlEscaped()
<< "</text>"
- << endl;
+ << Qt::endl;
}
QT_END_NAMESPACE
diff --git a/src/svg/qsvggraphics.cpp b/src/svg/qsvggraphics.cpp
index 5b273af..bcd2452 100644
--- a/src/svg/qsvggraphics.cpp
+++ b/src/svg/qsvggraphics.cpp
@@ -41,11 +41,12 @@
#include "qsvgfont_p.h"
-#include "qpainter.h"
-#include "qtextdocument.h"
-#include "qabstracttextdocumentlayout.h"
-#include "qtextcursor.h"
-#include "qdebug.h"
+#include <qabstracttextdocumentlayout.h>
+#include <qdebug.h>
+#include <qpainter.h>
+#include <qscopedvaluerollback.h>
+#include <qtextcursor.h>
+#include <qtextdocument.h>
#include <math.h>
#include <limits.h>
@@ -121,14 +122,14 @@ void QSvgArc::draw(QPainter *p, QSvgExtraStates &states)
}
QSvgImage::QSvgImage(QSvgNode *parent, const QImage &image,
- const QRect &bounds)
+ const QRectF &bounds)
: QSvgNode(parent), m_image(image),
m_bounds(bounds)
{
- if (m_bounds.width() == 0)
- m_bounds.setWidth(m_image.width());
- if (m_bounds.height() == 0)
- m_bounds.setHeight(m_image.height());
+ 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()));
}
void QSvgImage::draw(QPainter *p, QSvgExtraStates &states)
@@ -458,14 +459,14 @@ void QSvgText::addText(const QString &text)
}
QSvgUse::QSvgUse(const QPointF &start, QSvgNode *parent, QSvgNode *node)
- : QSvgNode(parent), m_link(node), m_start(start)
+ : QSvgNode(parent), m_link(node), m_start(start), m_recursing(false)
{
}
void QSvgUse::draw(QPainter *p, QSvgExtraStates &states)
{
- if (Q_UNLIKELY(!m_link || isDescendantOf(m_link)))
+ if (Q_UNLIKELY(!m_link || isDescendantOf(m_link) || m_recursing))
return;
applyStyle(p, states);
@@ -473,7 +474,10 @@ void QSvgUse::draw(QPainter *p, QSvgExtraStates &states)
if (!m_start.isNull()) {
p->translate(m_start);
}
- m_link->draw(p, states);
+ {
+ QScopedValueRollback<bool> guard(m_recursing, true);
+ m_link->draw(p, states);
+ }
if (!m_start.isNull()) {
p->translate(-m_start);
}
@@ -556,7 +560,8 @@ QSvgNode::Type QSvgVideo::type() const
QRectF QSvgUse::bounds(QPainter *p, QSvgExtraStates &states) const
{
QRectF bounds;
- if (Q_LIKELY(m_link && !isDescendantOf(m_link))) {
+ if (Q_LIKELY(m_link && !isDescendantOf(m_link) && !m_recursing)) {
+ QScopedValueRollback<bool> guard(m_recursing, true);
p->translate(m_start);
bounds = m_link->transformedBounds(p, states);
p->translate(-m_start);
diff --git a/src/svg/qsvggraphics_p.h b/src/svg/qsvggraphics_p.h
index 6e5b9d6..8488b33 100644
--- a/src/svg/qsvggraphics_p.h
+++ b/src/svg/qsvggraphics_p.h
@@ -104,13 +104,13 @@ class Q_SVG_PRIVATE_EXPORT QSvgImage : public QSvgNode
{
public:
QSvgImage(QSvgNode *parent, const QImage &image,
- const QRect &bounds);
+ const QRectF &bounds);
void draw(QPainter *p, QSvgExtraStates &states) override;
Type type() const override;
QRectF bounds(QPainter *p, QSvgExtraStates &states) const override;
private:
QImage m_image;
- QRect m_bounds;
+ QRectF m_bounds;
};
class Q_SVG_PRIVATE_EXPORT QSvgLine : public QSvgNode
@@ -251,6 +251,7 @@ private:
QSvgNode *m_link;
QPointF m_start;
QString m_linkId;
+ mutable bool m_recursing;
};
class QSvgVideo : public QSvgNode
diff --git a/src/svg/qsvghandler.cpp b/src/svg/qsvghandler.cpp
index 0468bbe..fe79977 100644
--- a/src/svg/qsvghandler.cpp
+++ b/src/svg/qsvghandler.cpp
@@ -774,21 +774,31 @@ static QVector<qreal> parsePercentageList(const QChar *&str)
static QString idFromUrl(const QString &url)
{
+ // The form is url(<IRI>), where IRI can be
+ // just an ID on #<id> form.
QString::const_iterator itr = url.constBegin();
QString::const_iterator end = url.constEnd();
+ QString id;
while (itr != end && (*itr).isSpace())
++itr;
if (itr != end && (*itr) == QLatin1Char('('))
++itr;
+ else
+ return QString();
while (itr != end && (*itr).isSpace())
++itr;
- if (itr != end && (*itr) == QLatin1Char('#'))
+ if (itr != end && (*itr) == QLatin1Char('#')) {
+ id += *itr;
++itr;
- QString id;
+ } else {
+ return QString();
+ }
while (itr != end && (*itr) != QLatin1Char(')')) {
id += *itr;
++itr;
}
+ if (itr == end || (*itr) != QLatin1Char(')'))
+ return QString();
return id;
}
@@ -1596,7 +1606,7 @@ static bool parsePathDataFast(const QStringRef &dataStr, QPainterPath &path)
const QChar *end = str + dataStr.size();
while (str != end) {
- while (str->isSpace())
+ while (str->isSpace() && (str + 1) != end)
++str;
QChar pathElem = *str;
++str;
@@ -2605,17 +2615,17 @@ static QSvgStyleProperty *createFontNode(QSvgNode *parent,
parent = parent->parent();
}
- if (parent) {
+ if (parent && !myId.isEmpty()) {
QSvgTinyDocument *doc = static_cast<QSvgTinyDocument*>(parent);
- QSvgFont *font = new QSvgFont(horizAdvX);
- font->setFamilyName(myId);
- if (!font->familyName().isEmpty()) {
- if (!doc->svgFont(font->familyName()))
- doc->addSvgFont(font);
+ QSvgFont *font = doc->svgFont(myId);
+ if (!font) {
+ font = new QSvgFont(horizAdvX);
+ font->setFamilyName(myId);
+ doc->addSvgFont(font);
}
return new QSvgFontStyle(font, doc);
}
- return 0;
+ return nullptr;
}
static bool parseFontFaceNode(QSvgStyleProperty *parent,
@@ -2792,10 +2802,10 @@ static QSvgNode *createImageNode(QSvgNode *parent,
QSvgNode *img = new QSvgImage(parent,
image,
- QRect(int(nx),
- int(ny),
- int(nwidth),
- int(nheight)));
+ QRectF(nx,
+ ny,
+ nwidth,
+ nheight));
return img;
}
@@ -3709,14 +3719,20 @@ bool QSvgHandler::startElement(const QString &localName,
}
break;
default:
+ const QByteArray msg = QByteArrayLiteral("Could not add child element to parent element because the types are incorrect.");
+ qCWarning(lcSvgHandler, "%s", prefixMessage(msg, xml).constData());
+ delete node;
+ node = 0;
break;
}
}
- parseCoreNode(node, attributes);
+ if (node) {
+ parseCoreNode(node, attributes);
#ifndef QT_NO_CSSPARSER
- cssStyleLookup(node, this, m_selector);
+ cssStyleLookup(node, this, m_selector);
#endif
- parseStyle(node, attributes, this);
+ parseStyle(node, attributes, this);
+ }
} else if (FactoryMethod method = findGraphicsFactory(localName)) {
//rendering element
Q_ASSERT(!m_nodes.isEmpty());
@@ -3728,6 +3744,13 @@ bool QSvgHandler::startElement(const QString &localName,
case QSvgNode::DEFS:
case QSvgNode::SWITCH:
{
+ if (node->type() == QSvgNode::TSPAN) {
+ const QByteArray msg = QByteArrayLiteral("\'tspan\' element in wrong context.");
+ qCWarning(lcSvgHandler, "%s", prefixMessage(msg, xml).constData());
+ delete node;
+ node = 0;
+ break;
+ }
QSvgStructureNode *group =
static_cast<QSvgStructureNode*>(m_nodes.top());
group->addChild(node, someId(attributes));
diff --git a/src/svg/qsvgstyle.cpp b/src/svg/qsvgstyle.cpp
index 5448797..b934f94 100644
--- a/src/svg/qsvgstyle.cpp
+++ b/src/svg/qsvgstyle.cpp
@@ -942,13 +942,20 @@ void QSvgGradientStyle::setStopLink(const QString &link, QSvgTinyDocument *doc)
void QSvgGradientStyle::resolveStops()
{
+ QStringList visited;
+ resolveStops_helper(&visited);
+}
+
+void QSvgGradientStyle::resolveStops_helper(QStringList *visited)
+{
if (!m_link.isEmpty() && m_doc) {
QSvgStyleProperty *prop = m_doc->styleProperty(m_link);
- if (prop && prop != this) {
+ if (prop && !visited->contains(m_link)) {
+ visited->append(m_link);
if (prop->type() == QSvgStyleProperty::GRADIENT) {
QSvgGradientStyle *st =
static_cast<QSvgGradientStyle*>(prop);
- st->resolveStops();
+ st->resolveStops_helper(visited);
m_gradient->setStops(st->qgradient()->stops());
m_gradientStopsSet = st->gradientStopsSet();
}
diff --git a/src/svg/qsvgstyle_p.h b/src/svg/qsvgstyle_p.h
index 916c9fa..39aa690 100644
--- a/src/svg/qsvgstyle_p.h
+++ b/src/svg/qsvgstyle_p.h
@@ -577,6 +577,7 @@ public:
void setStopLink(const QString &link, QSvgTinyDocument *doc);
QString stopLink() const { return m_link; }
void resolveStops();
+ void resolveStops_helper(QStringList *visited);
void setMatrix(const QMatrix &matrix);
QMatrix qmatrix() const
diff --git a/src/svg/qsvgtinydocument.cpp b/src/svg/qsvgtinydocument.cpp
index 15351bd..77aafb4 100644
--- a/src/svg/qsvgtinydocument.cpp
+++ b/src/svg/qsvgtinydocument.cpp
@@ -187,6 +187,7 @@ QSvgTinyDocument * QSvgTinyDocument::load(const QString &fileName)
} else {
qCWarning(lcSvgHandler, "Cannot read file '%s', because: %s (line %d)",
qPrintable(fileName), qPrintable(handler.errorString()), handler.lineNumber());
+ delete handler.document();
}
return doc;
}
@@ -207,6 +208,8 @@ QSvgTinyDocument * QSvgTinyDocument::load(const QByteArray &contents)
if (handler.ok()) {
doc = handler.document();
doc->m_animationDuration = handler.animationDuration();
+ } else {
+ delete handler.document();
}
return doc;
}
@@ -219,6 +222,8 @@ QSvgTinyDocument * QSvgTinyDocument::load(QXmlStreamReader *contents)
if (handler.ok()) {
doc = handler.document();
doc->m_animationDuration = handler.animationDuration();
+ } else {
+ delete handler.document();
}
return doc;
}
@@ -334,6 +339,7 @@ void QSvgTinyDocument::setHeight(int len, bool percent)
void QSvgTinyDocument::setViewBox(const QRectF &rect)
{
m_viewBox = rect;
+ m_implicitViewBox = false;
}
void QSvgTinyDocument::addSvgFont(QSvgFont *font)
@@ -358,7 +364,10 @@ QSvgNode *QSvgTinyDocument::namedNode(const QString &id) const
void QSvgTinyDocument::addNamedStyle(const QString &id, QSvgFillStyleProperty *style)
{
- m_namedStyles.insert(id, style);
+ if (!m_namedStyles.contains(id))
+ m_namedStyles.insert(id, style);
+ else
+ qCWarning(lcSvgHandler) << "Duplicate unique style id:" << id;
}
QSvgFillStyleProperty *QSvgTinyDocument::namedStyle(const QString &id) const
@@ -412,14 +421,35 @@ void QSvgTinyDocument::mapSourceToTarget(QPainter *p, const QRectF &targetRect,
source = viewBox();
if (source != target && !source.isNull()) {
- QTransform transform;
- transform.scale(target.width() / source.width(),
- target.height() / source.height());
- QRectF c2 = transform.mapRect(source);
- p->translate(target.x() - c2.x(),
- target.y() - c2.y());
- p->scale(target.width() / source.width(),
- target.height() / source.height());
+ if (m_implicitViewBox) {
+ QTransform transform;
+ transform.scale(target.width() / source.width(),
+ target.height() / source.height());
+ QRectF c2 = transform.mapRect(source);
+ p->translate(target.x() - c2.x(),
+ target.y() - c2.y());
+ p->scale(target.width() / source.width(),
+ target.height() / source.height());
+ } else {
+ // Code path used when a view box is specified and we're not rendering a specific element by id
+ // but the entire document. This attempts to emulate the default values of the <preserveAspectRatio>
+ // tag that's implicitly defined when <viewbox> is used.
+
+ // Apply the view box translation if specified.
+ p->translate(target.x() - source.x(),
+ target.y() - source.y());
+
+ // Scale the view box into the view port (target) by preserve the aspect ratio.
+ QSizeF viewBoxSize = source.size();
+ viewBoxSize.scale(target.width(), target.height(), Qt::KeepAspectRatio);
+
+ // Center the view box in the view port
+ p->translate((target.width() - viewBoxSize.width()) / 2,
+ (target.height() - viewBoxSize.height()) / 2);
+
+ p->scale(viewBoxSize.width() / source.width(),
+ viewBoxSize.height() / source.height());
+ }
}
}
@@ -455,7 +485,7 @@ QMatrix QSvgTinyDocument::matrixForElement(const QString &id) const
t *= node->m_style.transform->qtransform();
node = node->parent();
}
-
+
return t.toAffine();
}
diff --git a/src/svg/qsvgtinydocument_p.h b/src/svg/qsvgtinydocument_p.h
index aa51751..5f5d06b 100644
--- a/src/svg/qsvgtinydocument_p.h
+++ b/src/svg/qsvgtinydocument_p.h
@@ -125,6 +125,7 @@ private:
bool m_widthPercent;
bool m_heightPercent;
+ mutable bool m_implicitViewBox = true;
mutable QRectF m_viewBox;
QHash<QString, QSvgRefCounter<QSvgFont> > m_fonts;
@@ -173,8 +174,10 @@ inline bool QSvgTinyDocument::heightPercent() const
inline QRectF QSvgTinyDocument::viewBox() const
{
- if (m_viewBox.isNull())
+ if (m_viewBox.isNull()) {
m_viewBox = transformedBounds();
+ m_implicitViewBox = true;
+ }
return m_viewBox;
}
diff --git a/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp b/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp
index 553838e..cf19213 100644
--- a/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp
+++ b/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp
@@ -67,6 +67,8 @@ private slots:
void boundsOnElement() const;
void gradientStops() const;
void gradientRefs();
+ void recursiveRefs_data();
+ void recursiveRefs();
void fillRule();
void opacity();
void paths();
@@ -77,6 +79,7 @@ private slots:
void testUseElement();
void smallFont();
void styleSheet();
+ void duplicateStyleId();
#ifndef QT_NO_COMPRESS
void testGzLoading();
@@ -139,22 +142,28 @@ void tst_QSvgRenderer::invalidUrl_data()
{
QTest::addColumn<QByteArray>("svg");
- QTest::newRow("00") << QByteArray("<svg><circle fill=\"url\" /></svg>");
- QTest::newRow("01") << QByteArray("<svg><circle fill=\"url0\" /></svg>");
- QTest::newRow("02") << QByteArray("<svg><circle fill=\"url(0\" /></svg>");
- QTest::newRow("03") << QByteArray("<svg><circle fill=\"url (0\" /></svg>");
- QTest::newRow("04") << QByteArray("<svg><circle fill=\"url ( 0\" /></svg>");
- QTest::newRow("05") << QByteArray("<svg><circle fill=\"url#\" /></svg>");
- QTest::newRow("06") << QByteArray("<svg><circle fill=\"url#(\" /></svg>");
- QTest::newRow("07") << QByteArray("<svg><circle fill=\"url(#\" /></svg>");
- QTest::newRow("08") << QByteArray("<svg><circle fill=\"url(# \" /></svg>");
- QTest::newRow("09") << QByteArray("<svg><circle fill=\"url(# 0\" /></svg>");
+ QTest::newRow("01") << QByteArray("<svg><linearGradient id=\"0\"/><circle fill=\"url0\" /></svg>");
+ QTest::newRow("02") << QByteArray("<svg><linearGradient id=\"0\"/><circle fill=\"url(0\" /></svg>");
+ QTest::newRow("03") << QByteArray("<svg><linearGradient id=\"0\"/><circle fill=\"url (0\" /></svg>");
+ QTest::newRow("04") << QByteArray("<svg><linearGradient id=\"0\"/><circle fill=\"url ( 0\" /></svg>");
+ QTest::newRow("05") << QByteArray("<svg><linearGradient id=\"0\"/><circle fill=\"url#\" /></svg>");
+ QTest::newRow("06") << QByteArray("<svg><linearGradient id=\"0\"/><circle fill=\"url#(\" /></svg>");
+ QTest::newRow("07") << QByteArray("<svg><linearGradient id=\"0\"/><circle fill=\"url(#\" /></svg>");
+ QTest::newRow("08") << QByteArray("<svg><linearGradient id=\"0\"/><circle fill=\"url(# \" /></svg>");
+ QTest::newRow("09") << QByteArray("<svg><linearGradient id=\"0\"/><circle fill=\"url(# 0\" /></svg>");
+ QTest::newRow("10") << QByteArray("<svg><linearGradient id=\"blabla\"/><circle fill=\"urlblabla\" /></svg>");
+ QTest::newRow("11") << QByteArray("<svg><linearGradient id=\"blabla\"/><circle fill=\"url(blabla\" /></svg>");
+ QTest::newRow("12") << QByteArray("<svg><linearGradient id=\"blabla\"/><circle fill=\"url(blabla)\" /></svg>");
+ QTest::newRow("13") << QByteArray("<svg><linearGradient id=\"blabla\"/><circle fill=\"url(#blabla\" /></svg>");
}
void tst_QSvgRenderer::invalidUrl()
{
QFETCH(QByteArray, svg);
+#if QT_CONFIG(regularexpression)
+ QTest::ignoreMessage(QtWarningMsg, QRegularExpression("Could not resolve property"));
+#endif
QSvgRenderer renderer(svg);
QVERIFY(renderer.isValid());
}
@@ -259,6 +268,16 @@ void tst_QSvgRenderer::testMapViewBoxToTarget()
QCOMPARE(picture.boundingRect(), QRect(125, 125, 250, 250));
}
+ { // Viewport and viewBox specified -> scale 500x500 square to 1000x750 while preserving aspect ratio gives 750x750
+ // however the box is centered at 375, 250
+ data = "<svg width=\"1000\" height=\"750\" viewBox=\"-250 -250 500 500\"><g><rect x=\"0\" y=\"0\" width=\"500\" height=\"500\" /></g></svg>";
+ QPicture picture;
+ QPainter painter(&picture);
+ QSvgRenderer rend(data);
+ rend.render(&painter);
+ painter.end();
+ QCOMPARE(picture.boundingRect(), QRect(375, 250, 750, 750));
+ }
}
void tst_QSvgRenderer::testRenderElement()
@@ -476,15 +495,15 @@ void tst_QSvgRenderer::matrixForElement() const
QPainter painter(&image);
QSvgRenderer renderer(data);
- compareTransforms(QTransform(painter.worldMatrix()), QTransform(renderer.matrixForElement(QLatin1String("ichi"))));
+ compareTransforms(painter.worldTransform(), QTransform(renderer.matrixForElement(QLatin1String("ichi"))));
painter.translate(-3, 1);
- compareTransforms(QTransform(painter.worldMatrix()), QTransform(renderer.matrixForElement(QLatin1String("ni"))));
+ compareTransforms(painter.worldTransform(), QTransform(renderer.matrixForElement(QLatin1String("ni"))));
painter.rotate(45);
- compareTransforms(QTransform(painter.worldMatrix()), QTransform(renderer.matrixForElement(QLatin1String("san"))));
+ compareTransforms(painter.worldTransform(), QTransform(renderer.matrixForElement(QLatin1String("san"))));
painter.scale(4, 2);
- compareTransforms(QTransform(painter.worldMatrix()), QTransform(renderer.matrixForElement(QLatin1String("yon"))));
- painter.setWorldMatrix(QMatrix(1, 2, 3, 4, 5, 6), true);
- compareTransforms(QTransform(painter.worldMatrix()), QTransform(renderer.matrixForElement(QLatin1String("firkant"))));
+ compareTransforms(painter.worldTransform(), QTransform(renderer.matrixForElement(QLatin1String("yon"))));
+ painter.setWorldTransform(QTransform(1, 2, 3, 4, 5, 6), true);
+ compareTransforms(painter.worldTransform(), QTransform(renderer.matrixForElement(QLatin1String("firkant"))));
}
void tst_QSvgRenderer::boundsOnElement() const
@@ -667,6 +686,43 @@ void tst_QSvgRenderer::gradientRefs()
}
}
+void tst_QSvgRenderer::recursiveRefs_data()
+{
+ QTest::addColumn<QByteArray>("svg");
+
+ QTest::newRow("single") << QByteArray("<svg>"
+ "<linearGradient id='0' xlink:href='#0'/>"
+ "<rect x='0' y='0' width='20' height='20' fill='url(#0)'/>"
+ "</svg>");
+
+ QTest::newRow("double") << QByteArray("<svg>"
+ "<linearGradient id='0' xlink:href='#1'/>"
+ "<linearGradient id='1' xlink:href='#0'/>"
+ "<rect x='0' y='0' width='20' height='20' fill='url(#0)'/>"
+ "</svg>");
+
+ QTest::newRow("triple") << QByteArray("<svg>"
+ "<linearGradient id='0' xlink:href='#1'/>"
+ "<linearGradient id='1' xlink:href='#2'/>"
+ "<linearGradient id='2' xlink:href='#0'/>"
+ "<rect x='0' y='0' width='20' height='20' fill='url(#0)'/>"
+ "</svg>");
+}
+
+void tst_QSvgRenderer::recursiveRefs()
+{
+ QFETCH(QByteArray, svg);
+
+ QImage image(20, 20, QImage::Format_ARGB32_Premultiplied);
+ image.fill(Qt::green);
+ QImage refImage = image.copy();
+
+ QSvgRenderer renderer(svg);
+ QPainter painter(&image);
+ renderer.render(&painter);
+ QCOMPARE(image, refImage);
+}
+
#ifndef QT_NO_COMPRESS
void tst_QSvgRenderer::testGzLoading()
@@ -1377,6 +1433,16 @@ void tst_QSvgRenderer::testUseElement()
" <circle fill=\"#a6ce39\" cx=\"0\" cy=\"0\" r=\"33\" />"
" </g>"
" </defs>"
+ "</svg>",
+ // 17 - Indirect self referral
+ "<svg>"
+ " <defs>"
+ " <g id=\"g0\">"
+ " <g id=\"g1\"><use href=\"#g2\"/></g>"
+ " <g id=\"g2\"><use href=\"#g1\"/></g>"
+ " </g>"
+ " </defs>"
+ " <use xlink:href=\"#g0\" fill=\"black\"/>"
"</svg>"
};
@@ -1459,5 +1525,17 @@ void tst_QSvgRenderer::styleSheet()
QCOMPARE(images[0], images[1]);
}
+void tst_QSvgRenderer::duplicateStyleId()
+{
+ QByteArray svg = QByteArrayLiteral("<svg><linearGradient id=\"a\"/>"
+ "<rect style=\"fill:url(#a)\"/>"
+ "<linearGradient id=\"a\"/></svg>");
+ QTest::ignoreMessage(QtWarningMsg, "Duplicate unique style id: \"a\"");
+ QImage image(200, 200, QImage::Format_RGB32);
+ QPainter painter(&image);
+ QSvgRenderer renderer(svg);
+ renderer.render(&painter);
+}
+
QTEST_MAIN(tst_QSvgRenderer)
#include "tst_qsvgrenderer.moc"
diff --git a/tests/libfuzzer/svg/qsvgrenderer/render/main.cpp b/tests/libfuzzer/svg/qsvgrenderer/render/main.cpp
new file mode 100644
index 0000000..0197115
--- /dev/null
+++ b/tests/libfuzzer/svg/qsvgrenderer/render/main.cpp
@@ -0,0 +1,42 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QApplication>
+#include <QImage>
+#include <QPainter>
+#include <QSvgRenderer>
+
+extern "C" int LLVMFuzzerTestOneInput(const char *Data, size_t Size) {
+ static int c = 0;
+ static QApplication a(c, nullptr);
+ static QImage image(377, 233, QImage::Format_RGB32);
+ static QPainter painter(&image);
+ QSvgRenderer renderer(QByteArray(Data, Size));
+ renderer.render(&painter);
+ return 0;
+}
diff --git a/tests/libfuzzer/svg/qsvgrenderer/render/render.pro b/tests/libfuzzer/svg/qsvgrenderer/render/render.pro
new file mode 100644
index 0000000..be94755
--- /dev/null
+++ b/tests/libfuzzer/svg/qsvgrenderer/render/render.pro
@@ -0,0 +1,3 @@
+QT += svg
+SOURCES += main.cpp
+LIBS += -fsanitize=fuzzer
diff --git a/tests/manual/rendertestsuite/.gitignore b/tests/manual/rendertestsuite/.gitignore
new file mode 100644
index 0000000..54f7094
--- /dev/null
+++ b/tests/manual/rendertestsuite/.gitignore
@@ -0,0 +1,2 @@
+baseline
+difference
diff --git a/tests/manual/rendertestsuite/README b/tests/manual/rendertestsuite/README
new file mode 100644
index 0000000..0609d4f
--- /dev/null
+++ b/tests/manual/rendertestsuite/README
@@ -0,0 +1,32 @@
+This little helper program can be used together with the SVG Tiny 1.2 test
+suite to determine the visual impact of changes to the renderer on the basis of
+the test suite.
+
+Suppose you have the test suite installed in ~/tests, then you can create a
+base line of renderings of all the SVG images by running
+
+ mkdir baseline
+ ./rendertestsuite create-baseline ~/tests
+
+and the output will be stored in the 'baseline/' sub-directory.
+
+After making changes to the renderer, you can create a new set of images by running
+
+ mkdir difference
+ ./rendertestsuite diff ~/tests
+
+and side-by-side images of the old and the new images in case they differ will
+be stored in the sub-directory 'difference/'.
+
+An easy way to inspect the output is by using KDE's gwenview:
+
+ gwenview difference/
+
+
+Note that due to rounding errors there will always be subtle differences, even
+when not doing any changes to the renderer.
+
+
+You can find the test suite at
+
+ https://www.w3.org/Graphics/SVG/WG/wiki/Test_Suite_Overview#SVG_1.2_Tiny_Test_Suite:_12_September_2008
diff --git a/tests/manual/rendertestsuite/main.cpp b/tests/manual/rendertestsuite/main.cpp
new file mode 100644
index 0000000..dfdf88c
--- /dev/null
+++ b/tests/manual/rendertestsuite/main.cpp
@@ -0,0 +1,139 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt SVG module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore>
+#include <QtSvg>
+#include <QtGui>
+#include <stdlib.h>
+
+static QImage render(const QString &filePath)
+{
+ fprintf(stdout, "Rendering %s\n", qPrintable(filePath));
+ QSvgRenderer renderer(filePath);
+ if (!renderer.isValid()) {
+ fprintf(stderr, "Could not load SVG file %s\n", qPrintable(filePath));
+ return QImage();
+ }
+ QImage image(480, 360, QImage::Format_ARGB32);
+ image.fill(Qt::transparent);
+ {
+ QPainter p(&image);
+ renderer.render(&p);
+ }
+ return image;
+}
+
+int main(int argc, char **argv)
+{
+ QGuiApplication app(argc, argv);
+
+ QCommandLineParser parser;
+ parser.addHelpOption();
+ parser.addPositionalArgument(QLatin1String("command"), QLatin1String("[create-baseline,diff]"));
+ parser.addPositionalArgument(QLatin1String("path-to-svg-testsuite"), QLatin1String("Path to the svg/ sub-directory of the test suite"));
+
+ parser.process(app);
+
+ const auto args = parser.positionalArguments();
+
+ if (args.count() != 2) {
+ parser.showHelp(EXIT_FAILURE);
+ return EXIT_FAILURE; // never reached
+ }
+
+ const QString commandAsString = args.at(0);
+ const QString sourcePath = args.at(1);
+
+ QDirIterator sourceFileIterator(sourcePath, QStringList(QLatin1String("*.svg")), QDir::Files);
+ const QString baselinePath = "baseline";
+
+ const auto referenceFilePath = [baselinePath](const QFileInfo &testCaseFileInfo) -> QString {
+ return baselinePath + QLatin1Char('/') + testCaseFileInfo.baseName() + QLatin1String(".png");
+ };
+
+ if (commandAsString == "create-baseline") {
+ while (sourceFileIterator.hasNext()) {
+ sourceFileIterator.next();
+
+ QImage image = render(sourceFileIterator.filePath());
+ if (image.isNull())
+ return EXIT_FAILURE;
+ QString outputFileName = referenceFilePath(sourceFileIterator.fileInfo());
+ if (!image.save(outputFileName)) {
+ fprintf(stderr, "Could not save PNG file %s\n", qPrintable(outputFileName));
+ return EXIT_FAILURE;
+ }
+ }
+ } else if (commandAsString == "diff") {
+ while (sourceFileIterator.hasNext()) {
+ sourceFileIterator.next();
+
+ QImage actual = render(sourceFileIterator.filePath());
+ const QString referencePath = referenceFilePath(sourceFileIterator.fileInfo());
+ QImage reference;
+ if (!reference.load(referencePath)) {
+ fprintf(stderr, "Could not load reference file %s\n", qPrintable(referencePath));
+ return EXIT_FAILURE;
+ }
+
+ if (actual == reference)
+ continue;
+
+ QImage sideBySideImage(actual.width() * 2, actual.height(), QImage::Format_ARGB32);
+ sideBySideImage.fill(Qt::transparent);
+ {
+ QPainter p(&sideBySideImage);
+ p.drawImage(0, 0, actual);
+ p.drawImage(actual.width(), 0, reference);
+ }
+
+ const QString sideBySideFileName = "difference/" + sourceFileIterator.fileInfo().baseName() + QLatin1String(".png");
+ if (!sideBySideImage.save(sideBySideFileName)) {
+ fprintf(stderr, "Could not save side-by-side image at %s\n", qPrintable(sideBySideFileName));
+ return EXIT_FAILURE;
+ }
+ }
+ } else {
+ fprintf(stderr, "Unknown command %s\n", qPrintable(commandAsString));
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
+
diff --git a/tests/manual/rendertestsuite/rendertestsuite.pro b/tests/manual/rendertestsuite/rendertestsuite.pro
new file mode 100644
index 0000000..855d3fc
--- /dev/null
+++ b/tests/manual/rendertestsuite/rendertestsuite.pro
@@ -0,0 +1,3 @@
+TEMPLATE = app
+QT = core gui svg
+SOURCES = main.cpp