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