Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qsvgnode.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// Qt-Security score:significant reason:default
4
5
6#include "qsvgnode_p.h"
9#include <QtSvg/private/qsvgpaintserver_p.h>
10
11#include <QLoggingCategory>
12#include<QElapsedTimer>
13#include <QtGui/qimageiohandler.h>
14
15#include "qdebug.h"
16
17#include <QtGui/private/qoutlinemapper_p.h>
18
19#include <vector>
20
22
23using namespace Qt::StringLiterals;
24
25#ifndef QT_NO_DEBUG
26Q_STATIC_LOGGING_CATEGORY(lcSvgTiming, "qt.svg.timing")
27#endif
28
29#if !defined(QT_SVG_SIZE_LIMIT)
30# define QT_SVG_SIZE_LIMIT QT_RASTER_COORD_LIMIT
31#endif
32
33QSvgNode::QSvgNode(QSvgNode *parent)
34 : m_parent(parent),
35 m_displayMode(BlockMode),
36 m_visible(true)
37{
38}
39
40QSvgNode::~QSvgNode()
41{
42
43}
44
45void QSvgNode::draw(QPainter *p, QSvgExtraStates &states)
46{
47#ifndef QT_NO_DEBUG
48 QElapsedTimer qtSvgTimer; qtSvgTimer.start();
49#endif
50
51 if (shouldDrawNode(p, states)) {
52 quint8 remainingDepth = states.trustedSource ? states.remainingNestedNodes
53 : states.remainingNestedNodes - 1;
54 QScopedValueRollback<quint8> nestedNodesGuard(states.remainingNestedNodes, remainingDepth);
55 if (states.remainingNestedNodes == 0) {
56 qCWarning(lcSvgDraw).noquote() << "Too many nested nodes at" << typeName()
57 << "exceeding max nested limit of" << QtSvg::renderingMaxNestedNodes << "."
58 << "Enable AssumeTrustedSource in QSvgHandler or set QT_SVG_DEFAULT_OPTIONS=2 to disable this check.";
59 return;
60 }
61
62 applyStyle(p, states);
63 applyAnimatedStyle(p, states);
64 QSvgNode *maskNode = this->hasMask() ? document()->namedNode(this->maskId()) : nullptr;
65 QSvgFilterContainer *filterNode = this->hasFilter() ? static_cast<QSvgFilterContainer*>(document()->namedNode(this->filterId()))
66 : nullptr;
67 if (filterNode && filterNode->type() == QSvgNode::Filter && filterNode->supported()) {
68 QTransform xf = p->transform();
69 p->resetTransform();
70 QRectF localRect = internalBounds(p, states);
71 p->setTransform(xf);
72 QRectF boundsRect = xf.mapRect(filterNode->filterRegion(localRect));
73 QImage proxy = drawIntoBuffer(p, states, boundsRect.toRect());
74 proxy = filterNode->applyFilter(proxy, p, localRect);
75 if (maskNode && maskNode->type() == QSvgNode::Mask) {
76 boundsRect = QRectF(proxy.offset(), proxy.size());
77 localRect = p->transform().inverted().mapRect(boundsRect);
78 QImage mask = static_cast<QSvgMask*>(maskNode)->createMask(p, states, localRect, &boundsRect);
79 applyMaskToBuffer(&proxy, mask);
80 }
81 applyBufferToCanvas(p, proxy);
82
83 } else if (maskNode && maskNode->type() == QSvgNode::Mask) {
84 QRectF boundsRect;
85 QImage mask = static_cast<QSvgMask*>(maskNode)->createMask(p, states, this, &boundsRect);
86 drawWithMask(p, states, mask, boundsRect.toRect());
87 } else if (!qFuzzyCompare(p->opacity(), qreal(1.0)) && requiresGroupRendering()) {
88 QTransform xf = p->transform();
89 p->resetTransform();
90
91 QRectF localRect = decoratedInternalBounds(p, states);
92 // adding safety border needed because of the antialiazing effects
93 QRectF boundsRect = xf.mapRect(localRect);
94 const int deltaX = boundsRect.width() * 0.1;
95 const int deltaY = boundsRect.height() * 0.1;
96 boundsRect = boundsRect.adjusted(-deltaX, -deltaY, deltaX, deltaY);
97
98 p->setTransform(xf);
99
100 QImage proxy = drawIntoBuffer(p, states, boundsRect.toAlignedRect());
101 applyBufferToCanvas(p, proxy);
102 } else {
103 if (separateFillStroke(p, states))
104 fillThenStroke(p, states);
105 else
106 drawCommand(p, states);
107
108 }
109 revertAnimatedStyle(p ,states);
110 revertStyle(p, states);
111 }
112
113#ifndef QT_NO_DEBUG
114 if (Q_UNLIKELY(lcSvgTiming().isDebugEnabled()))
115 qCDebug(lcSvgTiming) << "Drawing" << typeName() << "took" << (qtSvgTimer.nsecsElapsed() / 1000000.0f) << "ms";
116#endif
117}
118
119void QSvgNode::fillThenStroke(QPainter *p, QSvgExtraStates &states)
120{
121 qreal oldOpacity = p->opacity();
122 if (p->brush().style() != Qt::NoBrush) {
123 QPen oldPen = p->pen();
124 p->setPen(Qt::NoPen);
125 p->setOpacity(oldOpacity * states.fillOpacity);
126
127 drawCommand(p, states);
128
129 p->setPen(oldPen);
130 }
131 if (p->pen() != Qt::NoPen && p->pen().brush() != Qt::NoBrush && p->pen().widthF() != 0) {
132 QBrush oldBrush = p->brush();
133 p->setOpacity(oldOpacity * states.strokeOpacity);
134 p->setBrush(Qt::NoBrush);
135
136 drawCommand(p, states);
137
138 p->setBrush(oldBrush);
139 }
140 p->setOpacity(oldOpacity);
141}
142
143void QSvgNode::drawWithMask(QPainter *p, QSvgExtraStates &states, const QImage &mask, const QRect &boundsRect)
144{
145 QImage proxy = drawIntoBuffer(p, states, boundsRect);
146 if (proxy.isNull())
147 return;
148 applyMaskToBuffer(&proxy, mask);
149
150 p->save();
151 p->resetTransform();
152 p->drawImage(boundsRect, proxy);
153 p->restore();
154}
155
156QImage QSvgNode::drawIntoBuffer(QPainter *p, QSvgExtraStates &states, const QRect &boundsRect)
157{
158 QImage proxy;
159 if (!QImageIOHandler::allocateImage(boundsRect.size(), QImage::Format_ARGB32_Premultiplied, &proxy)) {
160 qCWarning(lcSvgDraw) << "The requested buffer size is too big, ignoring";
161 return proxy;
162 }
163 proxy.setOffset(boundsRect.topLeft());
164 proxy.fill(Qt::transparent);
165 QPainter proxyPainter(&proxy);
166 proxyPainter.setPen(p->pen());
167 proxyPainter.setBrush(p->brush());
168 proxyPainter.setFont(p->font());
169 proxyPainter.translate(-boundsRect.topLeft());
170 proxyPainter.setTransform(p->transform(), true);
171 proxyPainter.setRenderHints(p->renderHints());
172 if (separateFillStroke(p, states))
173 fillThenStroke(&proxyPainter, states);
174 else
175 drawCommand(&proxyPainter, states);
176 return proxy;
177}
178
179void QSvgNode::applyMaskToBuffer(QImage *proxy, const QImage &mask) const
180{
181 QPainter proxyPainter(proxy);
182 proxyPainter.setCompositionMode(QPainter::CompositionMode_DestinationOut);
183 proxyPainter.resetTransform();
184 proxyPainter.drawImage(QRect(0, 0, mask.width(), mask.height()), mask);
185}
186
187void QSvgNode::applyBufferToCanvas(QPainter *p, const QImage &proxy) const
188{
189 QTransform xf = p->transform();
190 p->resetTransform();
191 p->drawImage(QRect(proxy.offset(), proxy.size()), proxy);
192 p->setTransform(xf);
193}
194
195bool QSvgNode::isDescendantOf(const QSvgNode *parent) const
196{
197 const QSvgNode *n = this;
198 while (n) {
199 if (n == parent)
200 return true;
201 n = n->m_parent;
202 }
203 return false;
204}
205
206void QSvgNode::appendStyleProperty(QSvgStylePropertyPtr prop)
207{
208 m_style.appendProperty(std::move(prop));
209}
210
211void QSvgNode::applyStyle(QPainter *p, QSvgExtraStates &states) const
212{
213 m_style.apply(p, this, states);
214}
215
216/*!
217 \internal
218
219 Apply the styles of all parents to the painter and the states.
220 The styles are applied from the top level node to the current node.
221 This function can be used to set the correct style for a node
222 if it's draw function is triggered out of the ordinary draw context,
223 for example the mask node, that is cross-referenced.
224*/
225void QSvgNode::applyStyleRecursive(QPainter *p, QSvgExtraStates &states) const
226{
227 std::vector<const QSvgNode *> parents;
228
229 const QSvgNode *current = this;
230 do {
231 parents.push_back(current);
232 current = current->parent();
233 } while (current);
234
235 for (auto i = parents.crbegin(); i != parents.crend(); ++i)
236 (*i)->applyStyle(p, states);
237}
238
239void QSvgNode::revertStyle(QPainter *p, QSvgExtraStates &states) const
240{
241 m_style.revert(p, states);
242}
243
244void QSvgNode::revertStyleRecursive(QPainter *p, QSvgExtraStates &states) const
245{
246 const QSvgNode *current = this;
247 do {
248 current->revertStyle(p, states);
249 current = current->parent();
250 } while (current);
251}
252
253void QSvgNode::applyAnimatedStyle(QPainter *p, QSvgExtraStates &states) const
254{
255 if (document()->animated())
256 m_animatedStyle.apply(p, this, states);
257}
258
259void QSvgNode::revertAnimatedStyle(QPainter *p, QSvgExtraStates &states) const
260{
261 if (document()->animated())
262 m_animatedStyle.revert(p, states);
263}
264
265QSvgStyleProperty *QSvgNode::styleProperty(QSvgStyleProperty::Type type) const
266{
267 const QSvgNode *node = this;
268 while (node) {
269 if (QSvgStyleProperty *prop = node->m_style.property(type))
270 return prop;
271
272 node = node->parent();
273 }
274
275 return nullptr;
276}
277
278QRectF QSvgNode::internalFastBounds(QPainter *p, QSvgExtraStates &states) const
279{
280 return internalBounds(p, states);
281}
282
283QRectF QSvgNode::internalBounds(QPainter *, QSvgExtraStates &) const
284{
285 return QRectF(0, 0, 0, 0);
286}
287
288QRectF QSvgNode::bounds() const
289{
290 if (!m_cachedBounds.isEmpty())
291 return m_cachedBounds;
292
293 QImage dummy(1, 1, QImage::Format_RGB32);
294 QPainter p(&dummy);
295 initPainter(&p);
296 QSvgExtraStates states;
297
298 if (parent())
299 parent()->applyStyleRecursive(&p, states);
300 p.setWorldTransform(QTransform());
301 m_cachedBounds = bounds(&p, states);
302 if (parent()) // always revert the style to not store old transformations
303 parent()->revertStyleRecursive(&p, states);
304 return m_cachedBounds;
305}
306
307QSvgDocument * QSvgNode::document() const
308{
309 QSvgDocument *doc = nullptr;
310 QSvgNode *node = const_cast<QSvgNode*>(this);
311 while (node && node->type() != QSvgNode::Doc) {
312 node = node->parent();
313 }
314 doc = static_cast<QSvgDocument*>(node);
315
316 return doc;
317}
318
319QLatin1StringView QSvgNode::typeName() const
320{
321 switch (type()) {
322 case Doc: return "svg"_L1;
323 case Group: return "g"_L1;
324 case Defs: return "defs"_L1;
325 case Switch: return "switch"_L1;
326 case AnimateColor: return "animateColor"_L1;
327 case AnimateTransform: return "animateTransform"_L1;
328 case Circle: return "circle"_L1;
329 case Ellipse: return "ellipse"_L1;
330 case Image: return "image"_L1;
331 case Line: return "line"_L1;
332 case Path: return "path"_L1;
333 case Polygon: return "polygon"_L1;
334 case Polyline: return "polyline"_L1;
335 case Rect: return "rect"_L1;
336 case Text: return "text"_L1;
337 case Textarea: return "textarea"_L1;
338 case Tspan: return "tspan"_L1;
339 case Use: return "use"_L1;
340 case Video: return "video"_L1;
341 case Mask: return "mask"_L1;
342 case Symbol: return "symbol"_L1;
343 case Marker: return "marker"_L1;
344 case Pattern: return "pattern"_L1;
345 case Filter: return "filter"_L1;
346 case FeMerge: return "feMerge"_L1;
347 case FeMergenode: return "feMergeNode"_L1;
348 case FeColormatrix: return "feColorMatrix"_L1;
349 case FeGaussianblur: return "feGaussianBlur"_L1;
350 case FeOffset: return "feOffset"_L1;
351 case FeComposite: return "feComposite"_L1;
352 case FeFlood: return "feFlood"_L1;
353 case FeBlend: return "feBlend"_L1;
354 case FeUnsupported: return "feUnsupported"_L1;
355 }
356 return "unknown"_L1;
357}
358
359void QSvgNode::setRequiredFeatures(const QStringList &lst)
360{
361 m_requiredFeatures = lst;
362}
363
364const QStringList & QSvgNode::requiredFeatures() const
365{
366 return m_requiredFeatures;
367}
368
369void QSvgNode::setRequiredExtensions(const QStringList &lst)
370{
371 m_requiredExtensions = lst;
372}
373
374const QStringList & QSvgNode::requiredExtensions() const
375{
376 return m_requiredExtensions;
377}
378
379void QSvgNode::setRequiredLanguages(const QStringList &lst)
380{
381 m_requiredLanguages = lst;
382}
383
384const QStringList & QSvgNode::requiredLanguages() const
385{
386 return m_requiredLanguages;
387}
388
389void QSvgNode::setRequiredFormats(const QStringList &lst)
390{
391 m_requiredFormats = lst;
392}
393
394const QStringList & QSvgNode::requiredFormats() const
395{
396 return m_requiredFormats;
397}
398
399void QSvgNode::setRequiredFonts(const QStringList &lst)
400{
401 m_requiredFonts = lst;
402}
403
404const QStringList & QSvgNode::requiredFonts() const
405{
406 return m_requiredFonts;
407}
408
409void QSvgNode::setVisible(bool visible)
410{
411 //propagate visibility change of true to the parent
412 //not propagating false is just a small performance
413 //degradation since we'll iterate over children without
414 //drawing any of them
415 if (m_parent && visible && !m_parent->isVisible())
416 m_parent->setVisible(true);
417
418 m_visible = visible;
419}
420
421QRectF QSvgNode::bounds(QPainter *p, QSvgExtraStates &states) const
422{
423 applyStyle(p, states);
424 QRectF rect = internalBounds(p, states);
425 revertStyle(p, states);
426 return rect;
427}
428
429QRectF QSvgNode::decoratedInternalBounds(QPainter *p, QSvgExtraStates &states) const
430{
431 return filterRegion(internalBounds(p, states));
432}
433
434QRectF QSvgNode::decoratedBounds(QPainter *p, QSvgExtraStates &states) const
435{
436 applyStyle(p, states);
437 QRectF rect = decoratedInternalBounds(p, states);
438 revertStyle(p, states);
439 return rect;
440}
441
442void QSvgNode::setNodeId(const QString &i)
443{
444 m_id = i;
445}
446
447void QSvgNode::setXmlClass(const QString &str)
448{
449 m_class = str;
450}
451
452QString QSvgNode::maskId() const
453{
454 return m_maskId;
455}
456
457void QSvgNode::setMaskId(const QString &str)
458{
459 m_maskId = str;
460}
461
462bool QSvgNode::hasMask() const
463{
464 return !m_maskId.isEmpty();
465}
466
467QString QSvgNode::filterId() const
468{
469 return m_filterId;
470}
471
472void QSvgNode::setFilterId(const QString &str)
473{
474 m_filterId = str;
475}
476
477bool QSvgNode::hasFilter() const
478{
479 return !m_filterId.isEmpty();
480}
481
482QString QSvgNode::markerStartId() const
483{
484 return m_markerStartId;
485}
486
487void QSvgNode::setMarkerStartId(const QString &str)
488{
489 m_markerStartId = str;
490}
491
492bool QSvgNode::hasMarkerStart() const
493{
494 return !m_markerStartId.isEmpty();
495}
496
497QString QSvgNode::markerMidId() const
498{
499 return m_markerMidId;
500}
501
502void QSvgNode::setMarkerMidId(const QString &str)
503{
504 m_markerMidId = str;
505}
506
507bool QSvgNode::hasMarkerMid() const
508{
509 return !m_markerMidId.isEmpty();
510}
511
512QString QSvgNode::markerEndId() const
513{
514 return m_markerEndId;
515}
516
517void QSvgNode::setMarkerEndId(const QString &str)
518{
519 m_markerEndId = str;
520}
521
522bool QSvgNode::hasMarkerEnd() const
523{
524 return !m_markerEndId.isEmpty();
525}
526
527bool QSvgNode::hasAnyMarker() const
528{
529 return hasMarkerStart() || hasMarkerMid() || hasMarkerEnd();
530}
531
532bool QSvgNode::requiresGroupRendering() const
533{
534 return false;
535}
536
537void QSvgNode::setDisplayMode(DisplayMode mode)
538{
539 m_displayMode = mode;
540}
541
542QSvgNode::DisplayMode QSvgNode::displayMode() const
543{
544 return m_displayMode;
545}
546
547qreal QSvgNode::strokeWidth(QPainter *p)
548{
549 const QPen &pen = p->pen();
550 if (pen.style() == Qt::NoPen || pen.brush().style() == Qt::NoBrush || pen.isCosmetic())
551 return 0;
552 return pen.widthF();
553}
554
555void QSvgNode::initPainter(QPainter *p)
556{
557 QPen pen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin);
558 pen.setMiterLimit(4);
559 p->setPen(pen);
560 p->setBrush(Qt::black);
561 p->setRenderHint(QPainter::Antialiasing);
562 p->setRenderHint(QPainter::SmoothPixmapTransform);
563 QFont font(p->font());
564 if (font.pointSize() < 0 && font.pixelSize() > 0) {
565 font.setPointSizeF(font.pixelSize() * 72.0 / p->device()->logicalDpiY());
566 p->setFont(font);
567 }
568}
569
570QRectF QSvgNode::boundsOnStroke(QPainter *p, const QPainterPath &path,
571 qreal width, BoundsMode mode)
572{
573 QPainterPathStroker stroker;
574 stroker.setWidth(width);
575 if (mode == BoundsMode::IncludeMiterLimit) {
576 stroker.setJoinStyle(p->pen().joinStyle());
577 stroker.setMiterLimit(p->pen().miterLimit());
578 }
579 QPainterPath stroke = stroker.createStroke(path);
580 return p->transform().map(stroke).boundingRect();
581}
582
583bool QSvgNode::shouldDrawNode(QPainter *p, QSvgExtraStates &states) const
584{
585 if (m_displayMode == DisplayMode::NoneMode)
586 return false;
587
588 if (document() && states.trustedSource)
589 return true;
590
591 QRectF brect = internalFastBounds(p, states);
592 if (brect.width() <= QT_SVG_SIZE_LIMIT && brect.height() <= QT_SVG_SIZE_LIMIT) {
593 return true;
594 } else {
595 qCWarning(lcSvgDraw) << "Shape of type" << type() << "ignored because it will take too long to rasterize (bounding rect=" << brect << ")."
596 << "Enable AssumeTrustedSource in QSvgHandler or set QT_SVG_DEFAULT_OPTIONS=2 to disable this check.";
597 return false;
598 }
599}
600
601QRectF QSvgNode::filterRegion(QRectF bounds) const
602{
603 QSvgFilterContainer *filterNode = hasFilter()
604 ? static_cast<QSvgFilterContainer*>(document()->namedNode(filterId()))
605 : nullptr;
606
607 if (filterNode && filterNode->type() == QSvgNode::Filter && filterNode->supported())
608 return filterNode->filterRegion(bounds);
609
610 return bounds;
611}
612
613QT_END_NAMESPACE
Combined button and popup list for selecting options.
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)
#define QT_SVG_SIZE_LIMIT
Definition qsvgnode.cpp:30