diff options
Diffstat (limited to 'src/svg')
-rw-r--r-- | src/svg/qgraphicssvgitem.cpp | 376 | ||||
-rw-r--r-- | src/svg/qgraphicssvgitem.h | 105 | ||||
-rw-r--r-- | src/svg/qsvgfont.cpp | 142 | ||||
-rw-r--r-- | src/svg/qsvgfont_p.h | 103 | ||||
-rw-r--r-- | src/svg/qsvggenerator.cpp | 1052 | ||||
-rw-r--r-- | src/svg/qsvggenerator.h | 111 | ||||
-rw-r--r-- | src/svg/qsvggraphics.cpp | 642 | ||||
-rw-r--r-- | src/svg/qsvggraphics_p.h | 247 | ||||
-rw-r--r-- | src/svg/qsvghandler.cpp | 3697 | ||||
-rw-r--r-- | src/svg/qsvghandler_p.h | 185 | ||||
-rw-r--r-- | src/svg/qsvgnode.cpp | 330 | ||||
-rw-r--r-- | src/svg/qsvgnode_p.h | 205 | ||||
-rw-r--r-- | src/svg/qsvgrenderer.cpp | 501 | ||||
-rw-r--r-- | src/svg/qsvgrenderer.h | 120 | ||||
-rw-r--r-- | src/svg/qsvgstructure.cpp | 424 | ||||
-rw-r--r-- | src/svg/qsvgstructure_p.h | 120 | ||||
-rw-r--r-- | src/svg/qsvgstyle.cpp | 820 | ||||
-rw-r--r-- | src/svg/qsvgstyle_p.h | 564 | ||||
-rw-r--r-- | src/svg/qsvgtinydocument.cpp | 459 | ||||
-rw-r--r-- | src/svg/qsvgtinydocument_p.h | 195 | ||||
-rw-r--r-- | src/svg/qsvgwidget.cpp | 183 | ||||
-rw-r--r-- | src/svg/qsvgwidget.h | 85 | ||||
-rw-r--r-- | src/svg/svg.pro | 50 |
23 files changed, 10716 insertions, 0 deletions
diff --git a/src/svg/qgraphicssvgitem.cpp b/src/svg/qgraphicssvgitem.cpp new file mode 100644 index 0000000..e17df03 --- /dev/null +++ b/src/svg/qgraphicssvgitem.cpp @@ -0,0 +1,376 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 QObjectPrivate +{ +public: + Q_DECLARE_PUBLIC(QGraphicsSvgItem) + + QGraphicsSvgItemPrivate() + : renderer(0), shared(false) + { + } + + void init() + { + Q_Q(QGraphicsSvgItem); + 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 multimedia + \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 setSize() method 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) + : QObject(*new QGraphicsSvgItemPrivate(), 0), QGraphicsItem(parent) +{ + Q_D(QGraphicsSvgItem); + d->init(); +} + +/*! + 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) + : QObject(*new QGraphicsSvgItemPrivate(), 0), QGraphicsItem(parent) +{ + Q_D(QGraphicsSvgItem); + d->init(); + 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 (qFuzzyCompare(qMax(murect.width(), murect.height()) + 1, 1)) + 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; +} + + +/*! + 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(); +} + +/*! + Sets the XML ID of the element that this item should render 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 renderer. 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..c6ab0f7 --- /dev/null +++ b/src/svg/qgraphicssvgitem.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QGRAPHICSSVGITEM_H +#define QGRAPHICSSVGITEM_H + +#include <QtGui/qgraphicsitem.h> +#include <QtCore/qobject.h> + +#ifndef QT_NO_GRAPHICSSVGITEM + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Svg) + +class QSvgRenderer; +class QGraphicsSvgItemPrivate; + +class Q_SVG_EXPORT QGraphicsSvgItem : public QObject, public QGraphicsItem +{ + Q_OBJECT + +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_WITH_BASE(QGraphicsSvgItem, QObject) + inline QGraphicsSvgItemPrivate *d_func() + { return reinterpret_cast<QGraphicsSvgItemPrivate *>(QObject::d_ptr); } + inline const QGraphicsSvgItemPrivate *d_func() const + { return reinterpret_cast<const QGraphicsSvgItemPrivate *>(QObject::d_ptr); } + friend class QGraphicsSvgItemPrivate; + + 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..cf90fb7 --- /dev/null +++ b/src/svg/qsvgfont.cpp @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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..069d8be --- /dev/null +++ b/src/svg/qsvgfont_p.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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..f7b2ae8 --- /dev/null +++ b/src/svg/qsvggenerator.cpp @@ -0,0 +1,1052 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 = 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("vector-effect=\"non-scaling-stroke\" "); + *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::fromLatin1("%1").arg(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) { + width = QLatin1String("1"); + stream() << "vector-effect=\"non-scaling-stroke\" "; + } + else + width = QString::number(spen.widthF()); + stream() <<"stroke-width=\""<<width<<"\" "; + + 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\" "; + stream() << "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\" "; + stream() << "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 << "\" "; + stream() << "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 multimedia + \since 4.3 + \brief The QSvgGenerator class provides a paint device that is used to create SVG drawings. + \reentrant + + \sa QSvgRenderer, QSvgWidget +*/ + +/*! + 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; + delete d_ptr; +} + +/*! + \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()<<"\" "; + stream() << "y=\""<<r.y()<<"\" "; + stream() << "width=\""<<r.width()<<"\" "; + stream() << "height=\""<<r.height()<<"\" "; + + QByteArray data; + QBuffer buffer(&data); + buffer.open(QBuffer::ReadWrite); + image.save(&buffer, "PNG"); + buffer.close(); + stream() << "xlink:href=\"data:image/png;base64," + << data.toBase64() + <<"\" "; + stream() << "/>\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 (!qFuzzyCompare(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 "; + + + *d->stream << "fill-rule="; + if (p.fillRule() == Qt::OddEvenFill) + *d->stream << "\"evenodd\" "; + else + *d->stream << "\"nonzero\" "; + + *d->stream << "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\" 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\" " + << "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..723a220 --- /dev/null +++ b/src/svg/qsvggenerator.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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> + +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: + 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..9ff9c26 --- /dev/null +++ b/src/svg/qsvggraphics.cpp @@ -0,0 +1,642 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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) \ + applyStyle(p, states); \ + qreal oldOpacity = p->opacity(); \ + QBrush oldBrush = p->brush(); \ + QPen oldPen = p->pen(); \ + p->setPen(Qt::NoPen); \ + p->setOpacity(oldOpacity * states.fillOpacity); \ + command; \ + p->setOpacity(oldOpacity); \ + p->setPen(oldPen); \ + p->setBrush(Qt::NoBrush); \ + command; \ + p->setBrush(oldBrush); \ + revertStyle(p, states); + + +void QSvgAnimation::draw(QPainter *, QSvgExtraStates &) +{ + qWarning("<animation> no implemented"); +} + +static inline QRectF boundsOnStroke(const QPainterPath &path, qreal width) +{ + QPainterPathStroker stroker; + stroker.setWidth(width); + QPainterPath stroke = stroker.createStroke(path); + return stroke.boundingRect(); +} + +QSvgCircle::QSvgCircle(QSvgNode *parent, const QRectF &rect) + : QSvgNode(parent), m_bounds(rect) +{ +} + + +QRectF QSvgCircle::bounds() const +{ + qreal sw = strokeWidth(); + if (qFuzzyCompare(sw + 1, 1)) + return m_bounds; + else { + QPainterPath path; + path.addRect(m_bounds); + return boundsOnStroke(path, sw); + } +} + +void QSvgCircle::draw(QPainter *p, QSvgExtraStates &states) +{ + QT_SVG_DRAW_SHAPE(p->drawEllipse(m_bounds)); +} + +QSvgArc::QSvgArc(QSvgNode *parent, const QPainterPath &path) + : QSvgNode(parent), cubic(path) +{ + m_cachedBounds = path.boundingRect(); +} + +void QSvgArc::draw(QPainter *p, QSvgExtraStates &states) +{ + applyStyle(p, states); + p->drawPath(cubic); + revertStyle(p, states); +} + +QSvgEllipse::QSvgEllipse(QSvgNode *parent, const QRectF &rect) + : QSvgNode(parent), m_bounds(rect) +{ +} + +QRectF QSvgEllipse::bounds() const +{ + qreal sw = strokeWidth(); + if (qFuzzyCompare(sw + 1, 1)) + return m_bounds; + else { + QPainterPath path; + path.addEllipse(m_bounds); + return boundsOnStroke(path, sw); + } +} + +void QSvgEllipse::draw(QPainter *p, QSvgExtraStates &states) +{ + QT_SVG_DRAW_SHAPE(p->drawEllipse(m_bounds)); +} + +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_bounds(line) +{ +} + + +void QSvgLine::draw(QPainter *p, QSvgExtraStates &states) +{ + applyStyle(p, states); + p->drawLine(m_bounds); + revertStyle(p, states); +} + +QSvgPath::QSvgPath(QSvgNode *parent, const QPainterPath &qpath) + : QSvgNode(parent), m_path(qpath) +{ + //m_cachedBounds = m_path.controlPointRect(); + m_cachedBounds = m_path.boundingRect(); +} + +void QSvgPath::draw(QPainter *p, QSvgExtraStates &states) +{ + QT_SVG_DRAW_SHAPE(p->drawPath(m_path)); +} + +QRectF QSvgPath::bounds() const +{ + qreal sw = strokeWidth(); + if (qFuzzyCompare(sw + 1, 1)) + return m_cachedBounds; + else { + return boundsOnStroke(m_path, sw); + } +} + +QSvgPolygon::QSvgPolygon(QSvgNode *parent, const QPolygonF &poly) + : QSvgNode(parent), m_poly(poly) +{ + +} + +QRectF QSvgPolygon::bounds() const +{ + qreal sw = strokeWidth(); + if (qFuzzyCompare(sw + 1, 1)) + return m_poly.boundingRect(); + else { + QPainterPath path; + path.addPolygon(m_poly); + return boundsOnStroke(path, sw); + } +} + +void QSvgPolygon::draw(QPainter *p, QSvgExtraStates &states) +{ + QT_SVG_DRAW_SHAPE(p->drawPolygon(m_poly)); +} + + +QSvgPolyline::QSvgPolyline(QSvgNode *parent, const QPolygonF &poly) + : QSvgNode(parent), m_poly(poly) +{ + +} + +void QSvgPolyline::draw(QPainter *p, QSvgExtraStates &states) +{ + applyStyle(p, states); + if (p->brush().style() != Qt::NoBrush) { + QPen save = p->pen(); + p->setPen(QPen(Qt::NoPen)); + p->drawPolygon(m_poly); + p->setPen(save); + } + p->drawPolyline(m_poly); + 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() const +{ + qreal sw = strokeWidth(); + if (qFuzzyCompare(sw + 1, 1)) + return m_rect; + else { + QPainterPath path; + path.addRect(m_rect); + return boundsOnStroke(path, sw); + } +} + +void QSvgRect::draw(QPainter *p, QSvgExtraStates &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)); + } +} + +QSvgText::QSvgText(QSvgNode *parent, const QPointF &coord) + : QSvgNode(parent) + , m_coord(coord) + , m_textAlignment(Qt::AlignLeft) + , m_scale(1) + , m_appendSpace(false) + , m_type(TEXT) + , m_size(0, 0) +{ + m_paragraphs.push_back(QString()); + m_formatRanges.push_back(QList<QTextLayout::FormatRange>()); +} + +QSvgText::~QSvgText() +{ +} + +void QSvgText::setTextArea(const QSizeF &size) +{ + m_size = size; + m_type = TEXTAREA; +} + +//QRectF QSvgText::bounds() const {} + +void QSvgText::draw(QPainter *p, QSvgExtraStates &states) +{ + applyStyle(p, states); + + QSvgFontStyle *fontStyle = static_cast<QSvgFontStyle*>( + styleProperty(QSvgStyleProperty::FONT)); + if (fontStyle && fontStyle->svgFont()) { + // SVG fonts not fully supported... + QString text = m_paragraphs.front(); + for (int i = 1; i < m_paragraphs.size(); ++i) { + text.append(QLatin1Char('\n')); + text.append(m_paragraphs[i]); + } + fontStyle->svgFont()->draw(p, m_coord, text, fontStyle->pointSize(), m_textAlignment); + revertStyle(p, states); + return; + } + + // Scale the font to its correct size. + QTransform oldTransform = p->worldTransform(); + p->scale(1 / m_scale, 1 / m_scale); + + qreal y = 0; + bool initial = true; + qreal px = m_coord.x() * m_scale; + qreal py = m_coord.y() * m_scale; + QSizeF scaledSize = m_size * m_scale; + + if (m_type == TEXTAREA) { + if (m_textAlignment == Qt::AlignHCenter) + px += scaledSize.width() / 2; + else if (m_textAlignment == Qt::AlignRight) + px += scaledSize.width(); + } + + QRectF bounds; + if (m_size.height() != 0) + bounds = QRectF(0, 0, 1, scaledSize.height()); + + for (int i = 0; i < m_paragraphs.size(); ++i) { + QTextLayout tl(m_paragraphs[i]); + QTextOption op = tl.textOption(); + op.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + tl.setTextOption(op); + tl.setAdditionalFormats(m_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 (m_textAlignment == Qt::AlignHCenter) + x -= line.naturalTextWidth() / 2; + else if (m_textAlignment == Qt::AlignRight) + x -= line.naturalTextWidth(); + + if (initial && m_type == TEXT) + y -= line.ascent(); + initial = false; + + line.setPosition(QPointF(x, y)); + if ((m_size.width() != 0 && line.naturalTextWidth() > scaledSize.width()) + || (m_size.height() != 0 && y + line.height() > scaledSize.height())) { + bounds.setHeight(y); + 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); + revertStyle(p, states); +} + +void QSvgText::insertText(const QString &text, WhitespaceMode mode) +{ + bool isTSpan = (m_formats.count() == 2); + QString newText(text); + newText.replace(QLatin1Char('\t'), QLatin1Char(' ')); + newText.replace(QLatin1Char('\n'), QLatin1Char(' ')); + + bool prependSpace = !m_appendSpace && !isTSpan && (mode == Default) && !m_paragraphs.back().isEmpty() && newText.startsWith(QLatin1Char(' ')); + if (m_appendSpace || prependSpace) + m_paragraphs.back().append(QLatin1Char(' ')); + + bool appendSpaceNext = (!isTSpan && (mode == Default) && newText.endsWith(QLatin1Char(' '))); + + if (mode == Default) { + newText = newText.simplified(); + if (newText.isEmpty()) + appendSpaceNext = false; + } + + if (!m_formats.isEmpty()) { + QTextLayout::FormatRange range; + range.start = m_paragraphs.back().length(); + range.length = newText.length(); + range.format = m_formats.top(); + if (m_appendSpace) { + Q_ASSERT(!m_formatRanges.back().isEmpty()); + ++m_formatRanges.back().back().length; + } else if (prependSpace) { + --range.start; + ++range.length; + } + m_formatRanges.back().append(range); + } + + m_appendSpace = appendSpaceNext; + m_paragraphs.back() += newText; +} + +void QSvgText::insertFormat(const QTextCharFormat &format) +{ + QTextCharFormat mergedFormat = format; + if (!m_formats.isEmpty()) { + mergedFormat = m_formats.top(); + mergedFormat.merge(format); + } + m_formats.push(mergedFormat); +} + +void QSvgText::insertLineBreak() +{ + if (m_type == TEXTAREA) { + if (m_paragraphs.back().isEmpty()) + insertText(QLatin1String(" "), Preserve); + m_appendSpace = false; + m_paragraphs.push_back(QString()); + m_formatRanges.push_back(QList<QTextLayout::FormatRange>()); + } +} + +void QSvgText::popFormat() +{ + if (m_formats.count() > 1) + m_formats.pop(); +} + +qreal QSvgText::scale() const +{ + return m_scale; +} + +void QSvgText::setScale(qreal scale) +{ + m_scale = scale; +} + +const QTextCharFormat &QSvgText::topFormat() const +{ + return m_formats.top(); +} + +void QSvgText::setTextAlignment(const Qt::Alignment &alignment) +{ + m_textAlignment = alignment; +} + +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() const +{ + if (m_link && m_bounds.isEmpty()) { + m_bounds = m_link->bounds(); + m_bounds = QRectF(m_bounds.x()+m_start.x(), + m_bounds.y()+m_start.y(), + m_bounds.width(), + m_bounds.height()); + + return m_bounds; + } + return m_bounds; +} + +QRectF QSvgUse::transformedBounds(const QTransform &transform) const +{ + QRectF bounds; + QTransform t = transform; + + if (m_link) { + QSvgTransformStyle *transStyle = m_style.transform; + if (transStyle) { + t = transStyle->qtransform() * t; + } + t.translate(m_start.x(), m_start.y()); + + bounds = m_link->transformedBounds(t); + + return bounds; + } + return bounds; +} + +QRectF QSvgPolyline::bounds() const +{ + qreal sw = strokeWidth(); + if (qFuzzyCompare(sw + 1, 1)) + return m_poly.boundingRect(); + else { + QPainterPath path; + path.addPolygon(m_poly); + return boundsOnStroke(path, sw); + } +} + +QRectF QSvgArc::bounds() const +{ + qreal sw = strokeWidth(); + if (qFuzzyCompare(sw + 1, 1)) + return m_cachedBounds; + else { + return boundsOnStroke(cubic, sw); + } +} + +QRectF QSvgImage::bounds() const +{ + return m_bounds; +} + +QRectF QSvgLine::bounds() const +{ + qreal sw = strokeWidth(); + if (qFuzzyCompare(sw + 1, 1)) { + qreal minX = qMin(m_bounds.x1(), m_bounds.x2()); + qreal minY = qMin(m_bounds.y1(), m_bounds.y2()); + qreal maxX = qMax(m_bounds.x1(), m_bounds.x2()); + qreal maxY = qMax(m_bounds.y1(), m_bounds.y2()); + return QRectF(minX, minY, maxX-minX, maxY-minY); + } else { + QPainterPath path; + path.moveTo(m_bounds.x1(), m_bounds.y1()); + path.lineTo(m_bounds.x2(), m_bounds.y2()); + return boundsOnStroke(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..4d0d318 --- /dev/null +++ b/src/svg/qsvggraphics_p.h @@ -0,0 +1,247 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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() const; +private: + QPainterPath cubic; + QRectF m_cachedBounds; +}; + +class QSvgCircle : public QSvgNode +{ +public: + QSvgCircle(QSvgNode *parent, const QRectF &rect); + virtual void draw(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; + virtual QRectF bounds() const; +private: + QRectF m_bounds; +}; + +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() const; +private: + QRectF m_bounds; +}; + +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() 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() const; +private: + QLineF m_bounds; +}; + +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() const; + + QPainterPath *qpath() { + return &m_path; + } +private: + QPainterPath m_path; + QRectF m_cachedBounds; +}; + +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() 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() 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() const; +private: + QRectF m_rect; + int m_rx, m_ry; +}; + +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 insertText(const QString &text, WhitespaceMode mode); + void insertFormat(const QTextCharFormat &format); + void insertLineBreak(); + void popFormat(); + void setTextAlignment(const Qt::Alignment &alignment); + const QTextCharFormat &topFormat() const; + qreal scale() const; + void setScale(qreal scale); + //virtual QRectF bounds() const; +private: + QPointF m_coord; + + QVector<QString> m_paragraphs; + QStack<QTextCharFormat> m_formats; + Qt::Alignment m_textAlignment; + QVector<QList<QTextLayout::FormatRange> > m_formatRanges; + qreal m_scale; + bool m_appendSpace; + Type m_type; + QSizeF m_size; +}; + +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() const; + virtual QRectF transformedBounds(const QTransform &transform) const; + +private: + QSvgNode *m_link; + QPointF m_start; + mutable QRectF m_bounds; +}; + +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..56dab5f --- /dev/null +++ b/src/svg/qsvghandler.cpp @@ -0,0 +1,3697 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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 "private/qmath_p.h" + +#include "float.h" + +QT_BEGIN_NAMESPACE + +double qstrtod(const char *s00, char const **se, bool *ok); + +static bool parsePathDataFast(const QStringRef &data, QPainterPath &path); + +struct QSvgAttributes +{ + QSvgAttributes(const QXmlStreamAttributes &xmlAttributes, QSvgHandler *handler); + + QStringRef value(const QLatin1String &name) const; + QStringRef value(const QString &namespaceUri, const QLatin1String &name) const; + + QXmlStreamAttributes m_xmlAttributes; + QVector<QSvgCssAttribute> m_cssAttributes; +}; + +QSvgAttributes::QSvgAttributes(const QXmlStreamAttributes &xmlAttributes, QSvgHandler *handler) + : m_xmlAttributes(xmlAttributes) +{ + QStringRef style = xmlAttributes.value(QLatin1String("style")); + if (!style.isEmpty()) + handler->parseCSStoXMLAttrs(style.toString(), &m_cssAttributes); +} + +QStringRef QSvgAttributes::value(const QLatin1String &name) const +{ + QStringRef v = m_xmlAttributes.value(name); + if (v.isEmpty()) { + for (int i = 0; i < m_cssAttributes.count(); ++i) { + if (m_cssAttributes.at(i).name == name) { + v = m_cssAttributes.at(i).value; + break; + } + } + } + return v; +} + +QStringRef QSvgAttributes::value(const QString &namespaceUri, const QLatin1String &name) const +{ + QStringRef v = m_xmlAttributes.value(namespaceUri, name); + if (v.isEmpty()) { + for (int i = 0; i < m_cssAttributes.count(); ++i) { + if (m_cssAttributes.at(i).name == name) { + v = m_cssAttributes.at(i).value; + break; + } + } + } + return v; +} + +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; +} +static inline QString someId(const QSvgAttributes &attributes) +{ return someId(attributes.m_xmlAttributes); } + + + +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); + } +}; + +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 (*str >= QLatin1Char('0') && *str <= QLatin1Char('9') && pos < maxLen) { + temp[pos++] = str->toLatin1(); + ++str; + } + if (*str == QLatin1Char('.') && pos < maxLen) { + temp[pos++] = '.'; + ++str; + } + while (*str >= QLatin1Char('0') && *str <= QLatin1Char('9') && pos < maxLen) { + temp[pos++] = str->toLatin1(); + ++str; + } + bool exponent = false; + if (*str == QLatin1Char('e') && pos < maxLen) { + exponent = true; + temp[pos++] = 'e'; + ++str; + if ((*str == QLatin1Char('-') || *str == QLatin1Char('+')) && pos < maxLen) { + temp[pos++] = str->toLatin1(); + ++str; + } + while (*str >= QLatin1Char('0') && *str <= QLatin1Char('9') && 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 { +#ifdef Q_WS_QWS + 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) +{ + const QChar *c = str.constData(); + return toDouble(c); +} + +static qreal toDouble(const QStringRef &str) +{ + const QChar *c = str.constData(); + return toDouble(c); +} + +static QVector<qreal> parseNumbersList(const QChar *&str) +{ + QVector<qreal> points; + if (!str) + return points; + points.reserve(32); + + while (*str == QLatin1Char(' ')) + ++str; + while ((*str >= QLatin1Char('0') && *str <= QLatin1Char('9')) || + *str == QLatin1Char('-') || *str == QLatin1Char('+') || + *str == QLatin1Char('.')) { + + points.append(toDouble(str)); + + while (*str == QLatin1Char(' ')) + ++str; + if (*str == QLatin1Char(',')) + ++str; + + //eat the rest of space + while (*str == QLatin1Char(' ')) + ++str; + } + + return points; +} + +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 == QLatin1Char(' ')) + ++str; + if (*str == QLatin1Char('%')) + ++str; + while (*str == QLatin1Char(' ')) + ++str; + if (*str == QLatin1Char(',')) + ++str; + + //eat the rest of space + while (*str == QLatin1Char(' ')) + ++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; +} + +/** + * returns true when successfuly set the color. false signifies + * that the color should be inherited + */ +static bool resolveColor(const QString &colorStr, QColor &color, QSvgHandler *handler) +{ + QString colorStrTr = colorStr.trimmed(); + if (colorStr.startsWith(QLatin1String("rgb("))) { + const QChar *s = colorStr.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) { + const QChar *s = colorStr.constData() + 4; + compo = parsePercentageList(s); + compo[0] *= (qreal)2.55; + compo[1] *= (qreal)2.55; + compo[2] *= (qreal)2.55; + } + + color = QColor(int(compo[0]), + int(compo[1]), + int(compo[2])); + return true; + } else if (colorStr == QLatin1String("inherited") || + colorStr == QLatin1String("inherit")) { + return false; + } else if (colorStr == QLatin1String("currentColor")) { + color = handler->currentColor(); + return true; + } + + color = QColor(colorStrTr); + return color.isValid(); +} + +static bool constructColor(const QString &colorStr, const QString &opacity, + QColor &color, QSvgHandler *handler) +{ + if (!resolveColor(colorStr, color, handler)) + return false; + if (!opacity.isEmpty()) { + qreal op = toDouble(opacity); + if (op <= 1) + op *= 255; + color.setAlpha(int(op)); + } + return true; +} + +static qreal parseLength(const QString &str, QSvgHandler::LengthType &type, + QSvgHandler *handler) +{ + 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); + //qDebug()<<"len is "<<len<<", from '"<<numStr << "'"; + return len; +} + +static inline qreal convertToNumber(const QString &str, QSvgHandler *handler) +{ + QSvgHandler::LengthType type; + qreal num = parseLength(str, type, handler); + 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) +{ + QString colorStr = attributes.value(QLatin1String("color")).toString(); + QString opacity = attributes.value(QLatin1String("color-opacity")).toString(); + QColor color; + if (constructColor(colorStr, opacity, color, handler)) { + handler->pushColor(color); + } +} + +static QSvgStyleProperty *styleFromUrl(QSvgNode *node, const QString &url) +{ + while (node && (node->type() != QSvgNode::DOC && + node->type() != QSvgNode::G && + node->type() != QSvgNode::DEFS && + node->type() != QSvgNode::SWITCH)) { + node = node->parent(); + } + if (!node) + return 0; + return static_cast<QSvgStructureNode*>(node)->scopeStyle(idFromUrl(url)); +} + +static void parseBrush(QSvgNode *node, + const QSvgAttributes &attributes, + QSvgHandler *handler) +{ + QString value = attributes.value(QLatin1String("fill")).toString(); + QString fillRule = attributes.value(QLatin1String("fill-rule")).toString(); + QString opacity = attributes.value(QLatin1String("fill-opacity")).toString(); + QString myId = someId(attributes); + + value = value.trimmed(); + fillRule = fillRule.trimmed(); + if (!value.isEmpty() || !fillRule.isEmpty()) { + Qt::FillRule f = Qt::WindingFill; + if (fillRule == QLatin1String("evenodd")) + f = Qt::OddEvenFill; + if (value.startsWith(QLatin1String("url"))) { + value = value.remove(0, 3); + QSvgStyleProperty *style = styleFromUrl(node, value); + if (style) { + QSvgFillStyle *prop = new QSvgFillStyle(style); + if (!opacity.isEmpty()) + prop->setFillOpacity(toDouble(opacity)); + node->appendStyleProperty(prop, myId); + } else { + qWarning("Couldn't resolve property: %s", qPrintable(idFromUrl(value))); + } + } else if (value != QLatin1String("none")) { + QColor color; + if (constructColor(value, opacity, color, handler)) { + QSvgFillStyle *prop = new QSvgFillStyle(QBrush(color)); + if (!fillRule.isEmpty()) + prop->setFillRule(f); + node->appendStyleProperty(prop, myId); + } + } else { + QSvgFillStyle *prop = new QSvgFillStyle(QBrush(Qt::NoBrush)); + if (!fillRule.isEmpty()) + prop->setFillRule(f); + node->appendStyleProperty(prop, myId); + } + } +} + +static void parseQPen(QPen &pen, QSvgNode *node, + const QSvgAttributes &attributes, + QSvgHandler *handler) +{ + QString value = attributes.value(QLatin1String("stroke")).toString(); + QString dashArray = attributes.value(QLatin1String("stroke-dasharray")).toString(); + QString dashOffset = attributes.value(QLatin1String("stroke-dashoffset")).toString(); + QString linecap = attributes.value(QLatin1String("stroke-linecap")).toString(); + QString linejoin = attributes.value(QLatin1String("stroke-linejoin")).toString(); + QString miterlimit = attributes.value(QLatin1String("stroke-miterlimit")).toString(); + QString opacity = attributes.value(QLatin1String("stroke-opacity")).toString(); + QString width = attributes.value(QLatin1String("stroke-width")).toString(); + QString myId = someId(attributes); + + if (!value.isEmpty() || !width.isEmpty()) { + if (value != QLatin1String("none")) { + if (!value.isEmpty()) { + if (node && value.startsWith(QLatin1String("url"))) { + value = value.remove(0, 3); + QSvgStyleProperty *style = styleFromUrl(node, value); + if (style) { + if (style->type() == QSvgStyleProperty::GRADIENT) { + QBrush b(*((QSvgGradientStyle*)style)->qgradient()); + pen.setBrush(b); + } else if (style->type() == QSvgStyleProperty::SOLID_COLOR) { + pen.setColor( + ((QSvgSolidColorStyle*)style)->qcolor()); + } + } else { + qWarning()<<"QSvgHandler::parsePen could not resolve property" << idFromUrl(value); + } + } else { + QColor color; + if (constructColor(value, opacity, color, handler)) + pen.setColor(color); + } + //since we could inherit stroke="none" + //we need to reset the style of our stroke to something + pen.setStyle(Qt::SolidLine); + } + if (!width.isEmpty()) { + QSvgHandler::LengthType lt; + qreal widthF = parseLength(width, lt, handler); + //### fixme + if (!widthF) { + pen.setStyle(Qt::NoPen); + return; + } + pen.setWidthF(widthF); + } + qreal penw = pen.widthF(); + + if (!linejoin.isEmpty()) { + if (linejoin == QLatin1String("miter")) + pen.setJoinStyle(Qt::SvgMiterJoin); + else if (linejoin == QLatin1String("round")) + pen.setJoinStyle(Qt::RoundJoin); + else if (linejoin == QLatin1String("bevel")) + pen.setJoinStyle(Qt::BevelJoin); + } + if (!miterlimit.isEmpty()) + pen.setMiterLimit(toDouble(miterlimit)); + + if (!linecap.isEmpty()) { + if (linecap == QLatin1String("butt")) + pen.setCapStyle(Qt::FlatCap); + else if (linecap == QLatin1String("round")) + pen.setCapStyle(Qt::RoundCap); + else if (linecap == QLatin1String("square")) + pen.setCapStyle(Qt::SquareCap); + } + + if (!dashArray.isEmpty()) { + const QChar *s = dashArray.constData(); + QVector<qreal> dashes = parseNumbersList(s); + qreal *d = dashes.data(); + if (penw != 0) + for (int i = 0; i < dashes.size(); ++i) { + *d /= penw; + ++d; + } + pen.setDashPattern(dashes); + } + if (!dashOffset.isEmpty()) { + pen.setDashOffset(toDouble(dashOffset)); + } + + } else { + pen.setStyle(Qt::NoPen); + } + } +} + +static QMatrix parseTransformationMatrix(const QString &value) +{ + QMatrix matrix; + const QChar *str = value.constData(); + + while (*str != QLatin1Char(0)) { + 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->isSpace()) + ++str; + if (*str != QLatin1Char('(')) + goto error; + ++str; + QVector<qreal> points = parseNumbersList(str); + 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(tan(points[0]*deg2rad), 0); + } else if (state == SkewY) { + if (points.count() != 1) + goto error; + const qreal deg2rad = qreal(0.017453292519943295769); + matrix.shear(0, tan(points[0]*deg2rad)); + } + } + error: + return matrix; +} + +static void parsePen(QSvgNode *node, + const QSvgAttributes &attributes, + QSvgHandler *handler) +{ + QString value = attributes.value(QLatin1String("stroke")).toString(); + QString dashArray = attributes.value(QLatin1String("stroke-dasharray")).toString(); + QString dashOffset = attributes.value(QLatin1String("stroke-dashoffset")).toString(); + QString linecap = attributes.value(QLatin1String("stroke-linecap")).toString(); + QString linejoin = attributes.value(QLatin1String("stroke-linejoin")).toString(); + QString miterlimit = attributes.value(QLatin1String("stroke-miterlimit")).toString(); + QString opacity = attributes.value(QLatin1String("stroke-opacity")).toString(); + QString width = attributes.value(QLatin1String("stroke-width")).toString(); + QString myId = someId(attributes); + + //qDebug()<<"Node "<<node->type()<<", attrs are "<<value<<width; + + if (!value.isEmpty() || !width.isEmpty() || !linecap.isEmpty() || !linejoin.isEmpty()) { + if (value != QLatin1String("none")) { + QSvgStrokeStyle *inherited = + static_cast<QSvgStrokeStyle*>(node->styleProperty( + QSvgStyleProperty::STROKE)); + if (!inherited) + inherited = static_cast<QSvgStrokeStyle*>(node->parent()->styleProperty( + QSvgStyleProperty::STROKE)); + QPen pen(handler->defaultPen()); + if (inherited) + pen = inherited->qpen(); + + if (!value.isEmpty()) { + if (value.startsWith(QLatin1String("url"))) { + value = value.remove(0, 3); + QSvgStyleProperty *style = styleFromUrl(node, value); + if (style) { + if (style->type() == QSvgStyleProperty::GRADIENT) { + QBrush b(*((QSvgGradientStyle*)style)->qgradient()); + pen.setBrush(b); + } else if (style->type() == QSvgStyleProperty::SOLID_COLOR) { + pen.setColor( + ((QSvgSolidColorStyle*)style)->qcolor()); + } + } else { + qWarning() << "QSvgHandler::parsePen could not resolve property" << idFromUrl(value); + } + } else { + QColor color; + if (constructColor(value, opacity, color, handler)) + pen.setColor(color); + } + //since we could inherit stroke="none" + //we need to reset the style of our stroke to something + pen.setStyle(Qt::SolidLine); + } + if (!width.isEmpty()) { + QSvgHandler::LengthType lt; + qreal widthF = parseLength(width, lt, handler); + //### fixme + if (!widthF) { + pen.setStyle(Qt::NoPen); + return; + } + pen.setWidthF(widthF); + } + + if (!linejoin.isEmpty()) { + if (linejoin == QLatin1String("miter")) + pen.setJoinStyle(Qt::SvgMiterJoin); + else if (linejoin == QLatin1String("round")) + pen.setJoinStyle(Qt::RoundJoin); + else if (linejoin == QLatin1String("bevel")) + pen.setJoinStyle(Qt::BevelJoin); + } + + if (!linecap.isEmpty()) { + if (linecap == QLatin1String("butt")) + pen.setCapStyle(Qt::FlatCap); + else if (linecap == QLatin1String("round")) + pen.setCapStyle(Qt::RoundCap); + else if (linecap == QLatin1String("square")) + pen.setCapStyle(Qt::SquareCap); + } + + qreal penw = pen.widthF(); + if (!dashArray.isEmpty()) { + const QChar *s = dashArray.constData(); + QVector<qreal> dashes = parseNumbersList(s); + qreal *d = dashes.data(); + if(penw != 0) + for (int i = 0; i < dashes.size(); ++i) { + *d /= penw; + ++d; + } + + // if the dash count is odd the dashes should be duplicated + if (dashes.size() % 2 != 0) + dashes << QVector<qreal>(dashes); + + pen.setDashPattern(dashes); + } + if (!dashOffset.isEmpty()) { + qreal doffset = toDouble(dashOffset); + if (penw != 0) + doffset /= penw; + pen.setDashOffset(doffset); + } + if (!miterlimit.isEmpty()) + pen.setMiterLimit(toDouble(miterlimit)); + + node->appendStyleProperty(new QSvgStrokeStyle(pen), myId); + } else { + QPen pen(handler->defaultPen()); + pen.setStyle(Qt::NoPen); + node->appendStyleProperty(new QSvgStrokeStyle(pen), myId); + } + } +} + + +static bool parseQBrush(const QSvgAttributes &attributes, QSvgNode *node, + QBrush &brush, QSvgHandler *handler) +{ + QString value = attributes.value(QLatin1String("fill")).toString(); + QString opacity = attributes.value(QLatin1String("fill-opacity")).toString(); + + QColor color; + if (!value.isEmpty() || !opacity.isEmpty()) { + if (value.startsWith(QLatin1String("url"))) { + value = value.remove(0, 3); + QSvgStyleProperty *style = styleFromUrl(node, value); + if (style) { + switch (style->type()) { + case QSvgStyleProperty::FILL: + { + brush = static_cast<QSvgFillStyle*>(style)->qbrush(); + break; + } + case QSvgStyleProperty::SOLID_COLOR: + { + brush = QBrush(static_cast<QSvgSolidColorStyle*>(style)->qcolor()); + break; + } + case QSvgStyleProperty::GRADIENT: + { + brush = QBrush(*static_cast<QSvgGradientStyle*>(style)->qgradient()); + break; + } + default: + qWarning("Cannot use property \"%s\" as brush.", qPrintable(idFromUrl(value))); + } + } else { + qWarning("Couldn't resolve property: %s", qPrintable(idFromUrl(value))); + } + } else if (value != QLatin1String("none")) { + if (constructColor(value, opacity, color, handler)) { + brush.setStyle(Qt::SolidPattern); + brush.setColor(color); + } + } else { + brush = QBrush(Qt::NoBrush); + } + return true; + } + return false; +} + +static bool parseQFont(const QSvgAttributes &attributes, + QFont &font, qreal &fontSize, QSvgHandler *handler) +{ + QString family = attributes.value(QLatin1String("font-family")).toString(); + QString size = attributes.value(QLatin1String("font-size")).toString(); + QString style = attributes.value(QLatin1String("font-style")).toString(); + QString weight = attributes.value(QLatin1String("font-weight")).toString(); + + if (!family.isEmpty() || !size.isEmpty() || + !style.isEmpty() || !weight.isEmpty()) { + + if (!family.isEmpty()) { + font.setFamily(family.trimmed()); + } + if (!size.isEmpty()) { + QSvgHandler::LengthType dummy; // should always be pixel size + fontSize = parseLength(size, dummy, handler); + if (fontSize <= 0) + fontSize = 1; + font.setPixelSize(qMax(1, int(fontSize))); + } + if (!style.isEmpty()) { + if (style == QLatin1String("normal")) { + font.setStyle(QFont::StyleNormal); + } else if (style == QLatin1String("italic")) { + font.setStyle(QFont::StyleItalic); + } else if (style == QLatin1String("oblique")) { + font.setStyle(QFont::StyleOblique); + } else if (style == QLatin1String("inherit")) { + //inherited already + } + } + if (!weight.isEmpty()) { + bool ok = false; + int weightNum = weight.toInt(&ok); + if (ok) { + switch (weightNum) { + case 100: + case 200: + font.setWeight(QFont::Light); + break; + case 300: + case 400: + font.setWeight(QFont::Normal); + break; + case 500: + case 600: + font.setWeight(QFont::DemiBold); + break; + case 700: + case 800: + font.setWeight(QFont::Bold); + break; + case 900: + font.setWeight(QFont::Black); + break; + default: + break; + } + } else { + if (weight == QLatin1String("normal")) { + font.setWeight(QFont::Normal); + } else if (weight == QLatin1String("bold")) { + font.setWeight(QFont::Bold); + } else if (weight == QLatin1String("bolder")) { + font.setWeight(QFont::DemiBold); + } else if (weight == QLatin1String("lighter")) { + font.setWeight(QFont::Light); + } + } + } + // QFontInfo fi(font); + // font.setPointSize(fi.pointSize()); + return true; + } + + return false; +} + +static void parseFont(QSvgNode *node, + const QSvgAttributes &attributes, + QSvgHandler *handler) +{ + QFont font; + font.setPixelSize(12); + qreal fontSize = font.pixelSize(); + + QSvgFontStyle *inherited = + static_cast<QSvgFontStyle*>(node->styleProperty( + QSvgStyleProperty::FONT)); + if (!inherited) + inherited = + static_cast<QSvgFontStyle*>(node->parent()->styleProperty( + QSvgStyleProperty::FONT)); + if (inherited) { + font = inherited->qfont(); + fontSize = inherited->pointSize(); + } + if (parseQFont(attributes, font, fontSize, handler)) { + QString myId = someId(attributes); + QString anchor = attributes.value(QLatin1String("text-anchor")).toString(); + QSvgTinyDocument *doc = node->document(); + QSvgFontStyle *fontStyle = 0; + QString family = (font.family().isEmpty())?myId:font.family(); + if (!family.isEmpty()) { + QSvgFont *svgFont = doc->svgFont(family); + if (svgFont) { + fontStyle = new QSvgFontStyle(svgFont, doc); + fontStyle->setPointSize(fontSize); + } + } + if (!fontStyle) { + fontStyle = new QSvgFontStyle(font, node->document()); + fontStyle->setPointSize(fontSize); + } + if (!anchor.isEmpty()) + fontStyle->setTextAnchor(anchor); + + node->appendStyleProperty(fontStyle, myId); + } +} + +static void parseTransform(QSvgNode *node, + const QSvgAttributes &attributes, + QSvgHandler *) +{ + QString value = attributes.value(QLatin1String("transform")).toString(); + QString myId = someId(attributes); + value = value.trimmed(); + if (value.isEmpty()) + return; + QMatrix matrix = parseTransformationMatrix(value); + + if (!matrix.isIdentity()) { + node->appendStyleProperty(new QSvgTransformStyle(QTransform(matrix)), myId); + } + +} + +static void parseVisibility(QSvgNode *node, + const QSvgAttributes &attributes, + QSvgHandler *) +{ + QString value = attributes.value(QLatin1String("visibility")).toString(); + QSvgNode *parent = node->parent(); + + if (parent && (value.isEmpty() || value == QLatin1String("inherit"))) + node->setVisible(parent->isVisible()); + else if (value == QLatin1String("hidden") || value == 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 = atan2(y0 - yc, x0 - xc); + th1 = atan2(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 == QLatin1Char(' ')) + ++str; + QChar pathElem = *str; + ++str; + QChar endc = *end; + *const_cast<QChar *>(end) = 0; // parseNumbersList requires 0-termination that QStringRef cannot guarantee + QVector<qreal> arg = parseNumbersList(str); + *const_cast<QChar *>(end) = endc; + if (pathElem == QLatin1Char('z') || pathElem == QLatin1Char('Z')) + arg.append(0);//dummy + while (!arg.isEmpty()) { + qreal offsetX = x; // correction offsets + qreal offsetY = y; // for relative commands + switch (pathElem.unicode()) { + case 'm': { + if (arg.count() < 2) { + arg.pop_front(); + break; + } + x = x0 = arg[0] + offsetX; + y = y0 = arg[1] + offsetY; + path.moveTo(x0, y0); + arg.pop_front(); arg.pop_front(); + } + break; + case 'M': { + if (arg.count() < 2) { + arg.pop_front(); + break; + } + x = x0 = arg[0]; + y = y0 = arg[1]; + + path.moveTo(x0, y0); + arg.pop_front(); arg.pop_front(); + } + break; + case 'z': + case 'Z': { + x = x0; + y = y0; + path.closeSubpath(); + arg.pop_front();//pop dummy + } + break; + case 'l': { + if (arg.count() < 2) { + arg.pop_front(); + break; + } + x = arg.front() + offsetX; + arg.pop_front(); + y = arg.front() + offsetY; + arg.pop_front(); + path.lineTo(x, y); + + } + break; + case 'L': { + if (arg.count() < 2) { + arg.pop_front(); + break; + } + x = arg.front(); arg.pop_front(); + y = arg.front(); arg.pop_front(); + path.lineTo(x, y); + } + break; + case 'h': { + x = arg.front() + offsetX; arg.pop_front(); + path.lineTo(x, y); + } + break; + case 'H': { + x = arg[0]; + path.lineTo(x, y); + arg.pop_front(); + } + break; + case 'v': { + y = arg[0] + offsetY; + path.lineTo(x, y); + arg.pop_front(); + } + break; + case 'V': { + y = arg[0]; + path.lineTo(x, y); + arg.pop_front(); + } + break; + case 'c': { + if (arg.count() < 6) { + while (arg.count()) + arg.pop_front(); + break; + } + QPointF c1(arg[0]+offsetX, arg[1]+offsetY); + QPointF c2(arg[2]+offsetX, arg[3]+offsetY); + QPointF e(arg[4]+offsetX, arg[5]+offsetY); + path.cubicTo(c1, c2, e); + ctrlPt = c2; + x = e.x(); + y = e.y(); + arg.pop_front(); arg.pop_front(); + arg.pop_front(); arg.pop_front(); + arg.pop_front(); arg.pop_front(); + break; + } + case 'C': { + if (arg.count() < 6) { + while (arg.count()) + arg.pop_front(); + break; + } + QPointF c1(arg[0], arg[1]); + QPointF c2(arg[2], arg[3]); + QPointF e(arg[4], arg[5]); + path.cubicTo(c1, c2, e); + ctrlPt = c2; + x = e.x(); + y = e.y(); + arg.pop_front(); arg.pop_front(); + arg.pop_front(); arg.pop_front(); + arg.pop_front(); arg.pop_front(); + break; + } + case 's': { + if (arg.count() < 4) { + while (arg.count()) + arg.pop_front(); + 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(arg[0]+offsetX, arg[1]+offsetY); + QPointF e(arg[2]+offsetX, arg[3]+offsetY); + path.cubicTo(c1, c2, e); + ctrlPt = c2; + x = e.x(); + y = e.y(); + arg.pop_front(); arg.pop_front(); + arg.pop_front(); arg.pop_front(); + break; + } + case 'S': { + if (arg.count() < 4) { + while (arg.count()) + arg.pop_front(); + 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(arg[0], arg[1]); + QPointF e(arg[2], arg[3]); + path.cubicTo(c1, c2, e); + ctrlPt = c2; + x = e.x(); + y = e.y(); + arg.pop_front(); arg.pop_front(); + arg.pop_front(); arg.pop_front(); + break; + } + case 'q': { + if (arg.count() < 4) { + while (arg.count()) + arg.pop_front(); + break; + } + QPointF c(arg[0]+offsetX, arg[1]+offsetY); + QPointF e(arg[2]+offsetX, arg[3]+offsetY); + path.quadTo(c, e); + ctrlPt = c; + x = e.x(); + y = e.y(); + arg.pop_front(); arg.pop_front(); + arg.pop_front(); arg.pop_front(); + break; + } + case 'Q': { + if (arg.count() < 4) { + while (arg.count()) + arg.pop_front(); + break; + } + QPointF c(arg[0], arg[1]); + QPointF e(arg[2], arg[3]); + path.quadTo(c, e); + ctrlPt = c; + x = e.x(); + y = e.y(); + arg.pop_front(); arg.pop_front(); + arg.pop_front(); arg.pop_front(); + break; + } + case 't': { + if (arg.count() < 2) { + while (arg.count()) + arg.pop_front(); + break; + } + QPointF e(arg[0]+offsetX, arg[1]+offsetY); + 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(); + arg.pop_front(); arg.pop_front(); + break; + } + case 'T': { + if (arg.count() < 2) { + while (arg.count()) + arg.pop_front(); + break; + } + QPointF e(arg[0], arg[1]); + 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(); + arg.pop_front(); arg.pop_front(); + break; + } + case 'a': { + if (arg.count() < 7) { + while (arg.count()) + arg.pop_front(); + break; + } + qreal rx = arg[0]; + qreal ry = arg[1]; + qreal xAxisRotation = arg[2]; + qreal largeArcFlag = arg[3]; + qreal sweepFlag = arg[4]; + qreal ex = arg[5] + offsetX; + qreal ey = arg[6] + offsetY; + qreal curx = x; + qreal cury = y; + pathArc(path, rx, ry, xAxisRotation, int(largeArcFlag), + int(sweepFlag), ex, ey, curx, cury); + + x = ex; + y = ey; + + arg.pop_front(); arg.pop_front(); + arg.pop_front(); arg.pop_front(); + arg.pop_front(); arg.pop_front(); + arg.pop_front(); + } + break; + case 'A': { + if (arg.count() < 7) { + while (arg.count()) + arg.pop_front(); + break; + } + qreal rx = arg[0]; + qreal ry = arg[1]; + qreal xAxisRotation = arg[2]; + qreal largeArcFlag = arg[3]; + qreal sweepFlag = arg[4]; + qreal ex = arg[5]; + qreal ey = arg[6]; + qreal curx = x; + qreal cury = y; + pathArc(path, rx, ry, xAxisRotation, int(largeArcFlag), + int(sweepFlag), ex, ey, curx, cury); + x = ex; + y = ey; + arg.pop_front(); arg.pop_front(); + arg.pop_front(); arg.pop_front(); + arg.pop_front(); arg.pop_front(); + arg.pop_front(); + } + 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; + 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(')')); + } 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 bool parseDefaultTextStyle(QSvgNode *node, + const QXmlStreamAttributes &attributes, + bool initial, + QSvgHandler *handler) +{ + Q_ASSERT(node->type() == QSvgText::TEXT || node->type() == QSvgNode::TEXTAREA); + QSvgText *textNode = static_cast<QSvgText*>(node); + + QSvgAttributes attrs(attributes, handler); + + QString fontFamily = attrs.value(QString(), QLatin1String("font-family")).toString(); + + QString anchor = attrs.value(QString(), QLatin1String("text-anchor")).toString(); + + QSvgFontStyle *fontStyle = static_cast<QSvgFontStyle*>( + node->styleProperty(QSvgStyleProperty::FONT)); + if (fontStyle) { + QSvgTinyDocument *doc = fontStyle->doc(); + if (doc && fontStyle->svgFont()) { + cssStyleLookup(node, handler, handler->selector()); + parseStyle(node, attrs, handler); + return true; + } + } else if (!fontFamily.isEmpty()) { + QSvgTinyDocument *doc = node->document(); + QSvgFont *svgFont = doc->svgFont(fontFamily); + if (svgFont) { + cssStyleLookup(node, handler, handler->selector()); + parseStyle(node, attrs, handler); + return true; + } + } + + QTextCharFormat format; + QBrush brush(QColor(0, 0, 0)); + QFont font; + font.setPixelSize(12); + qreal fontSize = font.pixelSize(); + + if (!initial) { + font = textNode->topFormat().font(); + fontSize = font.pixelSize() / textNode->scale(); + brush = textNode->topFormat().foreground(); + } else { + QSvgFontStyle *fontStyle = static_cast<QSvgFontStyle*>( + node->styleProperty(QSvgStyleProperty::FONT)); + if (!fontStyle) + fontStyle = static_cast<QSvgFontStyle*>( + node->parent()->styleProperty(QSvgStyleProperty::FONT)); + if (fontStyle) { + font = fontStyle->qfont(); + fontSize = fontStyle->pointSize(); + if (anchor.isEmpty()) + anchor = fontStyle->textAnchor(); + } + + Qt::Alignment align = Qt::AlignLeft; + if (anchor == QLatin1String("middle")) + align = Qt::AlignHCenter; + else if (anchor == QLatin1String("end")) + align = Qt::AlignRight; + textNode->setTextAlignment(align); + + QSvgFillStyle *fillStyle = static_cast<QSvgFillStyle*>( + node->styleProperty(QSvgStyleProperty::FILL)); + if (fillStyle) + brush = fillStyle->qbrush(); + } + + if (parseQFont(attrs, font, fontSize, handler) || initial) { + if (initial) + textNode->setScale(100 / fontSize); + font.setPixelSize(qMax(1, int(fontSize * textNode->scale()))); + format.setFont(font); + } + + if (parseQBrush(attrs, node, brush, handler) || initial) { + if (brush.style() != Qt::NoBrush || initial) + format.setForeground(brush); + } + + QPen pen(Qt::NoPen); +// QSvgStrokeStyle *inherited = +// static_cast<QSvgStrokeStyle*>(node->parent()->styleProperty( +// QSvgStyleProperty::STROKE)); +// if (inherited) +// pen = inherited->qpen(); + parseQPen(pen, node, attrs, handler); + if (pen.style() != Qt::NoPen) { + format.setTextOutline(pen); + } + + parseTransform(node, attrs, handler); + + textNode->insertFormat(format); + + return true; +} + +static inline QStringList stringToList(const QString &str) +{ + QStringList lst = str.split(QLatin1Char(','), QString::SkipEmptyParts); + return lst; +} + +static bool parseCoreNode(QSvgNode *node, + const QXmlStreamAttributes &attributes) +{ + QString featuresStr = attributes.value(QLatin1String("requiredFeatures")).toString(); + QString extensionsStr = attributes.value(QLatin1String("requiredExtensions")).toString(); + QString languagesStr = attributes.value(QLatin1String("systemLanguage")).toString(); + QString formatsStr = attributes.value(QLatin1String("requiredFormats")).toString(); + QString fontsStr = attributes.value(QLatin1String("requiredFonts")).toString(); + QString nodeIdStr = someId(attributes); + QString xmlClassStr = attributes.value(QLatin1String("class")).toString(); + + + QStringList features = stringToList(featuresStr); + QStringList extensions = stringToList(extensionsStr); + QStringList languages = stringToList(languagesStr); + QStringList formats = stringToList(formatsStr); + QStringList fonts = stringToList(fontsStr); + + node->setRequiredFeatures(features); + node->setRequiredExtensions(extensions); + node->setRequiredLanguages(languages); + node->setRequiredFormats(formats); + node->setRequiredFonts(fonts); + node->setNodeId(nodeIdStr); + node->setXmlClass(xmlClassStr); + + return true; +} + +static void parseOpacity(QSvgNode *node, + const QSvgAttributes &attributes, + QSvgHandler *) +{ + QString value = attributes.value(QLatin1String("opacity")).toString(); + value = value.trimmed(); + + bool ok = false; + qreal op = value.toDouble(&ok); + + if (ok) { + QSvgOpacityStyle *opacity = new QSvgOpacityStyle(op); + node->appendStyleProperty(opacity, someId(attributes)); + } +} + +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 *) +{ + QString value = attributes.value(QLatin1String("comp-op")).toString(); + value = value.trimmed(); + + if (!value.isEmpty()) { + QSvgCompOpStyle *compop = new QSvgCompOpStyle(svgToQtCompositionMode(value)); + node->appendStyleProperty(compop, someId(attributes)); + } +} + +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 == QLatin1String("inherit")) { + return QSvgNode::InheritMode; + } + return QSvgNode::BlockMode; +} + +static void parseOthers(QSvgNode *node, + const QSvgAttributes &attributes, + QSvgHandler *) +{ + QString displayStr = attributes.value(QLatin1String("display")).toString(); + displayStr = displayStr.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(); + QString fromStr = attributes.value(QLatin1String("from")).toString(); + QString toStr = attributes.value(QLatin1String("to")).toString(); + 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; + constructColor(fromStr, QString(), startColor, handler); + constructColor(toStr, QString(), 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; + constructColor(*itr, QString(), 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 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(); + + QVector<qreal> vals; + if (values.isEmpty()) { + const QChar *s = fromStr.constData(); + QVector<qreal> lst = parseNumbersList(s); + while (lst.count() < 3) + lst.append(0.0); + vals << lst; + + s = toStr.constData(); + lst = parseNumbersList(s); + while (lst.count() < 3) + lst.append(0.0); + vals << lst; + } else { + const QChar *s = values.constData(); + while (s && *s != QLatin1Char(0)) { + QVector<qreal> tmpVals = parseNumbersList(s); + while (tmpVals.count() < 3) + tmpVals.append(0.0); + + vals << tmpVals; + 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, 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; + } + + 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(); + QString trans = attributes.value(QLatin1String("gradientTransform")).toString(); + QString spread = attributes.value(QLatin1String("spreadMethod")).toString(); + QString units = attributes.value(QLatin1String("gradientUnits")).toString(); + + 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()); + + 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->setColorAt(qQNaN(), QColor()); + 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); + int i = 0; + QVector<qreal>::const_iterator itr = points.constBegin(); + while (itr != points.constEnd()) { + qreal one = *itr; ++itr; + qreal two = *itr; ++itr; + poly[i] = QPointF(one, two); + ++i; + } + 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); + int i = 0; + QVector<qreal>::const_iterator itr = points.constBegin(); + while (itr != points.constEnd()) { + qreal one = *itr; ++itr; + qreal two = *itr; ++itr; + poly[i] = QPointF(one, two); + ++i; + } + + 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->setColorAt(qQNaN(), QColor()); + 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 (nrx && !nry) + nry = nrx; + else if (nry && !nrx) + 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); + QString solidColorStr = attributes.value(QLatin1String("solid-color")).toString(); + QString solidOpacityStr = attributes.value(QLatin1String("solid-opacity")).toString(); + + if (solidOpacityStr.isEmpty()) + solidOpacityStr = attributes.value(QLatin1String("opacity")).toString(); + + 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); + + QSvgAttributes attrs(attributes, handler); + + 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(')')); + } + attrs.m_xmlAttributes.append(QString(), decl.d->property, valueStr); + } + + QSvgGradientStyle *style = + static_cast<QSvgGradientStyle*>(parent); + QString offsetStr = attrs.value(QString(), QLatin1String("offset")).toString(); + QString colorStr = attrs.value(QString(), QLatin1String("stop-color")).toString(); + QString opacityStr = attrs.value(QString(), QLatin1String("stop-opacity")).toString(); + QColor color; + qreal offset = convertToNumber(offsetStr, handler); + if (colorStr.isEmpty()) { + colorStr = QLatin1String("#000000"); + } + + bool colorOK = constructColor(colorStr, opacityStr, color, handler); + + QGradient *grad = style->qgradient(); + + offset = qMin(qreal(1), qMax(qreal(0), offset)); // Clamp to range [0, 1] + QGradientStops stops = grad->stops(); + // Check if the gradient is marked as empty (marked with one single stop at NaN). + if ((stops.size() == 1) && qIsNaN(stops.at(0).first)) { + stops.clear(); + grad->setStops(stops); + } else { + // 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); + if (!colorOK) + style->addResolve(offset); + 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); + } + + + if (!viewBoxStr.isEmpty()) { + QStringList lst = viewBoxStr.split(QLatin1Char(' '), QString::SkipEmptyParts); + if (lst.count() != 4) + lst = viewBoxStr.split(QLatin1Char(','), QString::SkipEmptyParts); + QString xStr = lst.at(0).trimmed(); + QString yStr = lst.at(1).trimmed(); + QString widthStr = lst.at(2).trimmed(); + QString heightStr = lst.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)->insertLineBreak(); + 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); + + //### not to pixels but to the default coordinate system + // and text should be already in the correct coordinate + // system here + //nx = convertToPixels(nx, true, type); + //ny = convertToPixels(ny, true, type); + + 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 bool parseTitleNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *) +{ + Q_UNUSED(parent); Q_UNUSED(attributes); + return true; +} + +static bool parseTspanNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *handler) +{ + + cssStyleLookup(parent, handler, handler->selector()); + return parseDefaultTextStyle(parent, attributes, false, handler); +} + +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; + 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; + if (ref == QLatin1String("span")) return parseTspanNode; + 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_defaultCoords = LT_PX; + m_defaultPen = QPen(Qt::black, 1, Qt::NoPen, 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. + startElement(xml->name().toString(), xml->attributes()); + 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: + ; + } + } +} + +bool QSvgHandler::startElement(const QString &localName, + const QXmlStreamAttributes &attributes) +{ + QSvgNode *node = 0; + + if (m_colorTagCount.count()) { + int top = m_colorTagCount.pop(); + ++top; + m_colorTagCount.push(top); + } + + /* 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(QSvgText::Default); + } + 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 (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; + default: + Q_ASSERT(!"not a grouping element is the parent"); + } + + parseCoreNode(node, attributes); + cssStyleLookup(node, this, m_selector); + if (node->type() != QSvgNode::TEXT && node->type() != QSvgNode::TEXTAREA) + parseStyle(node, attributes, this); + else + parseDefaultTextStyle(node, attributes, true, this); + } + } 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), true); + } else { + qWarning("Couldn't 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(); + + if (m_colorTagCount.count()) { + int top = m_colorTagCount.pop(); + --top; + if (!top) { + m_colorStack.pop(); + } else { + m_colorTagCount.push(top); + } + } + + if (node == Unknown) { + return true; + } + + if (m_inStyle && localName == QLatin1String("style")) { + m_inStyle = false; + } else if (m_nodes.top()->type() == QSvgNode::TEXT || m_nodes.top()->type() == QSvgNode::TEXTAREA) { + QSvgText *node = static_cast<QSvgText*>(m_nodes.top()); + if (localName == QLatin1String("tspan")) + node->popFormat(); + } + + if (node == Graphics) + m_nodes.pop(); + else if (m_style && !m_skipNodes.isEmpty() && m_skipNodes.top() != Style) + m_style = 0; + + return true; +} + +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) + return true; + + if (m_nodes.top()->type() == QSvgNode::TEXT || m_nodes.top()->type() == QSvgNode::TEXTAREA) { + QSvgText *node = static_cast<QSvgText*>(m_nodes.top()); + node->insertText(str.toString(), m_whitespaceMode.top()); + } + + 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); +} + +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..c4c2f3c --- /dev/null +++ b/src/svg/qsvghandler_p.h @@ -0,0 +1,185 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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); + 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(); + + 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..f1c1b38 --- /dev/null +++ b/src/svg/qsvgnode.cpp @@ -0,0 +1,330 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsvgnode_p.h" +#include "qsvgtinydocument_p.h" + +#ifndef QT_NO_SVG + +#include "qdebug.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, + bool justLink) +{ + //qDebug()<<"appending "<<prop->type()<< " ("<< id <<") "<<"to "<<this<<this->type(); + if (!justLink) { + 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); + break; + case QSvgStyleProperty::GRADIENT: + m_style.gradient = static_cast<QSvgGradientStyle*>(prop); + 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; + } + } + if (!id.isEmpty()) { + m_styles.insert(id, prop); + } +} + +void QSvgNode::applyStyle(QPainter *p, QSvgExtraStates &states) +{ + m_style.apply(p, bounds(), this, states); +} + +void QSvgNode::revertStyle(QPainter *p, QSvgExtraStates &states) +{ + 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; +} + +QSvgStyleProperty * QSvgNode::styleProperty(const QString &id) const +{ + QString rid = id; + if (rid.startsWith(QLatin1Char('#'))) + rid.remove(0, 1); + const QSvgNode *node = this; + while (node) { + QSvgStyleProperty *style = node->m_styles[rid]; + if (style) + return style; + node = node->parent(); + } + + return 0; +} + +QRectF QSvgNode::bounds() const +{ + return QRectF(0, 0, 0, 0); +} + +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(const QTransform &transform) const +{ + QTransform t = transform; + + QSvgTransformStyle *transStyle = m_style.transform; + if (transStyle) { + t = transStyle->qtransform() * t; + } + + QRectF rect = bounds(); + + rect = t.mapRect(rect); + + 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() const +{ + QSvgStrokeStyle *stroke = static_cast<QSvgStrokeStyle*>( + styleProperty(QSvgStyleProperty::STROKE)); + if (!stroke || stroke->qpen().style() == Qt::NoPen) + return 0; + return stroke->qpen().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..708b4c9 --- /dev/null +++ b/src/svg/qsvgnode_p.h @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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, + 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, + bool justLink=false); + void applyStyle(QPainter *p, QSvgExtraStates &states); + void revertStyle(QPainter *p, QSvgExtraStates &states); + QSvgStyleProperty *styleProperty(QSvgStyleProperty::Type type) const; + QSvgStyleProperty *styleProperty(const QString &id) const; + + QSvgTinyDocument *document() const; + + virtual Type type() const =0; + virtual QRectF bounds() const; + virtual QRectF transformedBounds(const QTransform &transform) 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: + QSvgStyle m_style; + + qreal strokeWidth() const; +private: + QSvgNode *m_parent; + QHash<QString, QSvgRefCounter<QSvgStyleProperty> > m_styles; + + 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; + + 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..d33b70c --- /dev/null +++ b/src/svg/qsvgrenderer.cpp @@ -0,0 +1,501 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 multimedia + + \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..43d0cd4 --- /dev/null +++ b/src/svg/qsvgrenderer.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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..3551fec --- /dev/null +++ b/src/svg/qsvgstructure.cpp @@ -0,0 +1,424 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsvgstructure_p.h" + +#ifndef QT_NO_SVG + +#include "qsvgnode_p.h" +#include "qsvgstyle_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); + + if (displayMode() != QSvgNode::NoneMode) { + while (itr != m_renderers.end()) { + QSvgNode *node = *itr; + if (node->isVisible()) + 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 +{ + const QSvgStructureNode *group = this; + while (group && group->type() != QSvgNode::DOC) { + group = static_cast<QSvgStructureNode*>(group->parent()); + } + if (group) + return group->m_scope[id]; + return 0; +} + +void QSvgStructureNode::addChild(QSvgNode *child, const QString &id, bool def) +{ + if (!def) + m_renderers.append(child); + + if (child->type() == QSvgNode::DEFS) { + QSvgDefs *defs = + static_cast<QSvgDefs*>(child); + m_linkedScopes.append(defs); + } + + if (id.isEmpty()) + return; //we can't add it to scope without id + + QSvgStructureNode *group = this; + while (group && group->type() != QSvgNode::DOC) { + group = static_cast<QSvgStructureNode*>(group->parent()); + } + if (group) + group->m_scope.insert(id, child); +} + +QSvgDefs::QSvgDefs(QSvgNode *parent) + : QSvgStructureNode(parent) +{ +} + +void QSvgDefs::draw(QPainter *, QSvgExtraStates &) +{ + //noop +} + +QSvgNode::Type QSvgDefs::type() const +{ + return DEFS; +} + +QSvgStyleProperty * QSvgStructureNode::scopeStyle(const QString &id) const +{ + const QSvgStructureNode *group = this; + while (group) { + QSvgStyleProperty *prop = group->styleProperty(id); + if (prop) + return prop; + QList<QSvgStructureNode*>::const_iterator itr = group->m_linkedScopes.constBegin(); + while (itr != group->m_linkedScopes.constEnd()) { + prop = (*itr)->styleProperty(id); + if (prop) + return prop; + ++itr; + } + group = static_cast<QSvgStructureNode*>(group->parent()); + } + return 0; +} + + +/* + 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); + + if (displayMode() != QSvgNode::NoneMode) { + while (itr != m_renderers.end()) { + QSvgNode *node = *itr; + if (node->isVisible()) { + 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() const +{ + if (m_bounds.isEmpty()) { + foreach(QSvgNode *node, m_renderers) { + m_bounds |= node->transformedBounds(QTransform()); + } + } + + return m_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..7cfd280 --- /dev/null +++ b/src/svg/qsvgstructure_p.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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; + QSvgStyleProperty *scopeStyle(const QString &id) const; + void addChild(QSvgNode *child, const QString &id, bool def = false); + virtual QRectF bounds() 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; + mutable QRectF m_bounds; +}; + +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..389f68f --- /dev/null +++ b/src/svg/qsvgstyle.cpp @@ -0,0 +1,820 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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) +{ +} + +QSvgStyleProperty::~QSvgStyleProperty() +{ +} + +QSvgQualityStyle::QSvgQualityStyle(int color) + : m_colorRendering(color) +{ + +} +void QSvgQualityStyle::apply(QPainter *, const QRectF &, QSvgNode *, QSvgExtraStates &) +{ + +} +void QSvgQualityStyle::revert(QPainter *, QSvgExtraStates &) +{ + +} + +QSvgFillStyle::QSvgFillStyle(const QBrush &brush) + : m_fill(brush), m_style(0), m_fillRuleSet(false), m_fillOpacitySet(false) +{ +} + +QSvgFillStyle::QSvgFillStyle(QSvgStyleProperty *style) + : m_style(style), m_fillRuleSet(false), m_fillOpacitySet(false) +{ +} + +void QSvgFillStyle::setFillRule(Qt::FillRule f) +{ + m_fillRuleSet = true; + m_fillRule = f; +} + +void QSvgFillStyle::setFillOpacity(qreal opacity) +{ + m_fillOpacitySet = true; + m_fillOpacity = opacity; +} + +static void recursivelySetFill(QSvgNode *node, Qt::FillRule f) +{ + if (node->type() == QSvgNode::PATH) { + QSvgPath *path = static_cast<QSvgPath*>(node); + path->qpath()->setFillRule(f); + } else if (node->type() == QSvgNode::G) { + QList<QSvgNode*> renderers = static_cast<QSvgG*>(node)->renderers(); + foreach(QSvgNode *n, renderers) { + recursivelySetFill(n, f); + } + } +} +void QSvgFillStyle::apply(QPainter *p, const QRectF &rect, QSvgNode *node, QSvgExtraStates &states) +{ + m_oldFill = p->brush(); + m_oldOpacity = states.fillOpacity; + + if (m_fillRuleSet) { + recursivelySetFill(node, m_fillRule); + m_fillRuleSet = false;//set it only on the first run + } + p->setBrush(m_fill); + if (m_fillOpacitySet) + states.fillOpacity = m_fillOpacity; + if (m_style) + m_style->apply(p, rect, node, states); +} + +void QSvgFillStyle::revert(QPainter *p, QSvgExtraStates &states) +{ + if (m_style) + m_style->revert(p, states); + p->setBrush(m_oldFill); + if (m_fillOpacitySet) + states.fillOpacity = m_oldOpacity; +} + +QSvgViewportFillStyle::QSvgViewportFillStyle(const QBrush &brush) + : m_viewportFill(brush) +{ +} + +void QSvgViewportFillStyle::apply(QPainter *p, const QRectF &, 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_font(font), m_pointSize(24), m_doc(doc) +{ +} + +QSvgFontStyle::QSvgFontStyle(const QFont &font, QSvgTinyDocument *doc) + : m_font(0), m_pointSize(24), m_doc(doc), m_qfont(font) +{ +} + + +void QSvgFontStyle::setPointSize(qreal size) +{ + m_pointSize = size; +} + +qreal QSvgFontStyle::pointSize() const +{ + return m_pointSize; +} + +void QSvgFontStyle::apply(QPainter *p, const QRectF &, QSvgNode *, QSvgExtraStates &) +{ + if (!m_font) { + m_oldFont = p->font(); + p->setFont(m_qfont); + } +} + +void QSvgFontStyle::revert(QPainter *p, QSvgExtraStates &) +{ + if (!m_font) { + p->setFont(m_oldFont); + } +} + +QSvgStrokeStyle::QSvgStrokeStyle(const QPen &pen) + : m_stroke(pen) +{ +} + +void QSvgStrokeStyle::apply(QPainter *p, const QRectF &, QSvgNode *, QSvgExtraStates &) +{ + m_oldStroke = p->pen(); + p->setPen(m_stroke); +} + +void QSvgStrokeStyle::revert(QPainter *p, QSvgExtraStates &) +{ + p->setPen(m_oldStroke); +} + +QSvgSolidColorStyle::QSvgSolidColorStyle(const QColor &color) + : m_solidColor(color) +{ +} + +void QSvgSolidColorStyle::apply(QPainter *p, const QRectF &, QSvgNode *, QSvgExtraStates &) +{ + m_oldFill = p->brush(); + m_oldStroke = p->pen(); + QBrush b = m_oldFill; + b.setColor(m_solidColor); + p->setBrush(b); + QPen pen = m_oldStroke; + pen.setColor(m_solidColor); + p->setPen(pen); +} + +void QSvgSolidColorStyle::revert(QPainter *p, QSvgExtraStates &) +{ + p->setBrush(m_oldFill); + p->setPen(m_oldStroke); +} + +QSvgGradientStyle::QSvgGradientStyle(QGradient *grad) + : m_gradient(grad) +{ +} + +void QSvgGradientStyle::apply(QPainter *p, const QRectF &/*rect*/, QSvgNode *, QSvgExtraStates &) +{ + if (!m_link.isEmpty()) { + resolveStops(); + } + + m_oldFill = p->brush(); + + //resolving stop colors + if (!m_resolvePoints.isEmpty()) { + QColor color = p->brush().color(); + if (!color.isValid()) + color = p->pen().color(); + QList<qreal>::const_iterator itr = m_resolvePoints.constBegin(); + for (; itr != m_resolvePoints.constEnd(); ++itr) { + //qDebug()<<"resolving "<<(*itr)<<" to "<<color; + m_gradient->setColorAt(*itr, color); + } + } + + // If the gradient is marked as empty, insert transparent black + QGradientStops stops = m_gradient->stops(); + if (stops.size() == 1 && qIsNaN(stops.at(0).first)) + m_gradient->setStops(QGradientStops() << QGradientStop(0.0, QColor(0, 0, 0, 0))); + + QGradient gradient = *m_gradient; + + QBrush brush; + brush = QBrush(gradient); + + if (!m_matrix.isIdentity()) + brush.setMatrix(m_matrix); + + p->setBrush(brush); +} + +void QSvgGradientStyle::revert(QPainter *p, QSvgExtraStates &) +{ + p->setBrush(m_oldFill); +} + + +void QSvgGradientStyle::setMatrix(const QMatrix &mat) +{ + m_matrix = mat; +} + +void QSvgGradientStyle::addResolve(qreal offset) +{ + m_resolvePoints.append(offset); +} + +QSvgTransformStyle::QSvgTransformStyle(const QTransform &trans) + : m_transform(trans) +{ +} + +void QSvgTransformStyle::apply(QPainter *p, const QRectF &, 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 QRectF &, 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 QRectF &rect, QSvgNode *node, QSvgExtraStates &states) +{ + if (quality) { + quality->apply(p, rect, node, states); + } + + if (fill) { + fill->apply(p, rect, node, states); + } + + if (viewportFill) { + viewportFill->apply(p, rect, node, states); + } + + if (font) { + font->apply(p, rect, node, states); + } + + if (stroke) { + stroke->apply(p, rect, node, states); + } + + if (solidColor) { + solidColor->apply(p, rect, node, states); + } + + if (gradient) { + gradient->apply(p, rect, node, states); + } + + if (transform) { + transform->apply(p, rect, node, states); + } + + if (animateColor) { + animateColor->apply(p, rect, node, states); + } + + //animated transforms have to be applied + //_after_ the original object transformations + if (!animateTransforms.isEmpty()) { + QList<QSvgRefCounter<QSvgAnimateTransform> >::const_iterator itr; + for (itr = animateTransforms.constBegin(); itr != animateTransforms.constEnd(); + ++itr) { + (*itr)->apply(p, rect, node, states); + } + } + + if (opacity) { + opacity->apply(p, rect, node, states); + } + + if (compop) { + compop->apply(p, rect, 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); + } + + if (solidColor) { + solidColor->revert(p, states); + } + + if (gradient) { + gradient->revert(p, states); + } + + //animated transforms need to be reverted _before_ + //the native transforms + if (!animateTransforms.isEmpty()) { + QList<QSvgRefCounter<QSvgAnimateTransform> >::const_iterator itr; + itr = animateTransforms.constBegin(); + //only need to rever the first one because that + //one has the original world matrix for the primitve + if (itr != animateTransforms.constEnd()) { + (*itr)->revert(p, states); + } + } + + 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_count(0), m_finished(false) +{ + m_totalRunningTime = m_to - m_from; +} + +void QSvgAnimateTransform::setArgs(TransformType type, const QVector<qreal> &args) +{ + m_type = type; + m_args = args; + Q_ASSERT(!(args.count()%3)); + m_count = args.count() / 3; +} + +void QSvgAnimateTransform::apply(QPainter *p, const QRectF &, QSvgNode *node, QSvgExtraStates &) +{ + m_oldWorldTransform = p->worldTransform(); + resolveMatrix(node); + if (!m_finished || m_freeze) + p->setWorldTransform(m_transform, true); +} + +void QSvgAnimateTransform::revert(QPainter *p, QSvgExtraStates &) +{ + if (!m_finished || m_freeze) { + p->setWorldTransform(m_oldWorldTransform, false /* don't combine */); + } +} + +void QSvgAnimateTransform::resolveMatrix(QSvgNode *node) +{ + static const qreal deg2rad = qreal(0.017453292519943295769); + qreal totalTimeElapsed = node->document()->currentElapsed(); + if (totalTimeElapsed < m_from || m_finished) + return; + + qreal animationFrame = (totalTimeElapsed - m_from) / m_to; + + 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, 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 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, 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 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, 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 transXDiff = (to1-from1) * percentOfAnimation; + qreal transX = from1 + transXDiff; + m_transform = QTransform(); + m_transform.shear(tan(transX * deg2rad), 0); + break; + } + case SkewY: { + 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 transYDiff = (to1 - from1) * percentOfAnimation; + qreal transY = from1 + transYDiff; + m_transform = QTransform(); + m_transform.shear(0, tan(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 QRectF &, QSvgNode *node, QSvgExtraStates &) +{ + qreal totalTimeElapsed = node->document()->currentElapsed(); + if (totalTimeElapsed < m_from || m_finished) + return; + + qreal animationFrame = (totalTimeElapsed - m_from) / m_to; + + 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; +} + +QString QSvgFontStyle::textAnchor() const +{ + return m_textAnchor; +} + +void QSvgFontStyle::setTextAnchor(const QString &anchor) +{ + m_textAnchor = anchor; +} + +QSvgOpacityStyle::QSvgOpacityStyle(qreal opacity) + : m_opacity(opacity) +{ + +} + +void QSvgOpacityStyle::apply(QPainter *p, const QRectF &, 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->scopeStyle(m_link); + if (prop) { + if (prop->type() == QSvgStyleProperty::GRADIENT) { + QSvgGradientStyle *st = + static_cast<QSvgGradientStyle*>(prop); + st->resolveStops(); + m_gradient->setStops(st->qgradient()->stops()); + } + } + 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..ff4058b --- /dev/null +++ b/src/svg/qsvgstyle_p.h @@ -0,0 +1,564 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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; +}; + +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 QRectF &, QSvgNode *node, QSvgExtraStates &states) =0; + virtual void revert(QPainter *p, QSvgExtraStates &states) =0; + virtual Type type() const=0; +}; + +class QSvgQualityStyle : public QSvgStyleProperty +{ +public: + QSvgQualityStyle(int color); + virtual void apply(QPainter *p, const QRectF &, 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 QRectF &, 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(const QBrush &brush); + QSvgFillStyle(QSvgStyleProperty *style); + virtual void apply(QPainter *p, const QRectF &, QSvgNode *node, QSvgExtraStates &states); + virtual void revert(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; + + void setFillRule(Qt::FillRule f); + void setFillOpacity(qreal opacity); + + const QBrush & qbrush() const + { + return m_fill; + } +private: + // fill v v 'inherit' | <Paint.datatype> + // fill-opacity v v 'inherit' | <OpacityValue.datatype> + QBrush m_fill; + QBrush m_oldFill; + QSvgStyleProperty *m_style; + + bool m_fillRuleSet; + Qt::FillRule m_fillRule; + bool m_fillOpacitySet; + qreal m_fillOpacity; + qreal m_oldOpacity; +}; + +class QSvgViewportFillStyle : public QSvgStyleProperty +{ +public: + QSvgViewportFillStyle(const QBrush &brush); + virtual void apply(QPainter *p, const QRectF &, 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: + QSvgFontStyle(QSvgFont *font, QSvgTinyDocument *doc); + QSvgFontStyle(const QFont &font, QSvgTinyDocument *doc); + virtual void apply(QPainter *p, const QRectF &, QSvgNode *node, QSvgExtraStates &states); + virtual void revert(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; + + void setPointSize(qreal size); + qreal pointSize() const; + + //### hack to avoid having a separate style element for text-anchor + QString textAnchor() const; + void setTextAnchor(const QString &anchor); + + QSvgFont * svgFont() const + { + return m_font; + } + QSvgTinyDocument *doc() const + { + return m_doc; + } + + const QFont & qfont() const + { + return m_qfont; + } +private: + QSvgFont *m_font; + qreal m_pointSize; + QSvgTinyDocument *m_doc; + + QString m_textAnchor; + + QFont m_qfont; + QFont m_oldFont; +}; + +class QSvgStrokeStyle : public QSvgStyleProperty +{ +public: + QSvgStrokeStyle(const QPen &pen); + virtual void apply(QPainter *p, const QRectF &, QSvgNode *node, QSvgExtraStates &states); + virtual void revert(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; + + const QPen & qpen() 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; +}; + + +class QSvgSolidColorStyle : public QSvgStyleProperty +{ +public: + QSvgSolidColorStyle(const QColor &color); + virtual void apply(QPainter *p, const QRectF &, QSvgNode *node, QSvgExtraStates &states); + virtual void revert(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; + + const QColor & qcolor() const + { + 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 QSvgStyleProperty +{ +public: + QSvgGradientStyle(QGradient *grad); + ~QSvgGradientStyle() { delete m_gradient; } + virtual void apply(QPainter *p, const QRectF &, QSvgNode *node, QSvgExtraStates &states); + virtual void revert(QPainter *p, QSvgExtraStates &states); + 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; + } + + void addResolve(qreal offset); +private: + QGradient *m_gradient; + QList<qreal> m_resolvePoints; + + QBrush m_oldFill; + + QMatrix m_matrix; + + QSvgTinyDocument *m_doc; + QString m_link; +}; + +class QSvgTransformStyle : public QSvgStyleProperty +{ +public: + QSvgTransformStyle(const QTransform &transform); + virtual void apply(QPainter *p, const QRectF &, 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 + }; +public: + QSvgAnimateTransform(int startMs, int endMs, int by = 0); + void setArgs(TransformType type, const QVector<qreal> &args); + void setFreeze(bool freeze); + void setRepeatCount(qreal repeatCount); + virtual void apply(QPainter *p, const QRectF &, QSvgNode *node, QSvgExtraStates &states); + virtual void revert(QPainter *p, QSvgExtraStates &states); + virtual Type type() const; +protected: + void resolveMatrix(QSvgNode *node); +private: + qreal m_from, m_to, m_by; + qreal m_totalRunningTime; + TransformType m_type; + QVector<qreal> m_args; + int m_count; + QTransform m_transform; + QTransform m_oldWorldTransform; + bool m_finished; + bool m_freeze; + qreal m_repeatCount; +}; + + +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 QRectF &, 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 QRectF &, 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 QRectF &rect, 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..b6b8526 --- /dev/null +++ b/src/svg/qsvgtinydocument.cpp @@ -0,0 +1,459 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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_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(); + } + + p->save(); + + //sets default style on the painter + //### not the most optimal way + mapSourceToTarget(p, bounds); + p->setPen(Qt::NoPen); + 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->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; + } + + p->save(); + + const QRectF elementBounds = node->transformedBounds(QTransform()); + + mapSourceToTarget(p, bounds, elementBounds); + QTransform originalTransform = p->worldTransform(); + + //XXX set default style on the painter + p->setPen(Qt::NoPen); + 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::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(QTransform()); +} + +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..0d54804 --- /dev/null +++ b/src/svg/qsvgtinydocument_p.h @@ -0,0 +1,195 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 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; + + 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(QTransform()); + } + + 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..a4200ca --- /dev/null +++ b/src/svg/qsvgwidget.cpp @@ -0,0 +1,183 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 multimedia + + \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: + QSvgWidgetPrivate() + : QWidgetPrivate() + { + Q_Q(QSvgWidget); + renderer = new QSvgRenderer(q); + } + QSvgWidgetPrivate(const QString &file) + : QWidgetPrivate() + { + Q_Q(QSvgWidget); + renderer = new QSvgRenderer(file, q); + } + QSvgRenderer *renderer; +}; + +/*! + Constructs a new SVG display widget with the given \a parent. +*/ +QSvgWidget::QSvgWidget(QWidget *parent) + : QWidget(*new QSvgWidgetPrivate, parent, 0) +{ + 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(file), parent, 0) +{ + 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..75eef32 --- /dev/null +++ b/src/svg/qsvgwidget.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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..662940a --- /dev/null +++ b/src/svg/svg.pro @@ -0,0 +1,50 @@ +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: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 + +#zlib support +contains(QT_CONFIG, zlib) { + INCLUDEPATH += ../3rdparty/zlib +} else:!contains(QT_CONFIG, no-zlib) { + unix:LIBS += -lz +} |