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, 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, 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(QSvgStyleProperty *prop)
203{
204 switch (prop->type()) {
205 case QSvgStyleProperty::QUALITY:
206 m_style.quality = static_cast<QSvgQualityStyle*>(prop);
207 break;
208 case QSvgStyleProperty::FILL:
209 m_style.fill = static_cast<QSvgFillStyle*>(prop);
210 break;
211 case QSvgStyleProperty::VIEWPORT_FILL:
212 m_style.viewportFill = static_cast<QSvgViewportFillStyle*>(prop);
213 break;
214 case QSvgStyleProperty::FONT:
215 m_style.font = static_cast<QSvgFontStyle*>(prop);
216 break;
217 case QSvgStyleProperty::STROKE:
218 m_style.stroke = static_cast<QSvgStrokeStyle*>(prop);
219 break;
220 case QSvgStyleProperty::TRANSFORM:
221 m_style.transform = static_cast<QSvgTransformStyle*>(prop);
222 break;
223 case QSvgStyleProperty::OPACITY:
224 m_style.opacity = static_cast<QSvgOpacityStyle*>(prop);
225 break;
226 case QSvgStyleProperty::COMP_OP:
227 m_style.compop = static_cast<QSvgCompOpStyle*>(prop);
228 break;
229 case QSvgStyleProperty::OFFSET:
230 m_style.offset = static_cast<QSvgOffsetStyle*>(prop);
231 break;
232 default:
233 qDebug("QSvgNode: Trying to append unknown property!");
234 break;
235 }
236}
237
238void QSvgNode::applyStyle(QPainter *p, QSvgExtraStates &states) const
239{
240 m_style.apply(p, this, states);
241}
242
243/*!
244 \internal
245
246 Apply the styles of all parents to the painter and the states.
247 The styles are applied from the top level node to the current node.
248 This function can be used to set the correct style for a node
249 if it's draw function is triggered out of the ordinary draw context,
250 for example the mask node, that is cross-referenced.
251*/
252void QSvgNode::applyStyleRecursive(QPainter *p, QSvgExtraStates &states) const
253{
254 if (parent())
255 parent()->applyStyleRecursive(p, states);
256 applyStyle(p, states);
257}
258
259void QSvgNode::revertStyle(QPainter *p, QSvgExtraStates &states) const
260{
261 m_style.revert(p, states);
262}
263
264void QSvgNode::revertStyleRecursive(QPainter *p, QSvgExtraStates &states) const
265{
266 revertStyle(p, states);
267 if (parent())
268 parent()->revertStyleRecursive(p, states);
269}
270
271void QSvgNode::applyAnimatedStyle(QPainter *p, QSvgExtraStates &states) const
272{
273 if (document()->animated())
274 m_animatedStyle.apply(p, this, states);
275}
276
277void QSvgNode::revertAnimatedStyle(QPainter *p, QSvgExtraStates &states) const
278{
279 if (document()->animated())
280 m_animatedStyle.revert(p, states);
281}
282
283QSvgStyleProperty * QSvgNode::styleProperty(QSvgStyleProperty::Type type) const
284{
285 const QSvgNode *node = this;
286 while (node) {
287 switch (type) {
288 case QSvgStyleProperty::QUALITY:
289 if (node->m_style.quality)
290 return node->m_style.quality;
291 break;
292 case QSvgStyleProperty::FILL:
293 if (node->m_style.fill)
294 return node->m_style.fill;
295 break;
296 case QSvgStyleProperty::VIEWPORT_FILL:
297 if (m_style.viewportFill)
298 return node->m_style.viewportFill;
299 break;
300 case QSvgStyleProperty::FONT:
301 if (node->m_style.font)
302 return node->m_style.font;
303 break;
304 case QSvgStyleProperty::STROKE:
305 if (node->m_style.stroke)
306 return node->m_style.stroke;
307 break;
308 case QSvgStyleProperty::TRANSFORM:
309 if (node->m_style.transform)
310 return node->m_style.transform;
311 break;
312 case QSvgStyleProperty::OPACITY:
313 if (node->m_style.opacity)
314 return node->m_style.opacity;
315 break;
316 case QSvgStyleProperty::COMP_OP:
317 if (node->m_style.compop)
318 return node->m_style.compop;
319 break;
320 default:
321 break;
322 }
323 node = node->parent();
324 }
325
326 return 0;
327}
328
329QRectF QSvgNode::internalFastBounds(QPainter *p, QSvgExtraStates &states) const
330{
331 return internalBounds(p, states);
332}
333
334QRectF QSvgNode::internalBounds(QPainter *, QSvgExtraStates &) const
335{
336 return QRectF(0, 0, 0, 0);
337}
338
339QRectF QSvgNode::bounds() const
340{
341 if (!m_cachedBounds.isEmpty())
342 return m_cachedBounds;
343
344 QImage dummy(1, 1, QImage::Format_RGB32);
345 QPainter p(&dummy);
346 initPainter(&p);
347 QSvgExtraStates states;
348
349 if (parent())
350 parent()->applyStyleRecursive(&p, states);
351 p.setWorldTransform(QTransform());
352 m_cachedBounds = bounds(&p, states);
353 if (parent()) // always revert the style to not store old transformations
354 parent()->revertStyleRecursive(&p, states);
355 return m_cachedBounds;
356}
357
358QSvgDocument * QSvgNode::document() const
359{
360 QSvgDocument *doc = nullptr;
361 QSvgNode *node = const_cast<QSvgNode*>(this);
362 while (node && node->type() != QSvgNode::Doc) {
363 node = node->parent();
364 }
365 doc = static_cast<QSvgDocument*>(node);
366
367 return doc;
368}
369
370QString QSvgNode::typeName() const
371{
372 switch (type()) {
373 case Doc: return QStringLiteral("svg");
374 case Group: return QStringLiteral("g");
375 case Defs: return QStringLiteral("defs");
376 case Switch: return QStringLiteral("switch");
377 case AnimateColor: return QStringLiteral("animateColor");
378 case AnimateTransform: return QStringLiteral("animateTransform");
379 case Circle: return QStringLiteral("circle");
380 case Ellipse: return QStringLiteral("ellipse");
381 case Image: return QStringLiteral("image");
382 case Line: return QStringLiteral("line");
383 case Path: return QStringLiteral("path");
384 case Polygon: return QStringLiteral("polygon");
385 case Polyline: return QStringLiteral("polyline");
386 case Rect: return QStringLiteral("rect");
387 case Text: return QStringLiteral("text");
388 case Textarea: return QStringLiteral("textarea");
389 case Tspan: return QStringLiteral("tspan");
390 case Use: return QStringLiteral("use");
391 case Video: return QStringLiteral("video");
392 case Mask: return QStringLiteral("mask");
393 case Symbol: return QStringLiteral("symbol");
394 case Marker: return QStringLiteral("marker");
395 case Pattern: return QStringLiteral("pattern");
396 case Filter: return QStringLiteral("filter");
397 case FeMerge: return QStringLiteral("feMerge");
398 case FeMergenode: return QStringLiteral("feMergeNode");
399 case FeColormatrix: return QStringLiteral("feColorMatrix");
400 case FeGaussianblur: return QStringLiteral("feGaussianBlur");
401 case FeOffset: return QStringLiteral("feOffset");
402 case FeComposite: return QStringLiteral("feComposite");
403 case FeFlood: return QStringLiteral("feFlood");
404 case FeBlend: return QStringLiteral("feBlend");
405 case FeUnsupported: return QStringLiteral("feUnsupported");
406 }
407 return QStringLiteral("unknown");
408}
409
410void QSvgNode::setRequiredFeatures(const QStringList &lst)
411{
412 m_requiredFeatures = lst;
413}
414
415const QStringList & QSvgNode::requiredFeatures() const
416{
417 return m_requiredFeatures;
418}
419
420void QSvgNode::setRequiredExtensions(const QStringList &lst)
421{
422 m_requiredExtensions = lst;
423}
424
425const QStringList & QSvgNode::requiredExtensions() const
426{
427 return m_requiredExtensions;
428}
429
430void QSvgNode::setRequiredLanguages(const QStringList &lst)
431{
432 m_requiredLanguages = lst;
433}
434
435const QStringList & QSvgNode::requiredLanguages() const
436{
437 return m_requiredLanguages;
438}
439
440void QSvgNode::setRequiredFormats(const QStringList &lst)
441{
442 m_requiredFormats = lst;
443}
444
445const QStringList & QSvgNode::requiredFormats() const
446{
447 return m_requiredFormats;
448}
449
450void QSvgNode::setRequiredFonts(const QStringList &lst)
451{
452 m_requiredFonts = lst;
453}
454
455const QStringList & QSvgNode::requiredFonts() const
456{
457 return m_requiredFonts;
458}
459
460void QSvgNode::setVisible(bool visible)
461{
462 //propagate visibility change of true to the parent
463 //not propagating false is just a small performance
464 //degradation since we'll iterate over children without
465 //drawing any of them
466 if (m_parent && visible && !m_parent->isVisible())
467 m_parent->setVisible(true);
468
469 m_visible = visible;
470}
471
472QRectF QSvgNode::bounds(QPainter *p, QSvgExtraStates &states) const
473{
474 applyStyle(p, states);
475 QRectF rect = internalBounds(p, states);
476 revertStyle(p, states);
477 return rect;
478}
479
480QRectF QSvgNode::decoratedInternalBounds(QPainter *p, QSvgExtraStates &states) const
481{
482 return filterRegion(internalBounds(p, states));
483}
484
485QRectF QSvgNode::decoratedBounds(QPainter *p, QSvgExtraStates &states) const
486{
487 applyStyle(p, states);
488 QRectF rect = decoratedInternalBounds(p, states);
489 revertStyle(p, states);
490 return rect;
491}
492
493void QSvgNode::setNodeId(const QString &i)
494{
495 m_id = i;
496}
497
498void QSvgNode::setXmlClass(const QString &str)
499{
500 m_class = str;
501}
502
503QString QSvgNode::maskId() const
504{
505 return m_maskId;
506}
507
508void QSvgNode::setMaskId(const QString &str)
509{
510 m_maskId = str;
511}
512
513bool QSvgNode::hasMask() const
514{
515 if (document()->options().testFlag(QtSvg::Tiny12FeaturesOnly))
516 return false;
517 return !m_maskId.isEmpty();
518}
519
520QString QSvgNode::filterId() const
521{
522 return m_filterId;
523}
524
525void QSvgNode::setFilterId(const QString &str)
526{
527 m_filterId = str;
528}
529
530bool QSvgNode::hasFilter() const
531{
532 if (document()->options().testFlag(QtSvg::Tiny12FeaturesOnly))
533 return false;
534 return !m_filterId.isEmpty();
535}
536
537QString QSvgNode::markerStartId() const
538{
539 return m_markerStartId;
540}
541
542void QSvgNode::setMarkerStartId(const QString &str)
543{
544 m_markerStartId = str;
545}
546
547bool QSvgNode::hasMarkerStart() const
548{
549 if (document()->options().testFlag(QtSvg::Tiny12FeaturesOnly))
550 return false;
551 return !m_markerStartId.isEmpty();
552}
553
554QString QSvgNode::markerMidId() const
555{
556 return m_markerMidId;
557}
558
559void QSvgNode::setMarkerMidId(const QString &str)
560{
561 m_markerMidId = str;
562}
563
564bool QSvgNode::hasMarkerMid() const
565{
566 if (document()->options().testFlag(QtSvg::Tiny12FeaturesOnly))
567 return false;
568 return !m_markerMidId.isEmpty();
569}
570
571QString QSvgNode::markerEndId() const
572{
573 return m_markerEndId;
574}
575
576void QSvgNode::setMarkerEndId(const QString &str)
577{
578 m_markerEndId = str;
579}
580
581bool QSvgNode::hasMarkerEnd() const
582{
583 if (document()->options().testFlag(QtSvg::Tiny12FeaturesOnly))
584 return false;
585 return !m_markerEndId.isEmpty();
586}
587
588bool QSvgNode::hasAnyMarker() const
589{
590 if (document()->options().testFlag(QtSvg::Tiny12FeaturesOnly))
591 return false;
592 return hasMarkerStart() || hasMarkerMid() || hasMarkerEnd();
593}
594
595bool QSvgNode::requiresGroupRendering() const
596{
597 return false;
598}
599
600void QSvgNode::setDisplayMode(DisplayMode mode)
601{
602 m_displayMode = mode;
603}
604
605QSvgNode::DisplayMode QSvgNode::displayMode() const
606{
607 return m_displayMode;
608}
609
610qreal QSvgNode::strokeWidth(QPainter *p)
611{
612 const QPen &pen = p->pen();
613 if (pen.style() == Qt::NoPen || pen.brush().style() == Qt::NoBrush || pen.isCosmetic())
614 return 0;
615 return pen.widthF();
616}
617
618void QSvgNode::initPainter(QPainter *p)
619{
620 QPen pen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin);
621 pen.setMiterLimit(4);
622 p->setPen(pen);
623 p->setBrush(Qt::black);
624 p->setRenderHint(QPainter::Antialiasing);
625 p->setRenderHint(QPainter::SmoothPixmapTransform);
626 QFont font(p->font());
627 if (font.pointSize() < 0 && font.pixelSize() > 0) {
628 font.setPointSizeF(font.pixelSize() * 72.0 / p->device()->logicalDpiY());
629 p->setFont(font);
630 }
631}
632
633QRectF QSvgNode::boundsOnStroke(QPainter *p, const QPainterPath &path,
634 qreal width, BoundsMode mode)
635{
636 QPainterPathStroker stroker;
637 stroker.setWidth(width);
638 if (mode == BoundsMode::IncludeMiterLimit) {
639 stroker.setJoinStyle(p->pen().joinStyle());
640 stroker.setMiterLimit(p->pen().miterLimit());
641 }
642 QPainterPath stroke = stroker.createStroke(path);
643 return p->transform().map(stroke).boundingRect();
644}
645
646bool QSvgNode::shouldDrawNode(QPainter *p, QSvgExtraStates &states) const
647{
648 if (m_displayMode == DisplayMode::NoneMode)
649 return false;
650
651 if (document() && states.trustedSource)
652 return true;
653
654 QRectF brect = internalFastBounds(p, states);
655 if (brect.width() <= QT_SVG_SIZE_LIMIT && brect.height() <= QT_SVG_SIZE_LIMIT) {
656 return true;
657 } else {
658 qCWarning(lcSvgDraw) << "Shape of type" << type() << "ignored because it will take too long to rasterize (bounding rect=" << brect << ")."
659 << "Enable AssumeTrustedSource in QSvgHandler or set QT_SVG_DEFAULT_OPTIONS=2 to disable this check.";
660 return false;
661 }
662}
663
664QRectF QSvgNode::filterRegion(QRectF bounds) const
665{
666 QSvgFilterContainer *filterNode = hasFilter()
667 ? static_cast<QSvgFilterContainer*>(document()->namedNode(filterId()))
668 : nullptr;
669
670 if (filterNode && filterNode->type() == QSvgNode::Filter && filterNode->supported())
671 return filterNode->filterRegion(bounds);
672
673 return bounds;
674}
675
676QT_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