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