24using namespace Qt::StringLiterals;
26QSvgTinyDocument::QSvgTinyDocument(QtSvg::Options options, QtSvg::AnimatorType type)
27 : QSvgStructureNode(0)
28 , m_widthPercent(
false)
29 , m_heightPercent(
false)
34 bool animationEnabled = !m_options.testFlag(QtSvg::DisableAnimations);
36 case QtSvg::AnimatorType::Automatic:
38 m_animator.reset(
new QSvgAnimator);
40 case QtSvg::AnimatorType::Controlled:
42 m_animator.reset(
new QSvgAnimationController);
46QSvgTinyDocument::~QSvgTinyDocument()
53 QString h = s.readAll();
54 QStringView th = QStringView(h).trimmed();
56 if (th.startsWith(
"<svg"_L1) || th.startsWith(
"<!DOCTYPE svg"_L1))
58 else if (th.startsWith(
"<?xml"_L1) || th.startsWith(
"<!--"_L1))
59 matched = th.contains(
"<!DOCTYPE svg"_L1) || th.contains(
"<svg"_L1);
65# ifdef QT_BUILD_INTERNAL
66Q_AUTOTEST_EXPORT QByteArray qt_inflateGZipDataFrom(QIODevice *device)
68 return qt_inflateSvgzDataFrom(device,
false);
77 if (!device->isOpen())
78 device->open(QIODevice::ReadOnly);
80 Q_ASSERT(device->isOpen() && device->isReadable());
82 static const int CHUNK_SIZE = 4096;
83 int zlibResult = Z_OK;
90 zlibStream.next_in = Z_NULL;
91 zlibStream.avail_in = 0;
92 zlibStream.avail_out = 0;
93 zlibStream.zalloc = Z_NULL;
94 zlibStream.zfree = Z_NULL;
95 zlibStream.opaque = Z_NULL;
98 if (inflateInit2(&zlibStream, MAX_WBITS + 16) != Z_OK) {
99 qCWarning(lcSvgHandler,
"Cannot initialize zlib, because: %s",
100 (zlibStream.msg != NULL ? zlibStream.msg :
"Unknown error"));
104 bool stillMoreWorkToDo =
true;
105 while (stillMoreWorkToDo) {
107 if (!zlibStream.avail_in) {
108 source = device->read(CHUNK_SIZE);
110 if (source.isEmpty())
113 zlibStream.avail_in = source.size();
114 zlibStream.next_in =
reinterpret_cast<Bytef*>(source.data());
119 int oldSize = destination.size();
120 if (oldSize > INT_MAX - CHUNK_SIZE) {
121 inflateEnd(&zlibStream);
122 qCWarning(lcSvgHandler,
"Error while inflating gzip file: integer size overflow");
126 destination.resize(oldSize + CHUNK_SIZE);
127 zlibStream.next_out =
reinterpret_cast<Bytef*>(
128 destination.data() + oldSize - zlibStream.avail_out);
129 zlibStream.avail_out += CHUNK_SIZE;
131 zlibResult = inflate(&zlibStream, Z_NO_FLUSH);
132 switch (zlibResult) {
137 inflateEnd(&zlibStream);
138 qCWarning(lcSvgHandler,
"Error while inflating gzip file: %s",
139 (zlibStream.msg != NULL ? zlibStream.msg :
"Unknown error"));
146 }
while (!zlibStream.avail_out);
148 if (doCheckContent) {
150 const qsizetype destinationContents =
std::min(destination.size(),
static_cast<qsizetype>(zlibStream.total_out));
151 Q_ASSERT(destinationContents ==
static_cast<qsizetype>(zlibStream.total_out));
152 if (!hasSvgHeader(QByteArray::fromRawData(destination.constData(), destinationContents))) {
153 inflateEnd(&zlibStream);
154 qCWarning(lcSvgHandler,
"Error while inflating gzip file: SVG format check failed");
157 doCheckContent =
false;
160 if (zlibResult == Z_STREAM_END) {
162 if (!(zlibStream.avail_in && inflateReset(&zlibStream) == Z_OK))
163 stillMoreWorkToDo =
false;
168 destination.chop(zlibStream.avail_out);
170 inflateEnd(&zlibStream);
174static QByteArray qt_inflateSvgzDataFrom(QIODevice *)
180QSvgTinyDocument *QSvgTinyDocument::load(
const QString &fileName, QtSvg::Options options,
181 QtSvg::AnimatorType type)
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 QSvgTinyDocument *doc =
nullptr;
196 QSvgHandler handler(&file, options, type);
198 doc = handler.document();
199 doc->m_animator->setAnimationDuration(handler.animationDuration());
201 qCWarning(lcSvgHandler,
"Cannot read file '%s', because: %s (line %d)",
203 delete handler.document();
208QSvgTinyDocument *QSvgTinyDocument::load(
const QByteArray &contents, QtSvg::Options options,
209 QtSvg::AnimatorType type)
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);
228 QSvgTinyDocument *doc =
nullptr;
230 doc = handler.document();
231 doc->m_animator->setAnimationDuration(handler.animationDuration());
233 delete handler.document();
238QSvgTinyDocument *QSvgTinyDocument::load(QXmlStreamReader *contents, QtSvg::Options options,
239 QtSvg::AnimatorType type)
241 QSvgHandler handler(contents, options, type);
243 QSvgTinyDocument *doc =
nullptr;
245 doc = handler.document();
246 doc->m_animator->setAnimationDuration(handler.animationDuration());
248 delete handler.document();
253void QSvgTinyDocument::draw(QPainter *p,
const QRectF &bounds)
255 if (displayMode() == QSvgNode::NoneMode)
261 mapSourceToTarget(p, bounds);
263 QList<QSvgNode*>::iterator itr = m_renderers.begin();
264 applyStyle(p, m_states);
265 while (itr != m_renderers.end()) {
266 QSvgNode *node = *itr;
267 if ((node->isVisible()) && (node->displayMode() != QSvgNode::NoneMode))
268 node->draw(p, m_states);
271 revertStyle(p, m_states);
276void QSvgTinyDocument::draw(QPainter *p,
const QString &id,
277 const QRectF &bounds)
279 QSvgNode *node = scopeNode(id);
282 qCDebug(lcSvgHandler,
"Couldn't find node %s. Skipping rendering.",
qPrintable(id));
286 if (node->displayMode() == QSvgNode::NoneMode)
291 const QRectF elementBounds = node->bounds();
293 mapSourceToTarget(p, bounds, elementBounds);
294 QTransform originalTransform = p->worldTransform();
297 QPen pen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin);
298 pen.setMiterLimit(4);
300 p->setBrush(Qt::black);
301 p->setRenderHint(QPainter::Antialiasing);
302 p->setRenderHint(QPainter::SmoothPixmapTransform);
304 QStack<QSvgNode*> parentApplyStack;
305 QSvgNode *parent = node->parent();
307 parentApplyStack.push(parent);
308 parent = parent->parent();
311 for (
int i = parentApplyStack.size() - 1; i >= 0; --i)
312 parentApplyStack[i]->applyStyle(p, m_states);
316 QTransform currentTransform = p->worldTransform();
317 p->setWorldTransform(originalTransform);
319 node->draw(p, m_states);
321 p->setWorldTransform(currentTransform);
323 for (
int i = 0; i < parentApplyStack.size(); ++i)
324 parentApplyStack[i]->revertStyle(p, m_states);
331QSvgNode::Type QSvgTinyDocument::type()
const
336void QSvgTinyDocument::setWidth(
int len,
bool percent)
338 m_size.setWidth(len);
339 m_widthPercent = percent;
342void QSvgTinyDocument::setHeight(
int len,
bool percent)
344 m_size.setHeight(len);
345 m_heightPercent = percent;
348void QSvgTinyDocument::setPreserveAspectRatio(
bool on)
350 m_preserveAspectRatio = on;
353void QSvgTinyDocument::setViewBox(
const QRectF &rect)
356 m_implicitViewBox = rect.isNull();
359QtSvg::Options QSvgTinyDocument::options()
const
364void QSvgTinyDocument::addSvgFont(QSvgFont *font)
366 m_fonts.insert(font->familyName(), font);
369QSvgFont * QSvgTinyDocument::svgFont(
const QString &family)
const
371 return m_fonts[family];
374void QSvgTinyDocument::addNamedNode(
const QString &id, QSvgNode *node)
376 m_namedNodes.insert(id, node);
379QSvgNode *QSvgTinyDocument::namedNode(
const QString &id)
const
381 return m_namedNodes.value(id);
384void QSvgTinyDocument::addNamedStyle(
const QString &id, QSvgPaintStyleProperty *style)
386 if (!m_namedStyles.contains(id))
387 m_namedStyles.insert(id, style);
389 qCWarning(lcSvgHandler) <<
"Duplicate unique style id:" << id;
392QSvgPaintStyleProperty *QSvgTinyDocument::namedStyle(
const QString &id)
const
394 return m_namedStyles.value(id);
397void QSvgTinyDocument::restartAnimation()
399 m_animator->restartAnimation();
402bool QSvgTinyDocument::animated()
const
407void QSvgTinyDocument::setAnimated(
bool a)
412void QSvgTinyDocument::draw(QPainter *p)
417void QSvgTinyDocument::drawCommand(QPainter *, QSvgExtraStates &)
419 qCDebug(lcSvgHandler) <<
"SVG Tiny does not support nested <svg> elements: ignored.";
425 qreal determinant = transform.determinant();
426 return qIsFinite(determinant);
429void QSvgTinyDocument::mapSourceToTarget(QPainter *p,
const QRectF &targetRect,
const QRectF &sourceRect)
431 QTransform oldTransform = p->worldTransform();
433 QRectF target = targetRect;
434 if (target.isEmpty()) {
435 QPaintDevice *dev = p->device();
436 QRectF deviceRect(0, 0, dev->width(), dev->height());
437 if (deviceRect.isEmpty()) {
438 if (sourceRect.isEmpty())
439 target = QRectF(QPointF(0, 0), size());
441 target = QRectF(QPointF(0, 0), sourceRect.size());
447 QRectF source = sourceRect;
448 if (source.isEmpty())
451 if (source != target && !qFuzzyIsNull(source.width()) && !qFuzzyIsNull(source.height())) {
452 if (m_implicitViewBox || !preserveAspectRatio()) {
454 QTransform transform;
455 transform.scale(target.width() / source.width(),
456 target.height() / source.height());
457 QRectF c2 = transform.mapRect(source);
458 p->translate(target.x() - c2.x(),
459 target.y() - c2.y());
460 p->scale(target.width() / source.width(),
461 target.height() / source.height());
467 QSizeF viewBoxSize = source.size();
468 viewBoxSize.scale(target.width(), target.height(), Qt::KeepAspectRatio);
471 p->translate(target.x() + (target.width() - viewBoxSize.width()) / 2,
472 target.y() + (target.height() - viewBoxSize.height()) / 2);
474 p->scale(viewBoxSize.width() / source.width(),
475 viewBoxSize.height() / source.height());
478 p->translate(-source.x(), -source.y());
482 if (!isValidMatrix(p->worldTransform()))
483 p->setWorldTransform(oldTransform);
486QRectF QSvgTinyDocument::boundsOnElement(
const QString &id)
const
488 const QSvgNode *node = scopeNode(id);
491 return node->bounds();
494bool QSvgTinyDocument::elementExists(
const QString &id)
const
496 QSvgNode *node = scopeNode(id);
501QTransform QSvgTinyDocument::transformForElement(
const QString &id)
const
503 QSvgNode *node = scopeNode(id);
506 qCDebug(lcSvgHandler,
"Couldn't find node %s. Skipping rendering.",
qPrintable(id));
512 node = node->parent();
514 if (node->m_style.transform)
515 t *= node->m_style.transform->qtransform();
516 node = node->parent();
522int QSvgTinyDocument::currentFrame()
const
524 const double runningPercentage = qMin(currentElapsed() /
double(animationDuration()), 1.);
525 const int totalFrames = m_fps * animationDuration() / 1000;
526 return int(runningPercentage * totalFrames);
529void QSvgTinyDocument::setCurrentFrame(
int frame)
531 const int totalFrames = m_fps * animationDuration() / 1000;
532 if (totalFrames == 0)
535 const int timeForFrame = frame * animationDuration() / totalFrames;
536 const int timeToAdd = timeForFrame - currentElapsed();
537 m_animator->setAnimatorTime(timeToAdd);
540void QSvgTinyDocument::setFramesPerSecond(
int num)
545QSharedPointer<QSvgAbstractAnimator> QSvgTinyDocument::animator()
const
550bool QSvgTinyDocument::isLikelySvg(QIODevice *device,
bool *isCompressed)
552 constexpr int bufSize = 4096;
554 char inflateBuf[bufSize];
555 bool useInflateBuf =
false;
556 int readLen = device->peek(buf, bufSize);
559#ifndef QT_NO_COMPRESS
560 if (quint8(buf[0]) == 0x1f && quint8(buf[1]) == 0x8b) {
563 zlibStream.avail_in = readLen;
564 zlibStream.next_out =
reinterpret_cast<Bytef *>(inflateBuf);
565 zlibStream.avail_out = bufSize;
566 zlibStream.next_in =
reinterpret_cast<Bytef *>(buf);
567 zlibStream.zalloc = Z_NULL;
568 zlibStream.zfree = Z_NULL;
569 zlibStream.opaque = Z_NULL;
570 if (inflateInit2(&zlibStream, MAX_WBITS + 16) != Z_OK)
572 int zlibResult = inflate(&zlibStream, Z_NO_FLUSH);
573 inflateEnd(&zlibStream);
574 if ((zlibResult != Z_OK && zlibResult != Z_STREAM_END) || zlibStream.total_out < 8)
576 readLen = zlibStream.total_out;
578 *isCompressed =
true;
579 useInflateBuf =
true;
582 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)