diff options
author | Alexandru Croitor <alexandru.croitor@qt.io> | 2019-06-17 13:28:57 +0200 |
---|---|---|
committer | Alexandru Croitor <alexandru.croitor@qt.io> | 2019-06-17 13:29:56 +0200 |
commit | f33a29a8dbe03cbe9e59f9e5124f15e90b6336fc (patch) | |
tree | 09ba56a3338604358e53c7a33c35d29dc32860a6 | |
parent | 3864601f76e0b212ec69ad64409d939fd021fbd2 (diff) | |
parent | 5c298ef5bea0930b68263ba84194250dbb9d26a9 (diff) | |
download | qtsvg-f33a29a8dbe03cbe9e59f9e5124f15e90b6336fc.tar.gz |
Merge remote-tracking branch 'origin/wip/qt6' into wip/cmake
Change-Id: Icb0da709c1cb8d2bdc2f33edfe656b7821cb148a
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 |