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