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 case Font: return "font"_L1;
356 }
357 return "unknown"_L1;
358}
359
360void QSvgNode::setRequiredFeatures(const QStringList &lst)
361{
362 m_requiredFeatures = lst;
363}
364
365const QStringList & QSvgNode::requiredFeatures() const
366{
367 return m_requiredFeatures;
368}
369
370void QSvgNode::setRequiredExtensions(const QStringList &lst)
371{
372 m_requiredExtensions = lst;
373}
374
375const QStringList & QSvgNode::requiredExtensions() const
376{
377 return m_requiredExtensions;
378}
379
380void QSvgNode::setRequiredLanguages(const QStringList &lst)
381{
382 m_requiredLanguages = lst;
383}
384
385const QStringList & QSvgNode::requiredLanguages() const
386{
387 return m_requiredLanguages;
388}
389
390void QSvgNode::setRequiredFormats(const QStringList &lst)
391{
392 m_requiredFormats = lst;
393}
394
395const QStringList & QSvgNode::requiredFormats() const
396{
397 return m_requiredFormats;
398}
399
400void QSvgNode::setRequiredFonts(const QStringList &lst)
401{
402 m_requiredFonts = lst;
403}
404
405const QStringList & QSvgNode::requiredFonts() const
406{
407 return m_requiredFonts;
408}
409
410void QSvgNode::setVisible(bool visible)
411{
412 //propagate visibility change of true to the parent
413 //not propagating false is just a small performance
414 //degradation since we'll iterate over children without
415 //drawing any of them
416 if (m_parent && visible && !m_parent->isVisible())
417 m_parent->setVisible(true);
418
419 m_visible = visible;
420}
421
422QRectF QSvgNode::bounds(QPainter *p, QSvgExtraStates &states) const
423{
424 applyStyle(p, states);
425 QRectF rect = internalBounds(p, states);
426 revertStyle(p, states);
427 return rect;
428}
429
430QRectF QSvgNode::decoratedInternalBounds(QPainter *p, QSvgExtraStates &states) const
431{
432 return filterRegion(internalBounds(p, states));
433}
434
435QRectF QSvgNode::decoratedBounds(QPainter *p, QSvgExtraStates &states) const
436{
437 applyStyle(p, states);
438 QRectF rect = decoratedInternalBounds(p, states);
439 revertStyle(p, states);
440 return rect;
441}
442
443void QSvgNode::setNodeId(const QString &i)
444{
445 m_id = i;
446}
447
448void QSvgNode::setXmlClass(const QString &str)
449{
450 m_class = str;
451}
452
453QString QSvgNode::maskId() const
454{
455 return m_maskId;
456}
457
458void QSvgNode::setMaskId(const QString &str)
459{
460 m_maskId = str;
461}
462
463bool QSvgNode::hasMask() const
464{
465 return !m_maskId.isEmpty();
466}
467
468QString QSvgNode::filterId() const
469{
470 return m_filterId;
471}
472
473void QSvgNode::setFilterId(const QString &str)
474{
475 m_filterId = str;
476}
477
478bool QSvgNode::hasFilter() const
479{
480 return !m_filterId.isEmpty();
481}
482
483QString QSvgNode::markerStartId() const
484{
485 return m_markerStartId;
486}
487
488void QSvgNode::setMarkerStartId(const QString &str)
489{
490 m_markerStartId = str;
491}
492
493bool QSvgNode::hasMarkerStart() const
494{
495 return !m_markerStartId.isEmpty();
496}
497
498QString QSvgNode::markerMidId() const
499{
500 return m_markerMidId;
501}
502
503void QSvgNode::setMarkerMidId(const QString &str)
504{
505 m_markerMidId = str;
506}
507
508bool QSvgNode::hasMarkerMid() const
509{
510 return !m_markerMidId.isEmpty();
511}
512
513QString QSvgNode::markerEndId() const
514{
515 return m_markerEndId;
516}
517
518void QSvgNode::setMarkerEndId(const QString &str)
519{
520 m_markerEndId = str;
521}
522
523bool QSvgNode::hasMarkerEnd() const
524{
525 return !m_markerEndId.isEmpty();
526}
527
528bool QSvgNode::hasAnyMarker() const
529{
530 return hasMarkerStart() || hasMarkerMid() || hasMarkerEnd();
531}
532
533bool QSvgNode::requiresGroupRendering() const
534{
535 return false;
536}
537
538void QSvgNode::setDisplayMode(DisplayMode mode)
539{
540 m_displayMode = mode;
541}
542
543QSvgNode::DisplayMode QSvgNode::displayMode() const
544{
545 return m_displayMode;
546}
547
548qreal QSvgNode::strokeWidth(QPainter *p)
549{
550 const QPen &pen = p->pen();
551 if (pen.style() == Qt::NoPen || pen.brush().style() == Qt::NoBrush || pen.isCosmetic())
552 return 0;
553 return pen.widthF();
554}
555
556void QSvgNode::initPainter(QPainter *p)
557{
558 QPen pen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin);
559 pen.setMiterLimit(4);
560 p->setPen(pen);
561 p->setBrush(Qt::black);
562 p->setRenderHint(QPainter::Antialiasing);
563 p->setRenderHint(QPainter::SmoothPixmapTransform);
564 QFont font(p->font());
565 if (font.pointSize() < 0 && font.pixelSize() > 0) {
566 font.setPointSizeF(font.pixelSize() * 72.0 / p->device()->logicalDpiY());
567 p->setFont(font);
568 }
569}
570
571QRectF QSvgNode::boundsOnStroke(QPainter *p, const QPainterPath &path,
572 qreal width, BoundsMode mode)
573{
574 QPainterPathStroker stroker;
575 stroker.setWidth(width);
576 if (mode == BoundsMode::IncludeMiterLimit) {
577 stroker.setJoinStyle(p->pen().joinStyle());
578 stroker.setMiterLimit(p->pen().miterLimit());
579 }
580 QPainterPath stroke = stroker.createStroke(path);
581 return p->transform().map(stroke).boundingRect();
582}
583
584bool QSvgNode::shouldDrawNode(QPainter *p, QSvgExtraStates &states) const
585{
586 if (m_displayMode == DisplayMode::NoneMode)
587 return false;
588
589 if (document() && states.trustedSource)
590 return true;
591
592 QRectF brect = internalFastBounds(p, states);
593 if (brect.width() <= QT_SVG_SIZE_LIMIT && brect.height() <= QT_SVG_SIZE_LIMIT) {
594 return true;
595 } else {
596 qCWarning(lcSvgDraw) << "Shape of type" << type() << "ignored because it will take too long to rasterize (bounding rect=" << brect << ")."
597 << "Enable AssumeTrustedSource in QSvgHandler or set QT_SVG_DEFAULT_OPTIONS=2 to disable this check.";
598 return false;
599 }
600}
601
602QRectF QSvgNode::filterRegion(QRectF bounds) const
603{
604 QSvgFilterContainer *filterNode = hasFilter()
605 ? static_cast<QSvgFilterContainer*>(document()->namedNode(filterId()))
606 : nullptr;
607
608 if (filterNode && filterNode->type() == QSvgNode::Filter && filterNode->supported())
609 return filterNode->filterRegion(bounds);
610
611 return bounds;
612}
613
614QT_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