Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qsvgtinydocument.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include "qsvghandler_p.h"
7#include "qsvgfont_p.h"
8
9#include "qpainter.h"
10#include "qfile.h"
11#include "qbuffer.h"
12#include "qbytearray.h"
13#include "qqueue.h"
14#include "qstack.h"
15#include "qtransform.h"
16#include "qdebug.h"
17
18#ifndef QT_NO_COMPRESS
19#include <zlib.h>
20#endif
21
23
24using namespace Qt::StringLiterals;
25
28 , m_widthPercent(false)
29 , m_heightPercent(false)
30 , m_time(0)
31 , m_animated(false)
32 , m_animationDuration(0)
33 , m_fps(30)
34 , m_options(options)
35{
36}
37
41
42static bool hasSvgHeader(const QByteArray &buf)
43{
44 QTextStream s(buf); // Handle multi-byte encodings
45 QString h = s.readAll();
47 bool matched = false;
48 if (th.startsWith("<svg"_L1) || th.startsWith("<!DOCTYPE svg"_L1))
49 matched = true;
50 else if (th.startsWith("<?xml"_L1) || th.startsWith("<!--"_L1))
51 matched = th.contains("<!DOCTYPE svg"_L1) || th.contains("<svg"_L1);
52 return matched;
53}
54
55#ifndef QT_NO_COMPRESS
56static QByteArray qt_inflateSvgzDataFrom(QIODevice *device, bool doCheckContent = true);
57# ifdef QT_BUILD_INTERNAL
58Q_AUTOTEST_EXPORT QByteArray qt_inflateGZipDataFrom(QIODevice *device)
59{
60 return qt_inflateSvgzDataFrom(device, false); // autotest wants unchecked result
61}
62# endif
63
65{
66 if (!device)
67 return QByteArray();
68
69 if (!device->isOpen())
71
72 Q_ASSERT(device->isOpen() && device->isReadable());
73
74 static const int CHUNK_SIZE = 4096;
75 int zlibResult = Z_OK;
76
79
80 // Initialize zlib stream struct
81 z_stream zlibStream;
82 zlibStream.next_in = Z_NULL;
83 zlibStream.avail_in = 0;
84 zlibStream.avail_out = 0;
85 zlibStream.zalloc = Z_NULL;
86 zlibStream.zfree = Z_NULL;
87 zlibStream.opaque = Z_NULL;
88
89 // Adding 16 to the window size gives us gzip decoding
90 if (inflateInit2(&zlibStream, MAX_WBITS + 16) != Z_OK) {
91 qCWarning(lcSvgHandler, "Cannot initialize zlib, because: %s",
92 (zlibStream.msg != NULL ? zlibStream.msg : "Unknown error"));
93 return QByteArray();
94 }
95
96 bool stillMoreWorkToDo = true;
97 while (stillMoreWorkToDo) {
98
99 if (!zlibStream.avail_in) {
100 source = device->read(CHUNK_SIZE);
101
102 if (source.isEmpty())
103 break;
104
105 zlibStream.avail_in = source.size();
106 zlibStream.next_in = reinterpret_cast<Bytef*>(source.data());
107 }
108
109 do {
110 // Prepare the destination buffer
111 int oldSize = destination.size();
112 if (oldSize > INT_MAX - CHUNK_SIZE) {
113 inflateEnd(&zlibStream);
114 qCWarning(lcSvgHandler, "Error while inflating gzip file: integer size overflow");
115 return QByteArray();
116 }
117
118 destination.resize(oldSize + CHUNK_SIZE);
119 zlibStream.next_out = reinterpret_cast<Bytef*>(
120 destination.data() + oldSize - zlibStream.avail_out);
121 zlibStream.avail_out += CHUNK_SIZE;
122
123 zlibResult = inflate(&zlibStream, Z_NO_FLUSH);
124 switch (zlibResult) {
125 case Z_NEED_DICT:
126 case Z_DATA_ERROR:
127 case Z_STREAM_ERROR:
128 case Z_MEM_ERROR: {
129 inflateEnd(&zlibStream);
130 qCWarning(lcSvgHandler, "Error while inflating gzip file: %s",
131 (zlibStream.msg != NULL ? zlibStream.msg : "Unknown error"));
132 return QByteArray();
133 }
134 }
135
136 // If the output buffer still has more room after calling inflate
137 // it means we have to provide more data, so exit the loop here
138 } while (!zlibStream.avail_out);
139
140 if (doCheckContent) {
141 // Quick format check, equivalent to QSvgIOHandler::canRead()
143 inflateEnd(&zlibStream);
144 qCWarning(lcSvgHandler, "Error while inflating gzip file: SVG format check failed");
145 return QByteArray();
146 }
147 doCheckContent = false; // Run only once, on first chunk
148 }
149
150 if (zlibResult == Z_STREAM_END) {
151 // Make sure there are no more members to process before exiting
152 if (!(zlibStream.avail_in && inflateReset(&zlibStream) == Z_OK))
153 stillMoreWorkToDo = false;
154 }
155 }
156
157 // Chop off trailing space in the buffer
158 destination.chop(zlibStream.avail_out);
159
160 inflateEnd(&zlibStream);
161 return destination;
162}
163#else
165{
166 return QByteArray();
167}
168#endif
169
171{
173 if (!file.open(QFile::ReadOnly)) {
174 qCWarning(lcSvgHandler, "Cannot open file '%s', because: %s",
176 return 0;
177 }
178
179 if (fileName.endsWith(QLatin1String(".svgz"), Qt::CaseInsensitive)
180 || fileName.endsWith(QLatin1String(".svg.gz"), Qt::CaseInsensitive)) {
182 }
183
184 QSvgTinyDocument *doc = nullptr;
185 QSvgHandler handler(&file, options);
186 if (handler.ok()) {
187 doc = handler.document();
188 doc->m_animationDuration = handler.animationDuration();
189 } else {
190 qCWarning(lcSvgHandler, "Cannot read file '%s', because: %s (line %d)",
191 qPrintable(fileName), qPrintable(handler.errorString()), handler.lineNumber());
192 delete handler.document();
193 }
194 return doc;
195}
196
198{
199 QByteArray svg;
200 // Check for gzip magic number and inflate if appropriate
201 if (contents.startsWith("\x1f\x8b")) {
205 } else {
206 svg = contents;
207 }
208 if (svg.isNull())
209 return nullptr;
210
212 buffer.setData(svg);
214 QSvgHandler handler(&buffer, options);
215
216 QSvgTinyDocument *doc = nullptr;
217 if (handler.ok()) {
218 doc = handler.document();
219 doc->m_animationDuration = handler.animationDuration();
220 } else {
221 delete handler.document();
222 }
223 return doc;
224}
225
226QSvgTinyDocument *QSvgTinyDocument::load(QXmlStreamReader *contents, QtSvg::Options options)
227{
228 QSvgHandler handler(contents, options);
229
230 QSvgTinyDocument *doc = nullptr;
231 if (handler.ok()) {
232 doc = handler.document();
233 doc->m_animationDuration = handler.animationDuration();
234 } else {
235 delete handler.document();
236 }
237 return doc;
238}
239
241{
242 if (m_time == 0)
244
246 return;
247
248 p->save();
249 //sets default style on the painter
250 //### not the most optimal way
251 mapSourceToTarget(p, bounds);
252 initPainter(p);
254 applyStyle(p, m_states);
255 while (itr != m_renderers.end()) {
256 QSvgNode *node = *itr;
257 if ((node->isVisible()) && (node->displayMode() != QSvgNode::NoneMode))
258 node->draw(p, m_states);
259 ++itr;
260 }
261 revertStyle(p, m_states);
262 p->restore();
263}
264
265
267 const QRectF &bounds)
268{
269 QSvgNode *node = scopeNode(id);
270
271 if (!node) {
272 qCDebug(lcSvgHandler, "Couldn't find node %s. Skipping rendering.", qPrintable(id));
273 return;
274 }
275 if (m_time == 0)
277
278 if (node->displayMode() == QSvgNode::NoneMode)
279 return;
280
281 p->save();
282
283 const QRectF elementBounds = node->transformedBounds();
284
285 mapSourceToTarget(p, bounds, elementBounds);
286 QTransform originalTransform = p->worldTransform();
287
288 //XXX set default style on the painter
290 pen.setMiterLimit(4);
291 p->setPen(pen);
292 p->setBrush(Qt::black);
293 p->setRenderHint(QPainter::Antialiasing);
294 p->setRenderHint(QPainter::SmoothPixmapTransform);
295
296 QStack<QSvgNode*> parentApplyStack;
297 QSvgNode *parent = node->parent();
298 while (parent) {
299 parentApplyStack.push(parent);
300 parent = parent->parent();
301 }
302
303 for (int i = parentApplyStack.size() - 1; i >= 0; --i)
304 parentApplyStack[i]->applyStyle(p, m_states);
305
306 // Reset the world transform so that our parents don't affect
307 // the position
308 QTransform currentTransform = p->worldTransform();
309 p->setWorldTransform(originalTransform);
310
311 node->draw(p, m_states);
312
313 p->setWorldTransform(currentTransform);
314
315 for (int i = 0; i < parentApplyStack.size(); ++i)
316 parentApplyStack[i]->revertStyle(p, m_states);
317
318 //p->fillRect(bounds.adjusted(-5, -5, 5, 5), QColor(0, 0, 255, 100));
319
320 p->restore();
321}
322
324{
325 return Doc;
326}
327
328void QSvgTinyDocument::setWidth(int len, bool percent)
329{
330 m_size.setWidth(len);
331 m_widthPercent = percent;
332}
333
334void QSvgTinyDocument::setHeight(int len, bool percent)
335{
336 m_size.setHeight(len);
337 m_heightPercent = percent;
338}
339
341{
342 m_preserveAspectRatio = on;
343}
344
346{
347 m_viewBox = rect;
348 m_implicitViewBox = rect.isNull();
349}
350
351QtSvg::Options QSvgTinyDocument::options() const
352{
353 return m_options;
354}
355
357{
358 m_fonts.insert(font->familyName(), font);
359}
360
362{
363 return m_fonts[family];
364}
365
367{
368 m_namedNodes.insert(id, node);
369}
370
372{
373 return m_namedNodes.value(id);
374}
375
377{
378 if (!m_namedStyles.contains(id))
379 m_namedStyles.insert(id, style);
380 else
381 qCWarning(lcSvgHandler) << "Duplicate unique style id:" << id;
382}
383
385{
386 return m_namedStyles.value(id);
387}
388
393
395{
396 return m_animated;
397}
398
400{
401 m_animated = a;
402}
403
405{
406 draw(p, QRectF());
407}
408
410{
411 qCDebug(lcSvgHandler) << "SVG Tiny does not support nested <svg> elements: ignored.";
412 return;
413}
414
416{
417 qreal determinant = transform.determinant();
418 return qIsFinite(determinant);
419}
420
421void QSvgTinyDocument::mapSourceToTarget(QPainter *p, const QRectF &targetRect, const QRectF &sourceRect)
422{
423 QTransform oldTransform = p->worldTransform();
424
425 QRectF target = targetRect;
426 if (target.isEmpty()) {
427 QPaintDevice *dev = p->device();
428 QRectF deviceRect(0, 0, dev->width(), dev->height());
429 if (deviceRect.isEmpty()) {
430 if (sourceRect.isEmpty())
431 target = QRectF(QPointF(0, 0), size());
432 else
433 target = QRectF(QPointF(0, 0), sourceRect.size());
434 } else {
436 }
437 }
438
439 QRectF source = sourceRect;
440 if (source.isEmpty())
441 source = viewBox();
442
443 if (source != target && !qFuzzyIsNull(source.width()) && !qFuzzyIsNull(source.height())) {
444 if (m_implicitViewBox || !preserveAspectRatio()) {
445 // Code path used when no view box is set, or IgnoreAspectRatio requested
447 transform.scale(target.width() / source.width(),
448 target.height() / source.height());
449 QRectF c2 = transform.mapRect(source);
450 p->translate(target.x() - c2.x(),
451 target.y() - c2.y());
452 p->scale(target.width() / source.width(),
453 target.height() / source.height());
454 } else {
455 // Code path used when KeepAspectRatio is requested. This attempts to emulate the default values
456 // of the <preserveAspectRatio tag that's implicitly defined when <viewbox> is used.
457
458 // Scale the view box into the view port (target) by preserve the aspect ratio.
459 QSizeF viewBoxSize = source.size();
460 viewBoxSize.scale(target.width(), target.height(), Qt::KeepAspectRatio);
461
462 // Center the view box in the view port
463 p->translate(target.x() + (target.width() - viewBoxSize.width()) / 2,
464 target.y() + (target.height() - viewBoxSize.height()) / 2);
465
466 p->scale(viewBoxSize.width() / source.width(),
467 viewBoxSize.height() / source.height());
468
469 // Apply the view box translation if specified.
470 p->translate(-source.x(), -source.y());
471 }
472 }
473
474 if (!isValidMatrix(p->worldTransform()))
475 p->setWorldTransform(oldTransform);
476}
477
479{
480 const QSvgNode *node = scopeNode(id);
481 if (!node)
482 node = this;
483 return node->transformedBounds();
484}
485
487{
488 QSvgNode *node = scopeNode(id);
489
490 return (node!=0);
491}
492
494{
495 QSvgNode *node = scopeNode(id);
496
497 if (!node) {
498 qCDebug(lcSvgHandler, "Couldn't find node %s. Skipping rendering.", qPrintable(id));
499 return QTransform();
500 }
501
503
504 node = node->parent();
505 while (node) {
506 if (node->m_style.transform)
507 t *= node->m_style.transform->qtransform();
508 node = node->parent();
509 }
510
511 return t;
512}
513
515{
516 double runningPercentage = qMin(currentElapsed() / double(m_animationDuration), 1.);
517
518 int totalFrames = m_fps * m_animationDuration;
519
520 return int(runningPercentage * totalFrames);
521}
522
524{
525 int totalFrames = m_fps * m_animationDuration;
526 double framePercentage = frame/double(totalFrames);
527 double timeForFrame = m_animationDuration * framePercentage; //in S
528 timeForFrame *= 1000; //in ms
529 int timeToAdd = int(timeForFrame - currentElapsed());
530 m_time += timeToAdd;
531}
532
534{
535 m_fps = num;
536}
537
539{
540 constexpr int bufSize = 4096;
541 char buf[bufSize];
542 char inflateBuf[bufSize];
543 bool useInflateBuf = false;
544 int readLen = device->peek(buf, bufSize);
545 if (readLen < 8)
546 return false;
547#ifndef QT_NO_COMPRESS
548 if (quint8(buf[0]) == 0x1f && quint8(buf[1]) == 0x8b) {
549 // Indicates gzip compressed content, i.e. svgz
550 z_stream zlibStream;
551 zlibStream.avail_in = readLen;
552 zlibStream.next_out = reinterpret_cast<Bytef *>(inflateBuf);
553 zlibStream.avail_out = bufSize;
554 zlibStream.next_in = reinterpret_cast<Bytef *>(buf);
555 zlibStream.zalloc = Z_NULL;
556 zlibStream.zfree = Z_NULL;
557 zlibStream.opaque = Z_NULL;
558 if (inflateInit2(&zlibStream, MAX_WBITS + 16) != Z_OK)
559 return false;
560 int zlibResult = inflate(&zlibStream, Z_NO_FLUSH);
561 inflateEnd(&zlibStream);
562 if ((zlibResult != Z_OK && zlibResult != Z_STREAM_END) || zlibStream.total_out < 8)
563 return false;
564 readLen = zlibStream.total_out;
565 if (isCompressed)
566 *isCompressed = true;
567 useInflateBuf = true;
568 }
569#endif
570 return hasSvgHeader(QByteArray::fromRawData(useInflateBuf ? inflateBuf : buf, readLen));
571}
572
IOBluetoothDevice * device
\inmodule QtCore \reentrant
Definition qbuffer.h:16
void setData(const QByteArray &data)
Sets the contents of the internal buffer to be data.
Definition qbuffer.cpp:259
\inmodule QtCore
Definition qbytearray.h:57
static QByteArray fromRawData(const char *data, qsizetype size)
Constructs a QByteArray that uses the first size bytes of the data array.
Definition qbytearray.h:409
static qint64 currentMSecsSinceEpoch() noexcept
\inmodule QtCore
Definition qfile.h:93
QFILE_MAYBE_NODISCARD bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:904
bool contains(const Key &key) const noexcept
Returns true if the hash contains an item with the key; otherwise returns false.
Definition qhash.h:1007
T value(const Key &key) const noexcept
Definition qhash.h:1054
iterator insert(const Key &key, const T &value)
Inserts a new item with the key and a value of value.
Definition qhash.h:1303
\inmodule QtCore \reentrant
Definition qiodevice.h:34
QString errorString() const
Returns a human-readable description of the last device error that occurred.
iterator end()
Definition qlist.h:626
iterator begin()
Definition qlist.h:625
int width() const
int height() const
The QPainter class performs low-level painting on widgets and other paint devices.
Definition qpainter.h:46
@ SmoothPixmapTransform
Definition qpainter.h:54
@ Antialiasing
Definition qpainter.h:52
\inmodule QtGui
Definition qpen.h:28
void setMiterLimit(qreal limit)
Sets the miter limit of this pen to the given limit.
Definition qpen.cpp:545
\inmodule QtCore\reentrant
Definition qpoint.h:217
\inmodule QtCore\reentrant
Definition qrect.h:484
constexpr bool isEmpty() const noexcept
Returns true if the rectangle is empty, otherwise returns false.
Definition qrect.h:661
constexpr QSizeF size() const noexcept
Returns the size of the rectangle.
Definition qrect.h:735
constexpr bool isEmpty() const noexcept
Returns true if the rectangle is empty, otherwise returns false.
Definition qrect.h:167
\inmodule QtCore
Definition qsize.h:208
void scale(qreal w, qreal h, Qt::AspectRatioMode mode) noexcept
Scales the size to a rectangle with the given width and height, according to the specified mode.
Definition qsize.h:347
constexpr void setWidth(int w) noexcept
Sets the width to the given width.
Definition qsize.h:136
constexpr void setHeight(int h) noexcept
Sets the height to the given height.
Definition qsize.h:139
\inmodule QtCore
Definition qstringview.h:78
bool contains(QChar c, Qt::CaseSensitivity cs=Qt::CaseSensitive) const noexcept
QStringView trimmed() const noexcept
Strips leading and trailing whitespace and returns the result.
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
QSvgTinyDocument * document() const
bool ok() const
int animationDuration() const
int lineNumber() const
QString errorString() const
friend class QSvgTinyDocument
Definition qsvgnode_p.h:199
void revertStyle(QPainter *p, QSvgExtraStates &states) const
Definition qsvgnode.cpp:253
virtual QRectF transformedBounds(QPainter *p, QSvgExtraStates &states) const
Definition qsvgnode.cpp:484
QSvgNode * parent() const
Definition qsvgnode_p.h:202
void applyStyle(QPainter *p, QSvgExtraStates &states) const
Definition qsvgnode.cpp:232
DisplayMode displayMode() const
Definition qsvgnode.cpp:599
QSvgStyle m_style
Definition qsvgnode_p.h:171
void draw(QPainter *p, QSvgExtraStates &states)
Definition qsvgnode.cpp:38
const QSvgStyle & style() const
Definition qsvgnode_p.h:169
bool isVisible() const
Definition qsvgnode_p.h:207
static void initPainter(QPainter *p)
Definition qsvgnode.cpp:612
QRectF bounds(QPainter *p, QSvgExtraStates &states) const override
QSvgNode * scopeNode(const QString &id) const
QList< QSvgNode * > m_renderers
QSvgRefCounter< QSvgTransformStyle > transform
bool elementExists(const QString &id) const
static bool isLikelySvg(QIODevice *device, bool *isCompressed=nullptr)
QSvgNode * namedNode(const QString &id) const
void setFramesPerSecond(int num)
Type type() const override
void setViewBox(const QRectF &rect)
void addNamedStyle(const QString &id, QSvgPaintStyleProperty *style)
void setWidth(int len, bool percent)
QtSvg::Options options() const
QRectF boundsOnElement(const QString &id) const
bool preserveAspectRatio() const
void addNamedNode(const QString &id, QSvgNode *node)
static QSvgTinyDocument * load(const QString &file, QtSvg::Options options={})
QSvgFont * svgFont(const QString &family) const
void draw(QPainter *p)
QRectF viewBox() const
void setPreserveAspectRatio(bool on)
void drawCommand(QPainter *, QSvgExtraStates &) override
QSvgPaintStyleProperty * namedStyle(const QString &id) const
void addSvgFont(QSvgFont *)
QTransform transformForElement(const QString &id) const
void setHeight(int len, bool percent)
const QTransform & qtransform() const
\inmodule QtCore
The QTransform class specifies 2D transformations of a coordinate system.
Definition qtransform.h:20
QTransform & scale(qreal sx, qreal sy)
Scales the coordinate system by sx horizontally and sy vertically, and returns a reference to the mat...
rect
[4]
Combined button and popup list for selecting options.
@ KeepAspectRatio
@ black
Definition qnamespace.h:30
@ SolidLine
@ SvgMiterJoin
@ CaseInsensitive
@ NoBrush
@ FlatCap
SharedPointerFileDialogOptions m_options
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char * destination
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
bool qIsFinite(qfloat16 f) noexcept
Definition qfloat16.h:285
bool qFuzzyIsNull(qfloat16 f) noexcept
Definition qfloat16.h:349
#define qCWarning(category,...)
#define qCDebug(category,...)
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
GLboolean GLboolean GLboolean GLboolean a
[7]
GLenum GLuint id
[7]
GLenum GLuint buffer
GLenum GLuint GLenum GLsizei const GLchar * buf
GLenum target
GLsizei bufSize
GLfloat GLfloat GLfloat GLfloat h
GLsizei GLsizei GLchar * source
GLuint GLenum GLenum transform
GLdouble s
[6]
Definition qopenglext.h:235
GLdouble GLdouble t
Definition qopenglext.h:243
GLfloat GLfloat p
[1]
GLenum GLsizei len
GLuint num
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define CHUNK_SIZE
#define qPrintable(string)
Definition qstring.h:1531
static QByteArray qt_inflateSvgzDataFrom(QIODevice *device, bool doCheckContent=true)
static bool isValidMatrix(const QTransform &transform)
static bool hasSvgHeader(const QByteArray &buf)
#define Q_AUTOTEST_EXPORT
double qreal
Definition qtypes.h:187
unsigned char quint8
Definition qtypes.h:46
static int inflate(Bytef *dest, ulong *destLen, const Bytef *source, ulong sourceLen)
Definition qzip.cpp:92
QFile file
[0]
MyCustomStruct c2
QFrame frame
[0]