23using namespace Qt::StringLiterals;
25QSvgDocument::QSvgDocument(QtSvg::Options options, QtSvg::AnimatorType type)
26 : QSvgStructureNode(0)
27 , m_widthPercent(
false)
28 , m_heightPercent(
false)
34 m_states.trustedSource = m_options.testFlag(QtSvg::AssumeTrustedSource);
35 bool animationEnabled = !m_options.testFlag(QtSvg::DisableAnimations);
37 case QtSvg::AnimatorType::Automatic:
39 m_animator.reset(
new QSvgAnimator);
41 case QtSvg::AnimatorType::Controlled:
43 m_animator.reset(
new QSvgAnimationController);
47QSvgDocument::~QSvgDocument()
51 if (!m_states.trustedSource)
58 QString h = s.readAll();
59 QStringView th = QStringView(h).trimmed();
61 if (th.startsWith(
"<svg"_L1) || th.startsWith(
"<!DOCTYPE svg"_L1))
63 else if (th.startsWith(
"<?xml"_L1) || th.startsWith(
"<!--"_L1))
64 matched = th.contains(
"<!DOCTYPE svg"_L1) || th.contains(
"<svg"_L1);
70# ifdef QT_BUILD_INTERNAL
71Q_AUTOTEST_EXPORT QByteArray qt_inflateGZipDataFrom(QIODevice *device)
73 return qt_inflateSvgzDataFrom(device,
false);
82 if (!device->isOpen())
83 device->open(QIODevice::ReadOnly);
85 Q_ASSERT(device->isOpen() && device->isReadable());
87 static const int CHUNK_SIZE = 4096;
88 int zlibResult = Z_OK;
95 zlibStream.next_in = Z_NULL;
96 zlibStream.avail_in = 0;
97 zlibStream.avail_out = 0;
98 zlibStream.zalloc = Z_NULL;
99 zlibStream.zfree = Z_NULL;
100 zlibStream.opaque = Z_NULL;
103 if (inflateInit2(&zlibStream, MAX_WBITS + 16) != Z_OK) {
104 qCWarning(lcSvgHandler,
"Cannot initialize zlib, because: %s",
105 (zlibStream.msg != NULL ? zlibStream.msg :
"Unknown error"));
109 bool stillMoreWorkToDo =
true;
110 while (stillMoreWorkToDo) {
112 if (!zlibStream.avail_in) {
113 source = device->read(CHUNK_SIZE);
115 if (source.isEmpty())
118 zlibStream.avail_in = source.size();
119 zlibStream.next_in =
reinterpret_cast<Bytef*>(source.data());
124 int oldSize = destination.size();
125 if (oldSize > INT_MAX - CHUNK_SIZE) {
126 inflateEnd(&zlibStream);
127 qCWarning(lcSvgHandler,
"Error while inflating gzip file: integer size overflow");
131 destination.resize(oldSize + CHUNK_SIZE);
132 zlibStream.next_out =
reinterpret_cast<Bytef*>(
133 destination.data() + oldSize - zlibStream.avail_out);
134 zlibStream.avail_out += CHUNK_SIZE;
136 zlibResult = inflate(&zlibStream, Z_NO_FLUSH);
137 switch (zlibResult) {
142 inflateEnd(&zlibStream);
143 qCWarning(lcSvgHandler,
"Error while inflating gzip file: %s",
144 (zlibStream.msg != NULL ? zlibStream.msg :
"Unknown error"));
151 }
while (!zlibStream.avail_out);
153 if (doCheckContent) {
155 const qsizetype destinationContents =
std::min(destination.size(),
static_cast<qsizetype>(zlibStream.total_out));
156 Q_ASSERT(destinationContents ==
static_cast<qsizetype>(zlibStream.total_out));
157 if (!hasSvgHeader(QByteArray::fromRawData(destination.constData(), destinationContents))) {
158 inflateEnd(&zlibStream);
159 qCWarning(lcSvgHandler,
"Error while inflating gzip file: SVG format check failed");
162 doCheckContent =
false;
165 if (zlibResult == Z_STREAM_END) {
167 if (!(zlibStream.avail_in && inflateReset(&zlibStream) == Z_OK))
168 stillMoreWorkToDo =
false;
173 destination.chop(zlibStream.avail_out);
175 inflateEnd(&zlibStream);
179static QByteArray qt_inflateSvgzDataFrom(QIODevice *)
185std::unique_ptr<QSvgDocument> QSvgDocument::load(
const QString &fileName, QtSvg::Options options,
186 QtSvg::AnimatorType type)
188 std::unique_ptr<QSvgDocument> doc;
189 QFile file(fileName);
190 if (!file.open(QFile::ReadOnly)) {
191 qCWarning(lcSvgHandler,
"Cannot open file '%s', because: %s",
196 if (fileName.endsWith(QLatin1String(
".svgz"), Qt::CaseInsensitive)
197 || fileName.endsWith(QLatin1String(
".svg.gz"), Qt::CaseInsensitive)) {
198 return load(qt_inflateSvgzDataFrom(&file));
201 QSvgHandler handler(&file, options, type);
203 doc = handler.takeDocument();
205 doc->m_animator->setAnimationDuration(handler.animationDuration());
207 qCWarning(lcSvgHandler,
"Cannot read file '%s', because: %s (line %d)",
213std::unique_ptr<QSvgDocument> QSvgDocument::load(
const QByteArray &contents, QtSvg::Options options,
214 QtSvg::AnimatorType type)
216 std::unique_ptr<QSvgDocument> doc;
219 if (contents.startsWith(
"\x1f\x8b")) {
221 buffer.setData(contents);
222 svg = qt_inflateSvgzDataFrom(&buffer);
231 buffer.open(QIODevice::ReadOnly);
232 QSvgHandler handler(&buffer, options, type);
235 doc = handler.takeDocument();
237 doc->m_animator->setAnimationDuration(handler.animationDuration());
242std::unique_ptr<QSvgDocument> QSvgDocument::load(QXmlStreamReader *contents, QtSvg::Options options,
243 QtSvg::AnimatorType type)
245 QSvgHandler handler(contents, options, type);
247 std::unique_ptr<QSvgDocument> doc;
249 doc = handler.takeDocument();
251 doc->m_animator->setAnimationDuration(handler.animationDuration());
256void QSvgDocument::draw(QPainter *p,
const QRectF &bounds)
258 if (displayMode() == QSvgNode::NoneMode)
264 mapSourceToTarget(p, bounds);
266 applyStyle(p, m_states);
267 for (
const auto &node : renderers()) {
268 if ((node->isVisible()) && (node->displayMode() != QSvgNode::NoneMode))
269 node->draw(p, m_states);
271 revertStyle(p, m_states);
276void QSvgDocument::draw(QPainter *p,
const QString &id,
277 const QRectF &bounds)
279 QSvgNode *node = namedNode(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(
this);
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 QSvgDocument::type()
const
336void QSvgDocument::setWidth(
int len,
bool percent)
338 m_size.setWidth(len);
339 m_widthPercent = percent;
342void QSvgDocument::setHeight(
int len,
bool percent)
344 m_size.setHeight(len);
345 m_heightPercent = percent;
348void QSvgDocument::setPreserveAspectRatio(
bool on)
350 m_preserveAspectRatio = on;
353void QSvgDocument::setViewBox(
const QRectF &rect)
356 m_implicitViewBox = rect.isNull();
359QtSvg::Options QSvgDocument::options()
const
364void QSvgDocument::addSvgFont(
const QString &name, QSvgFontPtr font)
369 if (!m_fonts.insert({ name, std::move(font) }).second)
370 qCWarning(lcSvgHandler) <<
"Font Family already added: " << name;
373QSvgFont *QSvgDocument::svgFont(
const QString &family)
const
375 auto it = m_fonts.find(family);
376 return it != m_fonts.end() ? it->second.get() :
nullptr;
379void QSvgDocument::addNamedNode(
const QString &id, QSvgNode *node)
381 m_namedNodes.insert(id, node);
384QSvgNode *QSvgDocument::namedNode(
const QString &id)
const
386 return m_namedNodes.value(id);
389void QSvgDocument::addPaintServer(QSvgPaintServerSharedPtr paintServer,
const QString &id)
394 if (!m_paintServers.insert({ id, paintServer }).second)
395 qCWarning(lcSvgHandler) <<
"Duplicate unique style id:" << id;
398QSvgPaintServerSharedPtr QSvgDocument::paintServer(QStringView id)
const
400 auto it = m_paintServers.find(id.toString());
401 return it != m_paintServers.end() ? it->second :
nullptr;
404void QSvgDocument::restartAnimation()
407 m_animator->restartAnimation();
410bool QSvgDocument::animated()
const
415void QSvgDocument::setAnimated(
bool a)
420void QSvgDocument::draw(QPainter *p)
425void QSvgDocument::drawCommand(QPainter *, QSvgExtraStates &)
427 qCDebug(lcSvgHandler) <<
"SVG Tiny does not support nested <svg> elements: ignored.";
433 qreal determinant = transform.determinant();
434 return qIsFinite(determinant);
437void QSvgDocument::mapSourceToTarget(QPainter *p,
const QRectF &targetRect,
const QRectF &sourceRect)
439 QTransform oldTransform = p->worldTransform();
441 QRectF target = targetRect;
442 if (target.isEmpty()) {
443 QPaintDevice *dev = p->device();
444 QRectF deviceRect(0, 0, dev->width(), dev->height());
445 if (deviceRect.isEmpty()) {
446 if (sourceRect.isEmpty())
447 target = QRectF(QPointF(0, 0), size());
449 target = QRectF(QPointF(0, 0), sourceRect.size());
455 QRectF source = sourceRect;
456 if (source.isEmpty())
459 if (source != target && !qFuzzyIsNull(source.width()) && !qFuzzyIsNull(source.height())) {
460 if (m_implicitViewBox || !preserveAspectRatio()) {
462 QTransform transform;
463 transform.scale(target.width() / source.width(),
464 target.height() / source.height());
465 QRectF c2 = transform.mapRect(source);
466 p->translate(target.x() - c2.x(),
467 target.y() - c2.y());
468 p->scale(target.width() / source.width(),
469 target.height() / source.height());
475 QSizeF viewBoxSize = source.size();
476 viewBoxSize.scale(target.width(), target.height(), Qt::KeepAspectRatio);
479 p->translate(target.x() + (target.width() - viewBoxSize.width()) / 2,
480 target.y() + (target.height() - viewBoxSize.height()) / 2);
482 p->scale(viewBoxSize.width() / source.width(),
483 viewBoxSize.height() / source.height());
486 p->translate(-source.x(), -source.y());
490 if (!isValidMatrix(p->worldTransform()))
491 p->setWorldTransform(oldTransform);
494QRectF QSvgDocument::boundsOnElement(
const QString &id)
const
496 const QSvgNode *node = namedNode(id);
499 return node->bounds(
this);
502bool QSvgDocument::elementExists(
const QString &id)
const
504 return bool(namedNode(id));
507QTransform QSvgDocument::transformForElement(
const QString &id)
const
509 QSvgNode *node = namedNode(id);
512 qCDebug(lcSvgHandler,
"Couldn't find node %s. Skipping rendering.",
qPrintable(id));
518 node = node->parent();
520 if (
auto prop = node->m_style.property(QSvgStyleProperty::Transform))
521 t *=
static_cast<QSvgTransformStyle*>(prop)->qtransform();
522 node = node->parent();
528int QSvgDocument::currentFrame()
const
530 const double runningPercentage = qMin(currentElapsed() /
double(animationDuration()), 1.);
531 const int totalFrames = m_fps * animationDuration() / 1000;
532 return int(runningPercentage * totalFrames);
535void QSvgDocument::setCurrentFrame(
int frame)
540 const int totalFrames = m_fps * animationDuration() / 1000;
541 if (totalFrames == 0)
544 const int timeForFrame = frame * animationDuration() / totalFrames;
545 const int timeToAdd = timeForFrame - currentElapsed();
546 m_animator->setAnimatorTime(timeToAdd);
549void QSvgDocument::setFramesPerSecond(
int num)
554QSharedPointer<QSvgAbstractAnimator> QSvgDocument::animator()
const
559bool QSvgDocument::isLikelySvg(QIODevice *device,
bool *isCompressed)
561 constexpr int bufSize = 4096;
563 char inflateBuf[bufSize];
564 bool useInflateBuf =
false;
565 int readLen = device->peek(buf, bufSize);
568#ifndef QT_NO_COMPRESS
569 if (quint8(buf[0]) == 0x1f && quint8(buf[1]) == 0x8b) {
572 zlibStream.avail_in = readLen;
573 zlibStream.next_out =
reinterpret_cast<Bytef *>(inflateBuf);
574 zlibStream.avail_out = bufSize;
575 zlibStream.next_in =
reinterpret_cast<Bytef *>(buf);
576 zlibStream.zalloc = Z_NULL;
577 zlibStream.zfree = Z_NULL;
578 zlibStream.opaque = Z_NULL;
579 if (inflateInit2(&zlibStream, MAX_WBITS + 16) != Z_OK)
581 int zlibResult = inflate(&zlibStream, Z_NO_FLUSH);
582 inflateEnd(&zlibStream);
583 if ((zlibResult != Z_OK && zlibResult != Z_STREAM_END) || zlibStream.total_out < 8)
585 readLen = zlibStream.total_out;
587 *isCompressed =
true;
588 useInflateBuf =
true;
591 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)