/src/qtsvg/src/svg/qsvgdocument.cpp
Line | Count | Source |
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 | | |
4 | | #include "qsvgdocument_p.h" |
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 "qstack.h" |
14 | | #include "qtransform.h" |
15 | | #include "qdebug.h" |
16 | | |
17 | | #ifndef QT_NO_COMPRESS |
18 | | #include <zlib.h> |
19 | | #endif |
20 | | |
21 | | QT_BEGIN_NAMESPACE |
22 | | |
23 | | using namespace Qt::StringLiterals; |
24 | | |
25 | | QSvgDocument::QSvgDocument(QtSvg::Options options, QtSvg::AnimatorType type) |
26 | 37.7k | : QSvgStructureNode(0) |
27 | 37.7k | , m_widthPercent(false) |
28 | 37.7k | , m_heightPercent(false) |
29 | 37.7k | , m_animated(false) |
30 | 37.7k | , m_fps(30) |
31 | 37.7k | , m_options(options) |
32 | 37.7k | { |
33 | 37.7k | m_states.trustedSource = m_options.testFlag(QtSvg::AssumeTrustedSource); |
34 | 37.7k | bool animationEnabled = !m_options.testFlag(QtSvg::DisableAnimations); |
35 | 37.7k | switch (type) { |
36 | 37.7k | case QtSvg::AnimatorType::Automatic: |
37 | 37.7k | if (animationEnabled) |
38 | 37.7k | m_animator.reset(new QSvgAnimator); |
39 | 37.7k | break; |
40 | 0 | case QtSvg::AnimatorType::Controlled: |
41 | 0 | if (animationEnabled) |
42 | 0 | m_animator.reset(new QSvgAnimationController); |
43 | 37.7k | } |
44 | 37.7k | } |
45 | | |
46 | | QSvgDocument::~QSvgDocument() |
47 | 37.7k | { |
48 | | // Only do that when AssumeTrustedSource is set to false. Otherwise, all nodes |
49 | | // will be deleted by recursive calls of destructors. |
50 | 37.7k | if (!m_states.trustedSource) |
51 | 37.7k | releaseDescendants(); |
52 | 37.7k | } |
53 | | |
54 | | static bool hasSvgHeader(const QByteArray &buf) |
55 | 99.4k | { |
56 | 99.4k | QTextStream s(buf); // Handle multi-byte encodings |
57 | 99.4k | QString h = s.readAll(); |
58 | 99.4k | QStringView th = QStringView(h).trimmed(); |
59 | 99.4k | bool matched = false; |
60 | 99.4k | if (th.startsWith("<svg"_L1) || th.startsWith("<!DOCTYPE svg"_L1)) |
61 | 48.7k | matched = true; |
62 | 50.7k | else if (th.startsWith("<?xml"_L1) || th.startsWith("<!--"_L1)) |
63 | 9.17k | matched = th.contains("<!DOCTYPE svg"_L1) || th.contains("<svg"_L1); |
64 | 99.4k | return matched; |
65 | 99.4k | } |
66 | | |
67 | | #ifndef QT_NO_COMPRESS |
68 | | static QByteArray qt_inflateSvgzDataFrom(QIODevice *device, bool doCheckContent = true); |
69 | | # ifdef QT_BUILD_INTERNAL |
70 | | Q_AUTOTEST_EXPORT QByteArray qt_inflateGZipDataFrom(QIODevice *device) |
71 | | { |
72 | | return qt_inflateSvgzDataFrom(device, false); // autotest wants unchecked result |
73 | | } |
74 | | # endif |
75 | | |
76 | | static QByteArray qt_inflateSvgzDataFrom(QIODevice *device, bool doCheckContent) |
77 | 2.09k | { |
78 | 2.09k | if (!device) |
79 | 0 | return QByteArray(); |
80 | | |
81 | 2.09k | if (!device->isOpen()) |
82 | 2.09k | device->open(QIODevice::ReadOnly); |
83 | | |
84 | 2.09k | Q_ASSERT(device->isOpen() && device->isReadable()); |
85 | | |
86 | 2.09k | static const int CHUNK_SIZE = 4096; |
87 | 2.09k | int zlibResult = Z_OK; |
88 | | |
89 | 2.09k | QByteArray source; |
90 | 2.09k | QByteArray destination; |
91 | | |
92 | | // Initialize zlib stream struct |
93 | 2.09k | z_stream zlibStream; |
94 | 2.09k | zlibStream.next_in = Z_NULL; |
95 | 2.09k | zlibStream.avail_in = 0; |
96 | 2.09k | zlibStream.avail_out = 0; |
97 | 2.09k | zlibStream.zalloc = Z_NULL; |
98 | 2.09k | zlibStream.zfree = Z_NULL; |
99 | 2.09k | zlibStream.opaque = Z_NULL; |
100 | | |
101 | | // Adding 16 to the window size gives us gzip decoding |
102 | 2.09k | if (inflateInit2(&zlibStream, MAX_WBITS + 16) != Z_OK) { |
103 | 0 | qCWarning(lcSvgHandler, "Cannot initialize zlib, because: %s", |
104 | 0 | (zlibStream.msg != NULL ? zlibStream.msg : "Unknown error")); |
105 | 0 | return QByteArray(); |
106 | 0 | } |
107 | | |
108 | 2.09k | bool stillMoreWorkToDo = true; |
109 | 7.07k | while (stillMoreWorkToDo) { |
110 | | |
111 | 7.07k | if (!zlibStream.avail_in) { |
112 | 7.07k | source = device->read(CHUNK_SIZE); |
113 | | |
114 | 7.07k | if (source.isEmpty()) |
115 | 2.05k | break; |
116 | | |
117 | 5.02k | zlibStream.avail_in = source.size(); |
118 | 5.02k | zlibStream.next_in = reinterpret_cast<Bytef*>(source.data()); |
119 | 5.02k | } |
120 | | |
121 | 6.40k | do { |
122 | | // Prepare the destination buffer |
123 | 6.40k | int oldSize = destination.size(); |
124 | 6.40k | if (oldSize > INT_MAX - CHUNK_SIZE) { |
125 | 0 | inflateEnd(&zlibStream); |
126 | 0 | qCWarning(lcSvgHandler, "Error while inflating gzip file: integer size overflow"); |
127 | 0 | return QByteArray(); |
128 | 0 | } |
129 | | |
130 | 6.40k | destination.resize(oldSize + CHUNK_SIZE); |
131 | 6.40k | zlibStream.next_out = reinterpret_cast<Bytef*>( |
132 | 6.40k | destination.data() + oldSize - zlibStream.avail_out); |
133 | 6.40k | zlibStream.avail_out += CHUNK_SIZE; |
134 | | |
135 | 6.40k | zlibResult = inflate(&zlibStream, Z_NO_FLUSH); |
136 | 6.40k | switch (zlibResult) { |
137 | 0 | case Z_NEED_DICT: |
138 | 43 | case Z_DATA_ERROR: |
139 | 43 | case Z_STREAM_ERROR: |
140 | 43 | case Z_MEM_ERROR: { |
141 | 43 | inflateEnd(&zlibStream); |
142 | 43 | qCWarning(lcSvgHandler, "Error while inflating gzip file: %s", |
143 | 43 | (zlibStream.msg != NULL ? zlibStream.msg : "Unknown error")); |
144 | 43 | return QByteArray(); |
145 | 43 | } |
146 | 6.40k | } |
147 | | |
148 | | // If the output buffer still has more room after calling inflate |
149 | | // it means we have to provide more data, so exit the loop here |
150 | 6.40k | } while (!zlibStream.avail_out); |
151 | | |
152 | 4.97k | if (doCheckContent) { |
153 | | // Quick format check, equivalent to QSvgIOHandler::canRead() |
154 | 2.09k | const qsizetype destinationContents = std::min(destination.size(), static_cast<qsizetype>(zlibStream.total_out)); |
155 | 2.09k | Q_ASSERT(destinationContents == static_cast<qsizetype>(zlibStream.total_out)); |
156 | 2.09k | if (!hasSvgHeader(QByteArray::fromRawData(destination.constData(), destinationContents))) { |
157 | 0 | inflateEnd(&zlibStream); |
158 | 0 | qCWarning(lcSvgHandler, "Error while inflating gzip file: SVG format check failed"); |
159 | 0 | return QByteArray(); |
160 | 0 | } |
161 | 2.09k | doCheckContent = false; // Run only once, on first chunk |
162 | 2.09k | } |
163 | | |
164 | 4.97k | if (zlibResult == Z_STREAM_END) { |
165 | | // Make sure there are no more members to process before exiting |
166 | 0 | if (!(zlibStream.avail_in && inflateReset(&zlibStream) == Z_OK)) |
167 | 0 | stillMoreWorkToDo = false; |
168 | 0 | } |
169 | 4.97k | } |
170 | | |
171 | | // Chop off trailing space in the buffer |
172 | 2.05k | destination.chop(zlibStream.avail_out); |
173 | | |
174 | 2.05k | inflateEnd(&zlibStream); |
175 | 2.05k | return destination; |
176 | 2.09k | } |
177 | | #else |
178 | | static QByteArray qt_inflateSvgzDataFrom(QIODevice *) |
179 | | { |
180 | | return QByteArray(); |
181 | | } |
182 | | #endif |
183 | | |
184 | | std::unique_ptr<QSvgDocument> QSvgDocument::load(const QString &fileName, QtSvg::Options options, |
185 | | QtSvg::AnimatorType type) |
186 | 9.64k | { |
187 | 9.64k | std::unique_ptr<QSvgDocument> doc; |
188 | 9.64k | QFile file(fileName); |
189 | 9.64k | if (!file.open(QFile::ReadOnly)) { |
190 | 0 | qCWarning(lcSvgHandler, "Cannot open file '%s', because: %s", |
191 | 0 | qPrintable(fileName), qPrintable(file.errorString())); |
192 | 0 | return doc; |
193 | 0 | } |
194 | | |
195 | 9.64k | if (fileName.endsWith(QLatin1String(".svgz"), Qt::CaseInsensitive) |
196 | 9.64k | || fileName.endsWith(QLatin1String(".svg.gz"), Qt::CaseInsensitive)) { |
197 | 0 | return load(qt_inflateSvgzDataFrom(&file)); |
198 | 0 | } |
199 | | |
200 | 9.64k | QSvgHandler handler(&file, options, type); |
201 | 9.64k | if (handler.ok()) { |
202 | 1.93k | doc.reset(handler.document()); |
203 | 1.93k | if (doc->m_animator) |
204 | 1.93k | doc->m_animator->setAnimationDuration(handler.animationDuration()); |
205 | 7.70k | } else { |
206 | 7.70k | qCWarning(lcSvgHandler, "Cannot read file '%s', because: %s (line %d)", |
207 | 7.70k | qPrintable(fileName), qPrintable(handler.errorString()), handler.lineNumber()); |
208 | 7.70k | delete handler.document(); |
209 | 7.70k | } |
210 | 9.64k | return doc; |
211 | 9.64k | } |
212 | | |
213 | | std::unique_ptr<QSvgDocument> QSvgDocument::load(const QByteArray &contents, QtSvg::Options options, |
214 | | QtSvg::AnimatorType type) |
215 | 10.7k | { |
216 | 10.7k | std::unique_ptr<QSvgDocument> doc; |
217 | 10.7k | QByteArray svg; |
218 | | // Check for gzip magic number and inflate if appropriate |
219 | 10.7k | if (contents.startsWith("\x1f\x8b")) { |
220 | 2.09k | QBuffer buffer; |
221 | 2.09k | buffer.setData(contents); |
222 | 2.09k | svg = qt_inflateSvgzDataFrom(&buffer); |
223 | 8.63k | } else { |
224 | 8.63k | svg = contents; |
225 | 8.63k | } |
226 | 10.7k | if (svg.isNull()) |
227 | 43 | return doc; |
228 | | |
229 | 10.6k | QBuffer buffer; |
230 | 10.6k | buffer.setData(svg); |
231 | 10.6k | buffer.open(QIODevice::ReadOnly); |
232 | 10.6k | QSvgHandler handler(&buffer, options, type); |
233 | | |
234 | 10.6k | if (handler.ok()) { |
235 | 22 | doc.reset(handler.document()); |
236 | 22 | if (doc->m_animator) |
237 | 22 | doc->m_animator->setAnimationDuration(handler.animationDuration()); |
238 | 10.6k | } else { |
239 | 10.6k | delete handler.document(); |
240 | 10.6k | } |
241 | 10.6k | return doc; |
242 | 10.7k | } |
243 | | |
244 | | std::unique_ptr<QSvgDocument> QSvgDocument::load(QXmlStreamReader *contents, QtSvg::Options options, |
245 | | QtSvg::AnimatorType type) |
246 | 21.3k | { |
247 | 21.3k | QSvgHandler handler(contents, options, type); |
248 | | |
249 | 21.3k | std::unique_ptr<QSvgDocument> doc; |
250 | 21.3k | if (handler.ok()) { |
251 | 96 | doc.reset(handler.document()); |
252 | 96 | if (doc->m_animator) |
253 | 96 | doc->m_animator->setAnimationDuration(handler.animationDuration()); |
254 | 21.3k | } else { |
255 | 21.3k | delete handler.document(); |
256 | 21.3k | } |
257 | 21.3k | return doc; |
258 | 21.3k | } |
259 | | |
260 | | void QSvgDocument::draw(QPainter *p, const QRectF &bounds) |
261 | 1.93k | { |
262 | 1.93k | if (displayMode() == QSvgNode::NoneMode) |
263 | 0 | return; |
264 | | |
265 | 1.93k | p->save(); |
266 | | //sets default style on the painter |
267 | | //### not the most optimal way |
268 | 1.93k | mapSourceToTarget(p, bounds); |
269 | 1.93k | initPainter(p); |
270 | 1.93k | applyStyle(p, m_states); |
271 | 61.9k | for (const auto &node : renderers()) { |
272 | 61.9k | if ((node->isVisible()) && (node->displayMode() != QSvgNode::NoneMode)) |
273 | 61.9k | node->draw(p, m_states); |
274 | 61.9k | } |
275 | 1.93k | revertStyle(p, m_states); |
276 | 1.93k | p->restore(); |
277 | 1.93k | } |
278 | | |
279 | | |
280 | | void QSvgDocument::draw(QPainter *p, const QString &id, |
281 | | const QRectF &bounds) |
282 | 0 | { |
283 | 0 | QSvgNode *node = scopeNode(id); |
284 | |
|
285 | 0 | if (!node) { |
286 | 0 | qCDebug(lcSvgHandler, "Couldn't find node %s. Skipping rendering.", qPrintable(id)); |
287 | 0 | return; |
288 | 0 | } |
289 | | |
290 | 0 | if (node->displayMode() == QSvgNode::NoneMode) |
291 | 0 | return; |
292 | | |
293 | 0 | p->save(); |
294 | |
|
295 | 0 | const QRectF elementBounds = node->bounds(); |
296 | |
|
297 | 0 | mapSourceToTarget(p, bounds, elementBounds); |
298 | 0 | QTransform originalTransform = p->worldTransform(); |
299 | | |
300 | | //XXX set default style on the painter |
301 | 0 | QPen pen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin); |
302 | 0 | pen.setMiterLimit(4); |
303 | 0 | p->setPen(pen); |
304 | 0 | p->setBrush(Qt::black); |
305 | 0 | p->setRenderHint(QPainter::Antialiasing); |
306 | 0 | p->setRenderHint(QPainter::SmoothPixmapTransform); |
307 | |
|
308 | 0 | QStack<QSvgNode*> parentApplyStack; |
309 | 0 | QSvgNode *parent = node->parent(); |
310 | 0 | while (parent) { |
311 | 0 | parentApplyStack.push(parent); |
312 | 0 | parent = parent->parent(); |
313 | 0 | } |
314 | |
|
315 | 0 | for (int i = parentApplyStack.size() - 1; i >= 0; --i) |
316 | 0 | parentApplyStack[i]->applyStyle(p, m_states); |
317 | | |
318 | | // Reset the world transform so that our parents don't affect |
319 | | // the position |
320 | 0 | QTransform currentTransform = p->worldTransform(); |
321 | 0 | p->setWorldTransform(originalTransform); |
322 | |
|
323 | 0 | node->draw(p, m_states); |
324 | |
|
325 | 0 | p->setWorldTransform(currentTransform); |
326 | |
|
327 | 0 | for (int i = 0; i < parentApplyStack.size(); ++i) |
328 | 0 | parentApplyStack[i]->revertStyle(p, m_states); |
329 | | |
330 | | //p->fillRect(bounds.adjusted(-5, -5, 5, 5), QColor(0, 0, 255, 100)); |
331 | |
|
332 | 0 | p->restore(); |
333 | 0 | } |
334 | | |
335 | | QSvgNode::Type QSvgDocument::type() const |
336 | 1.62M | { |
337 | 1.62M | return Doc; |
338 | 1.62M | } |
339 | | |
340 | | void QSvgDocument::setWidth(int len, bool percent) |
341 | 137 | { |
342 | 137 | m_size.setWidth(len); |
343 | 137 | m_widthPercent = percent; |
344 | 137 | } |
345 | | |
346 | | void QSvgDocument::setHeight(int len, bool percent) |
347 | 116 | { |
348 | 116 | m_size.setHeight(len); |
349 | 116 | m_heightPercent = percent; |
350 | 116 | } |
351 | | |
352 | | void QSvgDocument::setPreserveAspectRatio(bool on) |
353 | 0 | { |
354 | 0 | m_preserveAspectRatio = on; |
355 | 0 | } |
356 | | |
357 | | void QSvgDocument::setViewBox(const QRectF &rect) |
358 | 4.18k | { |
359 | 4.18k | m_viewBox = rect; |
360 | 4.18k | m_implicitViewBox = rect.isNull(); |
361 | 4.18k | } |
362 | | |
363 | | QtSvg::Options QSvgDocument::options() const |
364 | 404k | { |
365 | 404k | return m_options; |
366 | 404k | } |
367 | | |
368 | | void QSvgDocument::addSvgFont(QSvgFont *font) |
369 | 0 | { |
370 | 0 | m_fonts.emplace(font->familyName(), font); |
371 | 0 | } |
372 | | |
373 | | QSvgFont * QSvgDocument::svgFont(const QString &family) const |
374 | 0 | { |
375 | 0 | return m_fonts[family]; |
376 | 0 | } |
377 | | |
378 | | void QSvgDocument::addNamedNode(const QString &id, QSvgNode *node) |
379 | 131k | { |
380 | 131k | m_namedNodes.insert(id, node); |
381 | 131k | } |
382 | | |
383 | | QSvgNode *QSvgDocument::namedNode(const QString &id) const |
384 | 85.6k | { |
385 | 85.6k | return m_namedNodes.value(id); |
386 | 85.6k | } |
387 | | |
388 | | void QSvgDocument::addPaintServer(QSvgPaintServerSharedPtr paintServer, const QString &id) |
389 | 0 | { |
390 | 0 | if (id.isEmpty()) |
391 | 0 | return; |
392 | | |
393 | 0 | if (!m_paintServers.insert({ id, paintServer } ).second) |
394 | 0 | qCWarning(lcSvgHandler) << "Duplicate unique style id:" << id; |
395 | 0 | } |
396 | | |
397 | | QSvgPaintServerSharedPtr QSvgDocument::paintServer(QStringView id) const |
398 | 27.3k | { |
399 | 27.3k | auto it = m_paintServers.find(id.toString()); |
400 | 27.3k | return it != m_paintServers.end() ? it->second : nullptr; |
401 | 27.3k | } |
402 | | |
403 | | void QSvgDocument::restartAnimation() |
404 | 2.05k | { |
405 | 2.05k | if (m_animator) |
406 | 2.05k | m_animator->restartAnimation(); |
407 | 2.05k | } |
408 | | |
409 | | bool QSvgDocument::animated() const |
410 | 107k | { |
411 | 107k | return m_animated; |
412 | 107k | } |
413 | | |
414 | | void QSvgDocument::setAnimated(bool a) |
415 | 0 | { |
416 | 0 | m_animated = a; |
417 | 0 | } |
418 | | |
419 | | void QSvgDocument::draw(QPainter *p) |
420 | 0 | { |
421 | 0 | draw(p, QRectF()); |
422 | 0 | } |
423 | | |
424 | | void QSvgDocument::drawCommand(QPainter *, QSvgExtraStates &) |
425 | 0 | { |
426 | 0 | qCDebug(lcSvgHandler) << "SVG Tiny does not support nested <svg> elements: ignored."; |
427 | 0 | return; |
428 | 0 | } |
429 | | |
430 | | static bool isValidMatrix(const QTransform &transform) |
431 | 1.93k | { |
432 | 1.93k | qreal determinant = transform.determinant(); |
433 | 1.93k | return qIsFinite(determinant); |
434 | 1.93k | } |
435 | | |
436 | | void QSvgDocument::mapSourceToTarget(QPainter *p, const QRectF &targetRect, const QRectF &sourceRect) |
437 | 1.93k | { |
438 | 1.93k | QTransform oldTransform = p->worldTransform(); |
439 | | |
440 | 1.93k | QRectF target = targetRect; |
441 | 1.93k | if (target.isEmpty()) { |
442 | 126 | QPaintDevice *dev = p->device(); |
443 | 126 | QRectF deviceRect(0, 0, dev->width(), dev->height()); |
444 | 126 | if (deviceRect.isEmpty()) { |
445 | 126 | if (sourceRect.isEmpty()) |
446 | 126 | target = QRectF(QPointF(0, 0), size()); |
447 | 0 | else |
448 | 0 | target = QRectF(QPointF(0, 0), sourceRect.size()); |
449 | 126 | } else { |
450 | 0 | target = deviceRect; |
451 | 0 | } |
452 | 126 | } |
453 | | |
454 | 1.93k | QRectF source = sourceRect; |
455 | 1.93k | if (source.isEmpty()) |
456 | 1.93k | source = viewBox(); |
457 | | |
458 | 1.93k | if (source != target && !qFuzzyIsNull(source.width()) && !qFuzzyIsNull(source.height())) { |
459 | 1.44k | if (m_implicitViewBox || !preserveAspectRatio()) { |
460 | | // Code path used when no view box is set, or IgnoreAspectRatio requested |
461 | 1.44k | QTransform transform; |
462 | 1.44k | transform.scale(target.width() / source.width(), |
463 | 1.44k | target.height() / source.height()); |
464 | 1.44k | QRectF c2 = transform.mapRect(source); |
465 | 1.44k | p->translate(target.x() - c2.x(), |
466 | 1.44k | target.y() - c2.y()); |
467 | 1.44k | p->scale(target.width() / source.width(), |
468 | 1.44k | target.height() / source.height()); |
469 | 1.44k | } else { |
470 | | // Code path used when KeepAspectRatio is requested. This attempts to emulate the default values |
471 | | // of the <preserveAspectRatio tag that's implicitly defined when <viewbox> is used. |
472 | | |
473 | | // Scale the view box into the view port (target) by preserve the aspect ratio. |
474 | 0 | QSizeF viewBoxSize = source.size(); |
475 | 0 | viewBoxSize.scale(target.width(), target.height(), Qt::KeepAspectRatio); |
476 | | |
477 | | // Center the view box in the view port |
478 | 0 | p->translate(target.x() + (target.width() - viewBoxSize.width()) / 2, |
479 | 0 | target.y() + (target.height() - viewBoxSize.height()) / 2); |
480 | |
|
481 | 0 | p->scale(viewBoxSize.width() / source.width(), |
482 | 0 | viewBoxSize.height() / source.height()); |
483 | | |
484 | | // Apply the view box translation if specified. |
485 | 0 | p->translate(-source.x(), -source.y()); |
486 | 0 | } |
487 | 1.44k | } |
488 | | |
489 | 1.93k | if (!isValidMatrix(p->worldTransform())) |
490 | 0 | p->setWorldTransform(oldTransform); |
491 | 1.93k | } |
492 | | |
493 | | QRectF QSvgDocument::boundsOnElement(const QString &id) const |
494 | 0 | { |
495 | 0 | const QSvgNode *node = scopeNode(id); |
496 | 0 | if (!node) |
497 | 0 | node = this; |
498 | 0 | return node->bounds(); |
499 | 0 | } |
500 | | |
501 | | bool QSvgDocument::elementExists(const QString &id) const |
502 | 0 | { |
503 | 0 | QSvgNode *node = scopeNode(id); |
504 | |
|
505 | 0 | return (node!=0); |
506 | 0 | } |
507 | | |
508 | | QTransform QSvgDocument::transformForElement(const QString &id) const |
509 | 0 | { |
510 | 0 | QSvgNode *node = scopeNode(id); |
511 | |
|
512 | 0 | if (!node) { |
513 | 0 | qCDebug(lcSvgHandler, "Couldn't find node %s. Skipping rendering.", qPrintable(id)); |
514 | 0 | return QTransform(); |
515 | 0 | } |
516 | | |
517 | 0 | QTransform t; |
518 | |
|
519 | 0 | node = node->parent(); |
520 | 0 | while (node) { |
521 | 0 | if (node->m_style.transform) |
522 | 0 | t *= node->m_style.transform->qtransform(); |
523 | 0 | node = node->parent(); |
524 | 0 | } |
525 | |
|
526 | 0 | return t; |
527 | 0 | } |
528 | | |
529 | | int QSvgDocument::currentFrame() const |
530 | 0 | { |
531 | 0 | const double runningPercentage = qMin(currentElapsed() / double(animationDuration()), 1.); |
532 | 0 | const int totalFrames = m_fps * animationDuration() / 1000; |
533 | 0 | return int(runningPercentage * totalFrames); |
534 | 0 | } |
535 | | |
536 | | void QSvgDocument::setCurrentFrame(int frame) |
537 | 0 | { |
538 | 0 | if (!m_animator) |
539 | 0 | return; |
540 | | |
541 | 0 | const int totalFrames = m_fps * animationDuration() / 1000; |
542 | 0 | if (totalFrames == 0) |
543 | 0 | return; |
544 | | |
545 | 0 | const int timeForFrame = frame * animationDuration() / totalFrames; //in ms |
546 | 0 | const int timeToAdd = timeForFrame - currentElapsed(); |
547 | 0 | m_animator->setAnimatorTime(timeToAdd); |
548 | 0 | } |
549 | | |
550 | | void QSvgDocument::setFramesPerSecond(int num) |
551 | 0 | { |
552 | 0 | m_fps = num; |
553 | 0 | } |
554 | | |
555 | | QSharedPointer<QSvgAbstractAnimator> QSvgDocument::animator() const |
556 | 3.86k | { |
557 | 3.86k | return m_animator; |
558 | 3.86k | } |
559 | | |
560 | | bool QSvgDocument::isLikelySvg(QIODevice *device, bool *isCompressed) |
561 | 126k | { |
562 | 126k | constexpr int bufSize = 4096; |
563 | 126k | char buf[bufSize]; |
564 | 126k | char inflateBuf[bufSize]; |
565 | 126k | bool useInflateBuf = false; |
566 | 126k | int readLen = device->peek(buf, bufSize); |
567 | 126k | if (readLen < 8) |
568 | 28.4k | return false; |
569 | 98.5k | #ifndef QT_NO_COMPRESS |
570 | 98.5k | if (quint8(buf[0]) == 0x1f && quint8(buf[1]) == 0x8b) { |
571 | | // Indicates gzip compressed content, i.e. svgz |
572 | 5.50k | z_stream zlibStream; |
573 | 5.50k | zlibStream.avail_in = readLen; |
574 | 5.50k | zlibStream.next_out = reinterpret_cast<Bytef *>(inflateBuf); |
575 | 5.50k | zlibStream.avail_out = bufSize; |
576 | 5.50k | zlibStream.next_in = reinterpret_cast<Bytef *>(buf); |
577 | 5.50k | zlibStream.zalloc = Z_NULL; |
578 | 5.50k | zlibStream.zfree = Z_NULL; |
579 | 5.50k | zlibStream.opaque = Z_NULL; |
580 | 5.50k | if (inflateInit2(&zlibStream, MAX_WBITS + 16) != Z_OK) |
581 | 0 | return false; |
582 | 5.50k | int zlibResult = inflate(&zlibStream, Z_NO_FLUSH); |
583 | 5.50k | inflateEnd(&zlibStream); |
584 | 5.50k | if ((zlibResult != Z_OK && zlibResult != Z_STREAM_END) || zlibStream.total_out < 8) |
585 | 1.14k | return false; |
586 | 4.35k | readLen = zlibStream.total_out; |
587 | 4.35k | if (isCompressed) |
588 | 2.09k | *isCompressed = true; |
589 | 4.35k | useInflateBuf = true; |
590 | 4.35k | } |
591 | 97.3k | #endif |
592 | 97.3k | return hasSvgHeader(QByteArray::fromRawData(useInflateBuf ? inflateBuf : buf, readLen)); |
593 | 98.5k | } |
594 | | |
595 | | QT_END_NAMESPACE |