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