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