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