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(p, 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(p, 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
350QRectF QSvgNode::internalFastBounds(QPainter *p, QSvgExtraStates &states) const
351{
352 return internalBounds(p, states);
353}
354
355QRectF QSvgNode::internalBounds(QPainter *, QSvgExtraStates &) const
356{
357 return QRectF(0, 0, 0, 0);
358}
359
360QRectF QSvgNode::bounds() const
361{
362 if (!m_cachedBounds.isEmpty())
363 return m_cachedBounds;
364
365 QImage dummy(1, 1, QImage::Format_RGB32);
366 QPainter p(&dummy);
367 initPainter(&p);
368 QSvgExtraStates states;
369
370 if (parent())
371 parent()->applyStyleRecursive(&p, states);
372 p.setWorldTransform(QTransform());
373 m_cachedBounds = bounds(&p, states);
374 if (parent()) // always revert the style to not store old transformations
375 parent()->revertStyleRecursive(&p, states);
376 return m_cachedBounds;
377}
378
379QSvgDocument * QSvgNode::document() const
380{
381 QSvgDocument *doc = nullptr;
382 QSvgNode *node = const_cast<QSvgNode*>(this);
383 while (node && node->type() != QSvgNode::Doc) {
384 node = node->parent();
385 }
386 doc = static_cast<QSvgDocument*>(node);
387
388 return doc;
389}
390
391QString QSvgNode::typeName() const
392{
393 switch (type()) {
394 case Doc: return QStringLiteral("svg");
395 case Group: return QStringLiteral("g");
396 case Defs: return QStringLiteral("defs");
397 case Switch: return QStringLiteral("switch");
398 case AnimateColor: return QStringLiteral("animateColor");
399 case AnimateTransform: return QStringLiteral("animateTransform");
400 case Circle: return QStringLiteral("circle");
401 case Ellipse: return QStringLiteral("ellipse");
402 case Image: return QStringLiteral("image");
403 case Line: return QStringLiteral("line");
404 case Path: return QStringLiteral("path");
405 case Polygon: return QStringLiteral("polygon");
406 case Polyline: return QStringLiteral("polyline");
407 case Rect: return QStringLiteral("rect");
408 case Text: return QStringLiteral("text");
409 case Textarea: return QStringLiteral("textarea");
410 case Tspan: return QStringLiteral("tspan");
411 case Use: return QStringLiteral("use");
412 case Video: return QStringLiteral("video");
413 case Mask: return QStringLiteral("mask");
414 case Symbol: return QStringLiteral("symbol");
415 case Marker: return QStringLiteral("marker");
416 case Pattern: return QStringLiteral("pattern");
417 case Filter: return QStringLiteral("filter");
418 case FeMerge: return QStringLiteral("feMerge");
419 case FeMergenode: return QStringLiteral("feMergeNode");
420 case FeColormatrix: return QStringLiteral("feColorMatrix");
421 case FeGaussianblur: return QStringLiteral("feGaussianBlur");
422 case FeOffset: return QStringLiteral("feOffset");
423 case FeComposite: return QStringLiteral("feComposite");
424 case FeFlood: return QStringLiteral("feFlood");
425 case FeBlend: return QStringLiteral("feBlend");
426 case FeUnsupported: return QStringLiteral("feUnsupported");
427 }
428 return QStringLiteral("unknown");
429}
430
431void QSvgNode::setRequiredFeatures(const QStringList &lst)
432{
433 m_requiredFeatures = lst;
434}
435
436const QStringList & QSvgNode::requiredFeatures() const
437{
438 return m_requiredFeatures;
439}
440
441void QSvgNode::setRequiredExtensions(const QStringList &lst)
442{
443 m_requiredExtensions = lst;
444}
445
446const QStringList & QSvgNode::requiredExtensions() const
447{
448 return m_requiredExtensions;
449}
450
451void QSvgNode::setRequiredLanguages(const QStringList &lst)
452{
453 m_requiredLanguages = lst;
454}
455
456const QStringList & QSvgNode::requiredLanguages() const
457{
458 return m_requiredLanguages;
459}
460
461void QSvgNode::setRequiredFormats(const QStringList &lst)
462{
463 m_requiredFormats = lst;
464}
465
466const QStringList & QSvgNode::requiredFormats() const
467{
468 return m_requiredFormats;
469}
470
471void QSvgNode::setRequiredFonts(const QStringList &lst)
472{
473 m_requiredFonts = lst;
474}
475
476const QStringList & QSvgNode::requiredFonts() const
477{
478 return m_requiredFonts;
479}
480
481void QSvgNode::setVisible(bool visible)
482{
483 //propagate visibility change of true to the parent
484 //not propagating false is just a small performance
485 //degradation since we'll iterate over children without
486 //drawing any of them
487 if (m_parent && visible && !m_parent->isVisible())
488 m_parent->setVisible(true);
489
490 m_visible = visible;
491}
492
493QRectF QSvgNode::bounds(QPainter *p, QSvgExtraStates &states) const
494{
495 applyStyle(p, states);
496 QRectF rect = internalBounds(p, states);
497 revertStyle(p, states);
498 return rect;
499}
500
501QRectF QSvgNode::decoratedInternalBounds(QPainter *p, QSvgExtraStates &states) const
502{
503 return filterRegion(internalBounds(p, states));
504}
505
506QRectF QSvgNode::decoratedBounds(QPainter *p, QSvgExtraStates &states) const
507{
508 applyStyle(p, states);
509 QRectF rect = decoratedInternalBounds(p, states);
510 revertStyle(p, states);
511 return rect;
512}
513
514void QSvgNode::setNodeId(const QString &i)
515{
516 m_id = i;
517}
518
519void QSvgNode::setXmlClass(const QString &str)
520{
521 m_class = str;
522}
523
524QString QSvgNode::maskId() const
525{
526 return m_maskId;
527}
528
529void QSvgNode::setMaskId(const QString &str)
530{
531 m_maskId = str;
532}
533
534bool QSvgNode::hasMask() const
535{
536 if (document()->options().testFlag(QtSvg::Tiny12FeaturesOnly))
537 return false;
538 return !m_maskId.isEmpty();
539}
540
541QString QSvgNode::filterId() const
542{
543 return m_filterId;
544}
545
546void QSvgNode::setFilterId(const QString &str)
547{
548 m_filterId = str;
549}
550
551bool QSvgNode::hasFilter() const
552{
553 if (document()->options().testFlag(QtSvg::Tiny12FeaturesOnly))
554 return false;
555 return !m_filterId.isEmpty();
556}
557
558QString QSvgNode::markerStartId() const
559{
560 return m_markerStartId;
561}
562
563void QSvgNode::setMarkerStartId(const QString &str)
564{
565 m_markerStartId = str;
566}
567
568bool QSvgNode::hasMarkerStart() const
569{
570 if (document()->options().testFlag(QtSvg::Tiny12FeaturesOnly))
571 return false;
572 return !m_markerStartId.isEmpty();
573}
574
575QString QSvgNode::markerMidId() const
576{
577 return m_markerMidId;
578}
579
580void QSvgNode::setMarkerMidId(const QString &str)
581{
582 m_markerMidId = str;
583}
584
585bool QSvgNode::hasMarkerMid() const
586{
587 if (document()->options().testFlag(QtSvg::Tiny12FeaturesOnly))
588 return false;
589 return !m_markerMidId.isEmpty();
590}
591
592QString QSvgNode::markerEndId() const
593{
594 return m_markerEndId;
595}
596
597void QSvgNode::setMarkerEndId(const QString &str)
598{
599 m_markerEndId = str;
600}
601
602bool QSvgNode::hasMarkerEnd() const
603{
604 if (document()->options().testFlag(QtSvg::Tiny12FeaturesOnly))
605 return false;
606 return !m_markerEndId.isEmpty();
607}
608
609bool QSvgNode::hasAnyMarker() const
610{
611 if (document()->options().testFlag(QtSvg::Tiny12FeaturesOnly))
612 return false;
613 return hasMarkerStart() || hasMarkerMid() || hasMarkerEnd();
614}
615
616bool QSvgNode::requiresGroupRendering() const
617{
618 return false;
619}
620
621void QSvgNode::setDisplayMode(DisplayMode mode)
622{
623 m_displayMode = mode;
624}
625
626QSvgNode::DisplayMode QSvgNode::displayMode() const
627{
628 return m_displayMode;
629}
630
631qreal QSvgNode::strokeWidth(QPainter *p)
632{
633 const QPen &pen = p->pen();
634 if (pen.style() == Qt::NoPen || pen.brush().style() == Qt::NoBrush || pen.isCosmetic())
635 return 0;
636 return pen.widthF();
637}
638
639void QSvgNode::initPainter(QPainter *p)
640{
641 QPen pen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin);
642 pen.setMiterLimit(4);
643 p->setPen(pen);
644 p->setBrush(Qt::black);
645 p->setRenderHint(QPainter::Antialiasing);
646 p->setRenderHint(QPainter::SmoothPixmapTransform);
647 QFont font(p->font());
648 if (font.pointSize() < 0 && font.pixelSize() > 0) {
649 font.setPointSizeF(font.pixelSize() * 72.0 / p->device()->logicalDpiY());
650 p->setFont(font);
651 }
652}
653
654QRectF QSvgNode::boundsOnStroke(QPainter *p, const QPainterPath &path,
655 qreal width, BoundsMode mode)
656{
657 QPainterPathStroker stroker;
658 stroker.setWidth(width);
659 if (mode == BoundsMode::IncludeMiterLimit) {
660 stroker.setJoinStyle(p->pen().joinStyle());
661 stroker.setMiterLimit(p->pen().miterLimit());
662 }
663 QPainterPath stroke = stroker.createStroke(path);
664 return p->transform().map(stroke).boundingRect();
665}
666
667bool QSvgNode::shouldDrawNode(QPainter *p, QSvgExtraStates &states) const
668{
669 if (m_displayMode == DisplayMode::NoneMode)
670 return false;
671
672 if (document() && document()->options().testFlag(QtSvg::AssumeTrustedSource))
673 return true;
674
675 QRectF brect = internalFastBounds(p, states);
676 if (brect.width() <= QT_SVG_SIZE_LIMIT && brect.height() <= QT_SVG_SIZE_LIMIT) {
677 return true;
678 } else {
679 qCWarning(lcSvgDraw) << "Shape of type" << type() << "ignored because it will take too long to rasterize (bounding rect=" << brect << ")."
680 << "Enable AssumeTrustedSource in QSvgHandler or set QT_SVG_DEFAULT_OPTIONS=2 to disable this check.";
681 return false;
682 }
683}
684
685QRectF QSvgNode::filterRegion(QRectF bounds) const
686{
687 QSvgFilterContainer *filterNode = hasFilter()
688 ? static_cast<QSvgFilterContainer*>(document()->namedNode(filterId()))
689 : nullptr;
690
691 if (filterNode && filterNode->type() == QSvgNode::Filter && filterNode->supported())
692 return filterNode->filterRegion(bounds);
693
694 return bounds;
695}
696
697QT_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