diff options
Diffstat (limited to 'src/svg')
-rw-r--r-- | src/svg/qgraphicssvgitem.cpp | 391 | ||||
-rw-r--r-- | src/svg/qgraphicssvgitem.h | 101 | ||||
-rw-r--r-- | src/svg/qsvgfont.cpp | 142 | ||||
-rw-r--r-- | src/svg/qsvgfont_p.h | 103 | ||||
-rw-r--r-- | src/svg/qsvggenerator.cpp | 1072 | ||||
-rw-r--r-- | src/svg/qsvggenerator.h | 112 | ||||
-rw-r--r-- | src/svg/qsvggraphics.cpp | 615 | ||||
-rw-r--r-- | src/svg/qsvggraphics_p.h | 262 | ||||
-rw-r--r-- | src/svg/qsvghandler.cpp | 3923 | ||||
-rw-r--r-- | src/svg/qsvghandler_p.h | 188 | ||||
-rw-r--r-- | src/svg/qsvgnode.cpp | 345 | ||||
-rw-r--r-- | src/svg/qsvgnode_p.h | 206 | ||||
-rw-r--r-- | src/svg/qsvgrenderer.cpp | 501 | ||||
-rw-r--r-- | src/svg/qsvgrenderer.h | 120 | ||||
-rw-r--r-- | src/svg/qsvgstructure.cpp | 383 | ||||
-rw-r--r-- | src/svg/qsvgstructure_p.h | 118 | ||||
-rw-r--r-- | src/svg/qsvgstyle.cpp | 958 | ||||
-rw-r--r-- | src/svg/qsvgstyle_p.h | 826 | ||||
-rw-r--r-- | src/svg/qsvgtinydocument.cpp | 492 | ||||
-rw-r--r-- | src/svg/qsvgtinydocument_p.h | 200 | ||||
-rw-r--r-- | src/svg/qsvgwidget.cpp | 173 | ||||
-rw-r--r-- | src/svg/qsvgwidget.h | 85 | ||||
-rw-r--r-- | src/svg/svg.pro | 45 |
23 files changed, 11361 insertions, 0 deletions
diff --git a/src/svg/qgraphicssvgitem.cpp b/src/svg/qgraphicssvgitem.cpp new file mode 100644 index 0000000..9c3742e --- /dev/null +++ b/src/svg/qgraphicssvgitem.cpp @@ -0,0 +1,391 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSvg module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qgraphicssvgitem.h" + +#ifndef QT_NO_GRAPHICSSVGITEM + +#include "qpainter.h" +#include "qstyleoption.h" +#include "qsvgrenderer.h" +#include "qdebug.h" + +#include "private/qobject_p.h" +#include "private/qgraphicsitem_p.h" + +QT_BEGIN_NAMESPACE + +class QGraphicsSvgItemPrivate : public QGraphicsItemPrivate +{ +public: + Q_DECLARE_PUBLIC(QGraphicsSvgItem) + + QGraphicsSvgItemPrivate() + : renderer(0), shared(false) + { + } + + void init(QGraphicsItem *parent) + { + Q_Q(QGraphicsSvgItem); + q->setParentItem(parent); + renderer = new QSvgRenderer(q); + QObject::connect(renderer, SIGNAL(repaintNeeded()), + q, SLOT(_q_repaintItem())); + q->setCacheMode(QGraphicsItem::DeviceCoordinateCache); + q->setMaximumCacheSize(QSize(1024, 768)); + } + + void _q_repaintItem() + { + q_func()->update(); + } + + inline void updateDefaultSize() + { + QRectF bounds; + if (elemId.isEmpty()) { + bounds = QRectF(QPointF(0, 0), renderer->defaultSize()); + } else { + bounds = renderer->boundsOnElement(elemId); + } + if (boundingRect.size() != bounds.size()) { + q_func()->prepareGeometryChange(); + boundingRect.setSize(bounds.size()); + } + } + + QSvgRenderer *renderer; + QRectF boundingRect; + bool shared; + QString elemId; +}; + +/*! + \class QGraphicsSvgItem + \ingroup graphicsview-api + \brief The QGraphicsSvgItem class is a QGraphicsItem that can be used to render + the contents of SVG files. + + \since 4.2 + + QGraphicsSvgItem provides a way of rendering SVG files onto QGraphicsView. + QGraphicsSvgItem can be created by passing the SVG file to be rendered to + its constructor or by explicit setting a shared QSvgRenderer on it. + + Note that setting QSvgRenderer on a QGraphicsSvgItem doesn't make the item take + ownership of the renderer, therefore if using setSharedRenderer() method one has + to make sure that the lifetime of the QSvgRenderer object will be at least as long + as that of the QGraphicsSvgItem. + + QGraphicsSvgItem provides a way of rendering only parts of the SVG files via + the setElementId. If setElementId() method is called, only the SVG element + (and its children) with the passed id will be renderer. This provides a convenient + way of selectively rendering large SVG files that contain a number of discrete + elements. For example the following code renders only jokers from a SVG file + containing a whole card deck: + + \snippet doc/src/snippets/code/src_svg_qgraphicssvgitem.cpp 0 + + Size of the item can be set via the \l{QRectF::setSize()} + {setSize()} method of the \l{QGraphicsSvgItem::boundingRect()} + {bounding rectangle} or via direct manipulation of the items + transformation matrix. + + By default the SVG rendering is cached using QGraphicsItem::DeviceCoordinateCache + mode to speedup the display of items. Caching can be disabled by passing + QGraphicsItem::NoCache to the QGraphicsItem::setCacheMode() method. + + \sa QSvgWidget, {QtSvg Module}, QGraphicsItem, QGraphicsView +*/ + +/*! + Constructs a new SVG item with the given \a parent. +*/ +QGraphicsSvgItem::QGraphicsSvgItem(QGraphicsItem *parent) + : QGraphicsObject(*new QGraphicsSvgItemPrivate(), 0, 0) +{ + Q_D(QGraphicsSvgItem); + d->init(parent); +} + +/*! + Constructs a new item with the given \a parent and loads the contents of the + SVG file with the specified \a fileName. +*/ +QGraphicsSvgItem::QGraphicsSvgItem(const QString &fileName, QGraphicsItem *parent) + : QGraphicsObject(*new QGraphicsSvgItemPrivate(), 0, 0) +{ + Q_D(QGraphicsSvgItem); + d->init(parent); + d->renderer->load(fileName); + d->updateDefaultSize(); +} + +/*! + Returns the currently use QSvgRenderer. +*/ +QSvgRenderer *QGraphicsSvgItem::renderer() const +{ + return d_func()->renderer; +} + + +/*! + Returns the bounding rectangle of this item. +*/ +QRectF QGraphicsSvgItem::boundingRect() const +{ + Q_D(const QGraphicsSvgItem); + return d->boundingRect; +} + +/*! + \internal + + Highlights \a item as selected. + + NOTE: This function is a duplicate of qt_graphicsItem_highlightSelected() in qgraphicsitem.cpp! +*/ +static void qt_graphicsItem_highlightSelected( + QGraphicsItem *item, QPainter *painter, const QStyleOptionGraphicsItem *option) +{ + const QRectF murect = painter->transform().mapRect(QRectF(0, 0, 1, 1)); + if (qFuzzyIsNull(qMax(murect.width(), murect.height()))) + return; + + const QRectF mbrect = painter->transform().mapRect(item->boundingRect()); + if (qMin(mbrect.width(), mbrect.height()) < qreal(1.0)) + return; + + qreal itemPenWidth; + switch (item->type()) { + case QGraphicsEllipseItem::Type: + itemPenWidth = static_cast<QGraphicsEllipseItem *>(item)->pen().widthF(); + break; + case QGraphicsPathItem::Type: + itemPenWidth = static_cast<QGraphicsPathItem *>(item)->pen().widthF(); + break; + case QGraphicsPolygonItem::Type: + itemPenWidth = static_cast<QGraphicsPolygonItem *>(item)->pen().widthF(); + break; + case QGraphicsRectItem::Type: + itemPenWidth = static_cast<QGraphicsRectItem *>(item)->pen().widthF(); + break; + case QGraphicsSimpleTextItem::Type: + itemPenWidth = static_cast<QGraphicsSimpleTextItem *>(item)->pen().widthF(); + break; + case QGraphicsLineItem::Type: + itemPenWidth = static_cast<QGraphicsLineItem *>(item)->pen().widthF(); + break; + default: + itemPenWidth = 1.0; + } + const qreal pad = itemPenWidth / 2; + + const qreal penWidth = 0; // cosmetic pen + + const QColor fgcolor = option->palette.windowText().color(); + const QColor bgcolor( // ensure good contrast against fgcolor + fgcolor.red() > 127 ? 0 : 255, + fgcolor.green() > 127 ? 0 : 255, + fgcolor.blue() > 127 ? 0 : 255); + + painter->setPen(QPen(bgcolor, penWidth, Qt::SolidLine)); + painter->setBrush(Qt::NoBrush); + painter->drawRect(item->boundingRect().adjusted(pad, pad, -pad, -pad)); + + painter->setPen(QPen(option->palette.windowText(), 0, Qt::DashLine)); + painter->setBrush(Qt::NoBrush); + painter->drawRect(item->boundingRect().adjusted(pad, pad, -pad, -pad)); +} + +/*! + \reimp +*/ +void QGraphicsSvgItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ +// Q_UNUSED(option); + Q_UNUSED(widget); + + Q_D(QGraphicsSvgItem); + if (!d->renderer->isValid()) + return; + + if (d->elemId.isEmpty()) + d->renderer->render(painter, d->boundingRect); + else + d->renderer->render(painter, d->elemId, d->boundingRect); + + if (option->state & QStyle::State_Selected) + qt_graphicsItem_highlightSelected(this, painter, option); +} + +/*! + \reimp +*/ +int QGraphicsSvgItem::type() const +{ + return Type; +} + +/*! + \property QGraphicsSvgItem::maximumCacheSize + \since 4.6 + + This property holds the maximum size of the device coordinate cache + for this item. + */ + +/*! + Sets the maximum device coordinate cache size of the item to \a size. + If the item is cached using QGraphicsItem::DeviceCoordinateCache mode, + caching is bypassed if the extension of the item in device coordinates + is larger than \a size. + + The cache corresponds to the QPixmap which is used to cache the + results of the rendering. + Use QPixmapCache::setCacheLimit() to set limitations on the whole cache + and use setMaximumCacheSize() when setting cache size for individual + items. + + \sa QGraphicsItem::cacheMode() +*/ +void QGraphicsSvgItem::setMaximumCacheSize(const QSize &size) +{ + QGraphicsItem::d_ptr->setExtra(QGraphicsItemPrivate::ExtraMaxDeviceCoordCacheSize, size); + update(); +} + +/*! + Returns the current maximum size of the device coordinate cache for this item. + If the item is cached using QGraphicsItem::DeviceCoordinateCache mode, + caching is bypassed if the extension of the item in device coordinates + is larger than the maximum size. + + The default maximum cache size is 1024x768. + QPixmapCache::cacheLimit() gives the + cumulative bounds of the whole cache, whereas maximumCacheSize() refers + to a maximum cache size for this particular item. + + \sa QGraphicsItem::cacheMode() +*/ +QSize QGraphicsSvgItem::maximumCacheSize() const +{ + return QGraphicsItem::d_ptr->extra(QGraphicsItemPrivate::ExtraMaxDeviceCoordCacheSize).toSize(); +} + +/*! + \property QGraphicsSvgItem::elementId + \since 4.6 + + This property holds the element's XML ID. + */ + +/*! + Sets the XML ID of the element to \a id. +*/ +void QGraphicsSvgItem::setElementId(const QString &id) +{ + Q_D(QGraphicsSvgItem); + d->elemId = id; + d->updateDefaultSize(); + update(); +} + +/*! + Returns the XML ID the element that is currently + being rendered. Returns an empty string if the whole + file is being rendered. +*/ +QString QGraphicsSvgItem::elementId() const +{ + Q_D(const QGraphicsSvgItem); + return d->elemId; +} + +/*! + Sets \a renderer to be a shared QSvgRenderer on the item. By + using this method one can share the same QSvgRenderer on a number + of items. This means that the SVG file will be parsed only once. + QSvgRenderer passed to this method has to exist for as long as + this item is used. +*/ +void QGraphicsSvgItem::setSharedRenderer(QSvgRenderer *renderer) +{ + Q_D(QGraphicsSvgItem); + if (!d->shared) + delete d->renderer; + + d->renderer = renderer; + d->shared = true; + + d->updateDefaultSize(); + + update(); +} + +/*! + \obsolete + + Use QGraphicsItem::setCacheMode() instead. Passing true to this function is equivalent + to QGraphicsItem::setCacheMode(QGraphicsItem::DeviceCoordinateCache). +*/ +void QGraphicsSvgItem::setCachingEnabled(bool caching) +{ + setCacheMode(caching ? QGraphicsItem::DeviceCoordinateCache : QGraphicsItem::NoCache); +} + +/*! + \obsolete + + Use QGraphicsItem::cacheMode() instead. +*/ +bool QGraphicsSvgItem::isCachingEnabled() const +{ + return cacheMode() != QGraphicsItem::NoCache; +} + +QT_END_NAMESPACE + +#include "moc_qgraphicssvgitem.cpp" + +#endif // QT_NO_GRAPHICSSVGITEM diff --git a/src/svg/qgraphicssvgitem.h b/src/svg/qgraphicssvgitem.h new file mode 100644 index 0000000..6577db4 --- /dev/null +++ b/src/svg/qgraphicssvgitem.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSvg module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QGRAPHICSSVGITEM_H +#define QGRAPHICSSVGITEM_H + +#include <QtGui/qgraphicsitem.h> + +#ifndef QT_NO_GRAPHICSSVGITEM + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Svg) + +class QSvgRenderer; +class QGraphicsSvgItemPrivate; + +class Q_SVG_EXPORT QGraphicsSvgItem : public QGraphicsObject +{ + Q_OBJECT + Q_INTERFACES(QGraphicsItem) + Q_PROPERTY(QString elementId READ elementId WRITE setElementId) + Q_PROPERTY(QSize maximumCacheSize READ maximumCacheSize WRITE setMaximumCacheSize) + +public: + QGraphicsSvgItem(QGraphicsItem *parentItem=0); + QGraphicsSvgItem(const QString &fileName, QGraphicsItem *parentItem=0); + + void setSharedRenderer(QSvgRenderer *renderer); + QSvgRenderer *renderer() const; + + void setElementId(const QString &id); + QString elementId() const; + + void setCachingEnabled(bool); + bool isCachingEnabled() const; + + void setMaximumCacheSize(const QSize &size); + QSize maximumCacheSize() const; + + virtual QRectF boundingRect() const; + + virtual void paint(QPainter *painter, + const QStyleOptionGraphicsItem *option, + QWidget *widget=0); + + enum { Type = 13 }; + virtual int type() const; + +private: + Q_DISABLE_COPY(QGraphicsSvgItem) + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QGraphicsSvgItem) + + Q_PRIVATE_SLOT(d_func(), void _q_repaintItem()) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_GRAPHICSSVGITEM +#endif // QGRAPHICSSVGITEM_H diff --git a/src/svg/qsvgfont.cpp b/src/svg/qsvgfont.cpp new file mode 100644 index 0000000..66950ff --- /dev/null +++ b/src/svg/qsvgfont.cpp @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSvg module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsvgfont_p.h" + +#ifndef QT_NO_SVG + +#include "qpainter.h" +#include "qpen.h" +#include "qdebug.h" +#include "qpicture.h" + +QT_BEGIN_NAMESPACE + +QSvgGlyph::QSvgGlyph(QChar unicode, const QPainterPath &path, qreal horizAdvX) + : m_unicode(unicode), m_path(path), m_horizAdvX(horizAdvX) +{ + +} + + +QSvgFont::QSvgFont(qreal horizAdvX) + : m_horizAdvX(horizAdvX) +{ +} + + +QString QSvgFont::familyName() const +{ + return m_familyName; +} + + +void QSvgFont::addGlyph(QChar unicode, const QPainterPath &path, qreal horizAdvX ) +{ + m_glyphs.insert(unicode, QSvgGlyph(unicode, path, + (horizAdvX==-1)?m_horizAdvX:horizAdvX)); +} + + +void QSvgFont::draw(QPainter *p, const QPointF &point, const QString &str, qreal pixelSize, Qt::Alignment alignment) const +{ + p->save(); + p->translate(point); + p->scale(pixelSize / m_unitsPerEm, -pixelSize / m_unitsPerEm); + + // Calculate the text width to be used for alignment + int textWidth = 0; + QString::const_iterator itr = str.constBegin(); + for ( ; itr != str.constEnd(); ++itr) { + QChar unicode = *itr; + if (!m_glyphs.contains(*itr)) { + unicode = 0; + if (!m_glyphs.contains(unicode)) + continue; + } + textWidth += static_cast<int>(m_glyphs[unicode].m_horizAdvX); + } + + QPoint alignmentOffset(0, 0); + if (alignment == Qt::AlignHCenter) { + alignmentOffset.setX(-textWidth / 2); + } else if (alignment == Qt::AlignRight) { + alignmentOffset.setX(-textWidth); + } + + p->translate(alignmentOffset); + + // since in SVG the embedded font ain't really a path + // the outline has got to stay untransformed... + qreal penWidth = p->pen().widthF(); + penWidth /= (pixelSize/m_unitsPerEm); + QPen pen = p->pen(); + pen.setWidthF(penWidth); + p->setPen(pen); + + itr = str.constBegin(); + for ( ; itr != str.constEnd(); ++itr) { + QChar unicode = *itr; + if (!m_glyphs.contains(*itr)) { + unicode = 0; + if (!m_glyphs.contains(unicode)) + continue; + } + p->drawPath(m_glyphs[unicode].m_path); + p->translate(m_glyphs[unicode].m_horizAdvX, 0); + } + + p->restore(); +} + +void QSvgFont::setFamilyName(const QString &name) +{ + m_familyName = name; +} + +void QSvgFont::setUnitsPerEm(qreal upem) +{ + m_unitsPerEm = upem; +} + +QT_END_NAMESPACE + +#endif // QT_NO_SVG diff --git a/src/svg/qsvgfont_p.h b/src/svg/qsvgfont_p.h new file mode 100644 index 0000000..df82ee5 --- /dev/null +++ b/src/svg/qsvgfont_p.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSvg module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSVGFONT_P_H +#define QSVGFONT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qpainterpath.h" + +#ifndef QT_NO_SVG + +#include "qhash.h" +#include "qstring.h" +#include "qsvgstyle_p.h" + +QT_BEGIN_NAMESPACE + +class QSvgGlyph +{ +public: + QSvgGlyph(QChar unicode, const QPainterPath &path, qreal horizAdvX); + QSvgGlyph() : m_unicode(0), m_horizAdvX(0) {} + + QChar m_unicode; + QPainterPath m_path; + qreal m_horizAdvX; +}; + + +class QSvgFont : public QSvgRefCounted +{ +public: + QSvgFont(qreal horizAdvX); + + void setFamilyName(const QString &name); + QString familyName() const; + + void setUnitsPerEm(qreal upem); + + void addGlyph(QChar unicode, const QPainterPath &path, qreal horizAdvX = -1); + + void draw(QPainter *p, const QPointF &point, const QString &str, qreal pixelSize, Qt::Alignment alignment) const; +public: + QString m_familyName; + qreal m_unitsPerEm; + qreal m_ascent; + qreal m_descent; + qreal m_horizAdvX; + QHash<QChar, QSvgGlyph> m_glyphs; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_SVG +#endif // QSVGFONT_P_H diff --git a/src/svg/qsvggenerator.cpp b/src/svg/qsvggenerator.cpp new file mode 100644 index 0000000..d0d73ae --- /dev/null +++ b/src/svg/qsvggenerator.cpp @@ -0,0 +1,1072 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSvg module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsvggenerator.h" + +#ifndef QT_NO_SVGGENERATOR + +#include "qpainterpath.h" + +#include "private/qpaintengine_p.h" +#include "private/qtextengine_p.h" +#include "private/qdrawhelper_p.h" + +#include "qfile.h" +#include "qtextcodec.h" +#include "qtextstream.h" +#include "qbuffer.h" +#include "qmath.h" + +#include "qdebug.h" + +QT_BEGIN_NAMESPACE + +static void translate_color(const QColor &color, QString *color_string, + QString *opacity_string) +{ + Q_ASSERT(color_string); + Q_ASSERT(opacity_string); + + *color_string = + QString::fromLatin1("#%1%2%3") + .arg(color.red(), 2, 16, QLatin1Char('0')) + .arg(color.green(), 2, 16, QLatin1Char('0')) + .arg(color.blue(), 2, 16, QLatin1Char('0')); + *opacity_string = QString::number(color.alphaF()); +} + +static void translate_dashPattern(QVector<qreal> pattern, const qreal& width, QString *pattern_string) +{ + Q_ASSERT(pattern_string); + + // Note that SVG operates in absolute lengths, whereas Qt uses a length/width ratio. + foreach (qreal entry, pattern) + *pattern_string += QString::fromLatin1("%1,").arg(entry * width); + + pattern_string->chop(1); +} + +class QSvgPaintEnginePrivate : public QPaintEnginePrivate +{ +public: + QSvgPaintEnginePrivate() + { + size = QSize(); + viewBox = QRectF(); + outputDevice = 0; + resolution = 72; + + attributes.document_title = QLatin1String("Qt Svg Document"); + attributes.document_description = QLatin1String("Generated with Qt"); + attributes.font_family = QLatin1String("serif"); + attributes.font_size = QLatin1String("10pt"); + attributes.font_style = QLatin1String("normal"); + attributes.font_weight = QLatin1String("normal"); + + afterFirstUpdate = false; + numGradients = 0; + } + + QSize size; + QRectF viewBox; + QIODevice *outputDevice; + QTextStream *stream; + int resolution; + + QString header; + QString defs; + QString body; + bool afterFirstUpdate; + + QBrush brush; + QPen pen; + QMatrix matrix; + QFont font; + + QString generateGradientName() { + ++numGradients; + currentGradientName = QString::fromLatin1("gradient%1").arg(numGradients); + return currentGradientName; + } + + QString currentGradientName; + int numGradients; + + struct _attributes { + QString document_title; + QString document_description; + QString font_weight; + QString font_size; + QString font_family; + QString font_style; + QString stroke, strokeOpacity; + QString dashPattern, dashOffset; + QString fill, fillOpacity; + } attributes; +}; + +static inline QPaintEngine::PaintEngineFeatures svgEngineFeatures() +{ + return QPaintEngine::PaintEngineFeatures( + QPaintEngine::AllFeatures + & ~QPaintEngine::PatternBrush + & ~QPaintEngine::PerspectiveTransform + & ~QPaintEngine::ConicalGradientFill + & ~QPaintEngine::PorterDuff); +} + +class QSvgPaintEngine : public QPaintEngine +{ + Q_DECLARE_PRIVATE(QSvgPaintEngine) +public: + + QSvgPaintEngine() + : QPaintEngine(*new QSvgPaintEnginePrivate, + svgEngineFeatures()) + { + } + + bool begin(QPaintDevice *device); + bool end(); + + void updateState(const QPaintEngineState &state); + void popGroup(); + + void drawPath(const QPainterPath &path); + void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr); + void drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode); + void drawTextItem(const QPointF &pt, const QTextItem &item); + void drawImage(const QRectF &r, const QImage &pm, const QRectF &sr, + Qt::ImageConversionFlag = Qt::AutoColor); + + QPaintEngine::Type type() const { return QPaintEngine::SVG; } + + QSize size() const { return d_func()->size; } + void setSize(const QSize &size) { + Q_ASSERT(!isActive()); + d_func()->size = size; + } + + QRectF viewBox() const { return d_func()->viewBox; } + void setViewBox(const QRectF &viewBox) { + Q_ASSERT(!isActive()); + d_func()->viewBox = viewBox; + } + + QString documentTitle() const { return d_func()->attributes.document_title; } + void setDocumentTitle(const QString &title) { + d_func()->attributes.document_title = title; + } + + QString documentDescription() const { return d_func()->attributes.document_description; } + void setDocumentDescription(const QString &description) { + d_func()->attributes.document_description = description; + } + + QIODevice *outputDevice() const { return d_func()->outputDevice; } + void setOutputDevice(QIODevice *device) { + Q_ASSERT(!isActive()); + d_func()->outputDevice = device; + } + + int resolution() { return d_func()->resolution; } + void setResolution(int resolution) { + Q_ASSERT(!isActive()); + d_func()->resolution = resolution; + } + void saveLinearGradientBrush(const QGradient *g) + { + QTextStream str(&d_func()->defs, QIODevice::Append); + const QLinearGradient *grad = static_cast<const QLinearGradient*>(g); + str << QLatin1String("<linearGradient "); + saveGradientUnits(str, g); + if (grad) { + str << QLatin1String("x1=\"") <<grad->start().x()<< QLatin1String("\" ") + << QLatin1String("y1=\"") <<grad->start().y()<< QLatin1String("\" ") + << QLatin1String("x2=\"") <<grad->finalStop().x() << QLatin1String("\" ") + << QLatin1String("y2=\"") <<grad->finalStop().y() << QLatin1String("\" "); + } + + str << QLatin1String("id=\"") << d_func()->generateGradientName() << QLatin1String("\">\n"); + saveGradientStops(str, g); + str << QLatin1String("</linearGradient>") <<endl; + } + void saveRadialGradientBrush(const QGradient *g) + { + QTextStream str(&d_func()->defs, QIODevice::Append); + const QRadialGradient *grad = static_cast<const QRadialGradient*>(g); + str << QLatin1String("<radialGradient "); + saveGradientUnits(str, g); + if (grad) { + str << QLatin1String("cx=\"") <<grad->center().x()<< QLatin1String("\" ") + << QLatin1String("cy=\"") <<grad->center().y()<< QLatin1String("\" ") + << QLatin1String("r=\"") <<grad->radius() << QLatin1String("\" ") + << QLatin1String("fx=\"") <<grad->focalPoint().x() << QLatin1String("\" ") + << QLatin1String("fy=\"") <<grad->focalPoint().y() << QLatin1String("\" "); + } + str << QLatin1String("xml:id=\"") <<d_func()->generateGradientName()<< QLatin1String("\">\n"); + saveGradientStops(str, g); + str << QLatin1String("</radialGradient>") << endl; + } + void saveConicalGradientBrush(const QGradient *) + { + qWarning("svg's don't support conical gradients!"); + } + + void saveGradientStops(QTextStream &str, const QGradient *g) { + QGradientStops stops = g->stops(); + + if (g->interpolationMode() == QGradient::ColorInterpolation) { + bool constantAlpha = true; + int alpha = stops.at(0).second.alpha(); + for (int i = 1; i < stops.size(); ++i) + constantAlpha &= (stops.at(i).second.alpha() == alpha); + + if (!constantAlpha) { + const qreal spacing = qreal(0.02); + QGradientStops newStops; + QRgb fromColor = PREMUL(stops.at(0).second.rgba()); + QRgb toColor; + for (int i = 0; i + 1 < stops.size(); ++i) { + int parts = qCeil((stops.at(i + 1).first - stops.at(i).first) / spacing); + newStops.append(stops.at(i)); + toColor = PREMUL(stops.at(i + 1).second.rgba()); + + if (parts > 1) { + qreal step = (stops.at(i + 1).first - stops.at(i).first) / parts; + for (int j = 1; j < parts; ++j) { + QRgb color = INV_PREMUL(INTERPOLATE_PIXEL_256(fromColor, 256 - 256 * j / parts, toColor, 256 * j / parts)); + newStops.append(QGradientStop(stops.at(i).first + j * step, QColor::fromRgba(color))); + } + } + fromColor = toColor; + } + newStops.append(stops.back()); + stops = newStops; + } + } + + foreach(QGradientStop stop, stops) { + QString color = + QString::fromLatin1("#%1%2%3") + .arg(stop.second.red(), 2, 16, QLatin1Char('0')) + .arg(stop.second.green(), 2, 16, QLatin1Char('0')) + .arg(stop.second.blue(), 2, 16, QLatin1Char('0')); + str << QLatin1String(" <stop offset=\"")<< stop.first << QLatin1String("\" ") + << QLatin1String("stop-color=\"") << color << QLatin1String("\" ") + << QLatin1String("stop-opacity=\"") << stop.second.alphaF() <<QLatin1String("\" />\n"); + } + } + + void saveGradientUnits(QTextStream &str, const QGradient *gradient) + { + str << QLatin1String("gradientUnits=\""); + if (gradient && gradient->coordinateMode() == QGradient::ObjectBoundingMode) + str << QLatin1String("objectBoundingBox"); + else + str << QLatin1String("userSpaceOnUse"); + str << QLatin1String("\" "); + } + + void generateQtDefaults() + { + *d_func()->stream << QLatin1String("fill=\"none\" "); + *d_func()->stream << QLatin1String("stroke=\"black\" "); + *d_func()->stream << QLatin1String("stroke-width=\"1\" "); + *d_func()->stream << QLatin1String("fill-rule=\"evenodd\" "); + *d_func()->stream << QLatin1String("stroke-linecap=\"square\" "); + *d_func()->stream << QLatin1String("stroke-linejoin=\"bevel\" "); + *d_func()->stream << QLatin1String(">\n"); + } + inline QTextStream &stream() + { + return *d_func()->stream; + } + + + void qpenToSvg(const QPen &spen) + { + QString width; + + d_func()->pen = spen; + + switch (spen.style()) { + case Qt::NoPen: + stream() << QLatin1String("stroke=\"none\" "); + + d_func()->attributes.stroke = QLatin1String("none"); + d_func()->attributes.strokeOpacity = QString(); + return; + break; + case Qt::SolidLine: { + QString color, colorOpacity; + + translate_color(spen.color(), &color, + &colorOpacity); + d_func()->attributes.stroke = color; + d_func()->attributes.strokeOpacity = colorOpacity; + + stream() << QLatin1String("stroke=\"")<<color<< QLatin1String("\" "); + stream() << QLatin1String("stroke-opacity=\"")<<colorOpacity<< QLatin1String("\" "); + } + break; + case Qt::DashLine: + case Qt::DotLine: + case Qt::DashDotLine: + case Qt::DashDotDotLine: + case Qt::CustomDashLine: { + QString color, colorOpacity, dashPattern, dashOffset; + + qreal penWidth = spen.width() == 0 ? qreal(1) : spen.widthF(); + + translate_color(spen.color(), &color, &colorOpacity); + translate_dashPattern(spen.dashPattern(), penWidth, &dashPattern); + + // SVG uses absolute offset + dashOffset = QString::number(spen.dashOffset() * penWidth); + + d_func()->attributes.stroke = color; + d_func()->attributes.strokeOpacity = colorOpacity; + d_func()->attributes.dashPattern = dashPattern; + d_func()->attributes.dashOffset = dashOffset; + + stream() << QLatin1String("stroke=\"")<<color<< QLatin1String("\" "); + stream() << QLatin1String("stroke-opacity=\"")<<colorOpacity<< QLatin1String("\" "); + stream() << QLatin1String("stroke-dasharray=\"")<<dashPattern<< QLatin1String("\" "); + stream() << QLatin1String("stroke-dashoffset=\"")<<dashOffset<< QLatin1String("\" "); + break; + } + default: + qWarning("Unsupported pen style"); + break; + } + + if (spen.widthF() == 0) + stream() <<"stroke-width=\"1\" "; + else + stream() <<"stroke-width=\"" << spen.widthF() << "\" "; + + switch (spen.capStyle()) { + case Qt::FlatCap: + stream() << "stroke-linecap=\"butt\" "; + break; + case Qt::SquareCap: + stream() << "stroke-linecap=\"square\" "; + break; + case Qt::RoundCap: + stream() << "stroke-linecap=\"round\" "; + break; + default: + qWarning("Unhandled cap style"); + } + switch (spen.joinStyle()) { + case Qt::MiterJoin: + stream() << "stroke-linejoin=\"miter\" " + "stroke-miterlimit=\""<<spen.miterLimit()<<"\" "; + break; + case Qt::BevelJoin: + stream() << "stroke-linejoin=\"bevel\" "; + break; + case Qt::RoundJoin: + stream() << "stroke-linejoin=\"round\" "; + break; + case Qt::SvgMiterJoin: + stream() << "stroke-linejoin=\"miter\" " + "stroke-miterlimit=\""<<spen.miterLimit()<<"\" "; + break; + default: + qWarning("Unhandled join style"); + } + } + void qbrushToSvg(const QBrush &sbrush) + { + d_func()->brush = sbrush; + switch (sbrush.style()) { + case Qt::SolidPattern: { + QString color, colorOpacity; + translate_color(sbrush.color(), &color, &colorOpacity); + stream() << "fill=\"" << color << "\" " + "fill-opacity=\"" + << colorOpacity << "\" "; + d_func()->attributes.fill = color; + d_func()->attributes.fillOpacity = colorOpacity; + } + break; + case Qt::LinearGradientPattern: + saveLinearGradientBrush(sbrush.gradient()); + d_func()->attributes.fill = QString::fromLatin1("url(#%1)").arg(d_func()->currentGradientName); + d_func()->attributes.fillOpacity = QString(); + stream() << QLatin1String("fill=\"url(#") << d_func()->currentGradientName << QLatin1String(")\" "); + break; + case Qt::RadialGradientPattern: + saveRadialGradientBrush(sbrush.gradient()); + d_func()->attributes.fill = QString::fromLatin1("url(#%1)").arg(d_func()->currentGradientName); + d_func()->attributes.fillOpacity = QString(); + stream() << QLatin1String("fill=\"url(#") << d_func()->currentGradientName << QLatin1String(")\" "); + break; + case Qt::ConicalGradientPattern: + saveConicalGradientBrush(sbrush.gradient()); + d_func()->attributes.fill = QString::fromLatin1("url(#%1)").arg(d_func()->currentGradientName); + d_func()->attributes.fillOpacity = QString(); + stream() << QLatin1String("fill=\"url(#") << d_func()->currentGradientName << QLatin1String(")\" "); + break; + case Qt::NoBrush: + stream() << QLatin1String("fill=\"none\" "); + d_func()->attributes.fill = QLatin1String("none"); + d_func()->attributes.fillOpacity = QString(); + return; + break; + default: + break; + } + } + void qfontToSvg(const QFont &sfont) + { + Q_D(QSvgPaintEngine); + + d->font = sfont; + + if (d->font.pixelSize() == -1) + d->attributes.font_size = QString::number(d->font.pointSizeF() * d->resolution / 72); + else + d->attributes.font_size = QString::number(d->font.pixelSize()); + + int svgWeight = d->font.weight(); + switch (svgWeight) { + case QFont::Light: + svgWeight = 100; + break; + case QFont::Normal: + svgWeight = 400; + break; + case QFont::Bold: + svgWeight = 700; + break; + default: + svgWeight *= 10; + } + + d->attributes.font_weight = QString::number(svgWeight); + d->attributes.font_family = d->font.family(); + d->attributes.font_style = d->font.italic() ? QLatin1String("italic") : QLatin1String("normal"); + + *d->stream << "font-family=\"" << d->attributes.font_family << "\" " + "font-size=\"" << d->attributes.font_size << "\" " + "font-weight=\"" << d->attributes.font_weight << "\" " + "font-style=\"" << d->attributes.font_style << "\" " + << endl; + } +}; + +class QSvgGeneratorPrivate +{ +public: + QSvgPaintEngine *engine; + + uint owns_iodevice : 1; + QString fileName; +}; + +/*! + \class QSvgGenerator + \ingroup painting + \since 4.3 + \brief The QSvgGenerator class provides a paint device that is used to create SVG drawings. + \reentrant + + This paint device represents a Scalable Vector Graphics (SVG) drawing. Like QPrinter, it is + designed as a write-only device that generates output in a specific format. + + To write an SVG file, you first need to configure the output by setting the \l fileName + or \l outputDevice properties. It is usually necessary to specify the size of the drawing + by setting the \l size property, and in some cases where the drawing will be included in + another, the \l viewBox property also needs to be set. + + \snippet examples/painting/svggenerator/window.cpp configure SVG generator + + Other meta-data can be specified by setting the \a title, \a description and \a resolution + properties. + + As with other QPaintDevice subclasses, a QPainter object is used to paint onto an instance + of this class: + + \snippet examples/painting/svggenerator/window.cpp begin painting + \dots + \snippet examples/painting/svggenerator/window.cpp end painting + + Painting is performed in the same way as for any other paint device. However, + it is necessary to use the QPainter::begin() and \l{QPainter::}{end()} to + explicitly begin and end painting on the device. + + The \l{SVG Generator Example} shows how the same painting commands can be used + for painting a widget and writing an SVG file. + + \sa QSvgRenderer, QSvgWidget, {About SVG} +*/ + +/*! + Constructs a new generator. +*/ +QSvgGenerator::QSvgGenerator() + : d_ptr(new QSvgGeneratorPrivate) +{ + Q_D(QSvgGenerator); + + d->engine = new QSvgPaintEngine; + d->owns_iodevice = false; +} + +/*! + Destroys the generator. +*/ +QSvgGenerator::~QSvgGenerator() +{ + Q_D(QSvgGenerator); + if (d->owns_iodevice) + delete d->engine->outputDevice(); + delete d->engine; +} + +/*! + \property QSvgGenerator::title + \brief the title of the generated SVG drawing + \since 4.5 + \sa description +*/ +QString QSvgGenerator::title() const +{ + Q_D(const QSvgGenerator); + + return d->engine->documentTitle(); +} + +void QSvgGenerator::setTitle(const QString &title) +{ + Q_D(QSvgGenerator); + + d->engine->setDocumentTitle(title); +} + +/*! + \property QSvgGenerator::description + \brief the description of the generated SVG drawing + \since 4.5 + \sa title +*/ +QString QSvgGenerator::description() const +{ + Q_D(const QSvgGenerator); + + return d->engine->documentDescription(); +} + +void QSvgGenerator::setDescription(const QString &description) +{ + Q_D(QSvgGenerator); + + d->engine->setDocumentDescription(description); +} + +/*! + \property QSvgGenerator::size + \brief the size of the generated SVG drawing + \since 4.5 + + By default this property is set to \c{QSize(-1, -1)}, which + indicates that the generator should not output the width and + height attributes of the \c<svg> element. + + \note It is not possible to change this property while a + QPainter is active on the generator. + + \sa viewBox, resolution +*/ +QSize QSvgGenerator::size() const +{ + Q_D(const QSvgGenerator); + return d->engine->size(); +} + +void QSvgGenerator::setSize(const QSize &size) +{ + Q_D(QSvgGenerator); + if (d->engine->isActive()) { + qWarning("QSvgGenerator::setSize(), cannot set size while SVG is being generated"); + return; + } + d->engine->setSize(size); +} + +/*! + \property QSvgGenerator::viewBox + \brief the viewBox of the generated SVG drawing + \since 4.5 + + By default this property is set to \c{QRect(0, 0, -1, -1)}, which + indicates that the generator should not output the viewBox attribute + of the \c<svg> element. + + \note It is not possible to change this property while a + QPainter is active on the generator. + + \sa viewBox(), size, resolution +*/ +QRectF QSvgGenerator::viewBoxF() const +{ + Q_D(const QSvgGenerator); + return d->engine->viewBox(); +} + +/*! + \since 4.5 + + Returns viewBoxF().toRect(). + + \sa viewBoxF() +*/ +QRect QSvgGenerator::viewBox() const +{ + Q_D(const QSvgGenerator); + return d->engine->viewBox().toRect(); +} + +void QSvgGenerator::setViewBox(const QRectF &viewBox) +{ + Q_D(QSvgGenerator); + if (d->engine->isActive()) { + qWarning("QSvgGenerator::setViewBox(), cannot set viewBox while SVG is being generated"); + return; + } + d->engine->setViewBox(viewBox); +} + +void QSvgGenerator::setViewBox(const QRect &viewBox) +{ + setViewBox(QRectF(viewBox)); +} + +/*! + \property QSvgGenerator::fileName + \brief the target filename for the generated SVG drawing + \since 4.5 + + \sa outputDevice +*/ +QString QSvgGenerator::fileName() const +{ + Q_D(const QSvgGenerator); + return d->fileName; +} + +void QSvgGenerator::setFileName(const QString &fileName) +{ + Q_D(QSvgGenerator); + if (d->engine->isActive()) { + qWarning("QSvgGenerator::setFileName(), cannot set file name while SVG is being generated"); + return; + } + + if (d->owns_iodevice) + delete d->engine->outputDevice(); + + d->owns_iodevice = true; + + d->fileName = fileName; + QFile *file = new QFile(fileName); + d->engine->setOutputDevice(file); +} + +/*! + \property QSvgGenerator::outputDevice + \brief the output device for the generated SVG drawing + \since 4.5 + + If both output device and file name are specified, the output device + will have precedence. + + \sa fileName +*/ +QIODevice *QSvgGenerator::outputDevice() const +{ + Q_D(const QSvgGenerator); + return d->engine->outputDevice(); +} + +void QSvgGenerator::setOutputDevice(QIODevice *outputDevice) +{ + Q_D(QSvgGenerator); + if (d->engine->isActive()) { + qWarning("QSvgGenerator::setOutputDevice(), cannot set output device while SVG is being generated"); + return; + } + d->owns_iodevice = false; + d->engine->setOutputDevice(outputDevice); + d->fileName = QString(); +} + +/*! + \property QSvgGenerator::resolution + \brief the resolution of the generated output + \since 4.5 + + The resolution is specified in dots per inch, and is used to + calculate the physical size of an SVG drawing. + + \sa size, viewBox +*/ +int QSvgGenerator::resolution() const +{ + Q_D(const QSvgGenerator); + return d->engine->resolution(); +} + +void QSvgGenerator::setResolution(int dpi) +{ + Q_D(QSvgGenerator); + d->engine->setResolution(dpi); +} + +/*! + Returns the paint engine used to render graphics to be converted to SVG + format information. +*/ +QPaintEngine *QSvgGenerator::paintEngine() const +{ + Q_D(const QSvgGenerator); + return d->engine; +} + +/*! + \reimp +*/ +int QSvgGenerator::metric(QPaintDevice::PaintDeviceMetric metric) const +{ + Q_D(const QSvgGenerator); + switch (metric) { + case QPaintDevice::PdmDepth: + return 32; + case QPaintDevice::PdmWidth: + return d->engine->size().width(); + case QPaintDevice::PdmHeight: + return d->engine->size().height(); + case QPaintDevice::PdmDpiX: + return d->engine->resolution(); + case QPaintDevice::PdmDpiY: + return d->engine->resolution(); + case QPaintDevice::PdmHeightMM: + return qRound(d->engine->size().height() * 25.4 / d->engine->resolution()); + case QPaintDevice::PdmWidthMM: + return qRound(d->engine->size().width() * 25.4 / d->engine->resolution()); + case QPaintDevice::PdmNumColors: + return 0xffffffff; + case QPaintDevice::PdmPhysicalDpiX: + return d->engine->resolution(); + case QPaintDevice::PdmPhysicalDpiY: + return d->engine->resolution(); + default: + qWarning("QSvgGenerator::metric(), unhandled metric %d\n", metric); + break; + } + return 0; +} + +/***************************************************************************** + * class QSvgPaintEngine + */ + +bool QSvgPaintEngine::begin(QPaintDevice *) +{ + Q_D(QSvgPaintEngine); + if (!d->outputDevice) { + qWarning("QSvgPaintEngine::begin(), no output device"); + return false; + } + + if (!d->outputDevice->isOpen()) { + if (!d->outputDevice->open(QIODevice::WriteOnly | QIODevice::Text)) { + qWarning("QSvgPaintEngine::begin(), could not open output device: '%s'", + qPrintable(d->outputDevice->errorString())); + return false; + } + } else if (!d->outputDevice->isWritable()) { + qWarning("QSvgPaintEngine::begin(), could not write to read-only output device: '%s'", + qPrintable(d->outputDevice->errorString())); + return false; + } + + d->stream = new QTextStream(&d->header); + + // stream out the header... + *d->stream << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" << 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; + } + + if (d->viewBox.isValid()) { + *d->stream << " viewBox=\"" << d->viewBox.left() << ' ' << d->viewBox.top(); + *d->stream << ' ' << d->viewBox.width() << ' ' << d->viewBox.height() << '\"' << endl; + } + + *d->stream << " xmlns=\"http://www.w3.org/2000/svg\"" + " xmlns:xlink=\"http://www.w3.org/1999/xlink\" " + " version=\"1.2\" baseProfile=\"tiny\">" << endl; + + if (!d->attributes.document_title.isEmpty()) { + *d->stream << "<title>" << d->attributes.document_title << "</title>" << endl; + } + + if (!d->attributes.document_description.isEmpty()) { + *d->stream << "<desc>" << d->attributes.document_description << "</desc>" << endl; + } + + d->stream->setString(&d->defs); + *d->stream << "<defs>\n"; + + d->stream->setString(&d->body); + // Start the initial graphics state... + *d->stream << "<g "; + generateQtDefaults(); + *d->stream << endl; + + return true; +} + +bool QSvgPaintEngine::end() +{ + Q_D(QSvgPaintEngine); + + d->stream->setString(&d->defs); + *d->stream << "</defs>\n"; + + d->stream->setDevice(d->outputDevice); +#ifndef QT_NO_TEXTCODEC + d->stream->setCodec(QTextCodec::codecForName("UTF-8")); +#endif + + *d->stream << d->header; + *d->stream << d->defs; + *d->stream << d->body; + if (d->afterFirstUpdate) + *d->stream << "</g>" << endl; // close the updateState + + *d->stream << "</g>" << endl // close the Qt defaults + << "</svg>" << endl; + + delete d->stream; + + return true; +} + +void QSvgPaintEngine::drawPixmap(const QRectF &r, const QPixmap &pm, + const QRectF &sr) +{ + drawImage(r, pm.toImage(), sr); +} + +void QSvgPaintEngine::drawImage(const QRectF &r, const QImage &image, + const QRectF &sr, + Qt::ImageConversionFlag flags) +{ + //Q_D(QSvgPaintEngine); + + Q_UNUSED(sr); + Q_UNUSED(flags); + stream() << "<image "; + stream() << "x=\""<<r.x()<<"\" " + "y=\""<<r.y()<<"\" " + "width=\""<<r.width()<<"\" " + "height=\""<<r.height()<<"\" " + "preserveAspectRatio=\"none\" "; + + QByteArray data; + QBuffer buffer(&data); + buffer.open(QBuffer::ReadWrite); + image.save(&buffer, "PNG"); + buffer.close(); + stream() << "xlink:href=\"data:image/png;base64," + << data.toBase64() + <<"\" />\n"; +} + +void QSvgPaintEngine::updateState(const QPaintEngineState &state) +{ + Q_D(QSvgPaintEngine); + QPaintEngine::DirtyFlags flags = state.state(); + + // always stream full gstate, which is not required, but... + flags |= QPaintEngine::AllDirty; + + // close old state and start a new one... + if (d->afterFirstUpdate) + *d->stream << "</g>\n\n"; + + *d->stream << "<g "; + + if (flags & QPaintEngine::DirtyBrush) { + qbrushToSvg(state.brush()); + } + + if (flags & QPaintEngine::DirtyPen) { + qpenToSvg(state.pen()); + } + + if (flags & QPaintEngine::DirtyTransform) { + d->matrix = state.matrix(); + *d->stream << "transform=\"matrix(" << d->matrix.m11() << ',' + << d->matrix.m12() << ',' + << d->matrix.m21() << ',' << d->matrix.m22() << ',' + << d->matrix.dx() << ',' << d->matrix.dy() + << ")\"" + << endl; + } + + if (flags & QPaintEngine::DirtyFont) { + qfontToSvg(state.font()); + } + + if (flags & QPaintEngine::DirtyOpacity) { + if (!qFuzzyIsNull(state.opacity() - 1)) + stream() << "opacity=\""<<state.opacity()<<"\" "; + } + + *d->stream << '>' << endl; + + d->afterFirstUpdate = true; +} + +void QSvgPaintEngine::drawPath(const QPainterPath &p) +{ + Q_D(QSvgPaintEngine); + + *d->stream << "<path vector-effect=\"" + << (state->pen().isCosmetic() ? "non-scaling-stroke" : "none") + << "\" fill-rule=\"" + << (p.fillRule() == Qt::OddEvenFill ? "evenodd" : "nonzero") + << "\" d=\""; + + for (int i=0; i<p.elementCount(); ++i) { + const QPainterPath::Element &e = p.elementAt(i); + switch (e.type) { + case QPainterPath::MoveToElement: + *d->stream << 'M' << e.x << ',' << e.y; + break; + case QPainterPath::LineToElement: + *d->stream << 'L' << e.x << ',' << e.y; + break; + case QPainterPath::CurveToElement: + *d->stream << 'C' << e.x << ',' << e.y; + ++i; + while (i < p.elementCount()) { + const QPainterPath::Element &e = p.elementAt(i); + if (e.type != QPainterPath::CurveToDataElement) { + --i; + break; + } else + *d->stream << ' '; + *d->stream << e.x << ',' << e.y; + ++i; + } + break; + default: + break; + } + if (i != p.elementCount() - 1) { + *d->stream << ' '; + } + } + + *d->stream << "\"/>" << endl; +} + +void QSvgPaintEngine::drawPolygon(const QPointF *points, int pointCount, + PolygonDrawMode mode) +{ + Q_ASSERT(pointCount >= 2); + + //Q_D(QSvgPaintEngine); + + QPainterPath path(points[0]); + for (int i=1; i<pointCount; ++i) + path.lineTo(points[i]); + + if (mode == PolylineMode) { + stream() << "<polyline fill=\"none\" vector-effect=\"" + << (state->pen().isCosmetic() ? "non-scaling-stroke" : "none") + << "\" points=\""; + for (int i = 0; i < pointCount; ++i) { + const QPointF &pt = points[i]; + stream() << pt.x() << ',' << pt.y() << ' '; + } + stream() << "\" />" <<endl; + } else { + path.closeSubpath(); + drawPath(path); + } +} + +void QSvgPaintEngine::drawTextItem(const QPointF &pt, const QTextItem &textItem) +{ + Q_D(QSvgPaintEngine); + if (d->pen.style() == Qt::NoPen) + return; + + const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem); + QString s = QString::fromRawData(ti.chars, ti.num_chars); + + *d->stream << "<text " + "fill=\"" << d->attributes.stroke << "\" " + "fill-opacity=\"" << d->attributes.strokeOpacity << "\" " + "stroke=\"none\" " + "xml:space=\"preserve\" " + "x=\"" << pt.x() << "\" y=\"" << pt.y() << "\" "; + qfontToSvg(textItem.font()); + *d->stream << " >" + << Qt::escape(s) + << "</text>" + << endl; +} + +QT_END_NAMESPACE + +#endif // QT_NO_SVGGENERATOR diff --git a/src/svg/qsvggenerator.h b/src/svg/qsvggenerator.h new file mode 100644 index 0000000..62fc6f8 --- /dev/null +++ b/src/svg/qsvggenerator.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSvg module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSVGGENERATOR_H +#define QSVGGENERATOR_H + +#include <QtGui/qpaintdevice.h> + +#ifndef QT_NO_SVGGENERATOR + +#include <QtCore/qnamespace.h> +#include <QtCore/qiodevice.h> +#include <QtCore/qobjectdefs.h> +#include <QtCore/qscopedpointer.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Svg) + +class QSvgGeneratorPrivate; + +class Q_SVG_EXPORT QSvgGenerator : public QPaintDevice +{ + Q_DECLARE_PRIVATE(QSvgGenerator) + + Q_PROPERTY(QSize size READ size WRITE setSize) + Q_PROPERTY(QRectF viewBox READ viewBoxF WRITE setViewBox) + Q_PROPERTY(QString title READ title WRITE setTitle) + Q_PROPERTY(QString description READ description WRITE setDescription) + Q_PROPERTY(QString fileName READ fileName WRITE setFileName) + Q_PROPERTY(QIODevice* outputDevice READ outputDevice WRITE setOutputDevice) + Q_PROPERTY(int resolution READ resolution WRITE setResolution) +public: + QSvgGenerator(); + ~QSvgGenerator(); + + QString title() const; + void setTitle(const QString &title); + + QString description() const; + void setDescription(const QString &description); + + QSize size() const; + void setSize(const QSize &size); + + QRect viewBox() const; + QRectF viewBoxF() const; + void setViewBox(const QRect &viewBox); + void setViewBox(const QRectF &viewBox); + + QString fileName() const; + void setFileName(const QString &fileName); + + QIODevice *outputDevice() const; + void setOutputDevice(QIODevice *outputDevice); + + void setResolution(int dpi); + int resolution() const; +protected: + QPaintEngine *paintEngine() const; + int metric(QPaintDevice::PaintDeviceMetric metric) const; + +private: + QScopedPointer<QSvgGeneratorPrivate> d_ptr; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_SVGGENERATOR +#endif // QSVGGENERATOR_H diff --git a/src/svg/qsvggraphics.cpp b/src/svg/qsvggraphics.cpp new file mode 100644 index 0000000..0cfa2ed --- /dev/null +++ b/src/svg/qsvggraphics.cpp @@ -0,0 +1,615 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSvg module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsvggraphics_p.h" + +#ifndef QT_NO_SVG + +#include "qsvgfont_p.h" + +#include "qpainter.h" +#include "qtextdocument.h" +#include "qabstracttextdocumentlayout.h" +#include "qtextcursor.h" +#include "qdebug.h" + +#include <math.h> +#include <limits.h> + +QT_BEGIN_NAMESPACE + +#define QT_SVG_DRAW_SHAPE(command) \ + qreal oldOpacity = p->opacity(); \ + QBrush oldBrush = p->brush(); \ + QPen oldPen = p->pen(); \ + p->setPen(Qt::NoPen); \ + p->setOpacity(oldOpacity * states.fillOpacity); \ + command; \ + p->setPen(oldPen); \ + if (oldPen.widthF() != 0) { \ + p->setOpacity(oldOpacity * states.strokeOpacity); \ + p->setBrush(Qt::NoBrush); \ + command; \ + p->setBrush(oldBrush); \ + } \ + p->setOpacity(oldOpacity); + + +void QSvgAnimation::draw(QPainter *, QSvgExtraStates &) +{ + qWarning("<animation> no implemented"); +} + +static inline QRectF boundsOnStroke(QPainter *p, const QPainterPath &path, qreal width) +{ + QPainterPathStroker stroker; + stroker.setWidth(width); + QPainterPath stroke = stroker.createStroke(path); + return p->transform().map(stroke).boundingRect(); +} + +QSvgEllipse::QSvgEllipse(QSvgNode *parent, const QRectF &rect) + : QSvgNode(parent), m_bounds(rect) +{ +} + + +QRectF QSvgEllipse::bounds(QPainter *p, QSvgExtraStates &) const +{ + QPainterPath path; + path.addEllipse(m_bounds); + qreal sw = strokeWidth(p); + return qFuzzyIsNull(sw) ? p->transform().map(path).boundingRect() : boundsOnStroke(p, path, sw); +} + +void QSvgEllipse::draw(QPainter *p, QSvgExtraStates &states) +{ + applyStyle(p, states); + QT_SVG_DRAW_SHAPE(p->drawEllipse(m_bounds)); + revertStyle(p, states); +} + +QSvgArc::QSvgArc(QSvgNode *parent, const QPainterPath &path) + : QSvgNode(parent), m_path(path) +{ +} + +void QSvgArc::draw(QPainter *p, QSvgExtraStates &states) +{ + applyStyle(p, states); + if (p->pen().widthF() != 0) { + qreal oldOpacity = p->opacity(); + p->setOpacity(oldOpacity * states.strokeOpacity); + p->drawPath(m_path); + p->setOpacity(oldOpacity); + } + revertStyle(p, states); +} + +QSvgImage::QSvgImage(QSvgNode *parent, const QImage &image, + const QRect &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()); +} + +void QSvgImage::draw(QPainter *p, QSvgExtraStates &states) +{ + applyStyle(p, states); + p->drawImage(m_bounds, m_image); + revertStyle(p, states); +} + + +QSvgLine::QSvgLine(QSvgNode *parent, const QLineF &line) + : QSvgNode(parent), m_line(line) +{ +} + + +void QSvgLine::draw(QPainter *p, QSvgExtraStates &states) +{ + applyStyle(p, states); + if (p->pen().widthF() != 0) { + qreal oldOpacity = p->opacity(); + p->setOpacity(oldOpacity * states.strokeOpacity); + p->drawLine(m_line); + p->setOpacity(oldOpacity); + } + revertStyle(p, states); +} + +QSvgPath::QSvgPath(QSvgNode *parent, const QPainterPath &qpath) + : QSvgNode(parent), m_path(qpath) +{ +} + +void QSvgPath::draw(QPainter *p, QSvgExtraStates &states) +{ + applyStyle(p, states); + m_path.setFillRule(states.fillRule); + QT_SVG_DRAW_SHAPE(p->drawPath(m_path)); + revertStyle(p, states); +} + +QRectF QSvgPath::bounds(QPainter *p, QSvgExtraStates &) const +{ + qreal sw = strokeWidth(p); + return qFuzzyIsNull(sw) ? p->transform().map(m_path).boundingRect() + : boundsOnStroke(p, m_path, sw); +} + +QSvgPolygon::QSvgPolygon(QSvgNode *parent, const QPolygonF &poly) + : QSvgNode(parent), m_poly(poly) +{ +} + +QRectF QSvgPolygon::bounds(QPainter *p, QSvgExtraStates &) const +{ + qreal sw = strokeWidth(p); + if (qFuzzyIsNull(sw)) { + return p->transform().map(m_poly).boundingRect(); + } else { + QPainterPath path; + path.addPolygon(m_poly); + return boundsOnStroke(p, path, sw); + } +} + +void QSvgPolygon::draw(QPainter *p, QSvgExtraStates &states) +{ + applyStyle(p, states); + QT_SVG_DRAW_SHAPE(p->drawPolygon(m_poly, states.fillRule)); + revertStyle(p, states); +} + + +QSvgPolyline::QSvgPolyline(QSvgNode *parent, const QPolygonF &poly) + : QSvgNode(parent), m_poly(poly) +{ + +} + +void QSvgPolyline::draw(QPainter *p, QSvgExtraStates &states) +{ + applyStyle(p, states); + qreal oldOpacity = p->opacity(); + if (p->brush().style() != Qt::NoBrush) { + QPen save = p->pen(); + p->setPen(QPen(Qt::NoPen)); + p->setOpacity(oldOpacity * states.fillOpacity); + p->drawPolygon(m_poly, states.fillRule); + p->setPen(save); + } + if (p->pen().widthF() != 0) { + p->setOpacity(oldOpacity * states.strokeOpacity); + p->drawPolyline(m_poly); + } + p->setOpacity(oldOpacity); + revertStyle(p, states); +} + +QSvgRect::QSvgRect(QSvgNode *node, const QRectF &rect, int rx, int ry) + : QSvgNode(node), + m_rect(rect), m_rx(rx), m_ry(ry) +{ +} + +QRectF QSvgRect::bounds(QPainter *p, QSvgExtraStates &) const +{ + qreal sw = strokeWidth(p); + if (qFuzzyIsNull(sw)) { + return p->transform().mapRect(m_rect); + } else { + QPainterPath path; + path.addRect(m_rect); + return boundsOnStroke(p, path, sw); + } +} + +void QSvgRect::draw(QPainter *p, QSvgExtraStates &states) +{ + applyStyle(p, states); + if (m_rx || m_ry) { + QT_SVG_DRAW_SHAPE(p->drawRoundedRect(m_rect, m_rx, m_ry, Qt::RelativeSize)); + } else { + QT_SVG_DRAW_SHAPE(p->drawRect(m_rect)); + } + revertStyle(p, states); +} + +QSvgTspan * const QSvgText::LINEBREAK = 0; + +QSvgText::QSvgText(QSvgNode *parent, const QPointF &coord) + : QSvgNode(parent) + , m_coord(coord) + , m_type(TEXT) + , m_size(0, 0) + , m_mode(Default) +{ +} + +QSvgText::~QSvgText() +{ + for (int i = 0; i < m_tspans.size(); ++i) { + if (m_tspans[i] != LINEBREAK) + delete m_tspans[i]; + } +} + +void QSvgText::setTextArea(const QSizeF &size) +{ + m_size = size; + m_type = TEXTAREA; +} + +//QRectF QSvgText::bounds(QPainter *p, QSvgExtraStates &) const {} + +void QSvgText::draw(QPainter *p, QSvgExtraStates &states) +{ + applyStyle(p, states); + qreal oldOpacity = p->opacity(); + p->setOpacity(oldOpacity * states.fillOpacity); + + // Force the font to have a size of 100 pixels to avoid truncation problems + // when the font is very small. + qreal scale = 100.0 / p->font().pointSizeF(); + Qt::Alignment alignment = states.textAnchor; + + QTransform oldTransform = p->worldTransform(); + p->scale(1 / scale, 1 / scale); + + qreal y = 0; + bool initial = true; + qreal px = m_coord.x() * scale; + qreal py = m_coord.y() * scale; + QSizeF scaledSize = m_size * scale; + + if (m_type == TEXTAREA) { + if (alignment == Qt::AlignHCenter) + px += scaledSize.width() / 2; + else if (alignment == Qt::AlignRight) + px += scaledSize.width(); + } + + QRectF bounds; + if (m_size.height() != 0) + bounds = QRectF(0, py, 1, scaledSize.height()); // x and width are not used. + + bool appendSpace = false; + QVector<QString> paragraphs; + QStack<QTextCharFormat> formats; + QVector<QList<QTextLayout::FormatRange> > formatRanges; + paragraphs.push_back(QString()); + formatRanges.push_back(QList<QTextLayout::FormatRange>()); + + for (int i = 0; i < m_tspans.size(); ++i) { + if (m_tspans[i] == LINEBREAK) { + if (m_type == TEXTAREA) { + if (paragraphs.back().isEmpty()) { + QFont font = p->font(); + font.setPixelSize(font.pointSizeF() * scale); + + QTextLayout::FormatRange range; + range.start = 0; + range.length = 1; + range.format.setFont(font); + formatRanges.back().append(range); + + paragraphs.back().append(QLatin1Char(' '));; + } + appendSpace = false; + paragraphs.push_back(QString()); + formatRanges.push_back(QList<QTextLayout::FormatRange>()); + } + } else { + WhitespaceMode mode = m_tspans[i]->whitespaceMode(); + m_tspans[i]->applyStyle(p, states); + + QFont font = p->font(); + font.setPixelSize(font.pointSizeF() * scale); + + QString newText(m_tspans[i]->text()); + newText.replace(QLatin1Char('\t'), QLatin1Char(' ')); + newText.replace(QLatin1Char('\n'), QLatin1Char(' ')); + + bool prependSpace = !appendSpace && !m_tspans[i]->isTspan() && (mode == Default) && !paragraphs.back().isEmpty() && newText.startsWith(QLatin1Char(' ')); + if (appendSpace || prependSpace) + paragraphs.back().append(QLatin1Char(' ')); + + bool appendSpaceNext = (!m_tspans[i]->isTspan() && (mode == Default) && newText.endsWith(QLatin1Char(' '))); + + if (mode == Default) { + newText = newText.simplified(); + if (newText.isEmpty()) + appendSpaceNext = false; + } + + QTextLayout::FormatRange range; + range.start = paragraphs.back().length(); + range.length = newText.length(); + range.format.setFont(font); + range.format.setTextOutline(p->pen()); + range.format.setForeground(p->brush()); + + if (appendSpace) { + Q_ASSERT(!formatRanges.back().isEmpty()); + ++formatRanges.back().back().length; + } else if (prependSpace) { + --range.start; + ++range.length; + } + formatRanges.back().append(range); + + appendSpace = appendSpaceNext; + paragraphs.back() += newText; + + m_tspans[i]->revertStyle(p, states); + } + } + + if (states.svgFont) { + // SVG fonts not fully supported... + QString text = paragraphs.front(); + for (int i = 1; i < paragraphs.size(); ++i) { + text.append(QLatin1Char('\n')); + text.append(paragraphs[i]); + } + states.svgFont->draw(p, m_coord * scale, text, p->font().pointSizeF() * scale, states.textAnchor); + } else { + for (int i = 0; i < paragraphs.size(); ++i) { + QTextLayout tl(paragraphs[i]); + QTextOption op = tl.textOption(); + op.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + tl.setTextOption(op); + tl.setAdditionalFormats(formatRanges[i]); + tl.beginLayout(); + + forever { + QTextLine line = tl.createLine(); + if (!line.isValid()) + break; + if (m_size.width() != 0) + line.setLineWidth(scaledSize.width()); + } + tl.endLayout(); + + bool endOfBoundsReached = false; + for (int i = 0; i < tl.lineCount(); ++i) { + QTextLine line = tl.lineAt(i); + + qreal x = 0; + if (alignment == Qt::AlignHCenter) + x -= 0.5 * line.naturalTextWidth(); + else if (alignment == Qt::AlignRight) + x -= line.naturalTextWidth(); + + if (initial && m_type == TEXT) + y -= line.ascent(); + initial = false; + + line.setPosition(QPointF(x, y)); + + // Check if the current line fits into the bounding rectangle. + if ((m_size.width() != 0 && line.naturalTextWidth() > scaledSize.width()) + || (m_size.height() != 0 && y + line.height() > scaledSize.height())) { + // I need to set the bounds height to 'y-epsilon' to avoid drawing the current + // line. Since the font is scaled to 100 units, 1 should be a safe epsilon. + bounds.setHeight(y - 1); + endOfBoundsReached = true; + break; + } + + y += 1.1 * line.height(); + } + tl.draw(p, QPointF(px, py), QVector<QTextLayout::FormatRange>(), bounds); + + if (endOfBoundsReached) + break; + } + } + + p->setWorldTransform(oldTransform, false); + p->setOpacity(oldOpacity); + revertStyle(p, states); +} + +void QSvgText::addText(const QString &text) +{ + m_tspans.append(new QSvgTspan(this, false)); + m_tspans.back()->setWhitespaceMode(m_mode); + m_tspans.back()->addText(text); +} + +QSvgUse::QSvgUse(const QPointF &start, QSvgNode *parent, QSvgNode *node) + : QSvgNode(parent), m_link(node), m_start(start) +{ + +} + +void QSvgUse::draw(QPainter *p, QSvgExtraStates &states) +{ + applyStyle(p, states); + + if (!m_start.isNull()) { + p->translate(m_start); + } + m_link->draw(p, states); + if (!m_start.isNull()) { + p->translate(-m_start); + } + + revertStyle(p, states); +} + +void QSvgVideo::draw(QPainter *p, QSvgExtraStates &states) +{ + applyStyle(p, states); + + revertStyle(p, states); +} + +QSvgNode::Type QSvgAnimation::type() const +{ + return ANIMATION; +} + +QSvgNode::Type QSvgArc::type() const +{ + return ARC; +} + +QSvgNode::Type QSvgCircle::type() const +{ + return CIRCLE; +} + +QSvgNode::Type QSvgEllipse::type() const +{ + return ELLIPSE; +} + +QSvgNode::Type QSvgImage::type() const +{ + return IMAGE; +} + +QSvgNode::Type QSvgLine::type() const +{ + return LINE; +} + +QSvgNode::Type QSvgPath::type() const +{ + return PATH; +} + +QSvgNode::Type QSvgPolygon::type() const +{ + return POLYGON; +} + +QSvgNode::Type QSvgPolyline::type() const +{ + return POLYLINE; +} + +QSvgNode::Type QSvgRect::type() const +{ + return RECT; +} + +QSvgNode::Type QSvgText::type() const +{ + return m_type; +} + +QSvgNode::Type QSvgUse::type() const +{ + return USE; +} + +QSvgNode::Type QSvgVideo::type() const +{ + return VIDEO; +} + +QRectF QSvgUse::bounds(QPainter *p, QSvgExtraStates &states) const +{ + QRectF bounds; + if (m_link) { + p->translate(m_start); + bounds = m_link->transformedBounds(p, states); + p->translate(-m_start); + } + return bounds; +} + +QRectF QSvgPolyline::bounds(QPainter *p, QSvgExtraStates &) const +{ + qreal sw = strokeWidth(p); + if (qFuzzyIsNull(sw)) { + return p->transform().map(m_poly).boundingRect(); + } else { + QPainterPath path; + path.addPolygon(m_poly); + return boundsOnStroke(p, path, sw); + } +} + +QRectF QSvgArc::bounds(QPainter *p, QSvgExtraStates &) const +{ + qreal sw = strokeWidth(p); + return qFuzzyIsNull(sw) ? p->transform().map(m_path).boundingRect() + : boundsOnStroke(p, m_path, sw); +} + +QRectF QSvgImage::bounds(QPainter *p, QSvgExtraStates &) const +{ + return p->transform().mapRect(m_bounds); +} + +QRectF QSvgLine::bounds(QPainter *p, QSvgExtraStates &) const +{ + qreal sw = strokeWidth(p); + if (qFuzzyIsNull(sw)) { + QPointF p1 = p->transform().map(m_line.p1()); + QPointF p2 = p->transform().map(m_line.p2()); + qreal minX = qMin(p1.x(), p2.x()); + qreal minY = qMin(p1.y(), p2.y()); + qreal maxX = qMax(p1.x(), p2.x()); + qreal maxY = qMax(p1.y(), p2.y()); + return QRectF(minX, minY, maxX - minX, maxY - minY); + } else { + QPainterPath path; + path.moveTo(m_line.p1()); + path.lineTo(m_line.p2()); + return boundsOnStroke(p, path, sw); + } +} + +QT_END_NAMESPACE + +#endif // QT_NO_SVG diff --git a/src/svg/qsvggraphics_p.h b/src/svg/qsvggraphics_p.h new file mode 100644 index 0000000..58c4fdf --- /dev/null +++ b/src/svg/qsvggraphics_p.h @@ -0,0 +1,262 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSvg module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSVGGRAPHICS_P_H +#define QSVGGRAPHICS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qsvgnode_p.h" + +#ifndef QT_NO_SVG + +#include "QtGui/qpainterpath.h" +#include "QtGui/qimage.h" +#include "QtGui/qtextlayout.h" +#include "QtGui/qtextoption.h" +#include "QtCore/qstack.h" + +QT_BEGIN_NAMESPACE + +class QTextCharFormat; + +class QSvgAnimation : public QSvgNode +{ +public: + virtual void draw(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; +}; + +class QSvgArc : public QSvgNode +{ +public: + QSvgArc(QSvgNode *parent, const QPainterPath &path); + virtual void draw(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; + virtual QRectF bounds(QPainter *p, QSvgExtraStates &states) const; +private: + QPainterPath m_path; +}; + +class QSvgEllipse : public QSvgNode +{ +public: + QSvgEllipse(QSvgNode *parent, const QRectF &rect); + virtual void draw(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; + virtual QRectF bounds(QPainter *p, QSvgExtraStates &states) const; +private: + QRectF m_bounds; +}; + +class QSvgCircle : public QSvgEllipse +{ +public: + QSvgCircle(QSvgNode *parent, const QRectF &rect) : QSvgEllipse(parent, rect) { } + virtual Type type() const; +}; + +class QSvgImage : public QSvgNode +{ +public: + QSvgImage(QSvgNode *parent, const QImage &image, + const QRect &bounds); + virtual void draw(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; + virtual QRectF bounds(QPainter *p, QSvgExtraStates &states) const; +private: + QImage m_image; + QRect m_bounds; +}; + +class QSvgLine : public QSvgNode +{ +public: + QSvgLine(QSvgNode *parent, const QLineF &line); + virtual void draw(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; + virtual QRectF bounds(QPainter *p, QSvgExtraStates &states) const; +private: + QLineF m_line; +}; + +class QSvgPath : public QSvgNode +{ +public: + QSvgPath(QSvgNode *parent, const QPainterPath &qpath); + virtual void draw(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; + virtual QRectF bounds(QPainter *p, QSvgExtraStates &states) const; + + QPainterPath *qpath() { + return &m_path; + } +private: + QPainterPath m_path; +}; + +class QSvgPolygon : public QSvgNode +{ +public: + QSvgPolygon(QSvgNode *parent, const QPolygonF &poly); + virtual void draw(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; + virtual QRectF bounds(QPainter *p, QSvgExtraStates &states) const; +private: + QPolygonF m_poly; +}; + +class QSvgPolyline : public QSvgNode +{ +public: + QSvgPolyline(QSvgNode *parent, const QPolygonF &poly); + virtual void draw(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; + virtual QRectF bounds(QPainter *p, QSvgExtraStates &states) const; +private: + QPolygonF m_poly; +}; + +class QSvgRect : public QSvgNode +{ +public: + QSvgRect(QSvgNode *paren, const QRectF &rect, int rx=0, int ry=0); + virtual Type type() const; + virtual void draw(QPainter *p, QSvgExtraStates &states); + virtual QRectF bounds(QPainter *p, QSvgExtraStates &states) const; +private: + QRectF m_rect; + int m_rx, m_ry; +}; + +class QSvgTspan; + +class QSvgText : public QSvgNode +{ +public: + enum WhitespaceMode + { + Default, + Preserve + }; + + QSvgText(QSvgNode *parent, const QPointF &coord); + ~QSvgText(); + void setTextArea(const QSizeF &size); + + virtual void draw(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; + + void addTspan(QSvgTspan *tspan) {m_tspans.append(tspan);} + void addText(const QString &text); + void addLineBreak() {m_tspans.append(LINEBREAK);} + void setWhitespaceMode(WhitespaceMode mode) {m_mode = mode;} + + //virtual QRectF bounds(QPainter *p, QSvgExtraStates &states) const; +private: + static QSvgTspan * const LINEBREAK; + + QPointF m_coord; + + // 'm_tspans' is also used to store characters outside tspans and line breaks. + // If a 'm_tspan' item is null, it indicates a line break. + QVector<QSvgTspan *> m_tspans; + + Type m_type; + QSizeF m_size; + WhitespaceMode m_mode; +}; + +class QSvgTspan : public QSvgNode +{ +public: + // tspans are also used to store normal text, so the 'isProperTspan' is used to separate text from tspan. + QSvgTspan(QSvgNode *parent, bool isProperTspan = true) + : QSvgNode(parent), m_mode(QSvgText::Default), m_isTspan(isProperTspan) + { + } + ~QSvgTspan() { }; + virtual Type type() const {return TSPAN;} + virtual void draw(QPainter *, QSvgExtraStates &) {Q_ASSERT(!"Tspans should be drawn through QSvgText::draw().");} + void addText(const QString &text) {m_text += text;} + const QString &text() const {return m_text;} + bool isTspan() const {return m_isTspan;} + void setWhitespaceMode(QSvgText::WhitespaceMode mode) {m_mode = mode;} + QSvgText::WhitespaceMode whitespaceMode() const {return m_mode;} +private: + QString m_text; + QSvgText::WhitespaceMode m_mode; + bool m_isTspan; +}; + +class QSvgUse : public QSvgNode +{ +public: + QSvgUse(const QPointF &start, QSvgNode *parent, QSvgNode *link); + virtual void draw(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; + virtual QRectF bounds(QPainter *p, QSvgExtraStates &states) const; + +private: + QSvgNode *m_link; + QPointF m_start; +}; + +class QSvgVideo : public QSvgNode +{ +public: + virtual void draw(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_SVG +#endif // QSVGGRAPHICS_P_H diff --git a/src/svg/qsvghandler.cpp b/src/svg/qsvghandler.cpp new file mode 100644 index 0000000..3fbc08c --- /dev/null +++ b/src/svg/qsvghandler.cpp @@ -0,0 +1,3923 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSvg module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" + +#include "qsvghandler_p.h" + +#ifndef QT_NO_SVG + +#include "qsvgtinydocument_p.h" +#include "qsvgstructure_p.h" +#include "qsvggraphics_p.h" +#include "qsvgnode_p.h" +#include "qsvgfont_p.h" + +#include "qapplication.h" +#include "qwidget.h" +#include "qpen.h" +#include "qpainterpath.h" +#include "qbrush.h" +#include "qcolor.h" +#include "qtextformat.h" +#include "qvector.h" +#include "qfileinfo.h" +#include "qfile.h" +#include "qdebug.h" +#include "qmath.h" +#include "qnumeric.h" +#include "qvarlengtharray.h" +#include "private/qmath_p.h" + +#include "float.h" + +QT_BEGIN_NAMESPACE + +static const char *qt_inherit_text = "inherit"; +#define QT_INHERIT QLatin1String(qt_inherit_text) + +Q_CORE_EXPORT double qstrtod(const char *s00, char const **se, bool *ok); + +// ======== duplicated from qcolor_p + +static inline int qsvg_h2i(char hex) +{ + if (hex >= '0' && hex <= '9') + return hex - '0'; + if (hex >= 'a' && hex <= 'f') + return hex - 'a' + 10; + if (hex >= 'A' && hex <= 'F') + return hex - 'A' + 10; + return -1; +} + +static inline int qsvg_hex2int(const char *s) +{ + return (qsvg_h2i(s[0]) << 4) | qsvg_h2i(s[1]); +} + +static inline int qsvg_hex2int(char s) +{ + int h = qsvg_h2i(s); + return (h << 4) | h; +} + +bool qsvg_get_hex_rgb(const char *name, QRgb *rgb) +{ + if(name[0] != '#') + return false; + name++; + int len = qstrlen(name); + int r, g, b; + if (len == 12) { + r = qsvg_hex2int(name); + g = qsvg_hex2int(name + 4); + b = qsvg_hex2int(name + 8); + } else if (len == 9) { + r = qsvg_hex2int(name); + g = qsvg_hex2int(name + 3); + b = qsvg_hex2int(name + 6); + } else if (len == 6) { + r = qsvg_hex2int(name); + g = qsvg_hex2int(name + 2); + b = qsvg_hex2int(name + 4); + } else if (len == 3) { + r = qsvg_hex2int(name[0]); + g = qsvg_hex2int(name[1]); + b = qsvg_hex2int(name[2]); + } else { + r = g = b = -1; + } + if ((uint)r > 255 || (uint)g > 255 || (uint)b > 255) { + *rgb = 0; + return false; + } + *rgb = qRgb(r, g ,b); + return true; +} + +bool qsvg_get_hex_rgb(const QChar *str, int len, QRgb *rgb) +{ + if (len > 13) + return false; + char tmp[16]; + for(int i = 0; i < len; ++i) + tmp[i] = str[i].toLatin1(); + tmp[len] = 0; + return qsvg_get_hex_rgb(tmp, rgb); +} + +// ======== end of qcolor_p duplicate + +static bool parsePathDataFast(const QStringRef &data, QPainterPath &path); + +static inline QString someId(const QXmlStreamAttributes &attributes) +{ + QString id = attributes.value(QLatin1String("id")).toString(); + if (id.isEmpty()) + id = attributes.value(QLatin1String("xml:id")).toString(); + return id; +} + +struct QSvgAttributes +{ + QSvgAttributes(const QXmlStreamAttributes &xmlAttributes, QSvgHandler *handler); + + QString id; + + QStringRef color; + QStringRef colorOpacity; + QStringRef fill; + QStringRef fillRule; + QStringRef fillOpacity; + QStringRef stroke; + QStringRef strokeDashArray; + QStringRef strokeDashOffset; + QStringRef strokeLineCap; + QStringRef strokeLineJoin; + QStringRef strokeMiterLimit; + QStringRef strokeOpacity; + QStringRef strokeWidth; + QStringRef vectorEffect; + QStringRef fontFamily; + QStringRef fontSize; + QStringRef fontStyle; + QStringRef fontWeight; + QStringRef fontVariant; + QStringRef textAnchor; + QStringRef transform; + QStringRef visibility; + QStringRef opacity; + QStringRef compOp; + QStringRef display; + QStringRef offset; + QStringRef stopColor; + QStringRef stopOpacity; + + QVector<QSvgCssAttribute> m_cssAttributes; +}; + +QSvgAttributes::QSvgAttributes(const QXmlStreamAttributes &xmlAttributes, QSvgHandler *handler) +{ + QStringRef style = xmlAttributes.value(QLatin1String("style")); + if (!style.isEmpty()) { + handler->parseCSStoXMLAttrs(style.toString(), &m_cssAttributes); + for (int j = 0; j < m_cssAttributes.count(); ++j) { + const QSvgCssAttribute &attribute = m_cssAttributes.at(j); + QStringRef name = attribute.name; + QStringRef value = attribute.value; + if (name.isEmpty()) + continue; + + switch (name.at(0).unicode()) { + + case 'c': + if (name == QLatin1String("color")) + color = value; + else if (name == QLatin1String("color-opacity")) + colorOpacity = value; + else if (name == QLatin1String("comp-op")) + compOp = value; + break; + + case 'd': + if (name == QLatin1String("display")) + display = value; + break; + + case 'f': + if (name == QLatin1String("fill")) + fill = value; + else if (name == QLatin1String("fill-rule")) + fillRule = value; + else if (name == QLatin1String("fill-opacity")) + fillOpacity = value; + else if (name == QLatin1String("font-family")) + fontFamily = value; + else if (name == QLatin1String("font-size")) + fontSize = value; + else if (name == QLatin1String("font-style")) + fontStyle = value; + else if (name == QLatin1String("font-weight")) + fontWeight = value; + else if (name == QLatin1String("font-variant")) + fontVariant = value; + break; + + case 'o': + if (name == QLatin1String("opacity")) + opacity = value; + else if (name == QLatin1String("offset")) + offset = value; + break; + + case 's': + if (name.length() > 5 && QStringRef(name.string(), name.position() + 1, 5) == QLatin1String("troke")) { + QStringRef strokeRef(name.string(), name.position() + 6, name.length() - 6); + if (strokeRef.isEmpty()) + stroke = value; + else if (strokeRef == QLatin1String("-dasharray")) + strokeDashArray = value; + else if (strokeRef == QLatin1String("-dashoffset")) + strokeDashOffset = value; + else if (strokeRef == QLatin1String("-linecap")) + strokeLineCap = value; + else if (strokeRef == QLatin1String("-linejoin")) + strokeLineJoin = value; + else if (strokeRef == QLatin1String("-miterlimit")) + strokeMiterLimit = value; + else if (strokeRef == QLatin1String("-opacity")) + strokeOpacity = value; + else if (strokeRef == QLatin1String("-width")) + strokeWidth = value; + } + else if (name == QLatin1String("stop-color")) + stopColor = value; + else if (name == QLatin1String("stop-opacity")) + stopOpacity = value; + break; + + case 't': + if (name == QLatin1String("text-anchor")) + textAnchor = value; + else if (name == QLatin1String("transform")) + transform = value; + break; + + case 'v': + if (name == QLatin1String("vector-effect")) + vectorEffect = value; + else if (name == QLatin1String("visibility")) + visibility = value; + break; + + default: + break; + } + } + } + + for (int i = 0; i < xmlAttributes.count(); ++i) { + const QXmlStreamAttribute &attribute = xmlAttributes.at(i); + QStringRef name = attribute.qualifiedName(); + if (name.isEmpty()) + continue; + QStringRef value = attribute.value(); + + switch (name.at(0).unicode()) { + + case 'c': + if (name == QLatin1String("color")) + color = value; + else if (name == QLatin1String("color-opacity")) + colorOpacity = value; + else if (name == QLatin1String("comp-op")) + compOp = value; + break; + + case 'd': + if (name == QLatin1String("display")) + display = value; + break; + + case 'f': + if (name == QLatin1String("fill")) + fill = value; + else if (name == QLatin1String("fill-rule")) + fillRule = value; + else if (name == QLatin1String("fill-opacity")) + fillOpacity = value; + else if (name == QLatin1String("font-family")) + fontFamily = value; + else if (name == QLatin1String("font-size")) + fontSize = value; + else if (name == QLatin1String("font-style")) + fontStyle = value; + else if (name == QLatin1String("font-weight")) + fontWeight = value; + else if (name == QLatin1String("font-variant")) + fontVariant = value; + break; + + case 'i': + if (name == QLatin1String("id")) + id = value.toString(); + break; + + case 'o': + if (name == QLatin1String("opacity")) + opacity = value; + if (name == QLatin1String("offset")) + offset = value; + break; + + case 's': + if (name.length() > 5 && QStringRef(name.string(), name.position() + 1, 5) == QLatin1String("troke")) { + QStringRef strokeRef(name.string(), name.position() + 6, name.length() - 6); + if (strokeRef.isEmpty()) + stroke = value; + else if (strokeRef == QLatin1String("-dasharray")) + strokeDashArray = value; + else if (strokeRef == QLatin1String("-dashoffset")) + strokeDashOffset = value; + else if (strokeRef == QLatin1String("-linecap")) + strokeLineCap = value; + else if (strokeRef == QLatin1String("-linejoin")) + strokeLineJoin = value; + else if (strokeRef == QLatin1String("-miterlimit")) + strokeMiterLimit = value; + else if (strokeRef == QLatin1String("-opacity")) + strokeOpacity = value; + else if (strokeRef == QLatin1String("-width")) + strokeWidth = value; + } + else if (name == QLatin1String("stop-color")) + stopColor = value; + else if (name == QLatin1String("stop-opacity")) + stopOpacity = value; + break; + + case 't': + if (name == QLatin1String("text-anchor")) + textAnchor = value; + else if (name == QLatin1String("transform")) + transform = value; + break; + + case 'v': + if (name == QLatin1String("vector-effect")) + vectorEffect = value; + else if (name == QLatin1String("visibility")) + visibility = value; + break; + + case 'x': + if (name == QLatin1String("xml:id") && id.isEmpty()) + id = value.toString(); + break; + + default: + break; + } + } + +} + +static const char * QSvgStyleSelector_nodeString[] = { + "svg", + "g", + "defs", + "switch", + "animation", + "arc", + "circle", + "ellipse", + "image", + "line", + "path", + "polygon", + "polyline", + "rect", + "text", + "textarea", + "use", + "video" +}; + +class QSvgStyleSelector : public QCss::StyleSelector +{ +public: + QSvgStyleSelector() + { + nameCaseSensitivity = Qt::CaseInsensitive; + } + virtual ~QSvgStyleSelector() + { + } + + inline QString nodeToName(QSvgNode *node) const + { + return QLatin1String(QSvgStyleSelector_nodeString[node->type()]); + } + + inline QSvgNode *svgNode(NodePtr node) const + { + return (QSvgNode*)node.ptr; + } + inline QSvgStructureNode *nodeToStructure(QSvgNode *n) const + { + if (n && + (n->type() == QSvgNode::DOC || + n->type() == QSvgNode::G || + n->type() == QSvgNode::DEFS || + n->type() == QSvgNode::SWITCH)) { + return (QSvgStructureNode*)n; + } + return 0; + } + + inline QSvgStructureNode *svgStructure(NodePtr node) const + { + QSvgNode *n = svgNode(node); + QSvgStructureNode *st = nodeToStructure(n); + return st; + } + + virtual bool nodeNameEquals(NodePtr node, const QString& nodeName) const + { + QSvgNode *n = svgNode(node); + if (!n) + return false; + QString name = nodeToName(n); + return QString::compare(name, nodeName, Qt::CaseInsensitive) == 0; + } + virtual QString attribute(NodePtr node, const QString &name) const + { + QSvgNode *n = svgNode(node); + if ((!n->nodeId().isEmpty() && (name == QLatin1String("id") || + name == QLatin1String("xml:id")))) + return n->nodeId(); + if (!n->xmlClass().isEmpty() && name == QLatin1String("class")) + return n->xmlClass(); + return QString(); + } + virtual bool hasAttributes(NodePtr node) const + { + QSvgNode *n = svgNode(node); + return (n && + (!n->nodeId().isEmpty() || !n->xmlClass().isEmpty())); + } + + virtual QStringList nodeIds(NodePtr node) const + { + QSvgNode *n = svgNode(node); + QString nid; + if (n) + nid = n->nodeId(); + QStringList lst; lst.append(nid); + return lst; + } + + virtual QStringList nodeNames(NodePtr node) const + { + QSvgNode *n = svgNode(node); + if (n) + return QStringList(nodeToName(n)); + return QStringList(); + } + + virtual bool isNullNode(NodePtr node) const + { + return !node.ptr; + } + + virtual NodePtr parentNode(NodePtr node) const + { + QSvgNode *n = svgNode(node); + NodePtr newNode; + newNode.ptr = 0; + newNode.id = 0; + if (n) { + QSvgNode *svgParent = n->parent(); + if (svgParent) { + newNode.ptr = svgParent; + } + } + return newNode; + } + virtual NodePtr previousSiblingNode(NodePtr node) const + { + NodePtr newNode; + newNode.ptr = 0; + newNode.id = 0; + + QSvgNode *n = svgNode(node); + if (!n) + return newNode; + QSvgStructureNode *svgParent = nodeToStructure(n->parent()); + + if (svgParent) { + newNode.ptr = svgParent->previousSiblingNode(n); + } + return newNode; + } + virtual NodePtr duplicateNode(NodePtr node) const + { + NodePtr n; + n.ptr = node.ptr; + n.id = node.id; + return n; + } + virtual void freeNode(NodePtr node) const + { + Q_UNUSED(node); + } +}; + +// '0' is 0x30 and '9' is 0x39 +static inline bool isDigit(ushort ch) +{ + static quint16 magic = 0x3ff; + return ((ch >> 4) == 3) && (magic >> (ch & 15)); +} + +static qreal toDouble(const QChar *&str) +{ + const int maxLen = 255;//technically doubles can go til 308+ but whatever + char temp[maxLen+1]; + int pos = 0; + + if (*str == QLatin1Char('-')) { + temp[pos++] = '-'; + ++str; + } else if (*str == QLatin1Char('+')) { + ++str; + } + while (isDigit(str->unicode()) && pos < maxLen) { + temp[pos++] = str->toLatin1(); + ++str; + } + if (*str == QLatin1Char('.') && pos < maxLen) { + temp[pos++] = '.'; + ++str; + } + while (isDigit(str->unicode()) && pos < maxLen) { + temp[pos++] = str->toLatin1(); + ++str; + } + bool exponent = false; + if ((*str == QLatin1Char('e') || *str == QLatin1Char('E')) && pos < maxLen) { + exponent = true; + temp[pos++] = 'e'; + ++str; + if ((*str == QLatin1Char('-') || *str == QLatin1Char('+')) && pos < maxLen) { + temp[pos++] = str->toLatin1(); + ++str; + } + while (isDigit(str->unicode()) && pos < maxLen) { + temp[pos++] = str->toLatin1(); + ++str; + } + } + + temp[pos] = '\0'; + + qreal val; + if (!exponent && pos < 10) { + int ival = 0; + const char *t = temp; + bool neg = false; + if(*t == '-') { + neg = true; + ++t; + } + while(*t && *t != '.') { + ival *= 10; + ival += (*t) - '0'; + ++t; + } + if(*t == '.') { + ++t; + int div = 1; + while(*t) { + ival *= 10; + ival += (*t) - '0'; + div *= 10; + ++t; + } + val = ((qreal)ival)/((qreal)div); + } else { + val = ival; + } + if (neg) + val = -val; + } else { +#if defined(Q_WS_QWS) && !defined(Q_OS_VXWORKS) + if(sizeof(qreal) == sizeof(float)) + val = strtof(temp, 0); + else +#endif + { + bool ok = false; + val = qstrtod(temp, 0, &ok); + } + } + return val; + +} +static qreal toDouble(const QString &str, bool *ok = NULL) +{ + const QChar *c = str.constData(); + qreal res = toDouble(c); + if (ok) { + *ok = ((*c) == QLatin1Char('\0')); + } + return res; +} + +static qreal toDouble(const QStringRef &str, bool *ok = NULL) +{ + const QChar *c = str.constData(); + qreal res = toDouble(c); + if (ok) { + *ok = (c == (str.constData() + str.length())); + } + return res; +} + +static QVector<qreal> parseNumbersList(const QChar *&str) +{ + QVector<qreal> points; + if (!str) + return points; + points.reserve(32); + + while (str->isSpace()) + ++str; + while (isDigit(str->unicode()) || + *str == QLatin1Char('-') || *str == QLatin1Char('+') || + *str == QLatin1Char('.')) { + + points.append(toDouble(str)); + + while (str->isSpace()) + ++str; + if (*str == QLatin1Char(',')) + ++str; + + //eat the rest of space + while (str->isSpace()) + ++str; + } + + return points; +} + +static inline void parseNumbersArray(const QChar *&str, QVarLengthArray<qreal, 8> &points) +{ + while (str->isSpace()) + ++str; + while (isDigit(str->unicode()) || + *str == QLatin1Char('-') || *str == QLatin1Char('+') || + *str == QLatin1Char('.')) { + + points.append(toDouble(str)); + + while (str->isSpace()) + ++str; + if (*str == QLatin1Char(',')) + ++str; + + //eat the rest of space + while (str->isSpace()) + ++str; + } +} + +static QVector<qreal> parsePercentageList(const QChar *&str) +{ + QVector<qreal> points; + if (!str) + return points; + + while (str->isSpace()) + ++str; + while ((*str >= QLatin1Char('0') && *str <= QLatin1Char('9')) || + *str == QLatin1Char('-') || *str == QLatin1Char('+') || + *str == QLatin1Char('.')) { + + points.append(toDouble(str)); + + while (str->isSpace()) + ++str; + if (*str == QLatin1Char('%')) + ++str; + while (str->isSpace()) + ++str; + if (*str == QLatin1Char(',')) + ++str; + + //eat the rest of space + while (str->isSpace()) + ++str; + } + + return points; +} + +static QString idFromUrl(const QString &url) +{ + QString::const_iterator itr = url.constBegin(); + while ((*itr).isSpace()) + ++itr; + if ((*itr) == QLatin1Char('(')) + ++itr; + while ((*itr).isSpace()) + ++itr; + if ((*itr) == QLatin1Char('#')) + ++itr; + QString id; + while ((*itr) != QLatin1Char(')')) { + id += *itr; + ++itr; + } + return id; +} + +static inline QStringRef trimRef(const QStringRef &str) +{ + if (str.isEmpty()) + return QStringRef(); + const QChar *s = str.string()->constData() + str.position(); + int end = str.length() - 1; + if (!s[0].isSpace() && !s[end].isSpace()) + return str; + + int start = 0; + while (start<=end && s[start].isSpace()) // skip white space from start + start++; + if (start <= end) { // only white space + while (s[end].isSpace()) // skip white space from end + end--; + } + int l = end - start + 1; + if (l <= 0) + return QStringRef(); + return QStringRef(str.string(), str.position() + start, l); +} + +/** + * returns true when successfuly set the color. false signifies + * that the color should be inherited + */ +static bool resolveColor(const QStringRef &colorStr, QColor &color, QSvgHandler *handler) +{ + QStringRef colorStrTr = trimRef(colorStr); + if (colorStrTr.isEmpty()) + return false; + + switch(colorStrTr.at(0).unicode()) { + + case '#': + { + // #rrggbb is very very common, so let's tackle it here + // rather than falling back to QColor + QRgb rgb; + bool ok = qsvg_get_hex_rgb(colorStrTr.unicode(), colorStrTr.length(), &rgb); + if (ok) + color.setRgb(rgb); + return ok; + } + break; + + case 'r': + { + // starts with "rgb(", ends with ")" and consists of at least 7 characters "rgb(,,)" + if (colorStrTr.length() >= 7 && colorStrTr.at(colorStrTr.length() - 1) == QLatin1Char(')') + && QStringRef(colorStrTr.string(), colorStrTr.position(), 4) == QLatin1String("rgb(")) { + const QChar *s = colorStrTr.constData() + 4; + QVector<qreal> compo = parseNumbersList(s); + //1 means that it failed after reaching non-parsable + //character which is going to be "%" + if (compo.size() == 1) { + s = colorStrTr.constData() + 4; + compo = parsePercentageList(s); + for (int i = 0; i < compo.size(); ++i) + compo[i] *= (qreal)2.55; + } + + if (compo.size() == 3) { + color = QColor(int(compo[0]), + int(compo[1]), + int(compo[2])); + return true; + } + return false; + } + } + break; + + case 'c': + if (colorStrTr == QLatin1String("currentColor")) { + color = handler->currentColor(); + return true; + } + break; + case 'i': + if (colorStrTr == QT_INHERIT) + return false; + break; + default: + break; + } + + color = QColor(colorStrTr.toString()); + return color.isValid(); +} + +static bool constructColor(const QStringRef &colorStr, const QStringRef &opacity, + QColor &color, QSvgHandler *handler) +{ + if (!resolveColor(colorStr, color, handler)) + return false; + if (!opacity.isEmpty()) { + bool ok = true; + qreal op = qMin(qreal(1.0), qMax(qreal(0.0), toDouble(opacity, &ok))); + if (!ok) + op = 1.0; + color.setAlphaF(op); + } + return true; +} + +static qreal parseLength(const QString &str, QSvgHandler::LengthType &type, + QSvgHandler *handler, bool *ok = NULL) +{ + QString numStr = str.trimmed(); + + if (numStr.endsWith(QLatin1Char('%'))) { + numStr.chop(1); + type = QSvgHandler::LT_PERCENT; + } else if (numStr.endsWith(QLatin1String("px"))) { + numStr.chop(2); + type = QSvgHandler::LT_PX; + } else if (numStr.endsWith(QLatin1String("pc"))) { + numStr.chop(2); + type = QSvgHandler::LT_PC; + } else if (numStr.endsWith(QLatin1String("pt"))) { + numStr.chop(2); + type = QSvgHandler::LT_PT; + } else if (numStr.endsWith(QLatin1String("mm"))) { + numStr.chop(2); + type = QSvgHandler::LT_MM; + } else if (numStr.endsWith(QLatin1String("cm"))) { + numStr.chop(2); + type = QSvgHandler::LT_CM; + } else if (numStr.endsWith(QLatin1String("in"))) { + numStr.chop(2); + type = QSvgHandler::LT_IN; + } else { + type = handler->defaultCoordinateSystem(); + //type = QSvgHandler::LT_OTHER; + } + qreal len = toDouble(numStr, ok); + //qDebug()<<"len is "<<len<<", from '"<<numStr << "'"; + return len; +} + +static inline qreal convertToNumber(const QString &str, QSvgHandler *handler, bool *ok = NULL) +{ + QSvgHandler::LengthType type; + qreal num = parseLength(str, type, handler, ok); + if (type == QSvgHandler::LT_PERCENT) { + num = num/100.0; + } + return num; +} + +static bool createSvgGlyph(QSvgFont *font, const QXmlStreamAttributes &attributes) +{ + QStringRef uncStr = attributes.value(QLatin1String("unicode")); + QStringRef havStr = attributes.value(QLatin1String("horiz-adv-x")); + QStringRef pathStr = attributes.value(QLatin1String("d")); + + QChar unicode = (uncStr.isEmpty()) ? 0 : uncStr.at(0); + qreal havx = (havStr.isEmpty()) ? -1 : toDouble(havStr); + QPainterPath path; + path.setFillRule(Qt::WindingFill); + parsePathDataFast(pathStr, path); + + font->addGlyph(unicode, path, havx); + + return true; +} + +// this should really be called convertToDefaultCoordinateSystem +// and convert when type != QSvgHandler::defaultCoordinateSystem +static qreal convertToPixels(qreal len, bool , QSvgHandler::LengthType type) +{ + + switch (type) { + case QSvgHandler::LT_PERCENT: + break; + case QSvgHandler::LT_PX: + break; + case QSvgHandler::LT_PC: + break; + case QSvgHandler::LT_PT: + return len * 1.25; + break; + case QSvgHandler::LT_MM: + return len * 3.543307; + break; + case QSvgHandler::LT_CM: + return len * 35.43307; + break; + case QSvgHandler::LT_IN: + return len * 90; + break; + case QSvgHandler::LT_OTHER: + break; + default: + break; + } + return len; +} + +static void parseColor(QSvgNode *, + const QSvgAttributes &attributes, + QSvgHandler *handler) +{ + QColor color; + if (constructColor(attributes.color, attributes.colorOpacity, color, handler)) { + handler->popColor(); + handler->pushColor(color); + } +} + +static QSvgStyleProperty *styleFromUrl(QSvgNode *node, const QString &url) +{ + return node ? node->styleProperty(idFromUrl(url)) : 0; +} + +static void parseBrush(QSvgNode *node, + const QSvgAttributes &attributes, + QSvgHandler *handler) +{ + if (!attributes.fill.isEmpty() || !attributes.fillRule.isEmpty() || !attributes.fillOpacity.isEmpty()) { + QSvgFillStyle *prop = new QSvgFillStyle; + + //fill-rule attribute handling + if (!attributes.fillRule.isEmpty() && attributes.fillRule != QT_INHERIT) { + if (attributes.fillRule == QLatin1String("evenodd")) + prop->setFillRule(Qt::OddEvenFill); + else if (attributes.fillRule == QLatin1String("nonzero")) + prop->setFillRule(Qt::WindingFill); + } + + //fill-opacity atttribute handling + if (!attributes.fillOpacity.isEmpty() && attributes.fillOpacity != QT_INHERIT) { + prop->setFillOpacity(qMin(qreal(1.0), qMax(qreal(0.0), toDouble(attributes.fillOpacity)))); + } + + //fill attribute handling + if ((!attributes.fill.isEmpty()) && (attributes.fill != QT_INHERIT) ) { + if (attributes.fill.length() > 3 && + QStringRef(attributes.fill.string(), attributes.fill.position(), 3) == QLatin1String("url")) { + QStringRef urlRef(attributes.fill.string(), attributes.fill.position() + 3, attributes.fill.length() - 3); + QString value = urlRef.toString(); + QSvgStyleProperty *style = styleFromUrl(node, value); + if (style) { + if (style->type() == QSvgStyleProperty::SOLID_COLOR || style->type() == QSvgStyleProperty::GRADIENT) + prop->setFillStyle(reinterpret_cast<QSvgFillStyleProperty *>(style)); + } else { + QString id = idFromUrl(value); + prop->setGradientId(id); + prop->setGradientResolved(false); + } + } else if (attributes.fill != QLatin1String("none")) { + QColor color; + if (resolveColor(attributes.fill, color, handler)) + prop->setBrush(QBrush(color)); + } else { + prop->setBrush(QBrush(Qt::NoBrush)); + } + } + node->appendStyleProperty(prop, attributes.id); + } +} + + + +static QMatrix parseTransformationMatrix(const QStringRef &value) +{ + if (value.isEmpty()) + return QMatrix(); + + QMatrix matrix; + const QChar *str = value.constData(); + const QChar *end = str + value.length(); + + while (str < end) { + if (str->isSpace() || *str == QLatin1Char(',')) { + ++str; + continue; + } + enum State { + Matrix, + Translate, + Rotate, + Scale, + SkewX, + SkewY + }; + State state = Matrix; + if (*str == QLatin1Char('m')) { //matrix + const char *ident = "atrix"; + for (int i = 0; i < 5; ++i) + if (*(++str) != QLatin1Char(ident[i])) + goto error; + ++str; + state = Matrix; + } else if (*str == QLatin1Char('t')) { //translate + const char *ident = "ranslate"; + for (int i = 0; i < 8; ++i) + if (*(++str) != QLatin1Char(ident[i])) + goto error; + ++str; + state = Translate; + } else if (*str == QLatin1Char('r')) { //rotate + const char *ident = "otate"; + for (int i = 0; i < 5; ++i) + if (*(++str) != QLatin1Char(ident[i])) + goto error; + ++str; + state = Rotate; + } else if (*str == QLatin1Char('s')) { //scale, skewX, skewY + ++str; + if (*str == QLatin1Char('c')) { + const char *ident = "ale"; + for (int i = 0; i < 3; ++i) + if (*(++str) != QLatin1Char(ident[i])) + goto error; + ++str; + state = Scale; + } else if (*str == QLatin1Char('k')) { + if (*(++str) != QLatin1Char('e')) + goto error; + if (*(++str) != QLatin1Char('w')) + goto error; + ++str; + if (*str == QLatin1Char('X')) + state = SkewX; + else if (*str == QLatin1Char('Y')) + state = SkewY; + else + goto error; + ++str; + } else { + goto error; + } + } else { + goto error; + } + + + while (str < end && str->isSpace()) + ++str; + if (*str != QLatin1Char('(')) + goto error; + ++str; + QVarLengthArray<qreal, 8> points; + parseNumbersArray(str, points); + if (*str != QLatin1Char(')')) + goto error; + ++str; + + if(state == Matrix) { + if(points.count() != 6) + goto error; + matrix = matrix * QMatrix(points[0], points[1], + points[2], points[3], + points[4], points[5]); + } else if (state == Translate) { + if (points.count() == 1) + matrix.translate(points[0], 0); + else if (points.count() == 2) + matrix.translate(points[0], points[1]); + else + goto error; + } else if (state == Rotate) { + if(points.count() == 1) { + matrix.rotate(points[0]); + } else if (points.count() == 3) { + matrix.translate(points[1], points[2]); + matrix.rotate(points[0]); + matrix.translate(-points[1], -points[2]); + } else { + goto error; + } + } else if (state == Scale) { + if (points.count() < 1 || points.count() > 2) + goto error; + qreal sx = points[0]; + qreal sy = sx; + if(points.count() == 2) + sy = points[1]; + matrix.scale(sx, sy); + } else if (state == SkewX) { + if (points.count() != 1) + goto error; + const qreal deg2rad = qreal(0.017453292519943295769); + matrix.shear(qTan(points[0]*deg2rad), 0); + } else if (state == SkewY) { + if (points.count() != 1) + goto error; + const qreal deg2rad = qreal(0.017453292519943295769); + matrix.shear(0, qTan(points[0]*deg2rad)); + } + } + error: + return matrix; +} + +static void parsePen(QSvgNode *node, + const QSvgAttributes &attributes, + QSvgHandler *handler) +{ + //qDebug()<<"Node "<<node->type()<<", attrs are "<<value<<width; + + if (!attributes.stroke.isEmpty() || !attributes.strokeDashArray.isEmpty() || !attributes.strokeDashOffset.isEmpty() || !attributes.strokeLineCap.isEmpty() + || !attributes.strokeLineJoin.isEmpty() || !attributes.strokeMiterLimit.isEmpty() || !attributes.strokeOpacity.isEmpty() || !attributes.strokeWidth.isEmpty() + || !attributes.vectorEffect.isEmpty()) { + + QSvgStrokeStyle *prop = new QSvgStrokeStyle; + + //stroke attribute handling + if ((!attributes.stroke.isEmpty()) && (attributes.stroke != QT_INHERIT) ) { + if (attributes.stroke.length() > 3 && + QStringRef(attributes.stroke.string(), attributes.stroke.position(), 3) == QLatin1String("url")) { + QStringRef urlRef(attributes.stroke.string(), attributes.stroke.position() + 3, attributes.stroke.length() - 3); + QString value = urlRef.toString(); + QSvgStyleProperty *style = styleFromUrl(node, value); + if (style) { + if (style->type() == QSvgStyleProperty::SOLID_COLOR || style->type() == QSvgStyleProperty::GRADIENT) + prop->setStyle(reinterpret_cast<QSvgFillStyleProperty *>(style)); + } else { + QString id = idFromUrl(value); + prop->setGradientId(id); + prop->setGradientResolved(false); + } + } else if (attributes.stroke != QLatin1String("none")) { + QColor color; + if (resolveColor(attributes.stroke, color, handler)) + prop->setStroke(QBrush(color)); + } else { + prop->setStroke(QBrush(Qt::NoBrush)); + } + } + + //stroke-width handling + if (!attributes.strokeWidth.isEmpty() && attributes.strokeWidth != QT_INHERIT) { + QSvgHandler::LengthType lt; + prop->setWidth(parseLength(attributes.strokeWidth.toString(), lt, handler)); + } + + //stroke-dasharray + if (!attributes.strokeDashArray.isEmpty() && attributes.strokeDashArray != QT_INHERIT) { + if (attributes.strokeDashArray == QLatin1String("none")) { + prop->setDashArrayNone(); + } else { + QString dashArray = attributes.strokeDashArray.toString(); + const QChar *s = dashArray.constData(); + QVector<qreal> dashes = parseNumbersList(s); + // if the dash count is odd the dashes should be duplicated + if ((dashes.size() & 1) != 0) + dashes << QVector<qreal>(dashes); + prop->setDashArray(dashes); + } + } + + //stroke-linejoin attribute handling + if (!attributes.strokeLineJoin.isEmpty()) { + if (attributes.strokeLineJoin == QLatin1String("miter")) + prop->setLineJoin(Qt::SvgMiterJoin); + else if (attributes.strokeLineJoin == QLatin1String("round")) + prop->setLineJoin(Qt::RoundJoin); + else if (attributes.strokeLineJoin == QLatin1String("bevel")) + prop->setLineJoin(Qt::BevelJoin); + } + + //stroke-linecap attribute handling + if (!attributes.strokeLineCap.isEmpty()) { + if (attributes.strokeLineCap == QLatin1String("butt")) + prop->setLineCap(Qt::FlatCap); + else if (attributes.strokeLineCap == QLatin1String("round")) + prop->setLineCap(Qt::RoundCap); + else if (attributes.strokeLineCap == QLatin1String("square")) + prop->setLineCap(Qt::SquareCap); + } + + //stroke-dashoffset attribute handling + if (!attributes.strokeDashOffset.isEmpty() && attributes.strokeDashOffset != QT_INHERIT) + prop->setDashOffset(toDouble(attributes.strokeDashOffset)); + + //vector-effect attribute handling + if (!attributes.vectorEffect.isEmpty()) { + if (attributes.vectorEffect == QLatin1String("non-scaling-stroke")) + prop->setVectorEffect(true); + else if (attributes.vectorEffect == QLatin1String("none")) + prop->setVectorEffect(false); + } + + //stroke-miterlimit + if (!attributes.strokeMiterLimit.isEmpty() && attributes.strokeMiterLimit != QT_INHERIT) + prop->setMiterLimit(toDouble(attributes.strokeMiterLimit)); + + //stroke-opacity atttribute handling + if (!attributes.strokeOpacity.isEmpty() && attributes.strokeOpacity != QT_INHERIT) + prop->setOpacity(qMin(qreal(1.0), qMax(qreal(0.0), toDouble(attributes.strokeOpacity)))); + + node->appendStyleProperty(prop, attributes.id); + } +} + +static void parseFont(QSvgNode *node, + const QSvgAttributes &attributes, + QSvgHandler *handler) +{ + if (attributes.fontFamily.isEmpty() && attributes.fontSize.isEmpty() && attributes.fontStyle.isEmpty() && + attributes.fontWeight.isEmpty() && attributes.fontVariant.isEmpty() && attributes.textAnchor.isEmpty()) + return; + + QSvgTinyDocument *doc = node->document(); + QSvgFontStyle *fontStyle = 0; + if (!attributes.fontFamily.isEmpty()) { + QSvgFont *svgFont = doc->svgFont(attributes.fontFamily.toString()); + if (svgFont) + fontStyle = new QSvgFontStyle(svgFont, doc); + } + if (!fontStyle) + fontStyle = new QSvgFontStyle; + + if (!attributes.fontFamily.isEmpty() && attributes.fontFamily != QT_INHERIT) + fontStyle->setFamily(attributes.fontFamily.toString().trimmed()); + + if (!attributes.fontSize.isEmpty() && attributes.fontSize != QT_INHERIT) { + // TODO: Support relative sizes 'larger' and 'smaller'. + QSvgHandler::LengthType dummy; // should always be pixel size + qreal size = 0; + static const qreal sizeTable[] = { qreal(6.9), qreal(8.3), qreal(10.0), qreal(12.0), qreal(14.4), qreal(17.3), qreal(20.7) }; + enum AbsFontSize { XXSmall, XSmall, Small, Medium, Large, XLarge, XXLarge }; + switch (attributes.fontSize.at(0).unicode()) { + case 'x': + if (attributes.fontSize == QLatin1String("xx-small")) + size = sizeTable[XXSmall]; + else if (attributes.fontSize == QLatin1String("x-small")) + size = sizeTable[XSmall]; + else if (attributes.fontSize == QLatin1String("x-large")) + size = sizeTable[XLarge]; + else if (attributes.fontSize == QLatin1String("xx-large")) + size = sizeTable[XXLarge]; + break; + case 's': + if (attributes.fontSize == QLatin1String("small")) + size = sizeTable[Small]; + break; + case 'm': + if (attributes.fontSize == QLatin1String("medium")) + size = sizeTable[Medium]; + break; + case 'l': + if (attributes.fontSize == QLatin1String("large")) + size = sizeTable[Large]; + break; + default: + size = parseLength(attributes.fontSize.toString(), dummy, handler); + break; + } + fontStyle->setSize(size); + } + + if (!attributes.fontStyle.isEmpty() && attributes.fontStyle != QT_INHERIT) { + if (attributes.fontStyle == QLatin1String("normal")) { + fontStyle->setStyle(QFont::StyleNormal); + } else if (attributes.fontStyle == QLatin1String("italic")) { + fontStyle->setStyle(QFont::StyleItalic); + } else if (attributes.fontStyle == QLatin1String("oblique")) { + fontStyle->setStyle(QFont::StyleOblique); + } + } + + if (!attributes.fontWeight.isEmpty() && attributes.fontWeight != QT_INHERIT) { + bool ok = false; + int weightNum = attributes.fontWeight.toString().toInt(&ok); + if (ok) { + fontStyle->setWeight(weightNum); + } else { + if (attributes.fontWeight == QLatin1String("normal")) { + fontStyle->setWeight(400); + } else if (attributes.fontWeight == QLatin1String("bold")) { + fontStyle->setWeight(700); + } else if (attributes.fontWeight == QLatin1String("bolder")) { + fontStyle->setWeight(QSvgFontStyle::BOLDER); + } else if (attributes.fontWeight == QLatin1String("lighter")) { + fontStyle->setWeight(QSvgFontStyle::LIGHTER); + } + } + } + + if (!attributes.fontVariant.isEmpty() && attributes.fontVariant != QT_INHERIT) { + if (attributes.fontVariant == QLatin1String("normal")) + fontStyle->setVariant(QFont::MixedCase); + else if (attributes.fontVariant == QLatin1String("small-caps")) + fontStyle->setVariant(QFont::SmallCaps); + } + + if (!attributes.textAnchor.isEmpty() && attributes.textAnchor != QT_INHERIT) { + if (attributes.textAnchor == QLatin1String("start")) + fontStyle->setTextAnchor(Qt::AlignLeft); + if (attributes.textAnchor == QLatin1String("middle")) + fontStyle->setTextAnchor(Qt::AlignHCenter); + else if (attributes.textAnchor == QLatin1String("end")) + fontStyle->setTextAnchor(Qt::AlignRight); + } + + node->appendStyleProperty(fontStyle, attributes.id); +} + +static void parseTransform(QSvgNode *node, + const QSvgAttributes &attributes, + QSvgHandler *) +{ + if (attributes.transform.isEmpty()) + return; + QMatrix matrix = parseTransformationMatrix(trimRef(attributes.transform)); + + if (!matrix.isIdentity()) { + node->appendStyleProperty(new QSvgTransformStyle(QTransform(matrix)), attributes.id); + } + +} + +static void parseVisibility(QSvgNode *node, + const QSvgAttributes &attributes, + QSvgHandler *) +{ + QSvgNode *parent = node->parent(); + + if (parent && (attributes.visibility.isEmpty() || attributes.visibility == QT_INHERIT)) + node->setVisible(parent->isVisible()); + else if (attributes.visibility == QLatin1String("hidden") || attributes.visibility == QLatin1String("collapse")) { + node->setVisible(false); + } else + node->setVisible(true); +} + +static void pathArcSegment(QPainterPath &path, + qreal xc, qreal yc, + qreal th0, qreal th1, + qreal rx, qreal ry, qreal xAxisRotation) +{ + qreal sinTh, cosTh; + qreal a00, a01, a10, a11; + qreal x1, y1, x2, y2, x3, y3; + qreal t; + qreal thHalf; + + sinTh = qSin(xAxisRotation * (Q_PI / 180.0)); + cosTh = qCos(xAxisRotation * (Q_PI / 180.0)); + + a00 = cosTh * rx; + a01 = -sinTh * ry; + a10 = sinTh * rx; + a11 = cosTh * ry; + + thHalf = 0.5 * (th1 - th0); + t = (8.0 / 3.0) * qSin(thHalf * 0.5) * qSin(thHalf * 0.5) / qSin(thHalf); + x1 = xc + qCos(th0) - t * qSin(th0); + y1 = yc + qSin(th0) + t * qCos(th0); + x3 = xc + qCos(th1); + y3 = yc + qSin(th1); + x2 = x3 + t * qSin(th1); + y2 = y3 - t * qCos(th1); + + path.cubicTo(a00 * x1 + a01 * y1, a10 * x1 + a11 * y1, + a00 * x2 + a01 * y2, a10 * x2 + a11 * y2, + a00 * x3 + a01 * y3, a10 * x3 + a11 * y3); +} + +// the arc handling code underneath is from XSVG (BSD license) +/* + * Copyright 2002 USC/Information Sciences Institute + * + * Permission to use, copy, modify, distribute, and sell this software + * and its documentation for any purpose is hereby granted without + * fee, provided that the above copyright notice appear in all copies + * and that both that copyright notice and this permission notice + * appear in supporting documentation, and that the name of + * Information Sciences Institute not be used in advertising or + * publicity pertaining to distribution of the software without + * specific, written prior permission. Information Sciences Institute + * makes no representations about the suitability of this software for + * any purpose. It is provided "as is" without express or implied + * warranty. + * + * INFORMATION SCIENCES INSTITUTE DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL INFORMATION SCIENCES + * INSTITUTE BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA + * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ +static void pathArc(QPainterPath &path, + qreal rx, + qreal ry, + qreal x_axis_rotation, + int large_arc_flag, + int sweep_flag, + qreal x, + qreal y, + qreal curx, qreal cury) +{ + qreal sin_th, cos_th; + qreal a00, a01, a10, a11; + qreal x0, y0, x1, y1, xc, yc; + qreal d, sfactor, sfactor_sq; + qreal th0, th1, th_arc; + int i, n_segs; + qreal dx, dy, dx1, dy1, Pr1, Pr2, Px, Py, check; + + rx = qAbs(rx); + ry = qAbs(ry); + + sin_th = qSin(x_axis_rotation * (Q_PI / 180.0)); + cos_th = qCos(x_axis_rotation * (Q_PI / 180.0)); + + dx = (curx - x) / 2.0; + dy = (cury - y) / 2.0; + dx1 = cos_th * dx + sin_th * dy; + dy1 = -sin_th * dx + cos_th * dy; + Pr1 = rx * rx; + Pr2 = ry * ry; + Px = dx1 * dx1; + Py = dy1 * dy1; + /* Spec : check if radii are large enough */ + check = Px / Pr1 + Py / Pr2; + if (check > 1) { + rx = rx * qSqrt(check); + ry = ry * qSqrt(check); + } + + a00 = cos_th / rx; + a01 = sin_th / rx; + a10 = -sin_th / ry; + a11 = cos_th / ry; + x0 = a00 * curx + a01 * cury; + y0 = a10 * curx + a11 * cury; + x1 = a00 * x + a01 * y; + y1 = a10 * x + a11 * y; + /* (x0, y0) is current point in transformed coordinate space. + (x1, y1) is new point in transformed coordinate space. + + The arc fits a unit-radius circle in this space. + */ + d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0); + sfactor_sq = 1.0 / d - 0.25; + if (sfactor_sq < 0) sfactor_sq = 0; + sfactor = qSqrt(sfactor_sq); + if (sweep_flag == large_arc_flag) sfactor = -sfactor; + xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0); + yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0); + /* (xc, yc) is center of the circle. */ + + th0 = qAtan2(y0 - yc, x0 - xc); + th1 = qAtan2(y1 - yc, x1 - xc); + + th_arc = th1 - th0; + if (th_arc < 0 && sweep_flag) + th_arc += 2 * Q_PI; + else if (th_arc > 0 && !sweep_flag) + th_arc -= 2 * Q_PI; + + n_segs = qCeil(qAbs(th_arc / (Q_PI * 0.5 + 0.001))); + + for (i = 0; i < n_segs; i++) { + pathArcSegment(path, xc, yc, + th0 + i * th_arc / n_segs, + th0 + (i + 1) * th_arc / n_segs, + rx, ry, x_axis_rotation); + } +} + +static bool parsePathDataFast(const QStringRef &dataStr, QPainterPath &path) +{ + qreal x0 = 0, y0 = 0; // starting point + qreal x = 0, y = 0; // current point + char lastMode = 0; + QPointF ctrlPt; + const QChar *str = dataStr.constData(); + const QChar *end = str + dataStr.size(); + + while (str != end) { + while (str->isSpace()) + ++str; + QChar pathElem = *str; + ++str; + QChar endc = *end; + *const_cast<QChar *>(end) = 0; // parseNumbersArray requires 0-termination that QStringRef cannot guarantee + QVarLengthArray<qreal, 8> arg; + parseNumbersArray(str, arg); + *const_cast<QChar *>(end) = endc; + if (pathElem == QLatin1Char('z') || pathElem == QLatin1Char('Z')) + arg.append(0);//dummy + const qreal *num = arg.constData(); + int count = arg.count(); + while (count > 0) { + qreal offsetX = x; // correction offsets + qreal offsetY = y; // for relative commands + switch (pathElem.unicode()) { + case 'm': { + if (count < 2) { + num++; + count--; + break; + } + x = x0 = num[0] + offsetX; + y = y0 = num[1] + offsetY; + num += 2; + count -= 2; + path.moveTo(x0, y0); + + // As per 1.2 spec 8.3.2 The "moveto" commands + // If a 'moveto' is followed by multiple pairs of coordinates without explicit commands, + // the subsequent pairs shall be treated as implicit 'lineto' commands. + pathElem = QLatin1Char('l'); + } + break; + case 'M': { + if (count < 2) { + num++; + count--; + break; + } + x = x0 = num[0]; + y = y0 = num[1]; + num += 2; + count -= 2; + path.moveTo(x0, y0); + + // As per 1.2 spec 8.3.2 The "moveto" commands + // If a 'moveto' is followed by multiple pairs of coordinates without explicit commands, + // the subsequent pairs shall be treated as implicit 'lineto' commands. + pathElem = QLatin1Char('L'); + } + break; + case 'z': + case 'Z': { + x = x0; + y = y0; + count--; // skip dummy + num++; + path.closeSubpath(); + } + break; + case 'l': { + if (count < 2) { + num++; + count--; + break; + } + x = num[0] + offsetX; + y = num[1] + offsetY; + num += 2; + count -= 2; + path.lineTo(x, y); + + } + break; + case 'L': { + if (count < 2) { + num++; + count--; + break; + } + x = num[0]; + y = num[1]; + num += 2; + count -= 2; + path.lineTo(x, y); + } + break; + case 'h': { + x = num[0] + offsetX; + num++; + count--; + path.lineTo(x, y); + } + break; + case 'H': { + x = num[0]; + num++; + count--; + path.lineTo(x, y); + } + break; + case 'v': { + y = num[0] + offsetY; + num++; + count--; + path.lineTo(x, y); + } + break; + case 'V': { + y = num[0]; + num++; + count--; + path.lineTo(x, y); + } + break; + case 'c': { + if (count < 6) { + num += count; + count = 0; + break; + } + QPointF c1(num[0] + offsetX, num[1] + offsetY); + QPointF c2(num[2] + offsetX, num[3] + offsetY); + QPointF e(num[4] + offsetX, num[5] + offsetY); + num += 6; + count -= 6; + path.cubicTo(c1, c2, e); + ctrlPt = c2; + x = e.x(); + y = e.y(); + break; + } + case 'C': { + if (count < 6) { + num += count; + count = 0; + break; + } + QPointF c1(num[0], num[1]); + QPointF c2(num[2], num[3]); + QPointF e(num[4], num[5]); + num += 6; + count -= 6; + path.cubicTo(c1, c2, e); + ctrlPt = c2; + x = e.x(); + y = e.y(); + break; + } + case 's': { + if (count < 4) { + num += count; + count = 0; + break; + } + QPointF c1; + if (lastMode == 'c' || lastMode == 'C' || + lastMode == 's' || lastMode == 'S') + c1 = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y()); + else + c1 = QPointF(x, y); + QPointF c2(num[0] + offsetX, num[1] + offsetY); + QPointF e(num[2] + offsetX, num[3] + offsetY); + num += 4; + count -= 4; + path.cubicTo(c1, c2, e); + ctrlPt = c2; + x = e.x(); + y = e.y(); + break; + } + case 'S': { + if (count < 4) { + num += count; + count = 0; + break; + } + QPointF c1; + if (lastMode == 'c' || lastMode == 'C' || + lastMode == 's' || lastMode == 'S') + c1 = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y()); + else + c1 = QPointF(x, y); + QPointF c2(num[0], num[1]); + QPointF e(num[2], num[3]); + num += 4; + count -= 4; + path.cubicTo(c1, c2, e); + ctrlPt = c2; + x = e.x(); + y = e.y(); + break; + } + case 'q': { + if (count < 4) { + num += count; + count = 0; + break; + } + QPointF c(num[0] + offsetX, num[1] + offsetY); + QPointF e(num[2] + offsetX, num[3] + offsetY); + num += 4; + count -= 4; + path.quadTo(c, e); + ctrlPt = c; + x = e.x(); + y = e.y(); + break; + } + case 'Q': { + if (count < 4) { + num += count; + count = 0; + break; + } + QPointF c(num[0], num[1]); + QPointF e(num[2], num[3]); + num += 4; + count -= 4; + path.quadTo(c, e); + ctrlPt = c; + x = e.x(); + y = e.y(); + break; + } + case 't': { + if (count < 2) { + num += count; + count = 0; + break; + } + QPointF e(num[0] + offsetX, num[1] + offsetY); + num += 2; + count -= 2; + QPointF c; + if (lastMode == 'q' || lastMode == 'Q' || + lastMode == 't' || lastMode == 'T') + c = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y()); + else + c = QPointF(x, y); + path.quadTo(c, e); + ctrlPt = c; + x = e.x(); + y = e.y(); + break; + } + case 'T': { + if (count < 2) { + num += count; + count = 0; + break; + } + QPointF e(num[0], num[1]); + num += 2; + count -= 2; + QPointF c; + if (lastMode == 'q' || lastMode == 'Q' || + lastMode == 't' || lastMode == 'T') + c = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y()); + else + c = QPointF(x, y); + path.quadTo(c, e); + ctrlPt = c; + x = e.x(); + y = e.y(); + break; + } + case 'a': { + if (count < 7) { + num += count; + count = 0; + break; + } + qreal rx = (*num++); + qreal ry = (*num++); + qreal xAxisRotation = (*num++); + qreal largeArcFlag = (*num++); + qreal sweepFlag = (*num++); + qreal ex = (*num++) + offsetX; + qreal ey = (*num++) + offsetY; + count -= 7; + qreal curx = x; + qreal cury = y; + pathArc(path, rx, ry, xAxisRotation, int(largeArcFlag), + int(sweepFlag), ex, ey, curx, cury); + + x = ex; + y = ey; + } + break; + case 'A': { + if (count < 7) { + num += count; + count = 0; + break; + } + qreal rx = (*num++); + qreal ry = (*num++); + qreal xAxisRotation = (*num++); + qreal largeArcFlag = (*num++); + qreal sweepFlag = (*num++); + qreal ex = (*num++); + qreal ey = (*num++); + count -= 7; + qreal curx = x; + qreal cury = y; + pathArc(path, rx, ry, xAxisRotation, int(largeArcFlag), + int(sweepFlag), ex, ey, curx, cury); + + x = ex; + y = ey; + } + break; + default: + return false; + } + lastMode = pathElem.toLatin1(); + } + } + return true; +} + +static bool parseStyle(QSvgNode *node, + const QXmlStreamAttributes &attributes, + QSvgHandler *); + +static bool parseStyle(QSvgNode *node, + const QSvgAttributes &attributes, + QSvgHandler *); + +static void parseCSStoXMLAttrs(const QVector<QCss::Declaration> &declarations, + QXmlStreamAttributes &attributes) +{ + for (int i = 0; i < declarations.count(); ++i) { + const QCss::Declaration &decl = declarations.at(i); + if (decl.d->property.isEmpty()) + continue; + QCss::Value val = decl.d->values.first(); + QString valueStr; + if (decl.d->values.count() != 1) { + for (int i=0; i<decl.d->values.count(); ++i) { + const QString &value = decl.d->values[i].toString(); + if (value.isEmpty()) + valueStr += QLatin1Char(','); + else + valueStr += value; + } + } else { + valueStr = val.toString(); + } + if (val.type == QCss::Value::Uri) { + valueStr.prepend(QLatin1String("url(")); + valueStr.append(QLatin1Char(')')); + } else if (val.type == QCss::Value::Function) { + QStringList lst = val.variant.toStringList(); + valueStr.append(lst.at(0)); + valueStr.append(QLatin1Char('(')); + for (int i = 1; i < lst.count(); ++i) { + valueStr.append(lst.at(i)); + if ((i +1) < lst.count()) + valueStr.append(QLatin1Char(',')); + } + valueStr.append(QLatin1Char(')')); + } else if (val.type == QCss::Value::KnownIdentifier) { + switch (val.variant.toInt()) { + case QCss::Value_None: + valueStr = QLatin1String("none"); + break; + default: + break; + } + } + + attributes.append(QString(), decl.d->property, valueStr); + } +} + +void QSvgHandler::parseCSStoXMLAttrs(QString css, QVector<QSvgCssAttribute> *attributes) +{ + // preprocess (for unicode escapes), tokenize and remove comments + m_cssParser.init(css); + QString key; + + attributes->reserve(10); + + while (m_cssParser.hasNext()) { + m_cssParser.skipSpace(); + + if (!m_cssParser.hasNext()) + break; + m_cssParser.next(); + + QStringRef name; + if (m_cssParser.hasEscapeSequences) { + key = m_cssParser.lexem(); + name = QStringRef(&key, 0, key.length()); + } else { + const QCss::Symbol &sym = m_cssParser.symbol(); + name = QStringRef(&sym.text, sym.start, sym.len); + } + + m_cssParser.skipSpace(); + if (!m_cssParser.test(QCss::COLON)) + break; + + m_cssParser.skipSpace(); + if (!m_cssParser.hasNext()) + break; + + QSvgCssAttribute attribute; + attribute.name = QXmlStreamStringRef(name); + + const int firstSymbol = m_cssParser.index; + int symbolCount = 0; + do { + m_cssParser.next(); + ++symbolCount; + } while (m_cssParser.hasNext() && !m_cssParser.test(QCss::SEMICOLON)); + + bool canExtractValueByRef = !m_cssParser.hasEscapeSequences; + if (canExtractValueByRef) { + int len = m_cssParser.symbols.at(firstSymbol).len; + for (int i = firstSymbol + 1; i < firstSymbol + symbolCount; ++i) { + len += m_cssParser.symbols.at(i).len; + + if (m_cssParser.symbols.at(i - 1).start + m_cssParser.symbols.at(i - 1).len + != m_cssParser.symbols.at(i).start) { + canExtractValueByRef = false; + break; + } + } + if (canExtractValueByRef) { + const QCss::Symbol &sym = m_cssParser.symbols.at(firstSymbol); + attribute.value = QXmlStreamStringRef(QStringRef(&sym.text, sym.start, len)); + } + } + if (!canExtractValueByRef) { + QString value; + for (int i = firstSymbol; i < m_cssParser.index - 1; ++i) + value += m_cssParser.symbols.at(i).lexem(); + attribute.value = QXmlStreamStringRef(QStringRef(&value, 0, value.length())); + } + + attributes->append(attribute); + + m_cssParser.skipSpace(); + } +} + +static void cssStyleLookup(QSvgNode *node, + QSvgHandler *handler, + QSvgStyleSelector *selector) +{ + QCss::StyleSelector::NodePtr cssNode; + cssNode.ptr = node; + QVector<QCss::Declaration> decls = selector->declarationsForNode(cssNode); + + QXmlStreamAttributes attributes; + parseCSStoXMLAttrs(decls, attributes); + parseStyle(node, attributes, handler); +} + +static inline QStringList stringToList(const QString &str) +{ + QStringList lst = str.split(QLatin1Char(','), QString::SkipEmptyParts); + return lst; +} + +static bool parseCoreNode(QSvgNode *node, + const QXmlStreamAttributes &attributes) +{ + QStringList features; + QStringList extensions; + QStringList languages; + QStringList formats; + QStringList fonts; + QString xmlClassStr; + + for (int i = 0; i < attributes.count(); ++i) { + const QXmlStreamAttribute &attribute = attributes.at(i); + QStringRef name = attribute.qualifiedName(); + if (name.isEmpty()) + continue; + QStringRef value = attribute.value(); + switch (name.at(0).unicode()) { + case 'c': + if (name == QLatin1String("class")) + xmlClassStr = value.toString(); + break; + case 'r': + if (name == QLatin1String("requiredFeatures")) + features = stringToList(value.toString()); + else if (name == QLatin1String("requiredExtensions")) + extensions = stringToList(value.toString()); + else if (name == QLatin1String("requiredFormats")) + formats = stringToList(value.toString()); + else if (name == QLatin1String("requiredFonts")) + fonts = stringToList(value.toString()); + break; + case 's': + if (name == QLatin1String("systemLanguage")) + languages = stringToList(value.toString()); + break; + default: + break; + } + } + + node->setRequiredFeatures(features); + node->setRequiredExtensions(extensions); + node->setRequiredLanguages(languages); + node->setRequiredFormats(formats); + node->setRequiredFonts(fonts); + node->setNodeId(someId(attributes)); + node->setXmlClass(xmlClassStr); + + return true; +} + +static void parseOpacity(QSvgNode *node, + const QSvgAttributes &attributes, + QSvgHandler *) +{ + if (attributes.opacity.isEmpty()) + return; + + const QString value = attributes.opacity.toString().trimmed(); + + bool ok = false; + qreal op = value.toDouble(&ok); + + if (ok) { + QSvgOpacityStyle *opacity = new QSvgOpacityStyle(qBound(qreal(0.0), op, qreal(1.0))); + node->appendStyleProperty(opacity, attributes.id); + } +} + +static QPainter::CompositionMode svgToQtCompositionMode(const QString &op) +{ +#define NOOP qDebug()<<"Operation: "<<op<<" is not implemented" + if (op == QLatin1String("clear")) { + return QPainter::CompositionMode_Clear; + } else if (op == QLatin1String("src")) { + return QPainter::CompositionMode_Source; + } else if (op == QLatin1String("dst")) { + return QPainter::CompositionMode_Destination; + } else if (op == QLatin1String("src-over")) { + return QPainter::CompositionMode_SourceOver; + } else if (op == QLatin1String("dst-over")) { + return QPainter::CompositionMode_DestinationOver; + } else if (op == QLatin1String("src-in")) { + return QPainter::CompositionMode_SourceIn; + } else if (op == QLatin1String("dst-in")) { + return QPainter::CompositionMode_DestinationIn; + } else if (op == QLatin1String("src-out")) { + return QPainter::CompositionMode_SourceOut; + } else if (op == QLatin1String("dst-out")) { + return QPainter::CompositionMode_DestinationOut; + } else if (op == QLatin1String("src-atop")) { + return QPainter::CompositionMode_SourceAtop; + } else if (op == QLatin1String("dst-atop")) { + return QPainter::CompositionMode_DestinationAtop; + } else if (op == QLatin1String("xor")) { + return QPainter::CompositionMode_Xor; + } else if (op == QLatin1String("plus")) { + return QPainter::CompositionMode_Plus; + } else if (op == QLatin1String("multiply")) { + return QPainter::CompositionMode_Multiply; + } else if (op == QLatin1String("screen")) { + return QPainter::CompositionMode_Screen; + } else if (op == QLatin1String("overlay")) { + return QPainter::CompositionMode_Overlay; + } else if (op == QLatin1String("darken")) { + return QPainter::CompositionMode_Darken; + } else if (op == QLatin1String("lighten")) { + return QPainter::CompositionMode_Lighten; + } else if (op == QLatin1String("color-dodge")) { + return QPainter::CompositionMode_ColorDodge; + } else if (op == QLatin1String("color-burn")) { + return QPainter::CompositionMode_ColorBurn; + } else if (op == QLatin1String("hard-light")) { + return QPainter::CompositionMode_HardLight; + } else if (op == QLatin1String("soft-light")) { + return QPainter::CompositionMode_SoftLight; + } else if (op == QLatin1String("difference")) { + return QPainter::CompositionMode_Difference; + } else if (op == QLatin1String("exclusion")) { + return QPainter::CompositionMode_Exclusion; + } else { + NOOP; + } + + return QPainter::CompositionMode_SourceOver; +} + +static void parseCompOp(QSvgNode *node, + const QSvgAttributes &attributes, + QSvgHandler *) +{ + if (attributes.compOp.isEmpty()) + return; + QString value = attributes.compOp.toString().trimmed(); + + if (!value.isEmpty()) { + QSvgCompOpStyle *compop = new QSvgCompOpStyle(svgToQtCompositionMode(value)); + node->appendStyleProperty(compop, attributes.id); + } +} + +static inline QSvgNode::DisplayMode displayStringToEnum(const QString &str) +{ + if (str == QLatin1String("inline")) { + return QSvgNode::InlineMode; + } else if (str == QLatin1String("block")) { + return QSvgNode::BlockMode; + } else if (str == QLatin1String("list-item")) { + return QSvgNode::ListItemMode; + } else if (str == QLatin1String("run-in")) { + return QSvgNode::RunInMode; + } else if (str == QLatin1String("compact")) { + return QSvgNode::CompactMode; + } else if (str == QLatin1String("marker")) { + return QSvgNode::MarkerMode; + } else if (str == QLatin1String("table")) { + return QSvgNode::TableMode; + } else if (str == QLatin1String("inline-table")) { + return QSvgNode::InlineTableMode; + } else if (str == QLatin1String("table-row")) { + return QSvgNode::TableRowGroupMode; + } else if (str == QLatin1String("table-header-group")) { + return QSvgNode::TableHeaderGroupMode; + } else if (str == QLatin1String("table-footer-group")) { + return QSvgNode::TableFooterGroupMode; + } else if (str == QLatin1String("table-row")) { + return QSvgNode::TableRowMode; + } else if (str == QLatin1String("table-column-group")) { + return QSvgNode::TableColumnGroupMode; + } else if (str == QLatin1String("table-column")) { + return QSvgNode::TableColumnMode; + } else if (str == QLatin1String("table-cell")) { + return QSvgNode::TableCellMode; + } else if (str == QLatin1String("table-caption")) { + return QSvgNode::TableCaptionMode; + } else if (str == QLatin1String("none")) { + return QSvgNode::NoneMode; + } else if (str == QT_INHERIT) { + return QSvgNode::InheritMode; + } + return QSvgNode::BlockMode; +} + +static void parseOthers(QSvgNode *node, + const QSvgAttributes &attributes, + QSvgHandler *) +{ + if (attributes.display.isEmpty()) + return; + QString displayStr = attributes.display.toString().trimmed(); + + if (!displayStr.isEmpty()) { + node->setDisplayMode(displayStringToEnum(displayStr)); + } +} + +static bool parseStyle(QSvgNode *node, + const QSvgAttributes &attributes, + QSvgHandler *handler) +{ + parseColor(node, attributes, handler); + parseBrush(node, attributes, handler); + parsePen(node, attributes, handler); + parseFont(node, attributes, handler); + parseTransform(node, attributes, handler); + parseVisibility(node, attributes, handler); + parseOpacity(node, attributes, handler); + parseCompOp(node, attributes, handler); + parseOthers(node, attributes, handler); +#if 0 + value = attributes.value("audio-level"); + + value = attributes.value("color-rendering"); + + value = attributes.value("display-align"); + + value = attributes.value("image-rendering"); + + value = attributes.value("line-increment"); + + value = attributes.value("pointer-events"); + + value = attributes.value("shape-rendering"); + + value = attributes.value("solid-color"); + + value = attributes.value("solid-opacity"); + + value = attributes.value("text-rendering"); + + value = attributes.value("vector-effect"); + + value = attributes.value("viewport-fill"); + + value = attributes.value("viewport-fill-opacity"); +#endif + return true; +} + +static bool parseStyle(QSvgNode *node, + const QXmlStreamAttributes &attrs, + QSvgHandler *handler) +{ + return parseStyle(node, QSvgAttributes(attrs, handler), handler); +} + +static bool parseAnchorNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + Q_UNUSED(parent); Q_UNUSED(attributes); + return true; +} + +static bool parseAnimateNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + Q_UNUSED(parent); Q_UNUSED(attributes); + return true; +} + +static bool parseAnimateColorNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *handler) +{ + QString typeStr = attributes.value(QLatin1String("type")).toString(); + QStringRef fromStr = attributes.value(QLatin1String("from")); + QStringRef toStr = attributes.value(QLatin1String("to")); + QString valuesStr = attributes.value(QLatin1String("values")).toString(); + QString beginStr = attributes.value(QLatin1String("begin")).toString(); + QString durStr = attributes.value(QLatin1String("dur")).toString(); + QString targetStr = attributes.value(QLatin1String("attributeName")).toString(); + QString repeatStr = attributes.value(QLatin1String("repeatCount")).toString(); + QString fillStr = attributes.value(QLatin1String("fill")).toString(); + + QList<QColor> colors; + if (valuesStr.isEmpty()) { + QColor startColor, endColor; + resolveColor(fromStr, startColor, handler); + resolveColor(toStr, endColor, handler); + colors.append(startColor); + colors.append(endColor); + } else { + QStringList str = valuesStr.split(QLatin1Char(';')); + QStringList::const_iterator itr; + for (itr = str.constBegin(); itr != str.constEnd(); ++itr) { + QColor color; + QString str = *itr; + resolveColor(QStringRef(&str), color, handler); + colors.append(color); + } + } + + int ms = 1000; + beginStr = beginStr.trimmed(); + if (beginStr.endsWith(QLatin1String("ms"))) { + beginStr.chop(2); + ms = 1; + } else if (beginStr.endsWith(QLatin1String("s"))) { + beginStr.chop(1); + } + durStr = durStr.trimmed(); + if (durStr.endsWith(QLatin1String("ms"))) { + durStr.chop(2); + ms = 1; + } else if (durStr.endsWith(QLatin1String("s"))) { + durStr.chop(1); + } + int begin = static_cast<int>(toDouble(beginStr) * ms); + int end = static_cast<int>((toDouble(durStr) + begin) * ms); + + QSvgAnimateColor *anim = new QSvgAnimateColor(begin, end, 0); + anim->setArgs((targetStr == QLatin1String("fill")), colors); + anim->setFreeze(fillStr == QLatin1String("freeze")); + anim->setRepeatCount( + (repeatStr == QLatin1String("indefinite")) ? -1 : + (repeatStr == QLatin1String("")) ? 1 : toDouble(repeatStr)); + + parent->appendStyleProperty(anim, someId(attributes)); + parent->document()->setAnimated(true); + handler->setAnimPeriod(begin, end); + return true; +} + +static bool parseAimateMotionNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + Q_UNUSED(parent); Q_UNUSED(attributes); + return true; +} + +static void parseNumberTriplet(QVector<qreal> &values, const QChar *&s) +{ + QVector<qreal> list = parseNumbersList(s); + values << list; + for (int i = 3 - list.size(); i > 0; --i) + values.append(0.0); +} + +static bool parseAnimateTransformNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *handler) +{ + QString typeStr = attributes.value(QLatin1String("type")).toString(); + QString values = attributes.value(QLatin1String("values")).toString(); + QString beginStr = attributes.value(QLatin1String("begin")).toString(); + QString durStr = attributes.value(QLatin1String("dur")).toString(); + QString targetStr = attributes.value(QLatin1String("attributeName")).toString(); + QString repeatStr = attributes.value(QLatin1String("repeatCount")).toString(); + QString fillStr = attributes.value(QLatin1String("fill")).toString(); + QString fromStr = attributes.value(QLatin1String("from")).toString(); + QString toStr = attributes.value(QLatin1String("to")).toString(); + QString byStr = attributes.value(QLatin1String("by")).toString(); + QString addtv = attributes.value(QLatin1String("additive")).toString(); + + QSvgAnimateTransform::Additive additive = QSvgAnimateTransform::Replace; + if (addtv == QLatin1String("sum")) + additive = QSvgAnimateTransform::Sum; + + QVector<qreal> vals; + if (values.isEmpty()) { + const QChar *s; + if (fromStr.isEmpty()) { + if (!byStr.isEmpty()) { + // By-animation. + additive = QSvgAnimateTransform::Sum; + vals.append(0.0); + vals.append(0.0); + vals.append(0.0); + parseNumberTriplet(vals, s = byStr.constData()); + } else { + // To-animation not defined. + return false; + } + } else { + if (!toStr.isEmpty()) { + // From-to-animation. + parseNumberTriplet(vals, s = fromStr.constData()); + parseNumberTriplet(vals, s = toStr.constData()); + } else if (!byStr.isEmpty()) { + // From-by-animation. + parseNumberTriplet(vals, s = fromStr.constData()); + parseNumberTriplet(vals, s = byStr.constData()); + for (int i = vals.size() - 3; i < vals.size(); ++i) + vals[i] += vals[i - 3]; + } else { + return false; + } + } + } else { + const QChar *s = values.constData(); + while (s && *s != QLatin1Char(0)) { + parseNumberTriplet(vals, s); + if (*s == QLatin1Char(0)) + break; + ++s; + } + } + + int ms = 1000; + beginStr = beginStr.trimmed(); + if (beginStr.endsWith(QLatin1String("ms"))) { + beginStr.chop(2); + ms = 1; + } else if (beginStr.endsWith(QLatin1String("s"))) { + beginStr.chop(1); + } + int begin = static_cast<int>(toDouble(beginStr) * ms); + durStr = durStr.trimmed(); + if (durStr.endsWith(QLatin1String("ms"))) { + durStr.chop(2); + ms = 1; + } else if (durStr.endsWith(QLatin1String("s"))) { + durStr.chop(1); + ms = 1000; + } + int end = static_cast<int>(toDouble(durStr)*ms) + begin; + + QSvgAnimateTransform::TransformType type = QSvgAnimateTransform::Empty; + if (typeStr == QLatin1String("translate")) { + type = QSvgAnimateTransform::Translate; + } else if (typeStr == QLatin1String("scale")) { + type = QSvgAnimateTransform::Scale; + } else if (typeStr == QLatin1String("rotate")) { + type = QSvgAnimateTransform::Rotate; + } else if (typeStr == QLatin1String("skewX")) { + type = QSvgAnimateTransform::SkewX; + } else if (typeStr == QLatin1String("skewY")) { + type = QSvgAnimateTransform::SkewY; + } else { + return false; + } + + QSvgAnimateTransform *anim = new QSvgAnimateTransform(begin, end, 0); + anim->setArgs(type, additive, vals); + anim->setFreeze(fillStr == QLatin1String("freeze")); + anim->setRepeatCount( + (repeatStr == QLatin1String("indefinite"))? -1 : + (repeatStr == QLatin1String(""))? 1 : toDouble(repeatStr)); + + parent->appendStyleProperty(anim, someId(attributes)); + parent->document()->setAnimated(true); + handler->setAnimPeriod(begin, end); + return true; +} + +static QSvgNode * createAnimationNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + Q_UNUSED(parent); Q_UNUSED(attributes); + return 0; +} + +static bool parseAudioNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + Q_UNUSED(parent); Q_UNUSED(attributes); + return true; +} + +static QSvgNode *createCircleNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + QString cx = attributes.value(QLatin1String("cx")).toString(); + QString cy = attributes.value(QLatin1String("cy")).toString(); + QString r = attributes.value(QLatin1String("r")).toString(); + qreal ncx = toDouble(cx); + qreal ncy = toDouble(cy); + qreal nr = toDouble(r); + + QRectF rect(ncx-nr, ncy-nr, nr*2, nr*2); + QSvgNode *circle = new QSvgCircle(parent, rect); + return circle; +} + +static QSvgNode *createDefsNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + Q_UNUSED(attributes); + QSvgDefs *defs = new QSvgDefs(parent); + return defs; +} + +static bool parseDescNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + Q_UNUSED(parent); Q_UNUSED(attributes); + return true; +} + +static bool parseDiscardNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + Q_UNUSED(parent); Q_UNUSED(attributes); + return true; +} + +static QSvgNode *createEllipseNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + QString cx = attributes.value(QLatin1String("cx")).toString(); + QString cy = attributes.value(QLatin1String("cy")).toString(); + QString rx = attributes.value(QLatin1String("rx")).toString(); + QString ry = attributes.value(QLatin1String("ry")).toString(); + qreal ncx = toDouble(cx); + qreal ncy = toDouble(cy); + qreal nrx = toDouble(rx); + qreal nry = toDouble(ry); + + QRectF rect(ncx-nrx, ncy-nry, nrx*2, nry*2); + QSvgNode *ellipse = new QSvgEllipse(parent, rect); + return ellipse; +} + +static QSvgStyleProperty *createFontNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + QString hax = attributes.value(QLatin1String("horiz-adv-x")).toString(); + QString myId = someId(attributes); + + qreal horizAdvX = toDouble(hax); + + while (parent && parent->type() != QSvgNode::DOC) { + parent = parent->parent(); + } + + if (parent) { + 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); + } + return new QSvgFontStyle(font, doc); + } + return 0; +} + +static bool parseFontFaceNode(QSvgStyleProperty *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + if (parent->type() != QSvgStyleProperty::FONT) { + return false; + } + + QSvgFontStyle *style = static_cast<QSvgFontStyle*>(parent); + QSvgFont *font = style->svgFont(); + QString name = attributes.value(QLatin1String("font-family")).toString(); + QString unitsPerEmStr = attributes.value(QLatin1String("units-per-em")).toString(); + + qreal unitsPerEm = toDouble(unitsPerEmStr); + if (!unitsPerEm) + unitsPerEm = 1000; + + if (!name.isEmpty()) + font->setFamilyName(name); + font->setUnitsPerEm(unitsPerEm); + + if (!font->familyName().isEmpty()) + if (!style->doc()->svgFont(font->familyName())) + style->doc()->addSvgFont(font); + + return true; +} + +static bool parseFontFaceNameNode(QSvgStyleProperty *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + if (parent->type() != QSvgStyleProperty::FONT) { + return false; + } + + QSvgFontStyle *style = static_cast<QSvgFontStyle*>(parent); + QSvgFont *font = style->svgFont(); + QString name = attributes.value(QLatin1String("name")).toString(); + + if (!name.isEmpty()) + font->setFamilyName(name); + + if (!font->familyName().isEmpty()) + if (!style->doc()->svgFont(font->familyName())) + style->doc()->addSvgFont(font); + + return true; +} + +static bool parseFontFaceSrcNode(QSvgStyleProperty *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + Q_UNUSED(parent); Q_UNUSED(attributes); + return true; +} + +static bool parseFontFaceUriNode(QSvgStyleProperty *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + Q_UNUSED(parent); Q_UNUSED(attributes); + return true; +} + +static bool parseForeignObjectNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + Q_UNUSED(parent); Q_UNUSED(attributes); + return true; +} + +static QSvgNode *createGNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + Q_UNUSED(attributes); + QSvgG *node = new QSvgG(parent); + return node; +} + +static bool parseGlyphNode(QSvgStyleProperty *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + if (parent->type() != QSvgStyleProperty::FONT) { + return false; + } + + QSvgFontStyle *style = static_cast<QSvgFontStyle*>(parent); + QSvgFont *font = style->svgFont(); + createSvgGlyph(font, attributes); + return true; +} + +static bool parseHandlerNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + Q_UNUSED(parent); Q_UNUSED(attributes); + return true; +} + +static bool parseHkernNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + Q_UNUSED(parent); Q_UNUSED(attributes); + return true; +} + +static QSvgNode *createImageNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *handler) +{ + QString x = attributes.value(QLatin1String("x")).toString(); + QString y = attributes.value(QLatin1String("y")).toString(); + QString width = attributes.value(QLatin1String("width")).toString(); + QString height = attributes.value(QLatin1String("height")).toString(); + QString filename = attributes.value(QLatin1String("xlink:href")).toString(); + qreal nx = toDouble(x); + qreal ny = toDouble(y); + QSvgHandler::LengthType type; + qreal nwidth = parseLength(width, type, handler); + nwidth = convertToPixels(nwidth, true, type); + + qreal nheight = parseLength(height, type, handler); + nheight = convertToPixels(nheight, false, type); + + + filename = filename.trimmed(); + QImage image; + if (filename.startsWith(QLatin1String("data"))) { + int idx = filename.lastIndexOf(QLatin1String("base64,")); + if (idx != -1) { + idx += 7; + QString dataStr = filename.mid(idx); + QByteArray data = QByteArray::fromBase64(dataStr.toAscii()); + image = QImage::fromData(data); + } else { + qDebug()<<"QSvgHandler::createImageNode: Unrecognized inline image format!"; + } + } else + image = QImage(filename); + + if (image.isNull()) { + qDebug()<<"couldn't create image from "<<filename; + return 0; + } + + if (image.format() == QImage::Format_ARGB32) + image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); + + QSvgNode *img = new QSvgImage(parent, + image, + QRect(int(nx), + int(ny), + int(nwidth), + int(nheight))); + return img; +} + +static QSvgNode *createLineNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + QString x1 = attributes.value(QLatin1String("x1")).toString(); + QString y1 = attributes.value(QLatin1String("y1")).toString(); + QString x2 = attributes.value(QLatin1String("x2")).toString(); + QString y2 = attributes.value(QLatin1String("y2")).toString(); + qreal nx1 = toDouble(x1); + qreal ny1 = toDouble(y1); + qreal nx2 = toDouble(x2); + qreal ny2 = toDouble(y2); + + QLineF lineBounds(nx1, ny1, nx2, ny2); + QSvgNode *line = new QSvgLine(parent, lineBounds); + return line; +} + + +static void parseBaseGradient(QSvgNode *node, + const QXmlStreamAttributes &attributes, + QSvgGradientStyle *gradProp, + QSvgHandler *handler) +{ + QString link = attributes.value(QLatin1String("xlink:href")).toString(); + QStringRef trans = attributes.value(QLatin1String("gradientTransform")); + QString spread = attributes.value(QLatin1String("spreadMethod")).toString(); + QString units = attributes.value(QLatin1String("gradientUnits")).toString(); + QStringRef colorStr = attributes.value(QLatin1String("color")); + QStringRef colorOpacityStr = attributes.value(QLatin1String("color-opacity")); + + QColor color; + if (constructColor(colorStr, colorOpacityStr, color, handler)) { + handler->popColor(); + handler->pushColor(color); + } + + QMatrix matrix; + QGradient *grad = gradProp->qgradient(); + if (!link.isEmpty()) { + QSvgStyleProperty *prop = node->styleProperty(link); + //qDebug()<<"inherited "<<prop<<" ("<<link<<")"; + if (prop && prop->type() == QSvgStyleProperty::GRADIENT) { + QSvgGradientStyle *inherited = + static_cast<QSvgGradientStyle*>(prop); + if (!inherited->stopLink().isEmpty()) { + gradProp->setStopLink(inherited->stopLink(), handler->document()); + } else { + grad->setStops(inherited->qgradient()->stops()); + gradProp->setGradientStopsSet(inherited->gradientStopsSet()); + } + + matrix = inherited->qmatrix(); + } else { + gradProp->setStopLink(link, handler->document()); + } + } + + if (!trans.isEmpty()) { + matrix = parseTransformationMatrix(trans); + gradProp->setMatrix(matrix); + } else if (!matrix.isIdentity()) { + gradProp->setMatrix(matrix); + } + + if (!spread.isEmpty()) { + if (spread == QLatin1String("pad")) { + grad->setSpread(QGradient::PadSpread); + } else if (spread == QLatin1String("reflect")) { + grad->setSpread(QGradient::ReflectSpread); + } else if (spread == QLatin1String("repeat")) { + grad->setSpread(QGradient::RepeatSpread); + } + } + + if (units.isEmpty() || units == QLatin1String("objectBoundingBox")) { + grad->setCoordinateMode(QGradient::ObjectBoundingMode); + } +} + +static QSvgStyleProperty *createLinearGradientNode(QSvgNode *node, + const QXmlStreamAttributes &attributes, + QSvgHandler *handler) +{ + QString x1 = attributes.value(QLatin1String("x1")).toString(); + QString y1 = attributes.value(QLatin1String("y1")).toString(); + QString x2 = attributes.value(QLatin1String("x2")).toString(); + QString y2 = attributes.value(QLatin1String("y2")).toString(); + + qreal nx1 = 0.0; + qreal ny1 = 0.0; + qreal nx2 = 1.0; + qreal ny2 = 0.0; + + if (!x1.isEmpty()) + nx1 = convertToNumber(x1, handler); + if (!y1.isEmpty()) + ny1 = convertToNumber(y1, handler); + if (!x2.isEmpty()) + nx2 = convertToNumber(x2, handler); + if (!y2.isEmpty()) + ny2 = convertToNumber(y2, handler); + + QSvgNode *itr = node; + while (itr && itr->type() != QSvgNode::DOC) { + itr = itr->parent(); + } + + QLinearGradient *grad = new QLinearGradient(nx1, ny1, nx2, ny2); + grad->setInterpolationMode(QGradient::ComponentInterpolation); + QSvgGradientStyle *prop = new QSvgGradientStyle(grad); + parseBaseGradient(node, attributes, prop, handler); + + return prop; +} + +static bool parseMetadataNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + Q_UNUSED(parent); Q_UNUSED(attributes); + return true; +} + +static bool parseMissingGlyphNode(QSvgStyleProperty *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + if (parent->type() != QSvgStyleProperty::FONT) { + return false; + } + + QSvgFontStyle *style = static_cast<QSvgFontStyle*>(parent); + QSvgFont *font = style->svgFont(); + createSvgGlyph(font, attributes); + return true; +} + +static bool parseMpathNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + Q_UNUSED(parent); Q_UNUSED(attributes); + return true; +} + +static QSvgNode *createPathNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + QStringRef data = attributes.value(QLatin1String("d")); + + QPainterPath qpath; + qpath.setFillRule(Qt::WindingFill); + //XXX do error handling + parsePathDataFast(data, qpath); + + QSvgNode *path = new QSvgPath(parent, qpath); + return path; +} + +static QSvgNode *createPolygonNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + QString pointsStr = attributes.value(QLatin1String("points")).toString(); + + //same QPolygon parsing is in createPolylineNode + const QChar *s = pointsStr.constData(); + QVector<qreal> points = parseNumbersList(s); + QPolygonF poly(points.count()/2); + for (int i = 0; i < poly.size(); ++i) + poly[i] = QPointF(points.at(2 * i), points.at(2 * i + 1)); + QSvgNode *polygon = new QSvgPolygon(parent, poly); + return polygon; +} + +static QSvgNode *createPolylineNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + QString pointsStr = attributes.value(QLatin1String("points")).toString(); + + //same QPolygon parsing is in createPolygonNode + const QChar *s = pointsStr.constData(); + QVector<qreal> points = parseNumbersList(s); + QPolygonF poly(points.count()/2); + for (int i = 0; i < poly.size(); ++i) + poly[i] = QPointF(points.at(2 * i), points.at(2 * i + 1)); + + QSvgNode *line = new QSvgPolyline(parent, poly); + return line; +} + +static bool parsePrefetchNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + Q_UNUSED(parent); Q_UNUSED(attributes); + return true; +} + +static QSvgStyleProperty *createRadialGradientNode(QSvgNode *node, + const QXmlStreamAttributes &attributes, + QSvgHandler *handler) +{ + QString cx = attributes.value(QLatin1String("cx")).toString(); + QString cy = attributes.value(QLatin1String("cy")).toString(); + QString r = attributes.value(QLatin1String("r")).toString(); + QString fx = attributes.value(QLatin1String("fx")).toString(); + QString fy = attributes.value(QLatin1String("fy")).toString(); + + qreal ncx = 0.5; + qreal ncy = 0.5; + qreal nr = 0.5; + if (!cx.isEmpty()) + ncx = toDouble(cx); + if (!cy.isEmpty()) + ncy = toDouble(cy); + if (!r.isEmpty()) + nr = toDouble(r); + + qreal nfx = ncx; + if (!fx.isEmpty()) + nfx = toDouble(fx); + qreal nfy = ncy; + if (!fy.isEmpty()) + nfy = toDouble(fy); + + QRadialGradient *grad = new QRadialGradient(ncx, ncy, nr, nfx, nfy); + grad->setInterpolationMode(QGradient::ComponentInterpolation); + + QSvgGradientStyle *prop = new QSvgGradientStyle(grad); + parseBaseGradient(node, attributes, prop, handler); + + return prop; +} + +static QSvgNode *createRectNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *handler) +{ + QString x = attributes.value(QLatin1String("x")).toString(); + QString y = attributes.value(QLatin1String("y")).toString(); + QString width = attributes.value(QLatin1String("width")).toString(); + QString height = attributes.value(QLatin1String("height")).toString(); + QString rx = attributes.value(QLatin1String("rx")).toString(); + QString ry = attributes.value(QLatin1String("ry")).toString(); + + QSvgHandler::LengthType type; + qreal nwidth = parseLength(width, type, handler); + nwidth = convertToPixels(nwidth, true, type); + + qreal nheight = parseLength(height, type, handler); + nheight = convertToPixels(nheight, true, type); + qreal nrx = toDouble(rx); + qreal nry = toDouble(ry); + + QRectF bounds(toDouble(x), toDouble(y), + nwidth, nheight); + + //9.2 The 'rect' element clearly specifies it + // but the case might in fact be handled because + // we draw rounded rectangles differently + if (nrx > bounds.width()/2) + nrx = bounds.width()/2; + if (nry > bounds.height()/2) + nry = bounds.height()/2; + + if (!rx.isEmpty() && ry.isEmpty()) + nry = nrx; + else if (!ry.isEmpty() && rx.isEmpty()) + nrx = nry; + + //we draw rounded rect from 0...99 + //svg from 0...bounds.width()/2 so we're adjusting the + //coordinates + nrx *= (100/(bounds.width()/2)); + nry *= (100/(bounds.height()/2)); + + QSvgNode *rect = new QSvgRect(parent, bounds, + int(nrx), + int(nry)); + return rect; +} + +static bool parseScriptNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + Q_UNUSED(parent); Q_UNUSED(attributes); + return true; +} + +static bool parseSetNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + Q_UNUSED(parent); Q_UNUSED(attributes); + return true; +} + +static QSvgStyleProperty *createSolidColorNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *handler) +{ + Q_UNUSED(parent); Q_UNUSED(attributes); + QStringRef solidColorStr = attributes.value(QLatin1String("solid-color")); + QStringRef solidOpacityStr = attributes.value(QLatin1String("solid-opacity")); + + if (solidOpacityStr.isEmpty()) + solidOpacityStr = attributes.value(QLatin1String("opacity")); + + QColor color; + if (!constructColor(solidColorStr, solidOpacityStr, color, handler)) + return 0; + QSvgSolidColorStyle *style = new QSvgSolidColorStyle(color); + return style; +} + +static bool parseStopNode(QSvgStyleProperty *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *handler) +{ + if (parent->type() != QSvgStyleProperty::GRADIENT) + return false; + QString nodeIdStr = someId(attributes); + QString xmlClassStr = attributes.value(QLatin1String("class")).toString(); + + //### nasty hack because stop gradients are not in the rendering tree + // we force a dummy node with the same id and class into a rendering + // tree to figure out whether the selector has a style for it + // QSvgStyleSelector should be coded in a way that could avoid it + QSvgAnimation anim; + anim.setNodeId(nodeIdStr); + anim.setXmlClass(xmlClassStr); + + QCss::StyleSelector::NodePtr cssNode; + cssNode.ptr = &anim; + QVector<QCss::Declaration> decls = handler->selector()->declarationsForNode(cssNode); + + QXmlStreamAttributes xmlAttr = attributes; + for (int i = 0; i < decls.count(); ++i) { + const QCss::Declaration &decl = decls.at(i); + + if (decl.d->property.isEmpty()) + continue; + if (decl.d->values.count() != 1) + continue; + QCss::Value val = decl.d->values.first(); + QString valueStr = val.toString(); + if (val.type == QCss::Value::Uri) { + valueStr.prepend(QLatin1String("url(")); + valueStr.append(QLatin1Char(')')); + } + xmlAttr.append(QString(), decl.d->property, valueStr); + } + QSvgAttributes attrs(xmlAttr, handler); + + QSvgGradientStyle *style = + static_cast<QSvgGradientStyle*>(parent); + QString offsetStr = attrs.offset.toString(); + QStringRef colorStr = attrs.stopColor; + QColor color; + + bool ok = true; + qreal offset = convertToNumber(offsetStr, handler, &ok); + if (!ok) + offset = 0.0; + QString black = QString::fromLatin1("#000000"); + if (colorStr.isEmpty()) { + colorStr = QStringRef(&black); + } + + constructColor(colorStr, attrs.stopOpacity, color, handler); + + QGradient *grad = style->qgradient(); + + offset = qMin(qreal(1), qMax(qreal(0), offset)); // Clamp to range [0, 1] + QGradientStops stops; + if (style->gradientStopsSet()) { + stops = grad->stops(); + // If the stop offset equals the one previously added, add an epsilon to make it greater. + if (offset <= stops.back().first) + offset = stops.back().first + FLT_EPSILON; + } + + // If offset is greater than one, it must be clamped to one. + if (offset > 1.0) { + if ((stops.size() == 1) || (stops.at(stops.size() - 2).first < 1.0 - FLT_EPSILON)) { + stops.back().first = 1.0 - FLT_EPSILON; + grad->setStops(stops); + } + offset = 1.0; + } + + grad->setColorAt(offset, color); + style->setGradientStopsSet(true); + return true; +} + +static bool parseStyleNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *handler) +{ + Q_UNUSED(parent); + QString type = attributes.value(QLatin1String("type")).toString(); + type = type.toLower(); + + if (type == QLatin1String("text/css")) { + handler->setInStyle(true); + } + + return true; +} + +static QSvgNode *createSvgNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *handler) +{ + Q_UNUSED(parent); Q_UNUSED(attributes); + + QString baseProfile = attributes.value(QLatin1String("baseProfile")).toString(); +#if 0 + if (baseProfile.isEmpty() && baseProfile != QLatin1String("tiny")) { + qWarning("Profile is %s while we only support tiny!", + qPrintable(baseProfile)); + } +#endif + + QSvgTinyDocument *node = new QSvgTinyDocument(); + QString widthStr = attributes.value(QLatin1String("width")).toString(); + QString heightStr = attributes.value(QLatin1String("height")).toString(); + QString viewBoxStr = attributes.value(QLatin1String("viewBox")).toString(); + + QSvgHandler::LengthType type = QSvgHandler::LT_PX; // FIXME: is the default correct? + qreal width = 0; + if (!widthStr.isEmpty()) { + width = parseLength(widthStr, type, handler); + if (type != QSvgHandler::LT_PT) + width = convertToPixels(width, true, type); + node->setWidth(int(width), type == QSvgHandler::LT_PERCENT); + } + qreal height = 0; + if (!heightStr.isEmpty()) { + height = parseLength(heightStr, type, handler); + if (type != QSvgHandler::LT_PT) + height = convertToPixels(height, false, type); + node->setHeight(int(height), type == QSvgHandler::LT_PERCENT); + } + + QStringList viewBoxValues; + if (!viewBoxStr.isEmpty()) { + viewBoxStr = viewBoxStr.replace(QLatin1Char(' '), QLatin1Char(',')); + viewBoxStr = viewBoxStr.replace(QLatin1Char('\r'), QLatin1Char(',')); + viewBoxStr = viewBoxStr.replace(QLatin1Char('\n'), QLatin1Char(',')); + viewBoxStr = viewBoxStr.replace(QLatin1Char('\t'), QLatin1Char(',')); + viewBoxValues = viewBoxStr.split(QLatin1Char(','), QString::SkipEmptyParts); + } + if (viewBoxValues.count() == 4) { + QString xStr = viewBoxValues.at(0).trimmed(); + QString yStr = viewBoxValues.at(1).trimmed(); + QString widthStr = viewBoxValues.at(2).trimmed(); + QString heightStr = viewBoxValues.at(3).trimmed(); + + QSvgHandler::LengthType lt; + qreal x = parseLength(xStr, lt, handler); + qreal y = parseLength(yStr, lt, handler); + qreal w = parseLength(widthStr, lt, handler); + qreal h = parseLength(heightStr, lt, handler); + + node->setViewBox(QRectF(x, y, w, h)); + + } else if (width && height) { + if (type == QSvgHandler::LT_PT) { + width = convertToPixels(width, false, type); + height = convertToPixels(height, false, type); + } + node->setViewBox(QRectF(0, 0, width, height)); + } + handler->setDefaultCoordinateSystem(QSvgHandler::LT_PX); + + return node; +} + +static QSvgNode *createSwitchNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + Q_UNUSED(attributes); + QSvgSwitch *node = new QSvgSwitch(parent); + return node; +} + +static bool parseTbreakNode(QSvgNode *parent, + const QXmlStreamAttributes &, + QSvgHandler *) +{ + if (parent->type() != QSvgNode::TEXTAREA) + return false; + static_cast<QSvgText*>(parent)->addLineBreak(); + return true; +} + +static QSvgNode *createTextNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *handler) +{ + QString x = attributes.value(QLatin1String("x")).toString(); + QString y = attributes.value(QLatin1String("y")).toString(); + //### editable and rotate not handled + QSvgHandler::LengthType type; + qreal nx = parseLength(x, type, handler); + qreal ny = parseLength(y, type, handler); + + QSvgNode *text = new QSvgText(parent, QPointF(nx, ny)); + return text; +} + +static QSvgNode *createTextAreaNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *handler) +{ + QSvgText *node = static_cast<QSvgText *>(createTextNode(parent, attributes, handler)); + if (node) { + QSvgHandler::LengthType type; + qreal width = parseLength(attributes.value(QLatin1String("width")).toString(), type, handler); + qreal height = parseLength(attributes.value(QLatin1String("height")).toString(), type, handler); + node->setTextArea(QSizeF(width, height)); + } + return node; +} + +static QSvgNode *createTspanNode(QSvgNode *parent, + const QXmlStreamAttributes &, + QSvgHandler *) +{ + return new QSvgTspan(parent); +} + +static bool parseTitleNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + Q_UNUSED(parent); Q_UNUSED(attributes); + return true; +} + +static QSvgNode *createUseNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *handler) +{ + QString linkId = attributes.value(QLatin1String("xlink:href")).toString().remove(0, 1); + QString xStr = attributes.value(QLatin1String("x")).toString(); + QString yStr = attributes.value(QLatin1String("y")).toString(); + QSvgStructureNode *group = 0; + + if (linkId.isEmpty()) + linkId = attributes.value(QLatin1String("href")).toString().remove(0, 1); + switch (parent->type()) { + case QSvgNode::DOC: + case QSvgNode::DEFS: + case QSvgNode::G: + case QSvgNode::SWITCH: + group = static_cast<QSvgStructureNode*>(parent); + break; + default: + break; + } + + if (group) { + QSvgNode *link = group->scopeNode(linkId); + if (link) { + QPointF pt; + if (!xStr.isNull() || !yStr.isNull()) { + QSvgHandler::LengthType type; + qreal nx = parseLength(xStr, type, handler); + nx = convertToPixels(nx, true, type); + + qreal ny = parseLength(yStr, type, handler); + ny = convertToPixels(ny, true, type); + pt = QPointF(nx, ny); + } + + //delay link resolving till the first draw call on + //use nodes, link 2might have not been created yet + QSvgUse *node = new QSvgUse(pt, parent, link); + return node; + } + } + + qWarning("link %s hasn't been detected!", qPrintable(linkId)); + return 0; +} + +static QSvgNode *createVideoNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + Q_UNUSED(parent); Q_UNUSED(attributes); + return 0; +} + +typedef QSvgNode *(*FactoryMethod)(QSvgNode *, const QXmlStreamAttributes &, QSvgHandler *); + +static FactoryMethod findGroupFactory(const QString &name) +{ + if (name.isEmpty()) + return 0; + + QStringRef ref(&name, 1, name.length() - 1); + switch (name.at(0).unicode()) { + case 'd': + if (ref == QLatin1String("efs")) return createDefsNode; + break; + case 'g': + if (ref.isEmpty()) return createGNode; + break; + case 's': + if (ref == QLatin1String("vg")) return createSvgNode; + if (ref == QLatin1String("witch")) return createSwitchNode; + break; + default: + break; + } + return 0; +} + +static FactoryMethod findGraphicsFactory(const QString &name) +{ + if (name.isEmpty()) + return 0; + + QStringRef ref(&name, 1, name.length() - 1); + switch (name.at(0).unicode()) { + case 'a': + if (ref == QLatin1String("nimation")) return createAnimationNode; + break; + case 'c': + if (ref == QLatin1String("ircle")) return createCircleNode; + break; + case 'e': + if (ref == QLatin1String("llipse")) return createEllipseNode; + break; + case 'i': + if (ref == QLatin1String("mage")) return createImageNode; + break; + case 'l': + if (ref == QLatin1String("ine")) return createLineNode; + break; + case 'p': + if (ref == QLatin1String("ath")) return createPathNode; + if (ref == QLatin1String("olygon")) return createPolygonNode; + if (ref == QLatin1String("olyline")) return createPolylineNode; + break; + case 'r': + if (ref == QLatin1String("ect")) return createRectNode; + break; + case 't': + if (ref == QLatin1String("ext")) return createTextNode; + if (ref == QLatin1String("extArea")) return createTextAreaNode; + if (ref == QLatin1String("span")) return createTspanNode; + break; + case 'u': + if (ref == QLatin1String("se")) return createUseNode; + break; + case 'v': + if (ref == QLatin1String("ideo")) return createVideoNode; + break; + default: + break; + } + return 0; +} + +typedef bool (*ParseMethod)(QSvgNode *, const QXmlStreamAttributes &, QSvgHandler *); + +static ParseMethod findUtilFactory(const QString &name) +{ + if (name.isEmpty()) + return 0; + + QStringRef ref(&name, 1, name.length() - 1); + switch (name.at(0).unicode()) { + case 'a': + if (ref.isEmpty()) return parseAnchorNode; + if (ref == QLatin1String("nimate")) return parseAnimateNode; + if (ref == QLatin1String("nimateColor")) return parseAnimateColorNode; + if (ref == QLatin1String("nimateMotion")) return parseAimateMotionNode; + if (ref == QLatin1String("nimateTransform")) return parseAnimateTransformNode; + if (ref == QLatin1String("udio")) return parseAudioNode; + break; + case 'd': + if (ref == QLatin1String("esc")) return parseDescNode; + if (ref == QLatin1String("iscard")) return parseDiscardNode; + break; + case 'f': + if (ref == QLatin1String("oreignObject")) return parseForeignObjectNode; + break; + case 'h': + if (ref == QLatin1String("andler")) return parseHandlerNode; + if (ref == QLatin1String("kern")) return parseHkernNode; + break; + case 'm': + if (ref == QLatin1String("etadata")) return parseMetadataNode; + if (ref == QLatin1String("path")) return parseMpathNode; + break; + case 'p': + if (ref == QLatin1String("refetch")) return parsePrefetchNode; + break; + case 's': + if (ref == QLatin1String("cript")) return parseScriptNode; + if (ref == QLatin1String("et")) return parseSetNode; + if (ref == QLatin1String("tyle")) return parseStyleNode; + break; + case 't': + if (ref == QLatin1String("break")) return parseTbreakNode; + if (ref == QLatin1String("itle")) return parseTitleNode; + break; + default: + break; + } + return 0; +} + +typedef QSvgStyleProperty *(*StyleFactoryMethod)(QSvgNode *, + const QXmlStreamAttributes &, + QSvgHandler *); + +static StyleFactoryMethod findStyleFactoryMethod(const QString &name) +{ + if (name.isEmpty()) + return 0; + + QStringRef ref(&name, 1, name.length() - 1); + switch (name.at(0).unicode()) { + case 'f': + if (ref == QLatin1String("ont")) return createFontNode; + break; + case 'l': + if (ref == QLatin1String("inearGradient")) return createLinearGradientNode; + break; + case 'r': + if (ref == QLatin1String("adialGradient")) return createRadialGradientNode; + break; + case 's': + if (ref == QLatin1String("olidColor")) return createSolidColorNode; + break; + default: + break; + } + return 0; +} + +typedef bool (*StyleParseMethod)(QSvgStyleProperty *, + const QXmlStreamAttributes &, + QSvgHandler *); + +static StyleParseMethod findStyleUtilFactoryMethod(const QString &name) +{ + if (name.isEmpty()) + return 0; + + QStringRef ref(&name, 1, name.length() - 1); + switch (name.at(0).unicode()) { + case 'f': + if (ref == QLatin1String("ont-face")) return parseFontFaceNode; + if (ref == QLatin1String("ont-face-name")) return parseFontFaceNameNode; + if (ref == QLatin1String("ont-face-src")) return parseFontFaceSrcNode; + if (ref == QLatin1String("ont-face-uri")) return parseFontFaceUriNode; + break; + case 'g': + if (ref == QLatin1String("lyph")) return parseGlyphNode; + break; + case 'm': + if (ref == QLatin1String("issing-glyph")) return parseMissingGlyphNode; + break; + case 's': + if (ref == QLatin1String("top")) return parseStopNode; + break; + default: + break; + } + return 0; +} + +QSvgHandler::QSvgHandler(QIODevice *device) : xml(new QXmlStreamReader(device)) + , m_ownsReader(true) +{ + init(); +} + +QSvgHandler::QSvgHandler(const QByteArray &data) : xml(new QXmlStreamReader(data)) + , m_ownsReader(true) +{ + init(); +} + +QSvgHandler::QSvgHandler(QXmlStreamReader *const reader) : xml(reader) + , m_ownsReader(false) +{ + init(); +} + +void QSvgHandler::init() +{ + m_doc = 0; + m_style = 0; + m_animEnd = 0; + m_defaultCoords = LT_PX; + m_defaultPen = QPen(Qt::black, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin); + m_defaultPen.setMiterLimit(4); + parse(); +} + +void QSvgHandler::parse() +{ + xml->setNamespaceProcessing(false); + m_selector = new QSvgStyleSelector; + m_inStyle = false; + bool done = false; + while (!xml->atEnd() && !done) { + switch (xml->readNext()) { + case QXmlStreamReader::StartElement: + // he we could/should verify the namespaces, and simply + // call m_skipNodes(Unknown) if we don't know the + // namespace. We do support http://www.w3.org/2000/svg + // but also http://www.w3.org/2000/svg-20000303-stylable + // And if the document uses an external dtd, the reported + // namespaceUri is empty. The only possible strategy at + // this point is to do what everyone else seems to do and + // ignore the reported namespaceUri completely. + if (!startElement(xml->name().toString(), xml->attributes())) { + delete m_doc; + m_doc = 0; + return; + } + break; + case QXmlStreamReader::EndElement: + endElement(xml->name()); + // if we are using somebody else's qxmlstreamreader + // we should not read until the end of the stream + done = !m_ownsReader && (xml->name() == QLatin1String("svg")); + break; + case QXmlStreamReader::Characters: + characters(xml->text()); + break; + case QXmlStreamReader::ProcessingInstruction: + processingInstruction(xml->processingInstructionTarget().toString(), xml->processingInstructionData().toString()); + break; + default: + break; + } + } + resolveGradients(m_doc); +} + +bool QSvgHandler::startElement(const QString &localName, + const QXmlStreamAttributes &attributes) +{ + QSvgNode *node = 0; + + pushColorCopy(); + + /* The xml:space attribute may appear on any element. We do + * a lookup by the qualified name here, but this is namespace aware, since + * the XML namespace can only be bound to prefix "xml." */ + const QStringRef xmlSpace(attributes.value(QLatin1String("xml:space"))); + if (xmlSpace.isNull()) { + // This element has no xml:space attribute. + m_whitespaceMode.push(m_whitespaceMode.isEmpty() ? QSvgText::Default : m_whitespaceMode.top()); + } else if (xmlSpace == QLatin1String("preserve")) { + m_whitespaceMode.push(QSvgText::Preserve); + } else if (xmlSpace == QLatin1String("default")) { + m_whitespaceMode.push(QSvgText::Default); + } else { + qWarning() << QString::fromLatin1("\"%1\" is an invalid value for attribute xml:space. " + "Valid values are \"preserve\" and \"default\".").arg(xmlSpace.toString()); + m_whitespaceMode.push(QSvgText::Default); + } + + if (!m_doc && localName != QLatin1String("svg")) + return false; + + if (FactoryMethod method = findGroupFactory(localName)) { + //group + node = method(m_doc ? m_nodes.top() : 0, attributes, this); + Q_ASSERT(node); + if (!m_doc) { + Q_ASSERT(node->type() == QSvgNode::DOC); + m_doc = static_cast<QSvgTinyDocument*>(node); + } else { + switch (m_nodes.top()->type()) { + case QSvgNode::DOC: + case QSvgNode::G: + case QSvgNode::DEFS: + case QSvgNode::SWITCH: + { + QSvgStructureNode *group = + static_cast<QSvgStructureNode*>(m_nodes.top()); + group->addChild(node, someId(attributes)); + } + break; + default: + break; + } + } + parseCoreNode(node, attributes); + cssStyleLookup(node, this, m_selector); + parseStyle(node, attributes, this); + } else if (FactoryMethod method = findGraphicsFactory(localName)) { + //rendering element + Q_ASSERT(!m_nodes.isEmpty()); + node = method(m_nodes.top(), attributes, this); + if (node) { + switch (m_nodes.top()->type()) { + case QSvgNode::DOC: + case QSvgNode::G: + case QSvgNode::DEFS: + case QSvgNode::SWITCH: + { + QSvgStructureNode *group = + static_cast<QSvgStructureNode*>(m_nodes.top()); + group->addChild(node, someId(attributes)); + } + break; + case QSvgNode::TEXT: + case QSvgNode::TEXTAREA: + if (node->type() == QSvgNode::TSPAN) { + static_cast<QSvgText *>(m_nodes.top())->addTspan(static_cast<QSvgTspan *>(node)); + } else { + qWarning("\'text\' or \'textArea\' element contains invalid element type."); + delete node; + node = 0; + } + break; + default: + qWarning("Could not add child element to parent element because the types are incorrect."); + delete node; + node = 0; + break; + } + + if (node) { + parseCoreNode(node, attributes); + cssStyleLookup(node, this, m_selector); + parseStyle(node, attributes, this); + if (node->type() == QSvgNode::TEXT || node->type() == QSvgNode::TEXTAREA) { + static_cast<QSvgText *>(node)->setWhitespaceMode(m_whitespaceMode.top()); + } else if (node->type() == QSvgNode::TSPAN) { + static_cast<QSvgTspan *>(node)->setWhitespaceMode(m_whitespaceMode.top()); + } + } + } + } else if (ParseMethod method = findUtilFactory(localName)) { + Q_ASSERT(!m_nodes.isEmpty()); + if (!method(m_nodes.top(), attributes, this)) { + qWarning("Problem parsing %s", qPrintable(localName)); + } + } else if (StyleFactoryMethod method = findStyleFactoryMethod(localName)) { + QSvgStyleProperty *prop = method(m_nodes.top(), attributes, this); + if (prop) { + m_style = prop; + m_nodes.top()->appendStyleProperty(prop, someId(attributes)); + } else { + qWarning("Could not parse node: %s", qPrintable(localName)); + } + } else if (StyleParseMethod method = findStyleUtilFactoryMethod(localName)) { + if (m_style) { + if (!method(m_style, attributes, this)) { + qWarning("Problem parsing %s", qPrintable(localName)); + } + } + } else { + //qWarning()<<"Skipping unknown element!"<<namespaceURI<<"::"<<localName; + m_skipNodes.push(Unknown); + return true; + } + + if (node) { + m_nodes.push(node); + m_skipNodes.push(Graphics); + } else { + //qDebug()<<"Skipping "<<localName; + m_skipNodes.push(Style); + } + return true; +} + +bool QSvgHandler::endElement(const QStringRef &localName) +{ + CurrentNode node = m_skipNodes.top(); + m_skipNodes.pop(); + m_whitespaceMode.pop(); + + popColor(); + + if (node == Unknown) { + return true; + } + + if (m_inStyle && localName == QLatin1String("style")) + m_inStyle = false; + + if (node == Graphics) + m_nodes.pop(); + else if (m_style && !m_skipNodes.isEmpty() && m_skipNodes.top() != Style) + m_style = 0; + + return true; +} + +void QSvgHandler::resolveGradients(QSvgNode *node) +{ + if (!node || (node->type() != QSvgNode::DOC && node->type() != QSvgNode::G + && node->type() != QSvgNode::DEFS && node->type() != QSvgNode::SWITCH)) { + return; + } + QSvgStructureNode *structureNode = static_cast<QSvgStructureNode *>(node); + + QList<QSvgNode *> ren = structureNode->renderers(); + for (QList<QSvgNode *>::iterator it = ren.begin(); it != ren.end(); ++it) { + QSvgFillStyle *fill = static_cast<QSvgFillStyle *>((*it)->styleProperty(QSvgStyleProperty::FILL)); + if (fill && !fill->isGradientResolved()) { + QString id = fill->gradientId(); + QSvgFillStyleProperty *style = structureNode->styleProperty(id); + if (style) { + fill->setFillStyle(style); + } else { + qWarning("Could not resolve property : %s", qPrintable(id)); + fill->setBrush(Qt::NoBrush); + } + } + + QSvgStrokeStyle *stroke = static_cast<QSvgStrokeStyle *>((*it)->styleProperty(QSvgStyleProperty::STROKE)); + if (stroke && !stroke->isGradientResolved()) { + QString id = stroke->gradientId(); + QSvgFillStyleProperty *style = structureNode->styleProperty(id); + if (style) { + stroke->setStyle(style); + } else { + qWarning("Could not resolve property : %s", qPrintable(id)); + stroke->setStroke(Qt::NoBrush); + } + } + + resolveGradients(*it); + } +} + +bool QSvgHandler::characters(const QStringRef &str) +{ + if (m_inStyle) { + QString css = str.toString(); + QCss::StyleSheet sheet; + QCss::Parser(css).parse(&sheet); + m_selector->styleSheets.append(sheet); + return true; + } else if (m_skipNodes.isEmpty() || m_skipNodes.top() == Unknown || m_nodes.isEmpty()) + return true; + + if (m_nodes.top()->type() == QSvgNode::TEXT || m_nodes.top()->type() == QSvgNode::TEXTAREA) { + static_cast<QSvgText*>(m_nodes.top())->addText(str.toString()); + } else if (m_nodes.top()->type() == QSvgNode::TSPAN) { + static_cast<QSvgTspan*>(m_nodes.top())->addText(str.toString()); + } + + return true; +} + +QSvgTinyDocument * QSvgHandler::document() const +{ + return m_doc; +} + +QSvgHandler::LengthType QSvgHandler::defaultCoordinateSystem() const +{ + return m_defaultCoords; +} + +void QSvgHandler::setDefaultCoordinateSystem(LengthType type) +{ + m_defaultCoords = type; +} + +void QSvgHandler::pushColor(const QColor &color) +{ + m_colorStack.push(color); + m_colorTagCount.push(1); +} + +void QSvgHandler::pushColorCopy() +{ + if (m_colorTagCount.count()) + ++m_colorTagCount.top(); + else + pushColor(Qt::black); +} + +void QSvgHandler::popColor() +{ + if (m_colorTagCount.count()) { + if (!--m_colorTagCount.top()) { + m_colorStack.pop(); + m_colorTagCount.pop(); + } + } +} + +QColor QSvgHandler::currentColor() const +{ + if (!m_colorStack.isEmpty()) + return m_colorStack.top(); + else + return QColor(0, 0, 0); +} + +void QSvgHandler::setInStyle(bool b) +{ + m_inStyle = b; +} + +bool QSvgHandler::inStyle() const +{ + return m_inStyle; +} + +QSvgStyleSelector * QSvgHandler::selector() const +{ + return m_selector; +} + +bool QSvgHandler::processingInstruction(const QString &target, const QString &data) +{ + if (target == QLatin1String("xml-stylesheet")) { + QRegExp rx(QLatin1String("type=\\\"(.+)\\\"")); + rx.setMinimal(true); + bool isCss = false; + int pos = 0; + while ((pos = rx.indexIn(data, pos)) != -1) { + QString type = rx.cap(1); + if (type.toLower() == QLatin1String("text/css")) { + isCss = true; + } + pos += rx.matchedLength(); + } + + if (isCss) { + QRegExp rx(QLatin1String("href=\\\"(.+)\\\"")); + rx.setMinimal(true); + pos = 0; + pos = rx.indexIn(data, pos); + QString addr = rx.cap(1); + QFileInfo fi(addr); + //qDebug()<<"External CSS file "<<fi.absoluteFilePath()<<fi.exists(); + if (fi.exists()) { + QFile file(fi.absoluteFilePath()); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + return true; + } + QByteArray cssData = file.readAll(); + QString css = QString::fromUtf8(cssData); + + QCss::StyleSheet sheet; + QCss::Parser(css).parse(&sheet); + m_selector->styleSheets.append(sheet); + } + + } + } + + return true; +} + +void QSvgHandler::setAnimPeriod(int start, int end) +{ + Q_UNUSED(start); + m_animEnd = qMax(end, m_animEnd); +} + +int QSvgHandler::animationDuration() const +{ + return m_animEnd; +} + +QSvgHandler::~QSvgHandler() +{ + delete m_selector; + m_selector = 0; + + if(m_ownsReader) + delete xml; +} + +QT_END_NAMESPACE + +#endif // QT_NO_SVG diff --git a/src/svg/qsvghandler_p.h b/src/svg/qsvghandler_p.h new file mode 100644 index 0000000..1d1712e --- /dev/null +++ b/src/svg/qsvghandler_p.h @@ -0,0 +1,188 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSvg module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSVGHANDLER_P_H +#define QSVGHANDLER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtXml/qxmlstream.h" + +#ifndef QT_NO_SVG + +#include "QtCore/qhash.h" +#include "QtCore/qstack.h" +#include "qsvgstyle_p.h" +#include "private/qcssparser_p.h" +#include "private/qsvggraphics_p.h" + +QT_BEGIN_NAMESPACE + +class QSvgNode; +class QSvgTinyDocument; +class QSvgHandler; +class QColor; +class QSvgStyleSelector; +class QXmlStreamReader; + +struct QSvgCssAttribute +{ + QXmlStreamStringRef name; + QXmlStreamStringRef value; +}; + +class QSvgHandler +{ +public: + enum LengthType { + LT_PERCENT, + LT_PX, + LT_PC, + LT_PT, + LT_MM, + LT_CM, + LT_IN, + LT_OTHER + }; + +public: + QSvgHandler(QIODevice *device); + QSvgHandler(const QByteArray &data); + QSvgHandler(QXmlStreamReader *const data); + ~QSvgHandler(); + + QSvgTinyDocument *document() const; + + inline bool ok() const { + return document() != 0 && !xml->hasError(); + } + + inline QString errorString() const { return xml->errorString(); } + inline int lineNumber() const { return xml->lineNumber(); } + + void setDefaultCoordinateSystem(LengthType type); + LengthType defaultCoordinateSystem() const; + + void pushColor(const QColor &color); + void pushColorCopy(); + void popColor(); + QColor currentColor() const; + + void setInStyle(bool b); + bool inStyle() const; + + QSvgStyleSelector *selector() const; + + void setAnimPeriod(int start, int end); + int animationDuration() const; + + void parseCSStoXMLAttrs(QString css, QVector<QSvgCssAttribute> *attributes); + + inline QPen defaultPen() const + { return m_defaultPen; } + +public: + bool startElement(const QString &localName, const QXmlStreamAttributes &attributes); + bool endElement(const QStringRef &localName); + bool characters(const QStringRef &str); + bool processingInstruction(const QString &target, const QString &data); + +private: + void init(); + + QSvgTinyDocument *m_doc; + QStack<QSvgNode*> m_nodes; + + QList<QSvgNode*> m_resolveNodes; + + enum CurrentNode + { + Unknown, + Graphics, + Style + }; + QStack<CurrentNode> m_skipNodes; + + /*! + Follows the depths of elements. The top is current xml:space + value that applies for a given element. + */ + QStack<QSvgText::WhitespaceMode> m_whitespaceMode; + + QSvgRefCounter<QSvgStyleProperty> m_style; + + LengthType m_defaultCoords; + + QStack<QColor> m_colorStack; + QStack<int> m_colorTagCount; + + bool m_inStyle; + + QSvgStyleSelector *m_selector; + + int m_animEnd; + + QXmlStreamReader *const xml; + QCss::Parser m_cssParser; + void parse(); + void resolveGradients(QSvgNode *node); + + QPen m_defaultPen; + /** + * Whether we own the variable xml, and hence whether + * we need to delete it. + */ + const bool m_ownsReader; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_SVG +#endif // QSVGHANDLER_P_H diff --git a/src/svg/qsvgnode.cpp b/src/svg/qsvgnode.cpp new file mode 100644 index 0000000..5751f80 --- /dev/null +++ b/src/svg/qsvgnode.cpp @@ -0,0 +1,345 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSvg module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsvgnode_p.h" +#include "qsvgtinydocument_p.h" + +#ifndef QT_NO_SVG + +#include "qdebug.h" +#include "qstack.h" + +QT_BEGIN_NAMESPACE + +QSvgNode::QSvgNode(QSvgNode *parent) + : m_parent(parent), + m_visible(true), + m_displayMode(BlockMode) +{ +} + +QSvgNode::~QSvgNode() +{ + +} + +void QSvgNode::appendStyleProperty(QSvgStyleProperty *prop, const QString &id) +{ + //qDebug()<<"appending "<<prop->type()<< " ("<< id <<") "<<"to "<<this<<this->type(); + QSvgTinyDocument *doc; + switch (prop->type()) { + case QSvgStyleProperty::QUALITY: + m_style.quality = static_cast<QSvgQualityStyle*>(prop); + break; + case QSvgStyleProperty::FILL: + m_style.fill = static_cast<QSvgFillStyle*>(prop); + break; + case QSvgStyleProperty::VIEWPORT_FILL: + m_style.viewportFill = static_cast<QSvgViewportFillStyle*>(prop); + break; + case QSvgStyleProperty::FONT: + m_style.font = static_cast<QSvgFontStyle*>(prop); + break; + case QSvgStyleProperty::STROKE: + m_style.stroke = static_cast<QSvgStrokeStyle*>(prop); + break; + case QSvgStyleProperty::SOLID_COLOR: + m_style.solidColor = static_cast<QSvgSolidColorStyle*>(prop); + doc = document(); + if (doc && !id.isEmpty()) + doc->addNamedStyle(id, m_style.solidColor); + break; + case QSvgStyleProperty::GRADIENT: + m_style.gradient = static_cast<QSvgGradientStyle*>(prop); + doc = document(); + if (doc && !id.isEmpty()) + doc->addNamedStyle(id, m_style.gradient); + break; + case QSvgStyleProperty::TRANSFORM: + m_style.transform = static_cast<QSvgTransformStyle*>(prop); + break; + case QSvgStyleProperty::ANIMATE_COLOR: + m_style.animateColor = static_cast<QSvgAnimateColor*>(prop); + break; + case QSvgStyleProperty::ANIMATE_TRANSFORM: + m_style.animateTransforms.append( + static_cast<QSvgAnimateTransform*>(prop)); + break; + case QSvgStyleProperty::OPACITY: + m_style.opacity = static_cast<QSvgOpacityStyle*>(prop); + break; + case QSvgStyleProperty::COMP_OP: + m_style.compop = static_cast<QSvgCompOpStyle*>(prop); + break; + default: + qDebug("QSvgNode: Trying to append unknown property!"); + break; + } +} + +void QSvgNode::applyStyle(QPainter *p, QSvgExtraStates &states) const +{ + m_style.apply(p, this, states); +} + +void QSvgNode::revertStyle(QPainter *p, QSvgExtraStates &states) const +{ + m_style.revert(p, states); +} + +QSvgStyleProperty * QSvgNode::styleProperty(QSvgStyleProperty::Type type) const +{ + const QSvgNode *node = this; + while (node) { + switch (type) { + case QSvgStyleProperty::QUALITY: + if (node->m_style.quality) + return node->m_style.quality; + break; + case QSvgStyleProperty::FILL: + if (node->m_style.fill) + return node->m_style.fill; + break; + case QSvgStyleProperty::VIEWPORT_FILL: + if (m_style.viewportFill) + return node->m_style.viewportFill; + break; + case QSvgStyleProperty::FONT: + if (node->m_style.font) + return node->m_style.font; + break; + case QSvgStyleProperty::STROKE: + if (node->m_style.stroke) + return node->m_style.stroke; + break; + case QSvgStyleProperty::SOLID_COLOR: + if (node->m_style.solidColor) + return node->m_style.solidColor; + break; + case QSvgStyleProperty::GRADIENT: + if (node->m_style.gradient) + return node->m_style.gradient; + break; + case QSvgStyleProperty::TRANSFORM: + if (node->m_style.transform) + return node->m_style.transform; + break; + case QSvgStyleProperty::ANIMATE_COLOR: + if (node->m_style.animateColor) + return node->m_style.animateColor; + break; + case QSvgStyleProperty::ANIMATE_TRANSFORM: + if (!node->m_style.animateTransforms.isEmpty()) + return node->m_style.animateTransforms.first(); + break; + case QSvgStyleProperty::OPACITY: + if (node->m_style.opacity) + return node->m_style.opacity; + break; + case QSvgStyleProperty::COMP_OP: + if (node->m_style.compop) + return node->m_style.compop; + break; + default: + break; + } + node = node->parent(); + } + + return 0; +} + +QSvgFillStyleProperty * QSvgNode::styleProperty(const QString &id) const +{ + QString rid = id; + if (rid.startsWith(QLatin1Char('#'))) + rid.remove(0, 1); + QSvgTinyDocument *doc = document(); + return doc ? doc->namedStyle(rid) : 0; +} + +QRectF QSvgNode::bounds(QPainter *, QSvgExtraStates &) const +{ + return QRectF(0, 0, 0, 0); +} + +QRectF QSvgNode::transformedBounds() const +{ + if (!m_cachedBounds.isEmpty()) + return m_cachedBounds; + + QImage dummy(1, 1, QImage::Format_RGB32); + QPainter p(&dummy); + QSvgExtraStates states; + + QPen pen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); + pen.setMiterLimit(4); + p.setPen(pen); + + QStack<QSvgNode*> parentApplyStack; + QSvgNode *parent = m_parent; + while (parent) { + parentApplyStack.push(parent); + parent = parent->parent(); + } + + for (int i = parentApplyStack.size() - 1; i >= 0; --i) + parentApplyStack[i]->applyStyle(&p, states); + + p.setWorldTransform(QTransform()); + + m_cachedBounds = transformedBounds(&p, states); + return m_cachedBounds; +} + +QSvgTinyDocument * QSvgNode::document() const +{ + QSvgTinyDocument *doc = 0; + QSvgNode *node = const_cast<QSvgNode*>(this); + while (node && node->type() != QSvgNode::DOC) { + node = node->parent(); + } + doc = static_cast<QSvgTinyDocument*>(node); + + return doc; +} + +void QSvgNode::setRequiredFeatures(const QStringList &lst) +{ + m_requiredFeatures = lst; +} + +const QStringList & QSvgNode::requiredFeatures() const +{ + return m_requiredFeatures; +} + +void QSvgNode::setRequiredExtensions(const QStringList &lst) +{ + m_requiredExtensions = lst; +} + +const QStringList & QSvgNode::requiredExtensions() const +{ + return m_requiredExtensions; +} + +void QSvgNode::setRequiredLanguages(const QStringList &lst) +{ + m_requiredLanguages = lst; +} + +const QStringList & QSvgNode::requiredLanguages() const +{ + return m_requiredLanguages; +} + +void QSvgNode::setRequiredFormats(const QStringList &lst) +{ + m_requiredFormats = lst; +} + +const QStringList & QSvgNode::requiredFormats() const +{ + return m_requiredFormats; +} + +void QSvgNode::setRequiredFonts(const QStringList &lst) +{ + m_requiredFonts = lst; +} + +const QStringList & QSvgNode::requiredFonts() const +{ + return m_requiredFonts; +} + +void QSvgNode::setVisible(bool visible) +{ + //propagate visibility change of true to the parent + //not propagating false is just a small performance + //degradation since we'll iterate over children without + //drawing any of them + if (m_parent && visible && !m_parent->isVisible()) + m_parent->setVisible(true); + + m_visible = visible; +} + +QRectF QSvgNode::transformedBounds(QPainter *p, QSvgExtraStates &states) const +{ + applyStyle(p, states); + QRectF rect = bounds(p, states); + revertStyle(p, states); + return rect; +} + +void QSvgNode::setNodeId(const QString &i) +{ + m_id = i; +} + +void QSvgNode::setXmlClass(const QString &str) +{ + m_class = str; +} + +void QSvgNode::setDisplayMode(DisplayMode mode) +{ + m_displayMode = mode; +} + +QSvgNode::DisplayMode QSvgNode::displayMode() const +{ + return m_displayMode; +} + +qreal QSvgNode::strokeWidth(QPainter *p) +{ + QPen pen = p->pen(); + if (pen.style() == Qt::NoPen || pen.brush().style() == Qt::NoBrush || pen.isCosmetic()) + return 0; + return pen.widthF(); +} + +QT_END_NAMESPACE + +#endif // QT_NO_SVG diff --git a/src/svg/qsvgnode_p.h b/src/svg/qsvgnode_p.h new file mode 100644 index 0000000..9d06988 --- /dev/null +++ b/src/svg/qsvgnode_p.h @@ -0,0 +1,206 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSvg module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSVGNODE_P_H +#define QSVGNODE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qsvgstyle_p.h" + +#ifndef QT_NO_SVG + +#include "QtCore/qstring.h" +#include "QtCore/qhash.h" + +QT_BEGIN_NAMESPACE + +class QPainter; +class QSvgTinyDocument; + +class QSvgNode +{ +public: + enum Type + { + DOC, + G, + DEFS, + SWITCH, + ANIMATION, + ARC, + CIRCLE, + ELLIPSE, + IMAGE, + LINE, + PATH, + POLYGON, + POLYLINE, + RECT, + TEXT, + TEXTAREA, + TSPAN, + USE, + VIDEO + }; + enum DisplayMode { + InlineMode, + BlockMode, + ListItemMode, + RunInMode, + CompactMode, + MarkerMode, + TableMode, + InlineTableMode, + TableRowGroupMode, + TableHeaderGroupMode, + TableFooterGroupMode, + TableRowMode, + TableColumnGroupMode, + TableColumnMode, + TableCellMode, + TableCaptionMode, + NoneMode, + InheritMode + }; +public: + QSvgNode(QSvgNode *parent=0); + virtual ~QSvgNode(); + virtual void draw(QPainter *p, QSvgExtraStates &states) =0; + + QSvgNode *parent() const; + + void appendStyleProperty(QSvgStyleProperty *prop, const QString &id); + void applyStyle(QPainter *p, QSvgExtraStates &states) const; + void revertStyle(QPainter *p, QSvgExtraStates &states) const; + QSvgStyleProperty *styleProperty(QSvgStyleProperty::Type type) const; + QSvgFillStyleProperty *styleProperty(const QString &id) const; + + QSvgTinyDocument *document() const; + + virtual Type type() const =0; + virtual QRectF bounds(QPainter *p, QSvgExtraStates &states) const; + virtual QRectF transformedBounds(QPainter *p, QSvgExtraStates &states) const; + QRectF transformedBounds() const; + + void setRequiredFeatures(const QStringList &lst); + const QStringList & requiredFeatures() const; + + void setRequiredExtensions(const QStringList &lst); + const QStringList & requiredExtensions() const; + + void setRequiredLanguages(const QStringList &lst); + const QStringList & requiredLanguages() const; + + void setRequiredFormats(const QStringList &lst); + const QStringList & requiredFormats() const; + + void setRequiredFonts(const QStringList &lst); + const QStringList & requiredFonts() const; + + void setVisible(bool visible); + bool isVisible() const; + + void setDisplayMode(DisplayMode display); + DisplayMode displayMode() const; + + QString nodeId() const; + void setNodeId(const QString &i); + + QString xmlClass() const; + void setXmlClass(const QString &str); +protected: + mutable QSvgStyle m_style; + + static qreal strokeWidth(QPainter *p); +private: + QSvgNode *m_parent; + + QStringList m_requiredFeatures; + QStringList m_requiredExtensions; + QStringList m_requiredLanguages; + QStringList m_requiredFormats; + QStringList m_requiredFonts; + + bool m_visible; + + QString m_id; + QString m_class; + + DisplayMode m_displayMode; + mutable QRectF m_cachedBounds; + + friend class QSvgTinyDocument; +}; + +inline QSvgNode *QSvgNode::parent() const +{ + return m_parent; +} + +inline bool QSvgNode::isVisible() const +{ + return m_visible; +} + +inline QString QSvgNode::nodeId() const +{ + return m_id; +} + +inline QString QSvgNode::xmlClass() const +{ + return m_class; +} + +QT_END_NAMESPACE + +#endif // QT_NO_SVG +#endif // QSVGNODE_P_H diff --git a/src/svg/qsvgrenderer.cpp b/src/svg/qsvgrenderer.cpp new file mode 100644 index 0000000..9cc6b0c --- /dev/null +++ b/src/svg/qsvgrenderer.cpp @@ -0,0 +1,501 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSvg module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsvgrenderer.h" + +#ifndef QT_NO_SVGRENDERER + +#include "qsvgtinydocument_p.h" + +#include "qbytearray.h" +#include "qtimer.h" +#include "qdebug.h" +#include "private/qobject_p.h" + + +QT_BEGIN_NAMESPACE + +/*! + \class QSvgRenderer + \ingroup painting + + \brief The QSvgRenderer class is used to draw the contents of SVG files onto paint devices. + \since 4.1 + \reentrant + + Using QSvgRenderer, Scalable Vector Graphics (SVG) can be rendered onto any QPaintDevice + subclass, including QWidget, QImage, and QGLWidget. + + QSvgRenderer provides an API that supports basic features of SVG rendering, such as loading + and rendering of static drawings, and more interactive features like animation. Since the + rendering is performed using QPainter, SVG drawings can be rendered on any subclass of + QPaintDevice. + + SVG drawings are either loaded when an QSvgRenderer is constructed, or loaded later + using the load() functions. Data is either supplied directly as serialized XML, or + indirectly using a file name. If a valid file has been loaded, either when the renderer + is constructed or at some later time, isValid() returns true; otherwise it returns false. + QSvgRenderer provides the render() slot to render the current document, or the current + frame of an animated document, using a given painter. + + The defaultSize() function provides information about the amount of space that is required + to render the currently loaded SVG file. This is useful for paint devices, such as QWidget, + that often need to supply a size hint to their parent layout. + The default size of a drawing may differ from its visible area, found using the \l viewBox + property. + + Animated SVG drawings are supported, and can be controlled with a simple collection of + functions and properties: + + \list + \o The animated() function indicates whether a drawing contains animation information. + \omit + \o The animationDuration() function provides the duration in milliseconds of the + animation, without taking any looping into account. + \o The \l currentFrame property contains the current frame of the animation. + \endomit + \o The \l framesPerSecond property contains the rate at which the animation plays. + \endlist + + Finally, the QSvgRenderer class provides the repaintNeeded() signal which is emitted + whenever the rendering of the document needs to be updated. + + \sa QSvgWidget, {QtSvg Module}, {SVG Viewer Example}, QPicture +*/ + +class QSvgRendererPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QSvgRenderer) +public: + explicit QSvgRendererPrivate() + : QObjectPrivate(), + render(0), timer(0), + fps(30) + {} + ~QSvgRendererPrivate() + { + delete render; + } + + static void callRepaintNeeded(QSvgRenderer *const q); + + QSvgTinyDocument *render; + QTimer *timer; + int fps; +}; + +/*! + Constructs a new renderer with the given \a parent. +*/ +QSvgRenderer::QSvgRenderer(QObject *parent) + : QObject(*(new QSvgRendererPrivate), parent) +{ +} + +/*! + Constructs a new renderer with the given \a parent and loads the contents of the + SVG file with the specified \a filename. +*/ +QSvgRenderer::QSvgRenderer(const QString &filename, QObject *parent) + : QObject(*new QSvgRendererPrivate, parent) +{ + load(filename); +} + +/*! + Constructs a new renderer with the given \a parent and loads the SVG data + from the byte array specified by \a contents. +*/ +QSvgRenderer::QSvgRenderer(const QByteArray &contents, QObject *parent) + : QObject(*new QSvgRendererPrivate, parent) +{ + load(contents); +} + +/*! + \since 4.5 + + Constructs a new renderer with the given \a parent and loads the SVG data + using the stream reader specified by \a contents. +*/ +QSvgRenderer::QSvgRenderer(QXmlStreamReader *contents, QObject *parent) + : QObject(*new QSvgRendererPrivate, parent) +{ + load(contents); +} + +/*! + Destroys the renderer. +*/ +QSvgRenderer::~QSvgRenderer() +{ + +} + +/*! + Returns true if there is a valid current document; otherwise returns false. +*/ +bool QSvgRenderer::isValid() const +{ + Q_D(const QSvgRenderer); + return d->render; +} + +/*! + Returns the default size of the document contents. +*/ +QSize QSvgRenderer::defaultSize() const +{ + Q_D(const QSvgRenderer); + if (d->render) + return d->render->size(); + else + return QSize(); +} + +/*! + Returns viewBoxF().toRect(). + + \sa viewBoxF() +*/ +QRect QSvgRenderer::viewBox() const +{ + Q_D(const QSvgRenderer); + if (d->render) + return d->render->viewBox().toRect(); + else + return QRect(); +} + +/*! + \property QSvgRenderer::viewBox + \brief the rectangle specifying the visible area of the document in logical coordinates + \since 4.2 +*/ +void QSvgRenderer::setViewBox(const QRect &viewbox) +{ + Q_D(QSvgRenderer); + if (d->render) + d->render->setViewBox(viewbox); +} + +/*! + Returns true if the current document contains animated elements; otherwise + returns false. + + \sa framesPerSecond() +*/ +bool QSvgRenderer::animated() const +{ + Q_D(const QSvgRenderer); + if (d->render) + return d->render->animated(); + else + return false; +} + +/*! + \property QSvgRenderer::framesPerSecond + \brief the number of frames per second to be shown + + The number of frames per second is 0 if the current document is not animated. + + \sa animated() +*/ +int QSvgRenderer::framesPerSecond() const +{ + Q_D(const QSvgRenderer); + return d->fps; +} + +void QSvgRenderer::setFramesPerSecond(int num) +{ + Q_D(QSvgRenderer); + if (num < 0) { + qWarning("QSvgRenderer::setFramesPerSecond: Cannot set negative value %d", num); + return; + } + d->fps = num; +} + +/*! + \property QSvgRenderer::currentFrame + \brief the current frame of the document's animation, or 0 if the document is not animated + \internal + + \sa animationDuration(), framesPerSecond, animated() +*/ + +/*! + \internal +*/ +int QSvgRenderer::currentFrame() const +{ + Q_D(const QSvgRenderer); + return d->render->currentFrame(); +} + +/*! + \internal +*/ +void QSvgRenderer::setCurrentFrame(int frame) +{ + Q_D(QSvgRenderer); + d->render->setCurrentFrame(frame); +} + +/*! + \internal + + Returns the number of frames in the animation, or 0 if the current document is not + animated. + + \sa animated(), framesPerSecond +*/ +int QSvgRenderer::animationDuration() const +{ + Q_D(const QSvgRenderer); + return d->render->animationDuration(); +} + +/*! + \internal + \since 4.5 + + We can't have template functions, that's loadDocument(), as friends, for this + code, so we let this function be a friend of QSvgRenderer instead. + */ +void QSvgRendererPrivate::callRepaintNeeded(QSvgRenderer *const q) +{ + q->repaintNeeded(); +} + +template<typename TInputType> +static bool loadDocument(QSvgRenderer *const q, + QSvgRendererPrivate *const d, + const TInputType &in) +{ + delete d->render; + d->render = QSvgTinyDocument::load(in); + if (d->render && d->render->animated() && d->fps > 0) { + if (!d->timer) + d->timer = new QTimer(q); + else + d->timer->stop(); + q->connect(d->timer, SIGNAL(timeout()), + q, SIGNAL(repaintNeeded())); + d->timer->start(1000/d->fps); + } else if (d->timer) { + d->timer->stop(); + } + + //force first update + QSvgRendererPrivate::callRepaintNeeded(q); + + return d->render; +} + +/*! + Loads the SVG file specified by \a filename, returning true if the content + was successfully parsed; otherwise returns false. +*/ +bool QSvgRenderer::load(const QString &filename) +{ + Q_D(QSvgRenderer); + return loadDocument(this, d, filename); +} + +/*! + Loads the specified SVG format \a contents, returning true if the content + was successfully parsed; otherwise returns false. +*/ +bool QSvgRenderer::load(const QByteArray &contents) +{ + Q_D(QSvgRenderer); + return loadDocument(this, d, contents); +} + +/*! + Loads the specified SVG in \a contents, returning true if the content + was successfully parsed; otherwise returns false. + + The reader will be used from where it currently is positioned. If \a contents + is \c null, behavior is undefined. + + \since 4.5 +*/ +bool QSvgRenderer::load(QXmlStreamReader *contents) +{ + Q_D(QSvgRenderer); + return loadDocument(this, d, contents); +} + +/*! + Renders the current document, or the current frame of an animated + document, using the given \a painter. +*/ +void QSvgRenderer::render(QPainter *painter) +{ + Q_D(QSvgRenderer); + if (d->render) { + d->render->draw(painter); + } +} + +/*! + \fn void QSvgRenderer::repaintNeeded() + + This signal is emitted whenever the rendering of the document + needs to be updated, usually for the purposes of animation. +*/ + +/*! + Renders the given element with \a elementId using the given \a painter + on the specified \a bounds. If the bounding rectangle is not specified + the SVG element is mapped to the whole paint device. +*/ +void QSvgRenderer::render(QPainter *painter, const QString &elementId, + const QRectF &bounds) +{ + Q_D(QSvgRenderer); + if (d->render) { + d->render->draw(painter, elementId, bounds); + } +} + +/*! + Renders the current document, or the current frame of an animated + document, using the given \a painter on the specified \a bounds within + the painter. If the bounding rectangle is not specified + the SVG file is mapped to the whole paint device. +*/ +void QSvgRenderer::render(QPainter *painter, const QRectF &bounds) +{ + Q_D(QSvgRenderer); + if (d->render) { + d->render->draw(painter, bounds); + } +} + +QRectF QSvgRenderer::viewBoxF() const +{ + Q_D(const QSvgRenderer); + if (d->render) + return d->render->viewBox(); + else + return QRect(); +} + +void QSvgRenderer::setViewBox(const QRectF &viewbox) +{ + Q_D(QSvgRenderer); + if (d->render) + d->render->setViewBox(viewbox); +} + +/*! + \since 4.2 + + Returns bounding rectangle of the item with the given \a id. + The transformation matrix of parent elements is not affecting + the bounds of the element. + + \sa matrixForElement() +*/ +QRectF QSvgRenderer::boundsOnElement(const QString &id) const +{ + Q_D(const QSvgRenderer); + QRectF bounds; + if (d->render) + bounds = d->render->boundsOnElement(id); + return bounds; +} + + +/*! + \since 4.2 + + Returns true if the element with the given \a id exists + in the currently parsed SVG file and is a renderable + element. + + Note: this method returns true only for elements that + can be rendered. Which implies that elements that are considered + part of the fill/stroke style properties, e.g. radialGradients + even tough marked with "id" attributes will not be found by this + method. +*/ +bool QSvgRenderer::elementExists(const QString &id) const +{ + Q_D(const QSvgRenderer); + bool exists = false; + if (d->render) + exists = d->render->elementExists(id); + return exists; +} + +/*! + \since 4.2 + + Returns the transformation matrix for the element + with the given \a id. The matrix is a product of + the transformation of the element's parents. The transformation of + the element itself is not included. + + To find the bounding rectangle of the element in logical coordinates, + you can apply the matrix on the rectangle returned from boundsOnElement(). + + \sa boundsOnElement() +*/ +QMatrix QSvgRenderer::matrixForElement(const QString &id) const +{ + Q_D(const QSvgRenderer); + QMatrix mat; + if (d->render) + mat = d->render->matrixForElement(id); + return mat; +} + +QT_END_NAMESPACE + +#include "moc_qsvgrenderer.cpp" + +#endif // QT_NO_SVGRENDERER diff --git a/src/svg/qsvgrenderer.h b/src/svg/qsvgrenderer.h new file mode 100644 index 0000000..b1a20d2 --- /dev/null +++ b/src/svg/qsvgrenderer.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSvg module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSVGRENDERER_H +#define QSVGRENDERER_H + +#include <QtGui/qmatrix.h> + +#ifndef QT_NO_SVGRENDERER + +#include <QtCore/qobject.h> +#include <QtCore/qsize.h> +#include <QtCore/qrect.h> +#include <QtCore/qxmlstream.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Svg) + +class QSvgRendererPrivate; +class QPainter; +class QByteArray; + +class Q_SVG_EXPORT QSvgRenderer : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QRectF viewBox READ viewBoxF WRITE setViewBox) + Q_PROPERTY(int framesPerSecond READ framesPerSecond WRITE setFramesPerSecond) + Q_PROPERTY(int currentFrame READ currentFrame WRITE setCurrentFrame) +public: + QSvgRenderer(QObject *parent=0); + QSvgRenderer(const QString &filename, QObject *parent=0); + QSvgRenderer(const QByteArray &contents, QObject *parent=0); + QSvgRenderer(QXmlStreamReader *contents, QObject *parent=0); + ~QSvgRenderer(); + + bool isValid() const; + + QSize defaultSize() const; + + QRect viewBox() const; + QRectF viewBoxF() const; + void setViewBox(const QRect &viewbox); + void setViewBox(const QRectF &viewbox); + + bool animated() const; + int framesPerSecond() const; + void setFramesPerSecond(int num); + int currentFrame() const; + void setCurrentFrame(int); + int animationDuration() const;//in seconds + + QRectF boundsOnElement(const QString &id) const; + bool elementExists(const QString &id) const; + QMatrix matrixForElement(const QString &id) const; + +public Q_SLOTS: + bool load(const QString &filename); + bool load(const QByteArray &contents); + bool load(QXmlStreamReader *contents); + void render(QPainter *p); + void render(QPainter *p, const QRectF &bounds); + + void render(QPainter *p, const QString &elementId, + const QRectF &bounds=QRectF()); + +Q_SIGNALS: + void repaintNeeded(); + +private: + Q_DECLARE_PRIVATE(QSvgRenderer) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_SVGRENDERER +#endif // QSVGRENDERER_H diff --git a/src/svg/qsvgstructure.cpp b/src/svg/qsvgstructure.cpp new file mode 100644 index 0000000..71fc8d2 --- /dev/null +++ b/src/svg/qsvgstructure.cpp @@ -0,0 +1,383 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSvg module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsvgstructure_p.h" + +#ifndef QT_NO_SVG + +#include "qsvgnode_p.h" +#include "qsvgstyle_p.h" +#include "qsvgtinydocument_p.h" + +#include "qpainter.h" +#include "qlocale.h" +#include "qdebug.h" + +QT_BEGIN_NAMESPACE + +QSvgG::QSvgG(QSvgNode *parent) + : QSvgStructureNode(parent) +{ + +} + +QSvgStructureNode::~QSvgStructureNode() +{ + qDeleteAll(m_renderers); +} + +void QSvgG::draw(QPainter *p, QSvgExtraStates &states) +{ + QList<QSvgNode*>::iterator itr = m_renderers.begin(); + applyStyle(p, states); + + while (itr != m_renderers.end()) { + QSvgNode *node = *itr; + if ((node->isVisible()) && (node->displayMode() != QSvgNode::NoneMode)) + node->draw(p, states); + ++itr; + } + revertStyle(p, states); +} + +QSvgNode::Type QSvgG::type() const +{ + return G; +} + +QSvgStructureNode::QSvgStructureNode(QSvgNode *parent) + :QSvgNode(parent) +{ + +} + +QSvgNode * QSvgStructureNode::scopeNode(const QString &id) const +{ + QSvgTinyDocument *doc = document(); + return doc ? doc->namedNode(id) : 0; +} + +void QSvgStructureNode::addChild(QSvgNode *child, const QString &id) +{ + m_renderers.append(child); + + if (id.isEmpty()) + return; //we can't add it to scope without id + + QSvgTinyDocument *doc = document(); + if (doc) + doc->addNamedNode(id, child); +} + +QSvgDefs::QSvgDefs(QSvgNode *parent) + : QSvgStructureNode(parent) +{ +} + +void QSvgDefs::draw(QPainter *, QSvgExtraStates &) +{ + //noop +} + +QSvgNode::Type QSvgDefs::type() const +{ + return DEFS; +} + +/* + Below is a lookup function based on the gperf output using the following set: + + http://www.w3.org/Graphics/SVG/feature/1.2/#SVG + http://www.w3.org/Graphics/SVG/feature/1.2/#SVG-static + http://www.w3.org/Graphics/SVG/feature/1.2/#CoreAttribute + http://www.w3.org/Graphics/SVG/feature/1.2/#Structure + http://www.w3.org/Graphics/SVG/feature/1.2/#ConditionalProcessing + http://www.w3.org/Graphics/SVG/feature/1.2/#ConditionalProcessingAttribute + http://www.w3.org/Graphics/SVG/feature/1.2/#Image + http://www.w3.org/Graphics/SVG/feature/1.2/#Prefetch + http://www.w3.org/Graphics/SVG/feature/1.2/#Shape + http://www.w3.org/Graphics/SVG/feature/1.2/#Text + http://www.w3.org/Graphics/SVG/feature/1.2/#PaintAttribute + http://www.w3.org/Graphics/SVG/feature/1.2/#OpacityAttribute + http://www.w3.org/Graphics/SVG/feature/1.2/#GraphicsAttribute + http://www.w3.org/Graphics/SVG/feature/1.2/#Gradient + http://www.w3.org/Graphics/SVG/feature/1.2/#SolidColor + http://www.w3.org/Graphics/SVG/feature/1.2/#XlinkAttribute + http://www.w3.org/Graphics/SVG/feature/1.2/#ExternalResourcesRequiredAttribute + http://www.w3.org/Graphics/SVG/feature/1.2/#Font + http://www.w3.org/Graphics/SVG/feature/1.2/#Hyperlinking + http://www.w3.org/Graphics/SVG/feature/1.2/#Extensibility +*/ + +// ----- begin of generated code ----- + +/* C code produced by gperf version 3.0.2 */ +/* Command-line: gperf -c -L c svg */ +/* Computed positions: -k'45-46' */ + +#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \ + && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \ + && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \ + && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \ + && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \ + && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \ + && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \ + && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \ + && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \ + && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \ + && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \ + && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \ + && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \ + && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \ + && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \ + && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \ + && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \ + && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \ + && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \ + && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \ + && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \ + && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \ + && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126)) +/* The character set is not based on ISO-646. */ +#error "gperf generated tables don't work with this execution character set. Please report a bug to <bug-gnu-gperf@gnu.org>." +#endif + +enum { + TOTAL_KEYWORDS = 20, + MIN_WORD_LENGTH = 47, + MAX_WORD_LENGTH = 78, + MIN_HASH_VALUE = 48, + MAX_HASH_VALUE = 88 +}; +/* maximum key range = 41, duplicates = 0 */ + +inline static bool isSupportedSvgFeature(const QString &str) +{ + static const unsigned char asso_values[] = { + 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, + 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, + 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, + 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, + 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, + 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, + 89, 89, 89, 89, 89, 89, 89, 0, 89, 5, + 15, 5, 0, 10, 89, 89, 89, 89, 89, 0, + 15, 89, 89, 0, 0, 89, 5, 89, 0, 89, + 89, 89, 89, 89, 89, 89, 89, 0, 89, 89, + 89, 0, 89, 89, 0, 89, 89, 89, 0, 5, + 89, 0, 0, 89, 5, 89, 0, 89, 89, 89, + 5, 0, 89, 89, 89, 89, 89, 89, 89, 89, + 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, + 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, + 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, + 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, + 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, + 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, + 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, + 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, + 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, + 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, + 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, + 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, + 89, 89, 89, 89, 89, 89 + }; + + static const char * wordlist[] = { + "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", + "", "", "", + "http://www.w3.org/Graphics/SVG/feature/1.2/#Text", + "http://www.w3.org/Graphics/SVG/feature/1.2/#Shape", + "", "", + "http://www.w3.org/Graphics/SVG/feature/1.2/#SVG", + "http://www.w3.org/Graphics/SVG/feature/1.2/#Structure", + "http://www.w3.org/Graphics/SVG/feature/1.2/#SolidColor", + "", + "http://www.w3.org/Graphics/SVG/feature/1.2/#Hyperlinking", + "http://www.w3.org/Graphics/SVG/feature/1.2/#CoreAttribute", + "http://www.w3.org/Graphics/SVG/feature/1.2/#XlinkAttribute", + "http://www.w3.org/Graphics/SVG/feature/1.2/#SVG-static", + "http://www.w3.org/Graphics/SVG/feature/1.2/#OpacityAttribute", + "", + "http://www.w3.org/Graphics/SVG/feature/1.2/#Gradient", + "http://www.w3.org/Graphics/SVG/feature/1.2/#Font", + "http://www.w3.org/Graphics/SVG/feature/1.2/#Image", + "http://www.w3.org/Graphics/SVG/feature/1.2/#ConditionalProcessing", + "", + "http://www.w3.org/Graphics/SVG/feature/1.2/#Extensibility", + "", "", "", + "http://www.w3.org/Graphics/SVG/feature/1.2/#GraphicsAttribute", + "http://www.w3.org/Graphics/SVG/feature/1.2/#Prefetch", + "http://www.w3.org/Graphics/SVG/feature/1.2/#PaintAttribute", + "http://www.w3.org/Graphics/SVG/feature/1.2/#ConditionalProcessingAttribute", + "", "", "", "", "", "", "", "", "", + "", "", "", "", + "http://www.w3.org/Graphics/SVG/feature/1.2/#ExternalResourcesRequiredAttribute" + }; + + if (str.length() <= MAX_WORD_LENGTH && str.length() >= MIN_WORD_LENGTH) { + const int key = str.length() + + asso_values[str.at(45).unicode()] + + asso_values[str.at(44).unicode()]; + if (key <= MAX_HASH_VALUE && key >= 0) + return str == QLatin1String(wordlist[key]); + } + return false; +} + +// ----- end of generated code ----- + +static inline bool isSupportedSvgExtension(const QString &) +{ + return false; +} + + +QSvgSwitch::QSvgSwitch(QSvgNode *parent) + : QSvgStructureNode(parent) +{ + init(); +} + +void QSvgSwitch::draw(QPainter *p, QSvgExtraStates &states) +{ + QList<QSvgNode*>::iterator itr = m_renderers.begin(); + applyStyle(p, states); + + while (itr != m_renderers.end()) { + QSvgNode *node = *itr; + if (node->isVisible() && (node->displayMode() != QSvgNode::NoneMode)) { + const QStringList &features = node->requiredFeatures(); + const QStringList &extensions = node->requiredExtensions(); + const QStringList &languages = node->requiredLanguages(); + const QStringList &formats = node->requiredFormats(); + const QStringList &fonts = node->requiredFonts(); + + bool okToRender = true; + if (!features.isEmpty()) { + QStringList::const_iterator sitr = features.constBegin(); + for (; sitr != features.constEnd(); ++sitr) { + if (!isSupportedSvgFeature(*sitr)) { + okToRender = false; + break; + } + } + } + + if (okToRender && !extensions.isEmpty()) { + QStringList::const_iterator sitr = extensions.constBegin(); + for (; sitr != extensions.constEnd(); ++sitr) { + if (!isSupportedSvgExtension(*sitr)) { + okToRender = false; + break; + } + } + } + + if (okToRender && !languages.isEmpty()) { + QStringList::const_iterator sitr = languages.constBegin(); + okToRender = false; + for (; sitr != languages.constEnd(); ++sitr) { + if ((*sitr).startsWith(m_systemLanguagePrefix)) { + okToRender = true; + break; + } + } + } + + if (okToRender && !formats.isEmpty()) { + okToRender = false; + } + + if (okToRender && !fonts.isEmpty()) { + okToRender = false; + } + + if (okToRender) { + node->draw(p, states); + break; + } + } + ++itr; + } + revertStyle(p, states); +} + +QSvgNode::Type QSvgSwitch::type() const +{ + return SWITCH; +} + +void QSvgSwitch::init() +{ + QLocale locale; + m_systemLanguage = locale.name().replace(QLatin1Char('_'), QLatin1Char('-')); + int idx = m_systemLanguage.indexOf(QLatin1Char('-')); + m_systemLanguagePrefix = m_systemLanguage.mid(0, idx); +} + +QRectF QSvgStructureNode::bounds(QPainter *p, QSvgExtraStates &states) const +{ + QRectF bounds; + foreach(QSvgNode *node, m_renderers) + bounds |= node->transformedBounds(p, states); + return bounds; +} + +QSvgNode * QSvgStructureNode::previousSiblingNode(QSvgNode *n) const +{ + QSvgNode *prev = 0; + QList<QSvgNode*>::const_iterator itr = m_renderers.constBegin(); + while (itr != m_renderers.constEnd()) { + QSvgNode *node = *itr; + if (node == n) + return prev; + prev = node; + } + return prev; +} + +QT_END_NAMESPACE + +#endif // QT_NO_SVG diff --git a/src/svg/qsvgstructure_p.h b/src/svg/qsvgstructure_p.h new file mode 100644 index 0000000..f511c02 --- /dev/null +++ b/src/svg/qsvgstructure_p.h @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSvg module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSVGSTRUCTURE_P_H +#define QSVGSTRUCTURE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qsvgnode_p.h" + +#ifndef QT_NO_SVG + +#include "QtCore/qlist.h" +#include "QtCore/qhash.h" + +QT_BEGIN_NAMESPACE + +class QSvgTinyDocument; +class QSvgNode; +class QPainter; +class QSvgDefs; + +class QSvgStructureNode : public QSvgNode +{ +public: + QSvgStructureNode(QSvgNode *parent); + ~QSvgStructureNode(); + QSvgNode *scopeNode(const QString &id) const; + void addChild(QSvgNode *child, const QString &id); + virtual QRectF bounds(QPainter *p, QSvgExtraStates &states) const; + QSvgNode *previousSiblingNode(QSvgNode *n) const; + QList<QSvgNode*> renderers() const { return m_renderers; } +protected: + QList<QSvgNode*> m_renderers; + QHash<QString, QSvgNode*> m_scope; + QList<QSvgStructureNode*> m_linkedScopes; +}; + +class QSvgG : public QSvgStructureNode +{ +public: + QSvgG(QSvgNode *parent); + virtual void draw(QPainter *p, QSvgExtraStates &states); + Type type() const; +}; + +class QSvgDefs : public QSvgStructureNode +{ +public: + QSvgDefs(QSvgNode *parent); + virtual void draw(QPainter *p, QSvgExtraStates &states); + Type type() const; +}; + +class QSvgSwitch : public QSvgStructureNode +{ +public: + QSvgSwitch(QSvgNode *parent); + virtual void draw(QPainter *p, QSvgExtraStates &states); + Type type() const; +private: + void init(); +private: + QString m_systemLanguage; + QString m_systemLanguagePrefix; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_SVG +#endif // QSVGSTRUCTURE_P_H diff --git a/src/svg/qsvgstyle.cpp b/src/svg/qsvgstyle.cpp new file mode 100644 index 0000000..06adec5 --- /dev/null +++ b/src/svg/qsvgstyle.cpp @@ -0,0 +1,958 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSvg module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsvgstyle_p.h" + +#ifndef QT_NO_SVG + +#include "qsvgfont_p.h" +#include "qsvggraphics_p.h" +#include "qsvgnode_p.h" +#include "qsvgtinydocument_p.h" + +#include "qpainter.h" +#include "qpair.h" +#include "qcolor.h" +#include "qdebug.h" +#include "qmath.h" +#include "qnumeric.h" + +QT_BEGIN_NAMESPACE + +QSvgExtraStates::QSvgExtraStates() + : fillOpacity(1.0) + , strokeOpacity(1.0) + , svgFont(0) + , textAnchor(Qt::AlignLeft) + , fontWeight(400) + , fillRule(Qt::WindingFill) + , strokeDashOffset(0) + , vectorEffect(false) +{ +} + +QSvgStyleProperty::~QSvgStyleProperty() +{ +} + +void QSvgFillStyleProperty::apply(QPainter *, const QSvgNode *, QSvgExtraStates &) +{ + Q_ASSERT(!"This should not be called!"); +} + +void QSvgFillStyleProperty::revert(QPainter *, QSvgExtraStates &) +{ + Q_ASSERT(!"This should not be called!"); +} + + +QSvgQualityStyle::QSvgQualityStyle(int color) + : m_colorRendering(color) +{ + +} +void QSvgQualityStyle::apply(QPainter *, const QSvgNode *, QSvgExtraStates &) +{ + +} +void QSvgQualityStyle::revert(QPainter *, QSvgExtraStates &) +{ + +} + +QSvgFillStyle::QSvgFillStyle() + : m_style(0) + , m_fillRule(Qt::WindingFill) + , m_oldFillRule(Qt::WindingFill) + , m_fillOpacity(1.0) + , m_oldFillOpacity(0) + , m_gradientResolved(1) + , m_fillRuleSet(0) + , m_fillOpacitySet(0) + , m_fillSet(0) +{ +} + +void QSvgFillStyle::setFillRule(Qt::FillRule f) +{ + m_fillRuleSet = 1; + m_fillRule = f; +} + +void QSvgFillStyle::setFillOpacity(qreal opacity) +{ + m_fillOpacitySet = 1; + m_fillOpacity = opacity; +} + +void QSvgFillStyle::setFillStyle(QSvgFillStyleProperty* style) +{ + m_style = style; + m_fillSet = 1; +} + +void QSvgFillStyle::setBrush(QBrush brush) +{ + m_fill = brush; + m_style = 0; + m_fillSet = 1; +} + +void QSvgFillStyle::apply(QPainter *p, const QSvgNode *, QSvgExtraStates &states) +{ + m_oldFill = p->brush(); + m_oldFillRule = states.fillRule; + m_oldFillOpacity = states.fillOpacity; + + if (m_fillRuleSet) + states.fillRule = m_fillRule; + if (m_fillSet) { + if (m_style) + p->setBrush(m_style->brush(p, states)); + else + p->setBrush(m_fill); + } + if (m_fillOpacitySet) + states.fillOpacity = m_fillOpacity; +} + +void QSvgFillStyle::revert(QPainter *p, QSvgExtraStates &states) +{ + if (m_fillOpacitySet) + states.fillOpacity = m_oldFillOpacity; + if (m_fillSet) + p->setBrush(m_oldFill); + if (m_fillRuleSet) + states.fillRule = m_oldFillRule; +} + +QSvgViewportFillStyle::QSvgViewportFillStyle(const QBrush &brush) + : m_viewportFill(brush) +{ +} + +void QSvgViewportFillStyle::apply(QPainter *p, const QSvgNode *, QSvgExtraStates &) +{ + m_oldFill = p->brush(); + p->setBrush(m_viewportFill); +} + +void QSvgViewportFillStyle::revert(QPainter *p, QSvgExtraStates &) +{ + p->setBrush(m_oldFill); +} + +QSvgFontStyle::QSvgFontStyle(QSvgFont *font, QSvgTinyDocument *doc) + : m_svgFont(font) + , m_doc(doc) + , m_familySet(0) + , m_sizeSet(0) + , m_styleSet(0) + , m_variantSet(0) + , m_weightSet(0) + , m_textAnchorSet(0) +{ +} + +QSvgFontStyle::QSvgFontStyle() + : m_svgFont(0) + , m_doc(0) + , m_familySet(0) + , m_sizeSet(0) + , m_styleSet(0) + , m_variantSet(0) + , m_weightSet(0) + , m_textAnchorSet(0) +{ +} + +int QSvgFontStyle::SVGToQtWeight(int weight) { + switch (weight) { + case 100: + case 200: + return QFont::Light; + case 300: + case 400: + return QFont::Normal; + case 500: + case 600: + return QFont::DemiBold; + case 700: + case 800: + return QFont::Bold; + case 900: + return QFont::Black; + } + return QFont::Normal; +} + +void QSvgFontStyle::apply(QPainter *p, const QSvgNode *, QSvgExtraStates &states) +{ + m_oldQFont = p->font(); + m_oldSvgFont = states.svgFont; + m_oldTextAnchor = states.textAnchor; + m_oldWeight = states.fontWeight; + + if (m_textAnchorSet) + states.textAnchor = m_textAnchor; + + QFont font = m_oldQFont; + if (m_familySet) { + states.svgFont = m_svgFont; + font.setFamily(m_qfont.family()); + } + + if (m_sizeSet) + font.setPointSizeF(m_qfont.pointSizeF()); + + if (m_styleSet) + font.setStyle(m_qfont.style()); + + if (m_variantSet) + font.setCapitalization(m_qfont.capitalization()); + + if (m_weightSet) { + if (m_weight == BOLDER) { + states.fontWeight = qMin(states.fontWeight + 100, 900); + } else if (m_weight == LIGHTER) { + states.fontWeight = qMax(states.fontWeight - 100, 100); + } else { + states.fontWeight = m_weight; + } + font.setWeight(SVGToQtWeight(states.fontWeight)); + } + + p->setFont(font); +} + +void QSvgFontStyle::revert(QPainter *p, QSvgExtraStates &states) +{ + p->setFont(m_oldQFont); + states.svgFont = m_oldSvgFont; + states.textAnchor = m_oldTextAnchor; + states.fontWeight = m_oldWeight; +} + +QSvgStrokeStyle::QSvgStrokeStyle() + : m_strokeOpacity(1.0) + , m_oldStrokeOpacity(0.0) + , m_strokeDashOffset(0) + , m_oldStrokeDashOffset(0) + , m_style(0) + , m_gradientResolved(1) + , m_vectorEffect(0) + , m_oldVectorEffect(0) + , m_strokeSet(0) + , m_strokeDashArraySet(0) + , m_strokeDashOffsetSet(0) + , m_strokeLineCapSet(0) + , m_strokeLineJoinSet(0) + , m_strokeMiterLimitSet(0) + , m_strokeOpacitySet(0) + , m_strokeWidthSet(0) + , m_vectorEffectSet(0) +{ +} + +void QSvgStrokeStyle::apply(QPainter *p, const QSvgNode *, QSvgExtraStates &states) +{ + m_oldStroke = p->pen(); + m_oldStrokeOpacity = states.strokeOpacity; + m_oldStrokeDashOffset = states.strokeDashOffset; + m_oldVectorEffect = states.vectorEffect; + + QPen pen = p->pen(); + + qreal oldWidth = pen.widthF(); + qreal width = m_stroke.widthF(); + if (oldWidth == 0) + oldWidth = 1; + if (width == 0) + width = 1; + qreal scale = oldWidth / width; + + if (m_strokeOpacitySet) + states.strokeOpacity = m_strokeOpacity; + + if (m_vectorEffectSet) + states.vectorEffect = m_vectorEffect; + + if (m_strokeSet) { + if (m_style) + pen.setBrush(m_style->brush(p, states)); + else + pen.setBrush(m_stroke.brush()); + } + + if (m_strokeWidthSet) + pen.setWidthF(m_stroke.widthF()); + + bool setDashOffsetNeeded = false; + + if (m_strokeDashOffsetSet) { + states.strokeDashOffset = m_strokeDashOffset; + setDashOffsetNeeded = true; + } + + if (m_strokeDashArraySet) { + if (m_stroke.style() == Qt::SolidLine) { + pen.setStyle(Qt::SolidLine); + } else if (m_strokeWidthSet || oldWidth == 1) { + // If both width and dash array was set, the dash array is already scaled correctly. + pen.setDashPattern(m_stroke.dashPattern()); + setDashOffsetNeeded = true; + } else { + // If dash array was set, but not the width, the dash array has to be scaled with respect to the old width. + QVector<qreal> dashes = m_stroke.dashPattern(); + for (int i = 0; i < dashes.size(); ++i) + dashes[i] /= oldWidth; + pen.setDashPattern(dashes); + setDashOffsetNeeded = true; + } + } else if (m_strokeWidthSet && pen.style() != Qt::SolidLine && scale != 1) { + // If the width was set, but not the dash array, the old dash array must be scaled with respect to the new width. + QVector<qreal> dashes = pen.dashPattern(); + for (int i = 0; i < dashes.size(); ++i) + dashes[i] *= scale; + pen.setDashPattern(dashes); + setDashOffsetNeeded = true; + } + + if (m_strokeLineCapSet) + pen.setCapStyle(m_stroke.capStyle()); + if (m_strokeLineJoinSet) + pen.setJoinStyle(m_stroke.joinStyle()); + if (m_strokeMiterLimitSet) + pen.setMiterLimit(m_stroke.miterLimit()); + + // You can have dash offset on solid strokes in SVG files, but not in Qt. + // QPen::setDashOffset() will set the pen style to Qt::CustomDashLine, + // so don't call the method if the pen is solid. + if (setDashOffsetNeeded && pen.style() != Qt::SolidLine) { + qreal currentWidth = pen.widthF(); + if (currentWidth == 0) + currentWidth = 1; + pen.setDashOffset(states.strokeDashOffset / currentWidth); + } + + pen.setCosmetic(states.vectorEffect); + + p->setPen(pen); +} + +void QSvgStrokeStyle::revert(QPainter *p, QSvgExtraStates &states) +{ + p->setPen(m_oldStroke); + states.strokeOpacity = m_oldStrokeOpacity; + states.strokeDashOffset = m_oldStrokeDashOffset; + states.vectorEffect = m_oldVectorEffect; +} + +void QSvgStrokeStyle::setDashArray(const QVector<qreal> &dashes) +{ + if (m_strokeWidthSet) { + QVector<qreal> d = dashes; + qreal w = m_stroke.widthF(); + if (w != 0 && w != 1) { + for (int i = 0; i < d.size(); ++i) + d[i] /= w; + } + m_stroke.setDashPattern(d); + } else { + m_stroke.setDashPattern(dashes); + } + m_strokeDashArraySet = 1; +} + +QSvgSolidColorStyle::QSvgSolidColorStyle(const QColor &color) + : m_solidColor(color) +{ +} + +QSvgGradientStyle::QSvgGradientStyle(QGradient *grad) + : m_gradient(grad), m_gradientStopsSet(false) +{ +} + +QBrush QSvgGradientStyle::brush(QPainter *, QSvgExtraStates &) +{ + if (!m_link.isEmpty()) { + resolveStops(); + } + + // If the gradient is marked as empty, insert transparent black + if (!m_gradientStopsSet) { + m_gradient->setStops(QGradientStops() << QGradientStop(0.0, QColor(0, 0, 0, 0))); + m_gradientStopsSet = true; + } + + QBrush b(*m_gradient); + + if (!m_matrix.isIdentity()) + b.setMatrix(m_matrix); + + return b; +} + + +void QSvgGradientStyle::setMatrix(const QMatrix &mat) +{ + m_matrix = mat; +} + +QSvgTransformStyle::QSvgTransformStyle(const QTransform &trans) + : m_transform(trans) +{ +} + +void QSvgTransformStyle::apply(QPainter *p, const QSvgNode *, QSvgExtraStates &) +{ + m_oldWorldTransform = p->worldTransform(); + p->setWorldTransform(m_transform, true); +} + +void QSvgTransformStyle::revert(QPainter *p, QSvgExtraStates &) +{ + p->setWorldTransform(m_oldWorldTransform, false /* don't combine */); +} + +QSvgStyleProperty::Type QSvgQualityStyle::type() const +{ + return QUALITY; +} + +QSvgStyleProperty::Type QSvgFillStyle::type() const +{ + return FILL; +} + +QSvgStyleProperty::Type QSvgViewportFillStyle::type() const +{ + return VIEWPORT_FILL; +} + +QSvgStyleProperty::Type QSvgFontStyle::type() const +{ + return FONT; +} + +QSvgStyleProperty::Type QSvgStrokeStyle::type() const +{ + return STROKE; +} + +QSvgStyleProperty::Type QSvgSolidColorStyle::type() const +{ + return SOLID_COLOR; +} + +QSvgStyleProperty::Type QSvgGradientStyle::type() const +{ + return GRADIENT; +} + +QSvgStyleProperty::Type QSvgTransformStyle::type() const +{ + return TRANSFORM; +} + + +QSvgCompOpStyle::QSvgCompOpStyle(QPainter::CompositionMode mode) + : m_mode(mode) +{ + +} + +void QSvgCompOpStyle::apply(QPainter *p, const QSvgNode *, QSvgExtraStates &) +{ + m_oldMode = p->compositionMode(); + p->setCompositionMode(m_mode); +} + +void QSvgCompOpStyle::revert(QPainter *p, QSvgExtraStates &) +{ + p->setCompositionMode(m_oldMode); +} + +QSvgStyleProperty::Type QSvgCompOpStyle::type() const +{ + return COMP_OP; +} + +QSvgStyle::~QSvgStyle() +{ +} + +void QSvgStyle::apply(QPainter *p, const QSvgNode *node, QSvgExtraStates &states) +{ + if (quality) { + quality->apply(p, node, states); + } + + if (fill) { + fill->apply(p, node, states); + } + + if (viewportFill) { + viewportFill->apply(p, node, states); + } + + if (font) { + font->apply(p, node, states); + } + + if (stroke) { + stroke->apply(p, node, states); + } + + if (transform) { + transform->apply(p, node, states); + } + + if (animateColor) { + animateColor->apply(p, node, states); + } + + //animated transforms have to be applied + //_after_ the original object transformations + if (!animateTransforms.isEmpty()) { + qreal totalTimeElapsed = node->document()->currentElapsed(); + // Find the last animateTransform with additive="replace", since this will override all + // previous animateTransforms. + QList<QSvgRefCounter<QSvgAnimateTransform> >::const_iterator itr = animateTransforms.constEnd(); + do { + --itr; + if ((*itr)->animActive(totalTimeElapsed) + && (*itr)->additiveType() == QSvgAnimateTransform::Replace) { + // An animateTransform with additive="replace" will replace the transform attribute. + if (transform) + transform->revert(p, states); + break; + } + } while (itr != animateTransforms.constBegin()); + + // Apply the animateTransforms after and including the last one with additive="replace". + for (; itr != animateTransforms.constEnd(); ++itr) { + if ((*itr)->animActive(totalTimeElapsed)) + (*itr)->apply(p, node, states); + } + } + + if (opacity) { + opacity->apply(p, node, states); + } + + if (compop) { + compop->apply(p, node, states); + } +} + +void QSvgStyle::revert(QPainter *p, QSvgExtraStates &states) +{ + if (quality) { + quality->revert(p, states); + } + + if (fill) { + fill->revert(p, states); + } + + if (viewportFill) { + viewportFill->revert(p, states); + } + + if (font) { + font->revert(p, states); + } + + if (stroke) { + stroke->revert(p, states); + } + + //animated transforms need to be reverted _before_ + //the native transforms + if (!animateTransforms.isEmpty()) { + QList<QSvgRefCounter<QSvgAnimateTransform> >::const_iterator itr = animateTransforms.constBegin(); + for (; itr != animateTransforms.constEnd(); ++itr) { + if ((*itr)->transformApplied()) { + (*itr)->revert(p, states); + break; + } + } + for (; itr != animateTransforms.constEnd(); ++itr) + (*itr)->clearTransformApplied(); + } + + if (transform) { + transform->revert(p, states); + } + + if (animateColor) { + animateColor->revert(p, states); + } + + if (opacity) { + opacity->revert(p, states); + } + + if (compop) { + compop->revert(p, states); + } +} + +QSvgAnimateTransform::QSvgAnimateTransform(int startMs, int endMs, int byMs ) + : QSvgStyleProperty(), + m_from(startMs), m_to(endMs), m_by(byMs), + m_type(Empty), m_additive(Replace), m_count(0), m_finished(false), m_transformApplied(false) +{ + m_totalRunningTime = m_to - m_from; +} + +void QSvgAnimateTransform::setArgs(TransformType type, Additive additive, const QVector<qreal> &args) +{ + m_type = type; + m_args = args; + m_additive = additive; + Q_ASSERT(!(args.count()%3)); + m_count = args.count() / 3; +} + +void QSvgAnimateTransform::apply(QPainter *p, const QSvgNode *node, QSvgExtraStates &) +{ + m_oldWorldTransform = p->worldTransform(); + resolveMatrix(node); + p->setWorldTransform(m_transform, true); + m_transformApplied = true; +} + +void QSvgAnimateTransform::revert(QPainter *p, QSvgExtraStates &) +{ + p->setWorldTransform(m_oldWorldTransform, false /* don't combine */); + m_transformApplied = false; +} + +void QSvgAnimateTransform::resolveMatrix(const QSvgNode *node) +{ + static const qreal deg2rad = qreal(0.017453292519943295769); + qreal totalTimeElapsed = node->document()->currentElapsed(); + if (totalTimeElapsed < m_from || m_finished) + return; + + qreal animationFrame = 0; + if (m_totalRunningTime != 0) { + animationFrame = (totalTimeElapsed - m_from) / m_totalRunningTime; + + if (m_repeatCount >= 0 && m_repeatCount < animationFrame) { + m_finished = true; + animationFrame = m_repeatCount; + } + } + + qreal percentOfAnimation = animationFrame; + if (percentOfAnimation > 1) { + percentOfAnimation -= ((int)percentOfAnimation); + } + + qreal currentPosition = percentOfAnimation * (m_count - 1); + int startElem = qFloor(currentPosition); + int endElem = qCeil(currentPosition); + + switch(m_type) + { + case Translate: { + startElem *= 3; + endElem *= 3; + qreal from1, from2; + qreal to1, to2; + from1 = m_args[startElem++]; + from2 = m_args[startElem++]; + to1 = m_args[endElem++]; + to2 = m_args[endElem++]; + + qreal transXDiff = (to1-from1) * percentOfAnimation; + qreal transX = from1 + transXDiff; + qreal transYDiff = (to2-from2) * percentOfAnimation; + qreal transY = from2 + transYDiff; + m_transform = QTransform(); + m_transform.translate(transX, transY); + break; + } + case Scale: { + startElem *= 3; + endElem *= 3; + qreal from1, from2; + qreal to1, to2; + from1 = m_args[startElem++]; + from2 = m_args[startElem++]; + to1 = m_args[endElem++]; + to2 = m_args[endElem++]; + + qreal transXDiff = (to1-from1) * percentOfAnimation; + qreal transX = from1 + transXDiff; + qreal transYDiff = (to2-from2) * percentOfAnimation; + qreal transY = from2 + transYDiff; + if (transY == 0) + transY = transX; + m_transform = QTransform(); + m_transform.scale(transX, transY); + break; + } + case Rotate: { + startElem *= 3; + endElem *= 3; + qreal from1, from2, from3; + qreal to1, to2, to3; + from1 = m_args[startElem++]; + from2 = m_args[startElem++]; + from3 = m_args[startElem++]; + to1 = m_args[endElem++]; + to2 = m_args[endElem++]; + to3 = m_args[endElem++]; + + qreal rotationDiff = (to1 - from1) * percentOfAnimation; + //qreal rotation = from1 + rotationDiff; + + qreal transXDiff = (to2-from2) * percentOfAnimation; + qreal transX = from2 + transXDiff; + qreal transYDiff = (to3-from3) * percentOfAnimation; + qreal transY = from3 + transYDiff; + m_transform = QTransform(); + m_transform.translate(transX, transY); + m_transform.rotate(rotationDiff); + m_transform.translate(-transX, -transY); + break; + } + case SkewX: { + startElem *= 3; + endElem *= 3; + qreal from1; + qreal to1; + from1 = m_args[startElem++]; + to1 = m_args[endElem++]; + + qreal transXDiff = (to1-from1) * percentOfAnimation; + qreal transX = from1 + transXDiff; + m_transform = QTransform(); + m_transform.shear(qTan(transX * deg2rad), 0); + break; + } + case SkewY: { + startElem *= 3; + endElem *= 3; + qreal from1; + qreal to1; + from1 = m_args[startElem++]; + to1 = m_args[endElem++]; + + + qreal transYDiff = (to1 - from1) * percentOfAnimation; + qreal transY = from1 + transYDiff; + m_transform = QTransform(); + m_transform.shear(0, qTan(transY * deg2rad)); + break; + } + default: + break; + } +} + +QSvgStyleProperty::Type QSvgAnimateTransform::type() const +{ + return ANIMATE_TRANSFORM; +} + +void QSvgAnimateTransform::setFreeze(bool freeze) +{ + m_freeze = freeze; +} + +void QSvgAnimateTransform::setRepeatCount(qreal repeatCount) +{ + m_repeatCount = repeatCount; +} + +QSvgAnimateColor::QSvgAnimateColor(int startMs, int endMs, int byMs) + : QSvgStyleProperty(), + m_from(startMs), m_to(endMs), m_by(byMs), + m_finished(false) +{ + m_totalRunningTime = m_to - m_from; +} + +void QSvgAnimateColor::setArgs(bool fill, + const QList<QColor> &colors) +{ + m_fill = fill; + m_colors = colors; +} + +void QSvgAnimateColor::setFreeze(bool freeze) +{ + m_freeze = freeze; +} + +void QSvgAnimateColor::setRepeatCount(qreal repeatCount) +{ + m_repeatCount = repeatCount; +} + +void QSvgAnimateColor::apply(QPainter *p, const QSvgNode *node, QSvgExtraStates &) +{ + qreal totalTimeElapsed = node->document()->currentElapsed(); + if (totalTimeElapsed < m_from || m_finished) + return; + + qreal animationFrame = 0; + if (m_totalRunningTime != 0) + animationFrame = (totalTimeElapsed - m_from) / m_totalRunningTime; + + if (m_repeatCount >= 0 && m_repeatCount < animationFrame) { + m_finished = true; + animationFrame = m_repeatCount; + } + + qreal percentOfAnimation = animationFrame; + if (percentOfAnimation > 1) { + percentOfAnimation -= ((int)percentOfAnimation); + } + + qreal currentPosition = percentOfAnimation * (m_colors.count() - 1); + + int startElem = qFloor(currentPosition); + int endElem = qCeil(currentPosition); + QColor start = m_colors[startElem]; + QColor end = m_colors[endElem]; + + qreal percentOfColorMorph = currentPosition; + if (percentOfColorMorph > 1) { + percentOfColorMorph -= ((int)percentOfColorMorph); + } + + // Interpolate between the two fixed colors start and end + qreal aDiff = (end.alpha() - start.alpha()) * percentOfColorMorph; + qreal rDiff = (end.red() - start.red()) * percentOfColorMorph; + qreal gDiff = (end.green() - start.green()) * percentOfColorMorph; + qreal bDiff = (end.blue() - start.blue()) * percentOfColorMorph; + + int alpha = int(start.alpha() + aDiff); + int red = int(start.red() + rDiff); + int green = int(start.green() + gDiff); + int blue = int(start.blue() + bDiff); + + QColor color(red, green, blue, alpha); + + if (m_fill) { + QBrush b = p->brush(); + m_oldBrush = b; + b.setColor(color); + p->setBrush(b); + } else { + QPen pen = p->pen(); + m_oldPen = pen; + pen.setColor(color); + p->setPen(pen); + } +} + +void QSvgAnimateColor::revert(QPainter *p, QSvgExtraStates &) +{ + if (m_fill) { + p->setBrush(m_oldBrush); + } else { + p->setPen(m_oldPen); + } +} + +QSvgStyleProperty::Type QSvgAnimateColor::type() const +{ + return ANIMATE_COLOR; +} + +QSvgOpacityStyle::QSvgOpacityStyle(qreal opacity) + : m_opacity(opacity), m_oldOpacity(0) +{ + +} + +void QSvgOpacityStyle::apply(QPainter *p, const QSvgNode *, QSvgExtraStates &) +{ + m_oldOpacity = p->opacity(); + p->setOpacity(m_opacity * m_oldOpacity); +} + +void QSvgOpacityStyle::revert(QPainter *p, QSvgExtraStates &) +{ + p->setOpacity(m_oldOpacity); +} + +QSvgStyleProperty::Type QSvgOpacityStyle::type() const +{ + return OPACITY; +} + +void QSvgGradientStyle::setStopLink(const QString &link, QSvgTinyDocument *doc) +{ + m_link = link; + m_doc = doc; +} + +void QSvgGradientStyle::resolveStops() +{ + if (!m_link.isEmpty() && m_doc) { + QSvgStyleProperty *prop = m_doc->styleProperty(m_link); + if (prop) { + if (prop->type() == QSvgStyleProperty::GRADIENT) { + QSvgGradientStyle *st = + static_cast<QSvgGradientStyle*>(prop); + st->resolveStops(); + m_gradient->setStops(st->qgradient()->stops()); + m_gradientStopsSet = st->gradientStopsSet(); + } + } else { + qWarning("Could not resolve property : %s", qPrintable(m_link)); + } + m_link = QString(); + } +} + +QT_END_NAMESPACE + +#endif // QT_NO_SVG diff --git a/src/svg/qsvgstyle_p.h b/src/svg/qsvgstyle_p.h new file mode 100644 index 0000000..e53ab48 --- /dev/null +++ b/src/svg/qsvgstyle_p.h @@ -0,0 +1,826 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSvg module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSVGSTYLE_P_H +#define QSVGSTYLE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtGui/qpainter.h" + +#ifndef QT_NO_SVG + +#include "QtGui/qpen.h" +#include "QtGui/qbrush.h" +#include "QtGui/qmatrix.h" +#include "QtGui/qcolor.h" +#include "QtGui/qfont.h" +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +class QPainter; +class QSvgNode; +class QSvgFont; +class QSvgTinyDocument; + +template <class T> class QSvgRefCounter +{ +public: + QSvgRefCounter() { t = 0; } + QSvgRefCounter(T *_t) + { + t = _t; + if (t) + t->ref(); + } + QSvgRefCounter(const QSvgRefCounter &other) + { + t = other.t; + if (t) + t->ref(); + } + QSvgRefCounter &operator =(T *_t) + { + if(_t) + _t->ref(); + if (t) + t->deref(); + t = _t; + return *this; + } + QSvgRefCounter &operator =(const QSvgRefCounter &other) + { + if(other.t) + other.t->ref(); + if (t) + t->deref(); + t = other.t; + return *this; + } + ~QSvgRefCounter() + { + if (t) + t->deref(); + } + + inline T *operator->() const { return t; } + inline operator T*() const { return t; } + +private: + T *t; +}; + +class QSvgRefCounted +{ +public: + QSvgRefCounted() { _ref = 0; } + virtual ~QSvgRefCounted() {} + void ref() { + ++_ref; +// qDebug() << this << ": adding ref, now " << _ref; + } + void deref() { +// qDebug() << this << ": removing ref, now " << _ref; + if(!--_ref) { +// qDebug(" deleting"); + delete this; + } + } +private: + int _ref; +}; + +struct QSvgExtraStates +{ + QSvgExtraStates(); + + qreal fillOpacity; + qreal strokeOpacity; + QSvgFont *svgFont; + Qt::Alignment textAnchor; + int fontWeight; + Qt::FillRule fillRule; + qreal strokeDashOffset; + bool vectorEffect; // true if pen is cosmetic +}; + +class QSvgStyleProperty : public QSvgRefCounted +{ +public: + enum Type + { + QUALITY, + FILL, + VIEWPORT_FILL, + FONT, + STROKE, + SOLID_COLOR, + GRADIENT, + TRANSFORM, + ANIMATE_TRANSFORM, + ANIMATE_COLOR, + OPACITY, + COMP_OP + }; +public: + virtual ~QSvgStyleProperty(); + virtual void apply(QPainter *p, const QSvgNode *node, QSvgExtraStates &states) = 0; + virtual void revert(QPainter *p, QSvgExtraStates &states) =0; + virtual Type type() const=0; +}; + +class QSvgFillStyleProperty : public QSvgStyleProperty +{ +public: + virtual QBrush brush(QPainter *p, QSvgExtraStates &states) = 0; + virtual void apply(QPainter *p, const QSvgNode *node, QSvgExtraStates &states); + virtual void revert(QPainter *p, QSvgExtraStates &states); +}; + +class QSvgQualityStyle : public QSvgStyleProperty +{ +public: + QSvgQualityStyle(int color); + virtual void apply(QPainter *p, const QSvgNode *node, QSvgExtraStates &states); + virtual void revert(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; +private: + // color-render ing v v 'auto' | 'optimizeSpeed' | + // 'optimizeQuality' | 'inherit' + int m_colorRendering; + + // shape-rendering v v 'auto' | 'optimizeSpeed' | 'crispEdges' | + // 'geometricPrecision' | 'inherit' + //QSvgShapeRendering m_shapeRendering; + + + // text-rendering v v 'auto' | 'optimizeSpeed' | 'optimizeLegibility' + // | 'geometricPrecision' | 'inherit' + //QSvgTextRendering m_textRendering; + + + // vector-effect v x 'default' | 'non-scaling-stroke' | 'inherit' + //QSvgVectorEffect m_vectorEffect; + + // image-rendering v v 'auto' | 'optimizeSpeed' | 'optimizeQuality' | + // 'inherit' + //QSvgImageRendering m_imageRendering; +}; + + + +class QSvgOpacityStyle : public QSvgStyleProperty +{ +public: + QSvgOpacityStyle(qreal opacity); + virtual void apply(QPainter *p, const QSvgNode *node, QSvgExtraStates &states); + virtual void revert(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; +private: + qreal m_opacity; + qreal m_oldOpacity; +}; + +class QSvgFillStyle : public QSvgStyleProperty +{ +public: + QSvgFillStyle(); + virtual void apply(QPainter *p, const QSvgNode *node, QSvgExtraStates &states); + virtual void revert(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; + + void setFillRule(Qt::FillRule f); + void setFillOpacity(qreal opacity); + void setFillStyle(QSvgFillStyleProperty* style); + void setBrush(QBrush brush); + + const QBrush & qbrush() const + { + return m_fill; + } + + qreal fillOpacity() const + { + return m_fillOpacity; + } + + Qt::FillRule fillRule() const + { + return m_fillRule; + } + + QSvgFillStyleProperty* style() const + { + return m_style; + } + + void setGradientId(const QString &Id) + { + m_gradientId = Id; + } + + QString gradientId() const + { + return m_gradientId; + } + + void setGradientResolved(bool resolved) + { + m_gradientResolved = resolved; + } + + bool isGradientResolved() const + { + return m_gradientResolved; + } + +private: + // fill v v 'inherit' | <Paint.datatype> + // fill-opacity v v 'inherit' | <OpacityValue.datatype> + QBrush m_fill; + QBrush m_oldFill; + QSvgFillStyleProperty *m_style; + + Qt::FillRule m_fillRule; + Qt::FillRule m_oldFillRule; + qreal m_fillOpacity; + qreal m_oldFillOpacity; + + QString m_gradientId; + uint m_gradientResolved : 1; + + uint m_fillRuleSet : 1; + uint m_fillOpacitySet : 1; + uint m_fillSet : 1; +}; + +class QSvgViewportFillStyle : public QSvgStyleProperty +{ +public: + QSvgViewportFillStyle(const QBrush &brush); + virtual void apply(QPainter *p, const QSvgNode *node, QSvgExtraStates &states); + virtual void revert(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; + + const QBrush & qbrush() const + { + return m_viewportFill; + } +private: + // viewport-fill v x 'inherit' | <Paint.datatype> + // viewport-fill-opacity v x 'inherit' | <OpacityValue.datatype> + QBrush m_viewportFill; + + QBrush m_oldFill; +}; + +class QSvgFontStyle : public QSvgStyleProperty +{ +public: + static const int LIGHTER = -1; + static const int BOLDER = 1; + + QSvgFontStyle(QSvgFont *font, QSvgTinyDocument *doc); + QSvgFontStyle(); + virtual void apply(QPainter *p, const QSvgNode *node, QSvgExtraStates &states); + virtual void revert(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; + + void setSize(qreal size) + { + // Store the _pixel_ size in the font. Since QFont::setPixelSize() only takes an int, call + // QFont::SetPointSize() instead. Set proper font size just before rendering. + m_qfont.setPointSizeF(size); + m_sizeSet = 1; + } + + void setTextAnchor(Qt::Alignment anchor) + { + m_textAnchor = anchor; + m_textAnchorSet = 1; + } + + void setFamily(const QString &family) + { + m_qfont.setFamily(family); + m_familySet = 1; + } + + void setStyle(QFont::Style fontStyle) { + m_qfont.setStyle(fontStyle); + m_styleSet = 1; + } + + void setVariant(QFont::Capitalization fontVariant) + { + m_qfont.setCapitalization(fontVariant); + m_variantSet = 1; + } + + static int SVGToQtWeight(int weight); + + void setWeight(int weight) + { + m_weight = weight; + m_weightSet = 1; + } + + QSvgFont * svgFont() const + { + return m_svgFont; + } + + const QFont &qfont() const + { + return m_qfont; + } + + QSvgTinyDocument *doc() const {return m_doc;} + +private: + QSvgFont *m_svgFont; + QSvgTinyDocument *m_doc; + QFont m_qfont; + + int m_weight; + Qt::Alignment m_textAnchor; + + QSvgFont *m_oldSvgFont; + QFont m_oldQFont; + Qt::Alignment m_oldTextAnchor; + int m_oldWeight; + + uint m_familySet : 1; + uint m_sizeSet : 1; + uint m_styleSet : 1; + uint m_variantSet : 1; + uint m_weightSet : 1; + uint m_textAnchorSet : 1; +}; + +class QSvgStrokeStyle : public QSvgStyleProperty +{ +public: + QSvgStrokeStyle(); + virtual void apply(QPainter *p, const QSvgNode *node, QSvgExtraStates &states); + virtual void revert(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; + + void setStroke(QBrush brush) + { + m_stroke.setBrush(brush); + m_style = 0; + m_strokeSet = 1; + } + + void setStyle(QSvgFillStyleProperty *style) + { + m_style = style; + m_strokeSet = 1; + } + + void setDashArray(const QVector<qreal> &dashes); + + void setDashArrayNone() + { + m_stroke.setStyle(Qt::SolidLine); + m_strokeDashArraySet = 1; + } + + void setDashOffset(qreal offset) + { + m_strokeDashOffset = offset; + m_strokeDashOffsetSet = 1; + } + + void setLineCap(Qt::PenCapStyle cap) + { + m_stroke.setCapStyle(cap); + m_strokeLineCapSet = 1; + } + + void setLineJoin(Qt::PenJoinStyle join) + { + m_stroke.setJoinStyle(join); + m_strokeLineJoinSet = 1; + } + + void setMiterLimit(qreal limit) + { + m_stroke.setMiterLimit(limit); + m_strokeMiterLimitSet = 1; + } + + void setOpacity(qreal opacity) + { + m_strokeOpacity = opacity; + m_strokeOpacitySet = 1; + } + + void setWidth(qreal width) + { + m_stroke.setWidthF(width); + m_strokeWidthSet = 1; + Q_ASSERT(!m_strokeDashArraySet); // set width before dash array. + } + + qreal width() + { + return m_stroke.widthF(); + } + + void setVectorEffect(bool nonScalingStroke) + { + m_vectorEffect = nonScalingStroke; + m_vectorEffectSet = 1; + } + + QSvgFillStyleProperty* style() const + { + return m_style; + } + + void setGradientId(const QString &Id) + { + m_gradientId = Id; + } + + QString gradientId() const + { + return m_gradientId; + } + + void setGradientResolved(bool resolved) + { + m_gradientResolved = resolved; + } + + bool isGradientResolved() const + { + return m_gradientResolved; + } + + QPen stroke() const + { + return m_stroke; + } + +private: + // stroke v v 'inherit' | <Paint.datatype> + // stroke-dasharray v v 'inherit' | <StrokeDashArrayValue.datatype> + // stroke-dashoffset v v 'inherit' | <StrokeDashOffsetValue.datatype> + // stroke-linecap v v 'butt' | 'round' | 'square' | 'inherit' + // stroke-linejoin v v 'miter' | 'round' | 'bevel' | 'inherit' + // stroke-miterlimit v v 'inherit' | <StrokeMiterLimitValue.datatype> + // stroke-opacity v v 'inherit' | <OpacityValue.datatype> + // stroke-width v v 'inherit' | <StrokeWidthValue.datatype> + QPen m_stroke; + QPen m_oldStroke; + qreal m_strokeOpacity; + qreal m_oldStrokeOpacity; + qreal m_strokeDashOffset; + qreal m_oldStrokeDashOffset; + + QSvgFillStyleProperty *m_style; + QString m_gradientId; + uint m_gradientResolved : 1; + uint m_vectorEffect : 1; + uint m_oldVectorEffect : 1; + + uint m_strokeSet : 1; + uint m_strokeDashArraySet : 1; + uint m_strokeDashOffsetSet : 1; + uint m_strokeLineCapSet : 1; + uint m_strokeLineJoinSet : 1; + uint m_strokeMiterLimitSet : 1; + uint m_strokeOpacitySet : 1; + uint m_strokeWidthSet : 1; + uint m_vectorEffectSet : 1; +}; + +class QSvgSolidColorStyle : public QSvgFillStyleProperty +{ +public: + QSvgSolidColorStyle(const QColor &color); + virtual Type type() const; + + const QColor & qcolor() const + { + return m_solidColor; + } + + QBrush brush(QPainter *, QSvgExtraStates &) + { + return m_solidColor; + } + +private: + // solid-color v x 'inherit' | <SVGColor.datatype> + // solid-opacity v x 'inherit' | <OpacityValue.datatype> + QColor m_solidColor; + + QBrush m_oldFill; + QPen m_oldStroke; +}; + +class QSvgGradientStyle : public QSvgFillStyleProperty +{ +public: + QSvgGradientStyle(QGradient *grad); + ~QSvgGradientStyle() { delete m_gradient; } + virtual Type type() const; + + void setStopLink(const QString &link, QSvgTinyDocument *doc); + QString stopLink() const { return m_link; } + void resolveStops(); + + void setMatrix(const QMatrix &matrix); + QMatrix qmatrix() const + { + return m_matrix; + } + + QGradient *qgradient() const + { + return m_gradient; + } + + bool gradientStopsSet() const + { + return m_gradientStopsSet; + } + + void setGradientStopsSet(bool set) + { + m_gradientStopsSet = set; + } + + QBrush brush(QPainter *, QSvgExtraStates &); +private: + QGradient *m_gradient; + QMatrix m_matrix; + + QSvgTinyDocument *m_doc; + QString m_link; + bool m_gradientStopsSet; +}; + +class QSvgTransformStyle : public QSvgStyleProperty +{ +public: + QSvgTransformStyle(const QTransform &transform); + virtual void apply(QPainter *p, const QSvgNode *node, QSvgExtraStates &states); + virtual void revert(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; + + const QTransform & qtransform() const + { + return m_transform; + } +private: + //7.6 The transform attribute + QTransform m_transform; + QTransform m_oldWorldTransform; +}; + + +class QSvgAnimateTransform : public QSvgStyleProperty +{ +public: + enum TransformType + { + Empty, + Translate, + Scale, + Rotate, + SkewX, + SkewY + }; + enum Additive + { + Sum, + Replace + }; +public: + QSvgAnimateTransform(int startMs, int endMs, int by = 0); + void setArgs(TransformType type, Additive additive, const QVector<qreal> &args); + void setFreeze(bool freeze); + void setRepeatCount(qreal repeatCount); + virtual void apply(QPainter *p, const QSvgNode *node, QSvgExtraStates &states); + virtual void revert(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; + QSvgAnimateTransform::Additive additiveType() const + { + return m_additive; + } + + bool animActive(qreal totalTimeElapsed) + { + if (totalTimeElapsed < m_from) + return false; + if (m_freeze || m_repeatCount < 0) // fill="freeze" or repeat="indefinite" + return true; + if (m_totalRunningTime == 0) + return false; + qreal animationFrame = (totalTimeElapsed - m_from) / m_totalRunningTime; + if (animationFrame > m_repeatCount) + return false; + return true; + } + + bool transformApplied() const + { + return m_transformApplied; + } + + // Call this instead of revert if you know that revert is unnecessary. + void clearTransformApplied() + { + m_transformApplied = false; + } + +protected: + void resolveMatrix(const QSvgNode *node); +private: + qreal m_from, m_to, m_by; + qreal m_totalRunningTime; + TransformType m_type; + Additive m_additive; + QVector<qreal> m_args; + int m_count; + QTransform m_transform; + QTransform m_oldWorldTransform; + bool m_finished; + bool m_freeze; + qreal m_repeatCount; + bool m_transformApplied; +}; + + +class QSvgAnimateColor : public QSvgStyleProperty +{ +public: + QSvgAnimateColor(int startMs, int endMs, int by = 0); + void setArgs(bool fill, const QList<QColor> &colors); + void setFreeze(bool freeze); + void setRepeatCount(qreal repeatCount); + virtual void apply(QPainter *p, const QSvgNode *node, QSvgExtraStates &states); + virtual void revert(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; +private: + qreal m_from, m_to, m_by; + qreal m_totalRunningTime; + QList<QColor> m_colors; + QBrush m_oldBrush; + QPen m_oldPen; + bool m_fill; + bool m_finished; + bool m_freeze; + qreal m_repeatCount; +}; + + +class QSvgCompOpStyle : public QSvgStyleProperty +{ +public: + QSvgCompOpStyle(QPainter::CompositionMode mode); + virtual void apply(QPainter *p, const QSvgNode *node, QSvgExtraStates &states); + virtual void revert(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; + + const QPainter::CompositionMode & compOp() const + { + return m_mode; + } +private: + //comp-op attribute + QPainter::CompositionMode m_mode; + + QPainter::CompositionMode m_oldMode; +}; + + +class QSvgStyle +{ +public: + QSvgStyle() + : quality(0), + fill(0), + viewportFill(0), + font(0), + stroke(0), + solidColor(0), + gradient(0), + transform(0), + animateColor(0), + opacity(0), + compop(0) + {} + ~QSvgStyle(); + + void apply(QPainter *p, const QSvgNode *node, QSvgExtraStates &states); + void revert(QPainter *p, QSvgExtraStates &states); + QSvgRefCounter<QSvgQualityStyle> quality; + QSvgRefCounter<QSvgFillStyle> fill; + QSvgRefCounter<QSvgViewportFillStyle> viewportFill; + QSvgRefCounter<QSvgFontStyle> font; + QSvgRefCounter<QSvgStrokeStyle> stroke; + QSvgRefCounter<QSvgSolidColorStyle> solidColor; + QSvgRefCounter<QSvgGradientStyle> gradient; + QSvgRefCounter<QSvgTransformStyle> transform; + QSvgRefCounter<QSvgAnimateColor> animateColor; + QList<QSvgRefCounter<QSvgAnimateTransform> > animateTransforms; + QSvgRefCounter<QSvgOpacityStyle> opacity; + QSvgRefCounter<QSvgCompOpStyle> compop; +}; + +/********************************************************/ +// NOT implemented: + +// color v v 'inherit' | <Color.datatype> +//QColor m_color; + +// display v x 'inline' | 'block' | 'list-item' +// | 'run-in' | 'compact' | 'marker' | +// 'table' | 'inline-table' | +// 'table-row-group' | 'table-header-group' | +// 'table-footer-group' | 'table-row' | +// 'table-column-group' | 'table-column' | +// 'table-cell' | 'table-caption' | +// 'none' | 'inherit' +//QSvgDisplayStyle m_display; + +// display-align v v 'auto' | 'before' | 'center' | 'after' | 'inherit' +//QSvgDisplayAlign m_displayAlign; + +// line-increment v v 'auto' | 'inherit' | <Number.datatype> +//int m_lineIncrement; + +// text-anchor v v 'start' | 'middle' | 'end' | 'inherit' +//QSvgTextAnchor m_textAnchor; + +// visibility v v 'visible' | 'hidden' | 'inherit' +//QSvgVisibility m_visibility; + +/******************************************************/ +// the following do not make sense for us + +// pointer-events v v 'visiblePainted' | 'visibleFill' | 'visibleStroke' | +// 'visible' | 'painted' | 'fill' | 'stroke' | 'all' | +// 'none' | 'inherit' +//QSvgPointEvents m_pointerEvents; + +// audio-level v x 'inherit' | <Number.datatype> + +QT_END_NAMESPACE + +#endif // QT_NO_SVG +#endif // QSVGSTYLE_P_H diff --git a/src/svg/qsvgtinydocument.cpp b/src/svg/qsvgtinydocument.cpp new file mode 100644 index 0000000..7bd8004 --- /dev/null +++ b/src/svg/qsvgtinydocument.cpp @@ -0,0 +1,492 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSvg module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsvgtinydocument_p.h" + +#ifndef QT_NO_SVG + +#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 <zlib.h> +#endif + +QT_BEGIN_NAMESPACE + +QSvgTinyDocument::QSvgTinyDocument() + : QSvgStructureNode(0) + , m_widthPercent(false) + , m_heightPercent(false) + , 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) { + qWarning("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<Bytef*>(source.data()); + } + + do { + // Prepare the destination buffer + int oldSize = destination.size(); + destination.resize(oldSize + CHUNK_SIZE); + zlibStream.next_out = reinterpret_cast<Bytef*>( + 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); + qWarning("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)) { + qWarning("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 { + qWarning("Cannot read file '%s', because: %s (line %d)", + qPrintable(fileName), qPrintable(handler.errorString()), handler.lineNumber()); + } + 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<QByteArray *>(&contents)); + return load(qt_inflateGZipDataFrom(&buffer)); + } +#endif + + QSvgHandler handler(contents); + + QSvgTinyDocument *doc = 0; + if (handler.ok()) { + doc = handler.document(); + doc->m_animationDuration = handler.animationDuration(); + } + return doc; +} + +QSvgTinyDocument * QSvgTinyDocument::load(QXmlStreamReader *contents) +{ + QSvgHandler handler(contents); + + QSvgTinyDocument *doc = 0; + if (handler.ok()) { + doc = handler.document(); + doc->m_animationDuration = handler.animationDuration(); + } + return doc; +} + +void QSvgTinyDocument::draw(QPainter *p, const QRectF &bounds) +{ + if (m_time.isNull()) { + m_time.start(); + } + + 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::MiterJoin); + pen.setMiterLimit(4); + p->setPen(pen); + p->setBrush(Qt::black); + p->setRenderHint(QPainter::Antialiasing); + p->setRenderHint(QPainter::SmoothPixmapTransform); + QList<QSvgNode*>::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) { + qDebug("Couldn't find node %s. Skipping rendering.", qPrintable(id)); + return; + } + if (m_time.isNull()) { + m_time.start(); + } + + 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::MiterJoin); + pen.setMiterLimit(4); + p->setPen(pen); + p->setBrush(Qt::black); + p->setRenderHint(QPainter::Antialiasing); + p->setRenderHint(QPainter::SmoothPixmapTransform); + + QStack<QSvgNode*> 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; +} + +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) +{ + m_namedStyles.insert(id, style); +} + +QSvgFillStyleProperty *QSvgTinyDocument::namedStyle(const QString &id) const +{ + return m_namedStyles.value(id); +} + +void QSvgTinyDocument::restartAnimation() +{ + m_time.restart(); +} + +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()) { + 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()); + } +} + +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) { + qDebug("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(m_time.elapsed()/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 - m_time.elapsed()); + m_time = m_time.addMSecs(timeToAdd); +} + +void QSvgTinyDocument::setFramesPerSecond(int num) +{ + m_fps = num; +} + +QT_END_NAMESPACE + +#endif // QT_NO_SVG diff --git a/src/svg/qsvgtinydocument_p.h b/src/svg/qsvgtinydocument_p.h new file mode 100644 index 0000000..e54a41f --- /dev/null +++ b/src/svg/qsvgtinydocument_p.h @@ -0,0 +1,200 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSvg module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSVGTINYDOCUMENT_P_H +#define QSVGTINYDOCUMENT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qsvgstructure_p.h" + +#ifndef QT_NO_SVG + +#include "QtCore/qrect.h" +#include "QtCore/qlist.h" +#include "QtCore/qhash.h" +#include "QtCore/qdatetime.h" +#include "QtCore/qxmlstream.h" +#include "qsvgstyle_p.h" +#include "qsvgfont_p.h" + +QT_BEGIN_NAMESPACE + +class QPainter; +class QByteArray; +class QSvgFont; + +class Q_SVG_EXPORT QSvgTinyDocument : public QSvgStructureNode +{ +public: + static QSvgTinyDocument * load(const QString &file); + static QSvgTinyDocument * load(const QByteArray &contents); + static QSvgTinyDocument * load(QXmlStreamReader *contents); +public: + QSvgTinyDocument(); + ~QSvgTinyDocument(); + Type type() const; + + QSize size() const; + void setWidth(int len, bool percent); + void setHeight(int len, bool percent); + int width() const; + int height() const; + bool widthPercent() const; + bool heightPercent() const; + + bool preserveAspectRatio() const; + + QRectF viewBox() const; + void setViewBox(const QRectF &rect); + + virtual void draw(QPainter *p, QSvgExtraStates &);//from the QSvgNode + + void draw(QPainter *p); + void draw(QPainter *p, const QRectF &bounds); + void draw(QPainter *p, const QString &id, + const QRectF &bounds=QRectF()); + + QMatrix matrixForElement(const QString &id) const; + QRectF boundsOnElement(const QString &id) const; + bool elementExists(const QString &id) const; + + void addSvgFont(QSvgFont *); + QSvgFont *svgFont(const QString &family) const; + void addNamedNode(const QString &id, QSvgNode *node); + QSvgNode *namedNode(const QString &id) const; + void addNamedStyle(const QString &id, QSvgFillStyleProperty *style); + QSvgFillStyleProperty *namedStyle(const QString &id) const; + + void restartAnimation(); + int currentElapsed() const; + bool animated() const; + void setAnimated(bool a); + int animationDuration() const; + int currentFrame() const; + void setCurrentFrame(int); + void setFramesPerSecond(int num); +private: + void mapSourceToTarget(QPainter *p, const QRectF &targetRect, const QRectF &sourceRect = QRectF()); +private: + QSize m_size; + bool m_widthPercent; + bool m_heightPercent; + + mutable QRectF m_viewBox; + + QHash<QString, QSvgRefCounter<QSvgFont> > m_fonts; + QHash<QString, QSvgNode *> m_namedNodes; + QHash<QString, QSvgRefCounter<QSvgFillStyleProperty> > m_namedStyles; + + QTime m_time; + bool m_animated; + int m_animationDuration; + int m_fps; + + QSvgExtraStates m_states; +}; + +inline QSize QSvgTinyDocument::size() const +{ + if (m_size.isEmpty()) { + return viewBox().size().toSize(); + } else { + return m_size; + } +} + +inline int QSvgTinyDocument::width() const +{ + return size().width(); +} + +inline int QSvgTinyDocument::height() const +{ + return size().height(); +} + +inline bool QSvgTinyDocument::widthPercent() const +{ + return m_widthPercent; +} + +inline bool QSvgTinyDocument::heightPercent() const +{ + return m_heightPercent; +} + +inline QRectF QSvgTinyDocument::viewBox() const +{ + if (m_viewBox.isNull()) + m_viewBox = transformedBounds(); + + return m_viewBox; +} + +inline bool QSvgTinyDocument::preserveAspectRatio() const +{ + return false; +} + +inline int QSvgTinyDocument::currentElapsed() const +{ + return m_time.elapsed(); +} + +inline int QSvgTinyDocument::animationDuration() const +{ + return m_animationDuration; +} + +QT_END_NAMESPACE + +#endif // QT_NO_SVG +#endif // QSVGTINYDOCUMENT_P_H diff --git a/src/svg/qsvgwidget.cpp b/src/svg/qsvgwidget.cpp new file mode 100644 index 0000000..d43db92 --- /dev/null +++ b/src/svg/qsvgwidget.cpp @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSvg module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsvgwidget.h" + +#ifndef QT_NO_SVGWIDGET + +#include "qsvgrenderer.h" + +#include "qpainter.h" +#include "private/qwidget_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QSvgWidget + \ingroup painting + + \brief The QSvgWidget class provides a widget that is used to display the contents of + Scalable Vector Graphics (SVG) files. + \since 4.1 + + This class enables developers to display SVG drawings alongside standard widgets, and + is used in much the same way as QLabel is used for displaying text and bitmap images. + + Since QSvgWidget is a subclass of QWidget, SVG drawings are rendered using the properties + of the display. More control can be exercised over the rendering process with the + QSvgRenderer class, as this can be used to paint onto other paint devices, such as QImage + and QGLWidget. The renderer used by the widget can be obtained with the renderer() + function. + + Each QSvgWidget can be constructed with the file name of a SVG file, or they can be + constructed without a specific file to render and one can be supplied later. The load() + functions provide two different ways to load an SVG file: they accept either the file name + of an SVG file or a QByteArray containing the serialized XML representation of an SVG file. + + By default, the widget provides a size hint to reflect the size of the drawing that it + displays. If no data has been loaded, the widget provides the default QWidget size hint. + Subclass this class and reimplement sizeHint() if you need to customize this behavior. + + \sa QSvgRenderer, {QtSvg Module}, QPicture +*/ + +class QSvgWidgetPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QSvgWidget) +public: + QSvgRenderer *renderer; +}; + +/*! + Constructs a new SVG display widget with the given \a parent. +*/ +QSvgWidget::QSvgWidget(QWidget *parent) + : QWidget(*new QSvgWidgetPrivate, parent, 0) +{ + d_func()->renderer = new QSvgRenderer(this); + QObject::connect(d_func()->renderer, SIGNAL(repaintNeeded()), + this, SLOT(update())); +} + +/*! + Constructs a new SVG display widget with the given \a parent and loads the contents + of the specified \a file. +*/ +QSvgWidget::QSvgWidget(const QString &file, QWidget *parent) + : QWidget(*new QSvgWidgetPrivate, parent, 0) +{ + d_func()->renderer = new QSvgRenderer(file, this); + QObject::connect(d_func()->renderer, SIGNAL(repaintNeeded()), + this, SLOT(update())); +} + +/*! + Destroys the widget. +*/ +QSvgWidget::~QSvgWidget() +{ + +} + +/*! + Returns the renderer used to display the contents of the widget. +*/ +QSvgRenderer * QSvgWidget::renderer() const +{ + Q_D(const QSvgWidget); + return d->renderer; +} + + +/*! + \reimp +*/ +QSize QSvgWidget::sizeHint() const +{ + Q_D(const QSvgWidget); + if (d->renderer->isValid()) + return d->renderer->defaultSize(); + else + return QSize(128, 64); +} + + +/*! + \reimp +*/ +void QSvgWidget::paintEvent(QPaintEvent *) +{ + Q_D(QSvgWidget); + QPainter p(this); + d->renderer->render(&p); +} + +/*! + Loads the contents of the specified SVG \a file and updates the widget. +*/ +void QSvgWidget::load(const QString &file) +{ + Q_D(const QSvgWidget); + d->renderer->load(file); +} + +/*! + Loads the specified SVG format \a contents and updates the widget. +*/ +void QSvgWidget::load(const QByteArray &contents) +{ + Q_D(const QSvgWidget); + d->renderer->load(contents); +} + +QT_END_NAMESPACE + +#endif // QT_NO_SVGWIDGET diff --git a/src/svg/qsvgwidget.h b/src/svg/qsvgwidget.h new file mode 100644 index 0000000..d404e36 --- /dev/null +++ b/src/svg/qsvgwidget.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSvg module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSVGWIDGET_H +#define QSVGWIDGET_H + +#include <QtGui/qwidget.h> + +#ifndef QT_NO_SVGWIDGET + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Svg) + +class QSvgWidgetPrivate; +class QPaintEvent; +class QSvgRenderer; + +class Q_SVG_EXPORT QSvgWidget : public QWidget +{ + Q_OBJECT +public: + QSvgWidget(QWidget *parent=0); + QSvgWidget(const QString &file, QWidget *parent=0); + ~QSvgWidget(); + + QSvgRenderer *renderer() const; + + QSize sizeHint() const; +public Q_SLOTS: + void load(const QString &file); + void load(const QByteArray &contents); +protected: + void paintEvent(QPaintEvent *event); +private: + Q_DISABLE_COPY(QSvgWidget) + Q_DECLARE_PRIVATE(QSvgWidget) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_SVGWIDGET +#endif // QSVGWIDGET_H diff --git a/src/svg/svg.pro b/src/svg/svg.pro new file mode 100644 index 0000000..79f284a --- /dev/null +++ b/src/svg/svg.pro @@ -0,0 +1,45 @@ +TARGET = QtSvg +QPRO_PWD = $$PWD +QT = core gui +DEFINES += QT_BUILD_SVG_LIB +DEFINES += QT_NO_USING_NAMESPACE +win32-msvc*|win32-icc:QMAKE_LFLAGS += /BASE:0x66000000 +solaris-cc*:QMAKE_CXXFLAGS_RELEASE -= -O2 + +unix|win32-g++*:QMAKE_PKGCONFIG_REQUIRES = QtCore QtGui + +include(../qbase.pri) + + +HEADERS += \ + qsvggraphics_p.h \ + qsvghandler_p.h \ + qsvgnode_p.h \ + qsvgstructure_p.h \ + qsvgstyle_p.h \ + qsvgfont_p.h \ + qsvgtinydocument_p.h \ + qsvgrenderer.h \ + qsvgwidget.h \ + qgraphicssvgitem.h \ + qsvggenerator.h + + +SOURCES += \ + qsvggraphics.cpp \ + qsvghandler.cpp \ + qsvgnode.cpp \ + qsvgstructure.cpp \ + qsvgstyle.cpp \ + qsvgfont.cpp \ + qsvgtinydocument.cpp \ + qsvgrenderer.cpp \ + qsvgwidget.cpp \ + qgraphicssvgitem.cpp \ + qsvggenerator.cpp + +INCLUDEPATH += ../3rdparty/harfbuzz/src + +symbian:TARGET.UID3=0x2001B2E2 + +include(../3rdparty/zlib_dependency.pri) |