1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt SVG 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 The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qsvgtinydocument_p.h"
41
42#include "qsvghandler_p.h"
43#include "qsvgfont_p.h"
44
45#include "qpainter.h"
46#include "qfile.h"
47#include "qbuffer.h"
48#include "qbytearray.h"
49#include "qqueue.h"
50#include "qstack.h"
51#include "qtransform.h"
52#include "qdebug.h"
53
54#ifndef QT_NO_COMPRESS
55#include <zlib.h>
56#endif
57
58QT_BEGIN_NAMESPACE
59
60QSvgTinyDocument::QSvgTinyDocument()
61 : QSvgStructureNode(0)
62 , m_widthPercent(false)
63 , m_heightPercent(false)
64 , m_time(0)
65 , m_animated(false)
66 , m_animationDuration(0)
67 , m_fps(30)
68{
69}
70
71QSvgTinyDocument::~QSvgTinyDocument()
72{
73}
74
75#ifndef QT_NO_COMPRESS
76static QByteArray qt_inflateSvgzDataFrom(QIODevice *device, bool doCheckContent = true);
77# ifdef QT_BUILD_INTERNAL
78Q_AUTOTEST_EXPORT QByteArray qt_inflateGZipDataFrom(QIODevice *device)
79{
80 return qt_inflateSvgzDataFrom(device, doCheckContent: false); // autotest wants unchecked result
81}
82# endif
83
84static QByteArray qt_inflateSvgzDataFrom(QIODevice *device, bool doCheckContent)
85{
86 if (!device)
87 return QByteArray();
88
89 if (!device->isOpen())
90 device->open(mode: 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 qCWarning(lcSvgHandler, "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(maxlen: 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 if (oldSize > INT_MAX - CHUNK_SIZE) {
133 inflateEnd(strm: &zlibStream);
134 qCWarning(lcSvgHandler, "Error while inflating gzip file: integer size overflow");
135 return QByteArray();
136 }
137
138 destination.resize(size: oldSize + CHUNK_SIZE);
139 zlibStream.next_out = reinterpret_cast<Bytef*>(
140 destination.data() + oldSize - zlibStream.avail_out);
141 zlibStream.avail_out += CHUNK_SIZE;
142
143 zlibResult = inflate(strm: &zlibStream, Z_NO_FLUSH);
144 switch (zlibResult) {
145 case Z_NEED_DICT:
146 case Z_DATA_ERROR:
147 case Z_STREAM_ERROR:
148 case Z_MEM_ERROR: {
149 inflateEnd(strm: &zlibStream);
150 qCWarning(lcSvgHandler, "Error while inflating gzip file: %s",
151 (zlibStream.msg != NULL ? zlibStream.msg : "Unknown error"));
152 return QByteArray();
153 }
154 }
155
156 // If the output buffer still has more room after calling inflate
157 // it means we have to provide more data, so exit the loop here
158 } while (!zlibStream.avail_out);
159
160 if (doCheckContent) {
161 // Quick format check, equivalent to QSvgIOHandler::canRead()
162 QByteArray buf = destination.left(len: 16);
163 if (!buf.contains(c: "<?xml") && !buf.contains(c: "<svg") && !buf.contains(c: "<!--") && !buf.contains(c: "<!DOCTYPE svg")) {
164 inflateEnd(strm: &zlibStream);
165 qCWarning(lcSvgHandler, "Error while inflating gzip file: SVG format check failed");
166 return QByteArray();
167 }
168 doCheckContent = false; // Run only once, on first chunk
169 }
170
171 if (zlibResult == Z_STREAM_END) {
172 // Make sure there are no more members to process before exiting
173 if (!(zlibStream.avail_in && inflateReset(strm: &zlibStream) == Z_OK))
174 stillMoreWorkToDo = false;
175 }
176 }
177
178 // Chop off trailing space in the buffer
179 destination.chop(n: zlibStream.avail_out);
180
181 inflateEnd(strm: &zlibStream);
182 return destination;
183}
184#else
185static QByteArray qt_inflateSvgzDataFrom(QIODevice *)
186{
187 return QByteArray();
188}
189#endif
190
191QSvgTinyDocument * QSvgTinyDocument::load(const QString &fileName)
192{
193 QFile file(fileName);
194 if (!file.open(flags: QFile::ReadOnly)) {
195 qCWarning(lcSvgHandler, "Cannot open file '%s', because: %s",
196 qPrintable(fileName), qPrintable(file.errorString()));
197 return 0;
198 }
199
200 if (fileName.endsWith(s: QLatin1String(".svgz"), cs: Qt::CaseInsensitive)
201 || fileName.endsWith(s: QLatin1String(".svg.gz"), cs: Qt::CaseInsensitive)) {
202 return load(contents: qt_inflateSvgzDataFrom(device: &file));
203 }
204
205 QSvgTinyDocument *doc = 0;
206 QSvgHandler handler(&file);
207 if (handler.ok()) {
208 doc = handler.document();
209 doc->m_animationDuration = handler.animationDuration();
210 } else {
211 qCWarning(lcSvgHandler, "Cannot read file '%s', because: %s (line %d)",
212 qPrintable(fileName), qPrintable(handler.errorString()), handler.lineNumber());
213 delete handler.document();
214 }
215 return doc;
216}
217
218QSvgTinyDocument * QSvgTinyDocument::load(const QByteArray &contents)
219{
220 QByteArray svg;
221 // Check for gzip magic number and inflate if appropriate
222 if (contents.startsWith(c: "\x1f\x8b")) {
223 QBuffer buffer;
224 buffer.setData(contents);
225 svg = qt_inflateSvgzDataFrom(device: &buffer);
226 } else {
227 svg = contents;
228 }
229 if (svg.isNull())
230 return nullptr;
231
232 QBuffer buffer;
233 buffer.setData(svg);
234 buffer.open(openMode: QIODevice::ReadOnly);
235 QSvgHandler handler(&buffer);
236
237 QSvgTinyDocument *doc = nullptr;
238 if (handler.ok()) {
239 doc = handler.document();
240 doc->m_animationDuration = handler.animationDuration();
241 } else {
242 delete handler.document();
243 }
244 return doc;
245}
246
247QSvgTinyDocument * QSvgTinyDocument::load(QXmlStreamReader *contents)
248{
249 QSvgHandler handler(contents);
250
251 QSvgTinyDocument *doc = 0;
252 if (handler.ok()) {
253 doc = handler.document();
254 doc->m_animationDuration = handler.animationDuration();
255 } else {
256 delete handler.document();
257 }
258 return doc;
259}
260
261void QSvgTinyDocument::draw(QPainter *p, const QRectF &bounds)
262{
263 if (m_time == 0)
264 m_time = QDateTime::currentMSecsSinceEpoch();
265
266 if (displayMode() == QSvgNode::NoneMode)
267 return;
268
269 p->save();
270 //sets default style on the painter
271 //### not the most optimal way
272 mapSourceToTarget(p, targetRect: bounds);
273 QPen pen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin);
274 pen.setMiterLimit(4);
275 p->setPen(pen);
276 p->setBrush(Qt::black);
277 p->setRenderHint(hint: QPainter::Antialiasing);
278 p->setRenderHint(hint: QPainter::SmoothPixmapTransform);
279 QList<QSvgNode*>::iterator itr = m_renderers.begin();
280 applyStyle(p, states&: m_states);
281 while (itr != m_renderers.end()) {
282 QSvgNode *node = *itr;
283 if ((node->isVisible()) && (node->displayMode() != QSvgNode::NoneMode))
284 node->draw(p, states&: m_states);
285 ++itr;
286 }
287 revertStyle(p, states&: m_states);
288 p->restore();
289}
290
291
292void QSvgTinyDocument::draw(QPainter *p, const QString &id,
293 const QRectF &bounds)
294{
295 QSvgNode *node = scopeNode(id);
296
297 if (!node) {
298 qCDebug(lcSvgHandler, "Couldn't find node %s. Skipping rendering.", qPrintable(id));
299 return;
300 }
301 if (m_time == 0)
302 m_time = QDateTime::currentMSecsSinceEpoch();
303
304 if (node->displayMode() == QSvgNode::NoneMode)
305 return;
306
307 p->save();
308
309 const QRectF elementBounds = node->transformedBounds();
310
311 mapSourceToTarget(p, targetRect: bounds, sourceRect: elementBounds);
312 QTransform originalTransform = p->worldTransform();
313
314 //XXX set default style on the painter
315 QPen pen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin);
316 pen.setMiterLimit(4);
317 p->setPen(pen);
318 p->setBrush(Qt::black);
319 p->setRenderHint(hint: QPainter::Antialiasing);
320 p->setRenderHint(hint: QPainter::SmoothPixmapTransform);
321
322 QStack<QSvgNode*> parentApplyStack;
323 QSvgNode *parent = node->parent();
324 while (parent) {
325 parentApplyStack.push(t: parent);
326 parent = parent->parent();
327 }
328
329 for (int i = parentApplyStack.size() - 1; i >= 0; --i)
330 parentApplyStack[i]->applyStyle(p, states&: m_states);
331
332 // Reset the world transform so that our parents don't affect
333 // the position
334 QTransform currentTransform = p->worldTransform();
335 p->setWorldTransform(matrix: originalTransform);
336
337 node->draw(p, states&: m_states);
338
339 p->setWorldTransform(matrix: currentTransform);
340
341 for (int i = 0; i < parentApplyStack.size(); ++i)
342 parentApplyStack[i]->revertStyle(p, states&: m_states);
343
344 //p->fillRect(bounds.adjusted(-5, -5, 5, 5), QColor(0, 0, 255, 100));
345
346 p->restore();
347}
348
349
350QSvgNode::Type QSvgTinyDocument::type() const
351{
352 return DOC;
353}
354
355void QSvgTinyDocument::setWidth(int len, bool percent)
356{
357 m_size.setWidth(len);
358 m_widthPercent = percent;
359}
360
361void QSvgTinyDocument::setHeight(int len, bool percent)
362{
363 m_size.setHeight(len);
364 m_heightPercent = percent;
365}
366
367void QSvgTinyDocument::setPreserveAspectRatio(bool on)
368{
369 m_preserveAspectRatio = on;
370}
371
372void QSvgTinyDocument::setViewBox(const QRectF &rect)
373{
374 m_viewBox = rect;
375 m_implicitViewBox = rect.isNull();
376}
377
378void QSvgTinyDocument::addSvgFont(QSvgFont *font)
379{
380 m_fonts.insert(akey: font->familyName(), avalue: font);
381}
382
383QSvgFont * QSvgTinyDocument::svgFont(const QString &family) const
384{
385 return m_fonts[family];
386}
387
388void QSvgTinyDocument::addNamedNode(const QString &id, QSvgNode *node)
389{
390 m_namedNodes.insert(akey: id, avalue: node);
391}
392
393QSvgNode *QSvgTinyDocument::namedNode(const QString &id) const
394{
395 return m_namedNodes.value(akey: id);
396}
397
398void QSvgTinyDocument::addNamedStyle(const QString &id, QSvgFillStyleProperty *style)
399{
400 if (!m_namedStyles.contains(akey: id))
401 m_namedStyles.insert(akey: id, avalue: style);
402 else
403 qCWarning(lcSvgHandler) << "Duplicate unique style id:" << id;
404}
405
406QSvgFillStyleProperty *QSvgTinyDocument::namedStyle(const QString &id) const
407{
408 return m_namedStyles.value(akey: id);
409}
410
411void QSvgTinyDocument::restartAnimation()
412{
413 m_time = QDateTime::currentMSecsSinceEpoch();
414}
415
416bool QSvgTinyDocument::animated() const
417{
418 return m_animated;
419}
420
421void QSvgTinyDocument::setAnimated(bool a)
422{
423 m_animated = a;
424}
425
426void QSvgTinyDocument::draw(QPainter *p)
427{
428 draw(p, bounds: QRectF());
429}
430
431void QSvgTinyDocument::draw(QPainter *p, QSvgExtraStates &)
432{
433 draw(p);
434}
435
436void QSvgTinyDocument::mapSourceToTarget(QPainter *p, const QRectF &targetRect, const QRectF &sourceRect)
437{
438 QRectF target = targetRect;
439 if (target.isEmpty()) {
440 QPaintDevice *dev = p->device();
441 QRectF deviceRect(0, 0, dev->width(), dev->height());
442 if (deviceRect.isEmpty()) {
443 if (sourceRect.isEmpty())
444 target = QRectF(QPointF(0, 0), size());
445 else
446 target = QRectF(QPointF(0, 0), sourceRect.size());
447 } else {
448 target = deviceRect;
449 }
450 }
451
452 QRectF source = sourceRect;
453 if (source.isEmpty())
454 source = viewBox();
455
456 if (source != target && !qFuzzyIsNull(d: source.width()) && !qFuzzyIsNull(d: source.height())) {
457#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
458 if (m_implicitViewBox || !preserveAspectRatio()) {
459 // Code path used when no view box is set, or IgnoreAspectRatio requested
460#endif
461 QTransform transform;
462 transform.scale(sx: target.width() / source.width(),
463 sy: target.height() / source.height());
464 QRectF c2 = transform.mapRect(source);
465 p->translate(dx: target.x() - c2.x(),
466 dy: target.y() - c2.y());
467 p->scale(sx: target.width() / source.width(),
468 sy: target.height() / source.height());
469#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
470 } else {
471 // Code path used when KeepAspectRatio is requested. This attempts to emulate the default values
472 // of the <preserveAspectRatio tag that's implicitly defined when <viewbox> is used.
473
474 // Scale the view box into the view port (target) by preserve the aspect ratio.
475 QSizeF viewBoxSize = source.size();
476 viewBoxSize.scale(w: target.width(), h: target.height(), mode: Qt::KeepAspectRatio);
477
478 // Center the view box in the view port
479 p->translate(dx: target.x() + (target.width() - viewBoxSize.width()) / 2,
480 dy: target.y() + (target.height() - viewBoxSize.height()) / 2);
481
482 p->scale(sx: viewBoxSize.width() / source.width(),
483 sy: viewBoxSize.height() / source.height());
484
485 // Apply the view box translation if specified.
486 p->translate(dx: -source.x(), dy: -source.y());
487 }
488#endif
489 }
490}
491
492QRectF QSvgTinyDocument::boundsOnElement(const QString &id) const
493{
494 const QSvgNode *node = scopeNode(id);
495 if (!node)
496 node = this;
497 return node->transformedBounds();
498}
499
500bool QSvgTinyDocument::elementExists(const QString &id) const
501{
502 QSvgNode *node = scopeNode(id);
503
504 return (node!=0);
505}
506
507QTransform QSvgTinyDocument::transformForElement(const QString &id) const
508{
509 QSvgNode *node = scopeNode(id);
510
511 if (!node) {
512 qCDebug(lcSvgHandler, "Couldn't find node %s. Skipping rendering.", qPrintable(id));
513 return QTransform();
514 }
515
516 QTransform t;
517
518 node = node->parent();
519 while (node) {
520 if (node->m_style.transform)
521 t *= node->m_style.transform->qtransform();
522 node = node->parent();
523 }
524
525 return t;
526}
527
528int QSvgTinyDocument::currentFrame() const
529{
530 double runningPercentage = qMin(a: currentElapsed() / double(m_animationDuration), b: 1.);
531
532 int totalFrames = m_fps * m_animationDuration;
533
534 return int(runningPercentage * totalFrames);
535}
536
537void QSvgTinyDocument::setCurrentFrame(int frame)
538{
539 int totalFrames = m_fps * m_animationDuration;
540 double framePercentage = frame/double(totalFrames);
541 double timeForFrame = m_animationDuration * framePercentage; //in S
542 timeForFrame *= 1000; //in ms
543 int timeToAdd = int(timeForFrame - currentElapsed());
544 m_time += timeToAdd;
545}
546
547void QSvgTinyDocument::setFramesPerSecond(int num)
548{
549 m_fps = num;
550}
551
552QT_END_NAMESPACE
553

source code of qtsvg/src/svg/qsvgtinydocument.cpp