1/****************************************************************************
2**
3** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
4** Contact: http://www.qt-project.org/legal
5**
6** This file is part of the QtSvg module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and Digia. For licensing terms and
14** conditions see http://qt.digia.com/licensing. For further information
15** use the contact form at http://qt.digia.com/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 2.1 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 2.1 requirements
23** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24**
25** In addition, as a special exception, Digia gives you certain additional
26** rights. These rights are described in the Digia Qt LGPL Exception
27** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28**
29** GNU General Public License Usage
30** Alternatively, this file may be used under the terms of the GNU
31** General Public License version 3.0 as published by the Free Software
32** Foundation and appearing in the file LICENSE.GPL included in the
33** packaging of this file. Please review the following information to
34** ensure the GNU General Public License version 3.0 requirements will be
35** met: http://www.gnu.org/copyleft/gpl.html.
36**
37**
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qsvgtinydocument_p.h"
43
44#ifndef QT_NO_SVG
45
46#include "qsvghandler_p.h"
47#include "qsvgfont_p.h"
48
49#include "qpainter.h"
50#include "qfile.h"
51#include "qbuffer.h"
52#include "qbytearray.h"
53#include "qqueue.h"
54#include "qstack.h"
55#include "qdebug.h"
56
57#ifndef QT_NO_COMPRESS
58#include <zlib.h>
59#endif
60
61QT_BEGIN_NAMESPACE
62
63QSvgTinyDocument::QSvgTinyDocument()
64 : QSvgStructureNode(0)
65 , m_widthPercent(false)
66 , m_heightPercent(false)
67 , m_animated(false)
68 , m_animationDuration(0)
69 , m_fps(30)
70{
71}
72
73QSvgTinyDocument::~QSvgTinyDocument()
74{
75}
76
77#ifndef QT_NO_COMPRESS
78# ifdef QT_BUILD_INTERNAL
79Q_AUTOTEST_EXPORT QByteArray qt_inflateGZipDataFrom(QIODevice *device);
80# else
81static QByteArray qt_inflateGZipDataFrom(QIODevice *device);
82# endif
83
84QByteArray qt_inflateGZipDataFrom(QIODevice *device)
85{
86 if (!device)
87 return QByteArray();
88
89 if (!device->isOpen())
90 device->open(QIODevice::ReadOnly);
91
92 Q_ASSERT(device->isOpen() && device->isReadable());
93
94 static const int CHUNK_SIZE = 4096;
95 int zlibResult = Z_OK;
96
97 QByteArray source;
98 QByteArray destination;
99
100 // Initialize zlib stream struct
101 z_stream zlibStream;
102 zlibStream.next_in = Z_NULL;
103 zlibStream.avail_in = 0;
104 zlibStream.avail_out = 0;
105 zlibStream.zalloc = Z_NULL;
106 zlibStream.zfree = Z_NULL;
107 zlibStream.opaque = Z_NULL;
108
109 // Adding 16 to the window size gives us gzip decoding
110 if (inflateInit2(&zlibStream, MAX_WBITS + 16) != Z_OK) {
111 qWarning("Cannot initialize zlib, because: %s",
112 (zlibStream.msg != NULL ? zlibStream.msg : "Unknown error"));
113 return QByteArray();
114 }
115
116 bool stillMoreWorkToDo = true;
117 while (stillMoreWorkToDo) {
118
119 if (!zlibStream.avail_in) {
120 source = device->read(CHUNK_SIZE);
121
122 if (source.isEmpty())
123 break;
124
125 zlibStream.avail_in = source.size();
126 zlibStream.next_in = reinterpret_cast<Bytef*>(source.data());
127 }
128
129 do {
130 // Prepare the destination buffer
131 int oldSize = destination.size();
132 destination.resize(oldSize + CHUNK_SIZE);
133 zlibStream.next_out = reinterpret_cast<Bytef*>(
134 destination.data() + oldSize - zlibStream.avail_out);
135 zlibStream.avail_out += CHUNK_SIZE;
136
137 zlibResult = inflate(&zlibStream, Z_NO_FLUSH);
138 switch (zlibResult) {
139 case Z_NEED_DICT:
140 case Z_DATA_ERROR:
141 case Z_STREAM_ERROR:
142 case Z_MEM_ERROR: {
143 inflateEnd(&zlibStream);
144 qWarning("Error while inflating gzip file: %s",
145 (zlibStream.msg != NULL ? zlibStream.msg : "Unknown error"));
146 destination.chop(zlibStream.avail_out);
147 return destination;
148 }
149 }
150
151 // If the output buffer still has more room after calling inflate
152 // it means we have to provide more data, so exit the loop here
153 } while (!zlibStream.avail_out);
154
155 if (zlibResult == Z_STREAM_END) {
156 // Make sure there are no more members to process before exiting
157 if (!(zlibStream.avail_in && inflateReset(&zlibStream) == Z_OK))
158 stillMoreWorkToDo = false;
159 }
160 }
161
162 // Chop off trailing space in the buffer
163 destination.chop(zlibStream.avail_out);
164
165 inflateEnd(&zlibStream);
166 return destination;
167}
168#endif
169
170QSvgTinyDocument * QSvgTinyDocument::load(const QString &fileName)
171{
172 QFile file(fileName);
173 if (!file.open(QFile::ReadOnly)) {
174 qWarning("Cannot open file '%s', because: %s",
175 qPrintable(fileName), qPrintable(file.errorString()));
176 return 0;
177 }
178
179#ifndef QT_NO_COMPRESS
180 if (fileName.endsWith(QLatin1String(".svgz"), Qt::CaseInsensitive)
181 || fileName.endsWith(QLatin1String(".svg.gz"), Qt::CaseInsensitive)) {
182 return load(qt_inflateGZipDataFrom(&file));
183 }
184#endif
185
186 QSvgTinyDocument *doc = 0;
187 QSvgHandler handler(&file);
188 if (handler.ok()) {
189 doc = handler.document();
190 doc->m_animationDuration = handler.animationDuration();
191 } else {
192 qWarning("Cannot read file '%s', because: %s (line %d)",
193 qPrintable(fileName), qPrintable(handler.errorString()), handler.lineNumber());
194 }
195 return doc;
196}
197
198QSvgTinyDocument * QSvgTinyDocument::load(const QByteArray &contents)
199{
200#ifndef QT_NO_COMPRESS
201 // Check for gzip magic number and inflate if appropriate
202 if (contents.startsWith("\x1f\x8b")) {
203 QBuffer buffer(const_cast<QByteArray *>(&contents));
204 return load(qt_inflateGZipDataFrom(&buffer));
205 }
206#endif
207
208 QSvgHandler handler(contents);
209
210 QSvgTinyDocument *doc = 0;
211 if (handler.ok()) {
212 doc = handler.document();
213 doc->m_animationDuration = handler.animationDuration();
214 }
215 return doc;
216}
217
218QSvgTinyDocument * QSvgTinyDocument::load(QXmlStreamReader *contents)
219{
220 QSvgHandler handler(contents);
221
222 QSvgTinyDocument *doc = 0;
223 if (handler.ok()) {
224 doc = handler.document();
225 doc->m_animationDuration = handler.animationDuration();
226 }
227 return doc;
228}
229
230void QSvgTinyDocument::draw(QPainter *p, const QRectF &bounds)
231{
232 if (m_time.isNull()) {
233 m_time.start();
234 }
235
236 if (displayMode() == QSvgNode::NoneMode)
237 return;
238
239 p->save();
240 //sets default style on the painter
241 //### not the most optimal way
242 mapSourceToTarget(p, bounds);
243 QPen pen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin);
244 pen.setMiterLimit(4);
245 p->setPen(pen);
246 p->setBrush(Qt::black);
247 p->setRenderHint(QPainter::Antialiasing);
248 p->setRenderHint(QPainter::SmoothPixmapTransform);
249 QList<QSvgNode*>::iterator itr = m_renderers.begin();
250 applyStyle(p, m_states);
251 while (itr != m_renderers.end()) {
252 QSvgNode *node = *itr;
253 if ((node->isVisible()) && (node->displayMode() != QSvgNode::NoneMode))
254 node->draw(p, m_states);
255 ++itr;
256 }
257 revertStyle(p, m_states);
258 p->restore();
259}
260
261
262void QSvgTinyDocument::draw(QPainter *p, const QString &id,
263 const QRectF &bounds)
264{
265 QSvgNode *node = scopeNode(id);
266
267 if (!node) {
268 qDebug("Couldn't find node %s. Skipping rendering.", qPrintable(id));
269 return;
270 }
271 if (m_time.isNull()) {
272 m_time.start();
273 }
274
275 if (node->displayMode() == QSvgNode::NoneMode)
276 return;
277
278 p->save();
279
280 const QRectF elementBounds = node->transformedBounds();
281
282 mapSourceToTarget(p, bounds, elementBounds);
283 QTransform originalTransform = p->worldTransform();
284
285 //XXX set default style on the painter
286 QPen pen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin);
287 pen.setMiterLimit(4);
288 p->setPen(pen);
289 p->setBrush(Qt::black);
290 p->setRenderHint(QPainter::Antialiasing);
291 p->setRenderHint(QPainter::SmoothPixmapTransform);
292
293 QStack<QSvgNode*> parentApplyStack;
294 QSvgNode *parent = node->parent();
295 while (parent) {
296 parentApplyStack.push(parent);
297 parent = parent->parent();
298 }
299
300 for (int i = parentApplyStack.size() - 1; i >= 0; --i)
301 parentApplyStack[i]->applyStyle(p, m_states);
302
303 // Reset the world transform so that our parents don't affect
304 // the position
305 QTransform currentTransform = p->worldTransform();
306 p->setWorldTransform(originalTransform);
307
308 node->draw(p, m_states);
309
310 p->setWorldTransform(currentTransform);
311
312 for (int i = 0; i < parentApplyStack.size(); ++i)
313 parentApplyStack[i]->revertStyle(p, m_states);
314
315 //p->fillRect(bounds.adjusted(-5, -5, 5, 5), QColor(0, 0, 255, 100));
316
317 p->restore();
318}
319
320
321QSvgNode::Type QSvgTinyDocument::type() const
322{
323 return DOC;
324}
325
326void QSvgTinyDocument::setWidth(int len, bool percent)
327{
328 m_size.setWidth(len);
329 m_widthPercent = percent;
330}
331
332void QSvgTinyDocument::setHeight(int len, bool percent)
333{
334 m_size.setHeight(len);
335 m_heightPercent = percent;
336}
337
338void QSvgTinyDocument::setViewBox(const QRectF &rect)
339{
340 m_viewBox = rect;
341}
342
343void QSvgTinyDocument::addSvgFont(QSvgFont *font)
344{
345 m_fonts.insert(font->familyName(), font);
346}
347
348QSvgFont * QSvgTinyDocument::svgFont(const QString &family) const
349{
350 return m_fonts[family];
351}
352
353void QSvgTinyDocument::addNamedNode(const QString &id, QSvgNode *node)
354{
355 m_namedNodes.insert(id, node);
356}
357
358QSvgNode *QSvgTinyDocument::namedNode(const QString &id) const
359{
360 return m_namedNodes.value(id);
361}
362
363void QSvgTinyDocument::addNamedStyle(const QString &id, QSvgFillStyleProperty *style)
364{
365 m_namedStyles.insert(id, style);
366}
367
368QSvgFillStyleProperty *QSvgTinyDocument::namedStyle(const QString &id) const
369{
370 return m_namedStyles.value(id);
371}
372
373void QSvgTinyDocument::restartAnimation()
374{
375 m_time.restart();
376}
377
378bool QSvgTinyDocument::animated() const
379{
380 return m_animated;
381}
382
383void QSvgTinyDocument::setAnimated(bool a)
384{
385 m_animated = a;
386}
387
388void QSvgTinyDocument::draw(QPainter *p)
389{
390 draw(p, QRectF());
391}
392
393void QSvgTinyDocument::draw(QPainter *p, QSvgExtraStates &)
394{
395 draw(p);
396}
397
398void QSvgTinyDocument::mapSourceToTarget(QPainter *p, const QRectF &targetRect, const QRectF &sourceRect)
399{
400 QRectF target = targetRect;
401 if (target.isNull()) {
402 QPaintDevice *dev = p->device();
403 QRectF deviceRect(0, 0, dev->width(), dev->height());
404 if (deviceRect.isNull()) {
405 if (sourceRect.isNull())
406 target = QRectF(QPointF(0, 0), size());
407 else
408 target = QRectF(QPointF(0, 0), sourceRect.size());
409 } else {
410 target = deviceRect;
411 }
412 }
413
414 QRectF source = sourceRect;
415 if (source.isNull())
416 source = viewBox();
417
418 if (source != target && !source.isNull()) {
419 QTransform transform;
420 transform.scale(target.width() / source.width(),
421 target.height() / source.height());
422 QRectF c2 = transform.mapRect(source);
423 p->translate(target.x() - c2.x(),
424 target.y() - c2.y());
425 p->scale(target.width() / source.width(),
426 target.height() / source.height());
427 }
428}
429
430QRectF QSvgTinyDocument::boundsOnElement(const QString &id) const
431{
432 const QSvgNode *node = scopeNode(id);
433 if (!node)
434 node = this;
435 return node->transformedBounds();
436}
437
438bool QSvgTinyDocument::elementExists(const QString &id) const
439{
440 QSvgNode *node = scopeNode(id);
441
442 return (node!=0);
443}
444
445QMatrix QSvgTinyDocument::matrixForElement(const QString &id) const
446{
447 QSvgNode *node = scopeNode(id);
448
449 if (!node) {
450 qDebug("Couldn't find node %s. Skipping rendering.", qPrintable(id));
451 return QMatrix();
452 }
453
454 QTransform t;
455
456 node = node->parent();
457 while (node) {
458 if (node->m_style.transform)
459 t *= node->m_style.transform->qtransform();
460 node = node->parent();
461 }
462
463 return t.toAffine();
464}
465
466int QSvgTinyDocument::currentFrame() const
467{
468 double runningPercentage = qMin(m_time.elapsed()/double(m_animationDuration), 1.);
469
470 int totalFrames = m_fps * m_animationDuration;
471
472 return int(runningPercentage * totalFrames);
473}
474
475void QSvgTinyDocument::setCurrentFrame(int frame)
476{
477 int totalFrames = m_fps * m_animationDuration;
478 double framePercentage = frame/double(totalFrames);
479 double timeForFrame = m_animationDuration * framePercentage; //in S
480 timeForFrame *= 1000; //in ms
481 int timeToAdd = int(timeForFrame - m_time.elapsed());
482 m_time = m_time.addMSecs(timeToAdd);
483}
484
485void QSvgTinyDocument::setFramesPerSecond(int num)
486{
487 m_fps = num;
488}
489
490QT_END_NAMESPACE
491
492#endif // QT_NO_SVG
493