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