/**************************************************************************** ** ** Copyright (C) 2016 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 "qsvgtinydocument_p.h" #include "qsvghandler_p.h" #include "qsvgfont_p.h" #include "qpainter.h" #include "qfile.h" #include "qbuffer.h" #include "qbytearray.h" #include "qqueue.h" #include "qstack.h" #include "qdebug.h" #ifndef QT_NO_COMPRESS #include #endif QT_BEGIN_NAMESPACE QSvgTinyDocument::QSvgTinyDocument() : QSvgStructureNode(0) , m_widthPercent(false) , m_heightPercent(false) , m_time(0) , m_animated(false) , m_animationDuration(0) , m_fps(30) { } QSvgTinyDocument::~QSvgTinyDocument() { } #ifndef QT_NO_COMPRESS # ifdef QT_BUILD_INTERNAL Q_AUTOTEST_EXPORT QByteArray qt_inflateGZipDataFrom(QIODevice *device); # else static QByteArray qt_inflateGZipDataFrom(QIODevice *device); # endif QByteArray qt_inflateGZipDataFrom(QIODevice *device) { if (!device) return QByteArray(); if (!device->isOpen()) device->open(QIODevice::ReadOnly); Q_ASSERT(device->isOpen() && device->isReadable()); static const int CHUNK_SIZE = 4096; int zlibResult = Z_OK; QByteArray source; QByteArray destination; // Initialize zlib stream struct z_stream zlibStream; zlibStream.next_in = Z_NULL; zlibStream.avail_in = 0; zlibStream.avail_out = 0; zlibStream.zalloc = Z_NULL; zlibStream.zfree = Z_NULL; zlibStream.opaque = Z_NULL; // Adding 16 to the window size gives us gzip decoding if (inflateInit2(&zlibStream, MAX_WBITS + 16) != Z_OK) { qCWarning(lcSvgHandler, "Cannot initialize zlib, because: %s", (zlibStream.msg != NULL ? zlibStream.msg : "Unknown error")); return QByteArray(); } bool stillMoreWorkToDo = true; while (stillMoreWorkToDo) { if (!zlibStream.avail_in) { source = device->read(CHUNK_SIZE); if (source.isEmpty()) break; zlibStream.avail_in = source.size(); zlibStream.next_in = reinterpret_cast(source.data()); } do { // Prepare the destination buffer int oldSize = destination.size(); destination.resize(oldSize + CHUNK_SIZE); zlibStream.next_out = reinterpret_cast( destination.data() + oldSize - zlibStream.avail_out); zlibStream.avail_out += CHUNK_SIZE; zlibResult = inflate(&zlibStream, Z_NO_FLUSH); switch (zlibResult) { case Z_NEED_DICT: case Z_DATA_ERROR: case Z_STREAM_ERROR: case Z_MEM_ERROR: { inflateEnd(&zlibStream); qCWarning(lcSvgHandler, "Error while inflating gzip file: %s", (zlibStream.msg != NULL ? zlibStream.msg : "Unknown error")); destination.chop(zlibStream.avail_out); return destination; } } // If the output buffer still has more room after calling inflate // it means we have to provide more data, so exit the loop here } while (!zlibStream.avail_out); if (zlibResult == Z_STREAM_END) { // Make sure there are no more members to process before exiting if (!(zlibStream.avail_in && inflateReset(&zlibStream) == Z_OK)) stillMoreWorkToDo = false; } } // Chop off trailing space in the buffer destination.chop(zlibStream.avail_out); inflateEnd(&zlibStream); return destination; } #endif QSvgTinyDocument * QSvgTinyDocument::load(const QString &fileName) { QFile file(fileName); if (!file.open(QFile::ReadOnly)) { qCWarning(lcSvgHandler, "Cannot open file '%s', because: %s", qPrintable(fileName), qPrintable(file.errorString())); return 0; } #ifndef QT_NO_COMPRESS if (fileName.endsWith(QLatin1String(".svgz"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String(".svg.gz"), Qt::CaseInsensitive)) { return load(qt_inflateGZipDataFrom(&file)); } #endif QSvgTinyDocument *doc = 0; QSvgHandler handler(&file); if (handler.ok()) { doc = handler.document(); doc->m_animationDuration = handler.animationDuration(); } else { qCWarning(lcSvgHandler, "Cannot read file '%s', because: %s (line %d)", qPrintable(fileName), qPrintable(handler.errorString()), handler.lineNumber()); delete handler.document(); } return doc; } QSvgTinyDocument * QSvgTinyDocument::load(const QByteArray &contents) { #ifndef QT_NO_COMPRESS // Check for gzip magic number and inflate if appropriate if (contents.startsWith("\x1f\x8b")) { QBuffer buffer(const_cast(&contents)); return load(qt_inflateGZipDataFrom(&buffer)); } #endif QSvgHandler handler(contents); QSvgTinyDocument *doc = 0; if (handler.ok()) { doc = handler.document(); doc->m_animationDuration = handler.animationDuration(); } else { delete handler.document(); } return doc; } QSvgTinyDocument * QSvgTinyDocument::load(QXmlStreamReader *contents) { QSvgHandler handler(contents); QSvgTinyDocument *doc = 0; if (handler.ok()) { doc = handler.document(); doc->m_animationDuration = handler.animationDuration(); } else { delete handler.document(); } return doc; } void QSvgTinyDocument::draw(QPainter *p, const QRectF &bounds) { if (m_time == 0) m_time = QDateTime::currentMSecsSinceEpoch(); if (displayMode() == QSvgNode::NoneMode) return; p->save(); //sets default style on the painter //### not the most optimal way mapSourceToTarget(p, bounds); QPen pen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin); pen.setMiterLimit(4); p->setPen(pen); p->setBrush(Qt::black); p->setRenderHint(QPainter::Antialiasing); p->setRenderHint(QPainter::SmoothPixmapTransform); QList::iterator itr = m_renderers.begin(); applyStyle(p, m_states); while (itr != m_renderers.end()) { QSvgNode *node = *itr; if ((node->isVisible()) && (node->displayMode() != QSvgNode::NoneMode)) node->draw(p, m_states); ++itr; } revertStyle(p, m_states); p->restore(); } void QSvgTinyDocument::draw(QPainter *p, const QString &id, const QRectF &bounds) { QSvgNode *node = scopeNode(id); if (!node) { qCDebug(lcSvgHandler, "Couldn't find node %s. Skipping rendering.", qPrintable(id)); return; } if (m_time == 0) m_time = QDateTime::currentMSecsSinceEpoch(); if (node->displayMode() == QSvgNode::NoneMode) return; p->save(); const QRectF elementBounds = node->transformedBounds(); mapSourceToTarget(p, bounds, elementBounds); QTransform originalTransform = p->worldTransform(); //XXX set default style on the painter QPen pen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin); pen.setMiterLimit(4); p->setPen(pen); p->setBrush(Qt::black); p->setRenderHint(QPainter::Antialiasing); p->setRenderHint(QPainter::SmoothPixmapTransform); QStack parentApplyStack; QSvgNode *parent = node->parent(); while (parent) { parentApplyStack.push(parent); parent = parent->parent(); } for (int i = parentApplyStack.size() - 1; i >= 0; --i) parentApplyStack[i]->applyStyle(p, m_states); // Reset the world transform so that our parents don't affect // the position QTransform currentTransform = p->worldTransform(); p->setWorldTransform(originalTransform); node->draw(p, m_states); p->setWorldTransform(currentTransform); for (int i = 0; i < parentApplyStack.size(); ++i) parentApplyStack[i]->revertStyle(p, m_states); //p->fillRect(bounds.adjusted(-5, -5, 5, 5), QColor(0, 0, 255, 100)); p->restore(); } QSvgNode::Type QSvgTinyDocument::type() const { return DOC; } void QSvgTinyDocument::setWidth(int len, bool percent) { m_size.setWidth(len); m_widthPercent = percent; } void QSvgTinyDocument::setHeight(int len, bool percent) { m_size.setHeight(len); m_heightPercent = percent; } void QSvgTinyDocument::setViewBox(const QRectF &rect) { m_viewBox = rect; m_implicitViewBox = rect.isNull(); } void QSvgTinyDocument::addSvgFont(QSvgFont *font) { m_fonts.insert(font->familyName(), font); } QSvgFont * QSvgTinyDocument::svgFont(const QString &family) const { return m_fonts[family]; } void QSvgTinyDocument::addNamedNode(const QString &id, QSvgNode *node) { m_namedNodes.insert(id, node); } QSvgNode *QSvgTinyDocument::namedNode(const QString &id) const { return m_namedNodes.value(id); } void QSvgTinyDocument::addNamedStyle(const QString &id, QSvgFillStyleProperty *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 { return m_namedStyles.value(id); } void QSvgTinyDocument::restartAnimation() { m_time = QDateTime::currentMSecsSinceEpoch(); } bool QSvgTinyDocument::animated() const { return m_animated; } void QSvgTinyDocument::setAnimated(bool a) { m_animated = a; } void QSvgTinyDocument::draw(QPainter *p) { draw(p, QRectF()); } void QSvgTinyDocument::draw(QPainter *p, QSvgExtraStates &) { draw(p); } void QSvgTinyDocument::mapSourceToTarget(QPainter *p, const QRectF &targetRect, const QRectF &sourceRect) { QRectF target = targetRect; if (target.isNull()) { QPaintDevice *dev = p->device(); QRectF deviceRect(0, 0, dev->width(), dev->height()); if (deviceRect.isNull()) { if (sourceRect.isNull()) target = QRectF(QPointF(0, 0), size()); else target = QRectF(QPointF(0, 0), sourceRect.size()); } else { target = deviceRect; } } QRectF source = sourceRect; if (source.isNull()) source = viewBox(); if (source != target && !source.isNull()) { if (m_implicitViewBox || !sourceRect.isNull()) { // Code path used when no view box is set, or when an explicit source size is given which // overrides it (which is the case when we're rendering only a specific element by id). 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 // tag that's implicitly defined when is used. // 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()); // Apply the view box translation if specified. p->translate(target.x() - source.x(), target.y() - source.y()); } } } QRectF QSvgTinyDocument::boundsOnElement(const QString &id) const { const QSvgNode *node = scopeNode(id); if (!node) node = this; return node->transformedBounds(); } bool QSvgTinyDocument::elementExists(const QString &id) const { QSvgNode *node = scopeNode(id); return (node!=0); } QMatrix QSvgTinyDocument::matrixForElement(const QString &id) const { QSvgNode *node = scopeNode(id); if (!node) { qCDebug(lcSvgHandler, "Couldn't find node %s. Skipping rendering.", qPrintable(id)); return QMatrix(); } QTransform t; node = node->parent(); while (node) { if (node->m_style.transform) t *= node->m_style.transform->qtransform(); node = node->parent(); } return t.toAffine(); } int QSvgTinyDocument::currentFrame() const { double runningPercentage = qMin(currentElapsed() / double(m_animationDuration), 1.); int totalFrames = m_fps * m_animationDuration; return int(runningPercentage * totalFrames); } void QSvgTinyDocument::setCurrentFrame(int frame) { int totalFrames = m_fps * m_animationDuration; double framePercentage = frame/double(totalFrames); double timeForFrame = m_animationDuration * framePercentage; //in S timeForFrame *= 1000; //in ms int timeToAdd = int(timeForFrame - currentElapsed()); m_time += timeToAdd; } void QSvgTinyDocument::setFramesPerSecond(int num) { m_fps = num; } QT_END_NAMESPACE