23using namespace Qt::StringLiterals;
25QSvgDocument::QSvgDocument(QtSvg::Options options, QtSvg::AnimatorType type)
26 : QSvgStructureNode(0)
27 , m_widthPercent(
false)
28 , m_heightPercent(
false)
33 bool animationEnabled = !m_options.testFlag(QtSvg::DisableAnimations);
35 case QtSvg::AnimatorType::Automatic:
37 m_animator.reset(
new QSvgAnimator);
39 case QtSvg::AnimatorType::Controlled:
41 m_animator.reset(
new QSvgAnimationController);
45QSvgDocument::~QSvgDocument()
52 QString h = s.readAll();
53 QStringView th = QStringView(h).trimmed();
55 if (th.startsWith(
"<svg"_L1) || th.startsWith(
"<!DOCTYPE svg"_L1))
57 else if (th.startsWith(
"<?xml"_L1) || th.startsWith(
"<!--"_L1))
58 matched = th.contains(
"<!DOCTYPE svg"_L1) || th.contains(
"<svg"_L1);
64# ifdef QT_BUILD_INTERNAL
65Q_AUTOTEST_EXPORT QByteArray qt_inflateGZipDataFrom(QIODevice *device)
67 return qt_inflateSvgzDataFrom(device,
false);
76 if (!device->isOpen())
77 device->open(QIODevice::ReadOnly);
79 Q_ASSERT(device->isOpen() && device->isReadable());
81 static const int CHUNK_SIZE = 4096;
82 int zlibResult = Z_OK;
89 zlibStream.next_in = Z_NULL;
90 zlibStream.avail_in = 0;
91 zlibStream.avail_out = 0;
92 zlibStream.zalloc = Z_NULL;
93 zlibStream.zfree = Z_NULL;
94 zlibStream.opaque = Z_NULL;
97 if (inflateInit2(&zlibStream, MAX_WBITS + 16) != Z_OK) {
98 qCWarning(lcSvgHandler,
"Cannot initialize zlib, because: %s",
99 (zlibStream.msg != NULL ? zlibStream.msg :
"Unknown error"));
103 bool stillMoreWorkToDo =
true;
104 while (stillMoreWorkToDo) {
106 if (!zlibStream.avail_in) {
107 source = device->read(CHUNK_SIZE);
109 if (source.isEmpty())
112 zlibStream.avail_in = source.size();
113 zlibStream.next_in =
reinterpret_cast<Bytef*>(source.data());
118 int oldSize = destination.size();
119 if (oldSize > INT_MAX - CHUNK_SIZE) {
120 inflateEnd(&zlibStream);
121 qCWarning(lcSvgHandler,
"Error while inflating gzip file: integer size overflow");
125 destination.resize(oldSize + CHUNK_SIZE);
126 zlibStream.next_out =
reinterpret_cast<Bytef*>(
127 destination.data() + oldSize - zlibStream.avail_out);
128 zlibStream.avail_out += CHUNK_SIZE;
130 zlibResult = inflate(&zlibStream, Z_NO_FLUSH);
131 switch (zlibResult) {
136 inflateEnd(&zlibStream);
137 qCWarning(lcSvgHandler,
"Error while inflating gzip file: %s",
138 (zlibStream.msg != NULL ? zlibStream.msg :
"Unknown error"));
145 }
while (!zlibStream.avail_out);
147 if (doCheckContent) {
149 const qsizetype destinationContents =
std::min(destination.size(),
static_cast<qsizetype>(zlibStream.total_out));
150 Q_ASSERT(destinationContents ==
static_cast<qsizetype>(zlibStream.total_out));
151 if (!hasSvgHeader(QByteArray::fromRawData(destination.constData(), destinationContents))) {
152 inflateEnd(&zlibStream);
153 qCWarning(lcSvgHandler,
"Error while inflating gzip file: SVG format check failed");
156 doCheckContent =
false;
159 if (zlibResult == Z_STREAM_END) {
161 if (!(zlibStream.avail_in && inflateReset(&zlibStream) == Z_OK))
162 stillMoreWorkToDo =
false;
167 destination.chop(zlibStream.avail_out);
169 inflateEnd(&zlibStream);
173static QByteArray qt_inflateSvgzDataFrom(QIODevice *)
179std::unique_ptr<QSvgDocument> QSvgDocument::load(
const QString &fileName, QtSvg::Options options,
180 QtSvg::AnimatorType type)
182 std::unique_ptr<QSvgDocument> doc;
183 QFile file(fileName);
184 if (!file.open(QFile::ReadOnly)) {
185 qCWarning(lcSvgHandler,
"Cannot open file '%s', because: %s",
190 if (fileName.endsWith(QLatin1String(
".svgz"), Qt::CaseInsensitive)
191 || fileName.endsWith(QLatin1String(
".svg.gz"), Qt::CaseInsensitive)) {
192 return load(qt_inflateSvgzDataFrom(&file));
195 QSvgHandler handler(&file, options, type);
197 doc.reset(handler.document());
198 doc->m_animator->setAnimationDuration(handler.animationDuration());
200 qCWarning(lcSvgHandler,
"Cannot read file '%s', because: %s (line %d)",
202 delete handler.document();
207std::unique_ptr<QSvgDocument> QSvgDocument::load(
const QByteArray &contents, QtSvg::Options options,
208 QtSvg::AnimatorType type)
210 std::unique_ptr<QSvgDocument> doc;
213 if (contents.startsWith(
"\x1f\x8b")) {
215 buffer.setData(contents);
216 svg = qt_inflateSvgzDataFrom(&buffer);
225 buffer.open(QIODevice::ReadOnly);
226 QSvgHandler handler(&buffer, options, type);
229 doc.reset(handler.document());
230 doc->m_animator->setAnimationDuration(handler.animationDuration());
232 delete handler.document();
237std::unique_ptr<QSvgDocument> QSvgDocument::load(QXmlStreamReader *contents, QtSvg::Options options,
238 QtSvg::AnimatorType type)
240 QSvgHandler handler(contents, options, type);
242 std::unique_ptr<QSvgDocument> doc;
244 doc.reset(handler.document());
245 doc->m_animator->setAnimationDuration(handler.animationDuration());
247 delete handler.document();
252void QSvgDocument::draw(QPainter *p,
const QRectF &bounds)
254 if (displayMode() == QSvgNode::NoneMode)
260 mapSourceToTarget(p, bounds);
262 applyStyle(p, m_states);
263 for (
const auto &node : renderers()) {
264 if ((node->isVisible()) && (node->displayMode() != QSvgNode::NoneMode))
265 node->draw(p, m_states);
267 revertStyle(p, m_states);
272void QSvgDocument::draw(QPainter *p,
const QString &id,
273 const QRectF &bounds)
275 QSvgNode *node = scopeNode(id);
278 qCDebug(lcSvgHandler,
"Couldn't find node %s. Skipping rendering.",
qPrintable(id));
282 if (node->displayMode() == QSvgNode::NoneMode)
287 const QRectF elementBounds = node->bounds();
289 mapSourceToTarget(p, bounds, elementBounds);
290 QTransform originalTransform = p->worldTransform();
293 QPen pen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin);
294 pen.setMiterLimit(4);
296 p->setBrush(Qt::black);
297 p->setRenderHint(QPainter::Antialiasing);
298 p->setRenderHint(QPainter::SmoothPixmapTransform);
300 QStack<QSvgNode*> parentApplyStack;
301 QSvgNode *parent = node->parent();
303 parentApplyStack.push(parent);
304 parent = parent->parent();
307 for (
int i = parentApplyStack.size() - 1; i >= 0; --i)
308 parentApplyStack[i]->applyStyle(p, m_states);
312 QTransform currentTransform = p->worldTransform();
313 p->setWorldTransform(originalTransform);
315 node->draw(p, m_states);
317 p->setWorldTransform(currentTransform);
319 for (
int i = 0; i < parentApplyStack.size(); ++i)
320 parentApplyStack[i]->revertStyle(p, m_states);
327QSvgNode::Type QSvgDocument::type()
const
332void QSvgDocument::setWidth(
int len,
bool percent)
334 m_size.setWidth(len);
335 m_widthPercent = percent;
338void QSvgDocument::setHeight(
int len,
bool percent)
340 m_size.setHeight(len);
341 m_heightPercent = percent;
344void QSvgDocument::setPreserveAspectRatio(
bool on)
346 m_preserveAspectRatio = on;
349void QSvgDocument::setViewBox(
const QRectF &rect)
352 m_implicitViewBox = rect.isNull();
355QtSvg::Options QSvgDocument::options()
const
360void QSvgDocument::addSvgFont(QSvgFont *font)
362 m_fonts.insert(font->familyName(), font);
365QSvgFont * QSvgDocument::svgFont(
const QString &family)
const
367 return m_fonts[family];
370void QSvgDocument::addNamedNode(
const QString &id, QSvgNode *node)
372 m_namedNodes.insert(id, node);
375QSvgNode *QSvgDocument::namedNode(
const QString &id)
const
377 return m_namedNodes.value(id);
380void QSvgDocument::addNamedStyle(
const QString &id, QSvgPaintStyleProperty *style)
382 if (!m_namedStyles.contains(id))
383 m_namedStyles.insert(id, style);
385 qCWarning(lcSvgHandler) <<
"Duplicate unique style id:" << id;
388QSvgPaintStyleProperty *QSvgDocument::namedStyle(
const QString &id)
const
390 return m_namedStyles.value(id);
393void QSvgDocument::restartAnimation()
395 m_animator->restartAnimation();
398bool QSvgDocument::animated()
const
403void QSvgDocument::setAnimated(
bool a)
408void QSvgDocument::draw(QPainter *p)
413void QSvgDocument::drawCommand(QPainter *, QSvgExtraStates &)
415 qCDebug(lcSvgHandler) <<
"SVG Tiny does not support nested <svg> elements: ignored.";
421 qreal determinant = transform.determinant();
422 return qIsFinite(determinant);
425void QSvgDocument::mapSourceToTarget(QPainter *p,
const QRectF &targetRect,
const QRectF &sourceRect)
427 QTransform oldTransform = p->worldTransform();
429 QRectF target = targetRect;
430 if (target.isEmpty()) {
431 QPaintDevice *dev = p->device();
432 QRectF deviceRect(0, 0, dev->width(), dev->height());
433 if (deviceRect.isEmpty()) {
434 if (sourceRect.isEmpty())
435 target = QRectF(QPointF(0, 0), size());
437 target = QRectF(QPointF(0, 0), sourceRect.size());
443 QRectF source = sourceRect;
444 if (source.isEmpty())
447 if (source != target && !qFuzzyIsNull(source.width()) && !qFuzzyIsNull(source.height())) {
448 if (m_implicitViewBox || !preserveAspectRatio()) {
450 QTransform transform;
451 transform.scale(target.width() / source.width(),
452 target.height() / source.height());
453 QRectF c2 = transform.mapRect(source);
454 p->translate(target.x() - c2.x(),
455 target.y() - c2.y());
456 p->scale(target.width() / source.width(),
457 target.height() / source.height());
463 QSizeF viewBoxSize = source.size();
464 viewBoxSize.scale(target.width(), target.height(), Qt::KeepAspectRatio);
467 p->translate(target.x() + (target.width() - viewBoxSize.width()) / 2,
468 target.y() + (target.height() - viewBoxSize.height()) / 2);
470 p->scale(viewBoxSize.width() / source.width(),
471 viewBoxSize.height() / source.height());
474 p->translate(-source.x(), -source.y());
478 if (!isValidMatrix(p->worldTransform()))
479 p->setWorldTransform(oldTransform);
482QRectF QSvgDocument::boundsOnElement(
const QString &id)
const
484 const QSvgNode *node = scopeNode(id);
487 return node->bounds();
490bool QSvgDocument::elementExists(
const QString &id)
const
492 QSvgNode *node = scopeNode(id);
497QTransform QSvgDocument::transformForElement(
const QString &id)
const
499 QSvgNode *node = scopeNode(id);
502 qCDebug(lcSvgHandler,
"Couldn't find node %s. Skipping rendering.",
qPrintable(id));
508 node = node->parent();
510 if (node->m_style.transform)
511 t *= node->m_style.transform->qtransform();
512 node = node->parent();
518int QSvgDocument::currentFrame()
const
520 const double runningPercentage = qMin(currentElapsed() /
double(animationDuration()), 1.);
521 const int totalFrames = m_fps * animationDuration() / 1000;
522 return int(runningPercentage * totalFrames);
525void QSvgDocument::setCurrentFrame(
int frame)
527 const int totalFrames = m_fps * animationDuration() / 1000;
528 if (totalFrames == 0)
531 const int timeForFrame = frame * animationDuration() / totalFrames;
532 const int timeToAdd = timeForFrame - currentElapsed();
533 m_animator->setAnimatorTime(timeToAdd);
536void QSvgDocument::setFramesPerSecond(
int num)
541QSharedPointer<QSvgAbstractAnimator> QSvgDocument::animator()
const
546bool QSvgDocument::isLikelySvg(QIODevice *device,
bool *isCompressed)
548 constexpr int bufSize = 4096;
550 char inflateBuf[bufSize];
551 bool useInflateBuf =
false;
552 int readLen = device->peek(buf, bufSize);
555#ifndef QT_NO_COMPRESS
556 if (quint8(buf[0]) == 0x1f && quint8(buf[1]) == 0x8b) {
559 zlibStream.avail_in = readLen;
560 zlibStream.next_out =
reinterpret_cast<Bytef *>(inflateBuf);
561 zlibStream.avail_out = bufSize;
562 zlibStream.next_in =
reinterpret_cast<Bytef *>(buf);
563 zlibStream.zalloc = Z_NULL;
564 zlibStream.zfree = Z_NULL;
565 zlibStream.opaque = Z_NULL;
566 if (inflateInit2(&zlibStream, MAX_WBITS + 16) != Z_OK)
568 int zlibResult = inflate(&zlibStream, Z_NO_FLUSH);
569 inflateEnd(&zlibStream);
570 if ((zlibResult != Z_OK && zlibResult != Z_STREAM_END) || zlibStream.total_out < 8)
572 readLen = zlibStream.total_out;
574 *isCompressed =
true;
575 useInflateBuf =
true;
578 return hasSvgHeader(QByteArray::fromRawData(useInflateBuf ? inflateBuf : buf, readLen));
#define qPrintable(string)
static QByteArray qt_inflateSvgzDataFrom(QIODevice *device, bool doCheckContent=true)
static bool isValidMatrix(const QTransform &transform)
static bool hasSvgHeader(const QByteArray &buf)