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
qsvgstructure.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
8
9#include "qsvgstyle_p.h"
10#include "qsvgdocument_p.h"
11#include "qsvggraphics_p.h"
12#include "qsvgstyle_p.h"
13#include "qsvgfilter_p.h"
14
15#include "qpainter.h"
16#include "qlocale.h"
17#include "qdebug.h"
18
19#include <QLoggingCategory>
20#include <qscopedvaluerollback.h>
21#include <QtGui/qimageiohandler.h>
22
23#include <QtCore/qlatin1stringview.h>
24#include <QtCore/qstringview.h>
25#include <QtCore/private/qoffsetstringarray_p.h>
26
28
29using namespace Qt::StringLiterals;
30
31QSvgG::QSvgG(QSvgNode *parent)
32 : QSvgStructureNode(parent)
33{
34
35}
36
37QSvgG::~QSvgG()
38 = default;
39
40void QSvgG::drawCommand(QPainter *p, QSvgExtraStates &states)
41{
42 for (const auto &node : renderers()) {
43 if ((node->isVisible()) && (node->displayMode() != QSvgNode::NoneMode))
44 node->draw(p, states);
45 }
46}
47
48bool QSvgG::shouldDrawNode(QPainter *, QSvgExtraStates &) const
49{
50 return true;
51}
52
53QSvgNode::Type QSvgG::type() const
54{
55 return Group;
56}
57
58bool QSvgG::requiresGroupRendering() const
59{
60 return m_renderers.size() > 1;
61}
62
63QSvgStructureNode::QSvgStructureNode(QSvgNode *parent)
64 :QSvgNode(parent)
65{
66
67}
68
69QSvgStructureNode::~QSvgStructureNode()
70 = default;
71
72QSvgNode * QSvgStructureNode::scopeNode(const QString &id) const
73{
74 QSvgDocument *doc = document();
75 return doc ? doc->namedNode(id) : 0;
76}
77
78void QSvgStructureNode::addChild(std::unique_ptr<QSvgNode> child, const QString &id)
79{
80 if (!id.isEmpty()) {
81 QSvgDocument *doc = document();
82 if (doc)
83 doc->addNamedNode(id, child.get());
84 }
85
86 m_renderers.push_back(std::move(child));
87}
88
89QSvgDefs::QSvgDefs(QSvgNode *parent)
90 : QSvgStructureNode(parent)
91{
92}
93
94QSvgDefs::~QSvgDefs()
95 = default;
96
97bool QSvgDefs::shouldDrawNode(QPainter *, QSvgExtraStates &) const
98{
99 return false;
100}
101
102QSvgNode::Type QSvgDefs::type() const
103{
104 return Defs;
105}
106
107QSvgSymbolLike::QSvgSymbolLike(QSvgNode *parent, QRectF bounds, QRectF viewBox, QPointF refP,
108 QSvgSymbolLike::PreserveAspectRatios pAspectRatios, QSvgSymbolLike::Overflow overflow)
109 : QSvgStructureNode(parent)
110 , m_rect(bounds)
111 , m_viewBox(viewBox)
112 , m_refP(refP)
113 , m_pAspectRatios(pAspectRatios)
114 , m_overflow(overflow)
115{
116
117}
118
119QSvgSymbolLike::~QSvgSymbolLike()
120 = default;
121
122QRectF QSvgSymbolLike::decoratedInternalBounds(QPainter *p, QSvgExtraStates &states) const
123{
124 p->save();
125 setPainterToRectAndAdjustment(p);
126 QRectF rect = internalBounds(p, states);
127 p->restore();
128 return rect;
129}
130
131bool QSvgSymbolLike::requiresGroupRendering() const
132{
133 return m_renderers.size() > 1;
134}
135
136QRectF QSvgSymbolLike::clipRect() const
137{
138 if (m_overflow != Overflow::Hidden || !m_viewBox.isValid())
139 return QRectF{};
140
141 qreal scaleX = 1.0;
142 if (m_rect.width() > 0 && m_viewBox.width() > 0)
143 scaleX = m_rect.width() / m_viewBox.width();
144 qreal scaleY = 1.0;
145 if (m_rect.height() > 0 && m_viewBox.height() > 0)
146 scaleY = m_rect.height() / m_viewBox.height();
147
148 QTransform t;
149 t.translate(- m_refP.x() * scaleX - m_rect.left() - m_viewBox.left() * scaleX,
150 - m_refP.y() * scaleY - m_rect.top() - m_viewBox.top() * scaleY);
151 t.scale(scaleX, scaleY);
152
153 return t.mapRect(m_viewBox);
154}
155
156QTransform QSvgSymbolLike::aspectRatioTransform() const
157{
158 QTransform xform;
159
160 qreal offsetX = 0.0;
161 qreal offsetY = 0.0;
162
163 qreal scaleX = 1.0;
164 if (m_rect.width() > 0 && m_viewBox.width() > 0)
165 scaleX = m_rect.width() / m_viewBox.width();
166 qreal scaleY = 1.0;
167 if (m_rect.height() > 0 && m_viewBox.height() > 0)
168 scaleY = m_rect.height() / m_viewBox.height();
169
170 if (!qFuzzyCompare(scaleX, scaleY) &&
171 m_pAspectRatios.testAnyFlag(PreserveAspectRatio::xyMask)) {
172
173 if (m_pAspectRatios.testAnyFlag(PreserveAspectRatio::meet))
174 scaleX = scaleY = qMin(scaleX, scaleY);
175 else
176 scaleX = scaleY = qMax(scaleX, scaleY);
177
178 qreal xOverflow = scaleX * m_viewBox.width() - m_rect.width();
179 qreal yOverflow = scaleY * m_viewBox.height() - m_rect.height();
180
181 if ((m_pAspectRatios & PreserveAspectRatio::xMask) == PreserveAspectRatio::xMid)
182 offsetX -= xOverflow / 2.;
183 else if ((m_pAspectRatios & PreserveAspectRatio::xMask) == PreserveAspectRatio::xMax)
184 offsetX -= xOverflow;
185
186 if ((m_pAspectRatios & PreserveAspectRatio::yMask) == PreserveAspectRatio::yMid)
187 offsetY -= yOverflow / 2.;
188 else if ((m_pAspectRatios & PreserveAspectRatio::yMask) == PreserveAspectRatio::yMax)
189 offsetY -= yOverflow;
190 }
191
192 xform.translate(offsetX - m_refP.x() * scaleX, offsetY - m_refP.y() * scaleY);
193 xform.scale(scaleX, scaleY);
194
195 return xform;
196}
197
198void QSvgSymbolLike::setPainterToRectAndAdjustment(QPainter *p) const
199{
200 QRectF clip = clipRect();
201 if (!clip.isEmpty())
202 p->setClipRect(clip);
203
204 p->setTransform(aspectRatioTransform(), true);
205}
206
207QSvgSymbol::QSvgSymbol(QSvgNode *parent, QRectF bounds, QRectF viewBox, QPointF refP,
208 QSvgSymbol::PreserveAspectRatios pAspectRatios,
209 QSvgSymbol::Overflow overflow)
210 : QSvgSymbolLike(parent, bounds, viewBox, refP, pAspectRatios, overflow)
211{
212}
213
214QSvgSymbol::~QSvgSymbol()
215 = default;
216
217void QSvgSymbol::drawCommand(QPainter *p, QSvgExtraStates &states)
218{
219 if (!states.inUse) //Symbol is only drawn when within a use node.
220 return;
221
222 p->save();
223 setPainterToRectAndAdjustment(p);
224 for (const auto &node : renderers()) {
225 if ((node->isVisible()) && (node->displayMode() != QSvgNode::NoneMode))
226 node->draw(p, states);
227 }
228 p->restore();
229}
230
231QSvgNode::Type QSvgSymbol::type() const
232{
233 return Symbol;
234}
235
236QSvgMarker::QSvgMarker(QSvgNode *parent, QRectF bounds, QRectF viewBox, QPointF refP,
237 QSvgSymbol::PreserveAspectRatios pAspectRatios, QSvgSymbol::Overflow overflow,
238 Orientation orientation, qreal orientationAngle, MarkerUnits markerUnits)
239 : QSvgSymbolLike(parent, bounds, viewBox, refP, pAspectRatios, overflow)
240 , m_orientation(orientation)
241 , m_orientationAngle(orientationAngle)
242 , m_markerUnits(markerUnits)
243{
244 // apply the svg standard style
245 QSvgFillStyle *fillProp = new QSvgFillStyle();
246 fillProp->setBrush(Qt::black);
247 appendStyleProperty(fillProp);
248
249 QSvgStrokeStyle *strokeProp = new QSvgStrokeStyle();
250 strokeProp->setMiterLimit(4);
251 strokeProp->setWidth(1);
252 strokeProp->setLineCap(Qt::FlatCap);
253 strokeProp->setLineJoin(Qt::SvgMiterJoin);
254 strokeProp->setStroke(Qt::NoBrush);
255 appendStyleProperty(strokeProp);
256}
257
258QSvgMarker::~QSvgMarker()
259 = default;
260
261QSvgFilterContainer::QSvgFilterContainer(QSvgNode *parent, const QSvgRectF &bounds,
262 QtSvg::UnitTypes filterUnits, QtSvg::UnitTypes primitiveUnits)
263 : QSvgStructureNode(parent)
264 , m_rect(bounds)
265 , m_filterUnits(filterUnits)
266 , m_primitiveUnits(primitiveUnits)
267 , m_supported(true)
268{
269
270}
271
272QSvgFilterContainer::~QSvgFilterContainer()
273 = default;
274
275bool QSvgFilterContainer::shouldDrawNode(QPainter *, QSvgExtraStates &) const
276{
277 return false;
278}
279
280void QSvgMarker::drawCommand(QPainter *p, QSvgExtraStates &states)
281{
282 if (!states.inUse) //Symbol is only drawn in combination with another node.
283 return;
284
285 if (Q_UNLIKELY(m_recursing))
286 return;
287 QScopedValueRollback<bool> recursingGuard(m_recursing, true);
288
289 p->save();
290 setPainterToRectAndAdjustment(p);
291
292 for (const auto &node : renderers()) {
293 if ((node->isVisible()) && (node->displayMode() != QSvgNode::NoneMode))
294 node->draw(p, states);
295 }
296 p->restore();
297}
298
299namespace {
300
301struct PositionMarkerPair {
302 qreal x;
303 qreal y;
304 qreal angle;
305 QString markerId;
306 bool isStartNode = false;
307};
308
309QList<PositionMarkerPair> markersForNode(const QSvgNode *node)
310{
311 if (!node->hasAnyMarker())
312 return {};
313
314 auto getMeanAngle = [](QPointF p0, QPointF p1, QPointF p2) -> qreal {
315 QPointF t1 = p1 - p0;
316 QPointF t2 = p2 - p1;
317 qreal hyp1 = hypot(t1.x(), t1.y());
318 if (hyp1 > 0)
319 t1 /= hyp1;
320 else
321 return 0.;
322 qreal hyp2 = hypot(t2.x(), t2.y());
323 if (hyp2 > 0)
324 t2 /= hyp2;
325 else
326 return 0.;
327 QPointF tangent = t1 + t2;
328 return -atan2(tangent.y(), tangent.x()) / M_PI * 180.;
329 };
330
331 QList<PositionMarkerPair> markers;
332
333 switch (node->type()) {
334 case QSvgNode::Line: {
335 const QSvgLine *line = static_cast<const QSvgLine*>(node);
336 if (node->hasMarkerStart())
337 markers << PositionMarkerPair { line->line().p1().x(), line->line().p1().y(),
338 line->line().angle(), line->markerStartId(),
339 true};
340 if (node->hasMarkerEnd())
341 markers << PositionMarkerPair { line->line().p2().x(), line->line().p2().y(),
342 line->line().angle(), line->markerEndId() };
343 break;
344 }
345 case QSvgNode::Polyline:
346 case QSvgNode::Polygon: {
347 const QPolygonF &polyData = (node->type() == QSvgNode::Polyline)
348 ? static_cast<const QSvgPolyline*>(node)->polygon()
349 : static_cast<const QSvgPolygon*>(node)->polygon();
350
351 if (node->hasMarkerStart() && polyData.size() > 1) {
352 QLineF line(polyData.at(0), polyData.at(1));
353 markers << PositionMarkerPair { line.p1().x(),
354 line.p1().y(),
355 line.angle(),
356 node->markerStartId(),
357 true };
358 }
359 if (node->hasMarkerMid()) {
360 for (int i = 1; i < polyData.size() - 1; i++) {
361 QPointF p0 = polyData.at(i - 1);
362 QPointF p1 = polyData.at(i);
363 QPointF p2 = polyData.at(i + 1);
364
365 markers << PositionMarkerPair { p1.x(),
366 p1.y(),
367 getMeanAngle(p0, p1, p2),
368 node->markerStartId() };
369 }
370 }
371 if (node->hasMarkerEnd() && polyData.size() > 1) {
372 QLineF line(polyData.at(polyData.size() - 1), polyData.last());
373 markers << PositionMarkerPair { line.p2().x(),
374 line.p2().y(),
375 line.angle(),
376 node->markerEndId() };
377 }
378 break;
379 }
380 case QSvgNode::Path: {
381 const QSvgPath *path = static_cast<const QSvgPath*>(node);
382 if (node->hasMarkerStart())
383 markers << PositionMarkerPair { path->path().pointAtPercent(0.).x(),
384 path->path().pointAtPercent(0.).y(),
385 path->path().angleAtPercent(0.),
386 path->markerStartId(),
387 true };
388 if (node->hasMarkerMid()) {
389 for (int i = 1; i < path->path().elementCount() - 1; i++) {
390 if (path->path().elementAt(i).type == QPainterPath::MoveToElement)
391 continue;
392 if (path->path().elementAt(i).type == QPainterPath::CurveToElement)
393 continue;
394 if (( path->path().elementAt(i).type == QPainterPath::CurveToDataElement &&
395 path->path().elementAt(i + 1).type != QPainterPath::CurveToDataElement ) ||
396 path->path().elementAt(i).type == QPainterPath::LineToElement) {
397
398 QPointF p0(path->path().elementAt(i - 1).x, path->path().elementAt(i - 1).y);
399 QPointF p1(path->path().elementAt(i).x, path->path().elementAt(i).y);
400 QPointF p2(path->path().elementAt(i + 1).x, path->path().elementAt(i + 1).y);
401
402 markers << PositionMarkerPair { p1.x(),
403 p1.y(),
404 getMeanAngle(p0, p1, p2),
405 path->markerMidId() };
406 }
407 }
408 }
409 if (node->hasMarkerEnd())
410 markers << PositionMarkerPair { path->path().pointAtPercent(1.).x(),
411 path->path().pointAtPercent(1.).y(),
412 path->path().angleAtPercent(1.),
413 path->markerEndId() };
414 break;
415 }
416 default:
417 Q_UNREACHABLE();
418 break;
419 }
420
421 return markers;
422}
423
424} // anonymous namespace
425
426
427void QSvgMarker::drawMarkersForNode(QSvgNode *node, QPainter *p, QSvgExtraStates &states)
428{
429 drawHelper(node, p, states);
430}
431
432QRectF QSvgMarker::markersBoundsForNode(const QSvgNode *node, QPainter *p, QSvgExtraStates &states)
433{
434 QRectF bounds;
435 drawHelper(node, p, states, &bounds);
436 return bounds;
437}
438
439QSvgNode::Type QSvgMarker::type() const
440{
441 return Marker;
442}
443
444void QSvgMarker::drawHelper(const QSvgNode *node, QPainter *p,
445 QSvgExtraStates &states, QRectF *boundingRect)
446{
447 QScopedValueRollback<bool> inUseGuard(states.inUse, true);
448
449 const bool isPainting = (boundingRect == nullptr);
450 const auto markers = markersForNode(node);
451 for (auto &i : markers) {
452 QSvgNode *referencedNode = node->document()->namedNode(i.markerId);
453 if (!referencedNode || referencedNode->type() != QSvgNode::Marker)
454 continue;
455 QSvgMarker *markNode = static_cast<QSvgMarker *>(referencedNode);
456
457 p->save();
458 p->translate(i.x, i.y);
459 if (markNode->orientation() == QSvgMarker::Orientation::Value) {
460 p->rotate(markNode->orientationAngle());
461 } else {
462 p->rotate(-i.angle);
463 if (i.isStartNode && markNode->orientation()
464 == QSvgMarker::Orientation::AutoStartReverse) {
465 p->scale(-1, -1);
466 }
467 }
468 QRectF oldRect = markNode->m_rect;
469 if (markNode->markerUnits() == QSvgMarker::MarkerUnits::StrokeWidth) {
470 markNode->m_rect.setWidth(markNode->m_rect.width() * p->pen().widthF());
471 markNode->m_rect.setHeight(markNode->m_rect.height() * p->pen().widthF());
472 }
473 if (isPainting)
474 markNode->draw(p, states);
475
476 if (boundingRect) {
477 QTransform xf = p->transform();
478 p->resetTransform();
479 *boundingRect |= xf.mapRect(markNode->decoratedInternalBounds(p, states));
480 }
481
482 markNode->m_rect = oldRect;
483 p->restore();
484 }
485}
486
487QImage QSvgFilterContainer::applyFilter(const QImage &buffer, QPainter *p, const QRectF &bounds) const
488{
489 QRectF localFilterRegion = m_rect.resolveRelativeLengths(bounds, m_filterUnits);
490 QRect globalFilterRegion = p->transform().mapRect(localFilterRegion).toRect();
491 QRect globalFilterRegionRel = globalFilterRegion.translated(-buffer.offset());
492
493 if (globalFilterRegionRel.isEmpty())
494 return buffer;
495
496 QImage proxy;
497 if (!QImageIOHandler::allocateImage(globalFilterRegionRel.size(), buffer.format(), &proxy)) {
498 qCWarning(lcSvgDraw) << "The requested filter is too big, ignoring";
499 return buffer;
500 }
501 proxy = buffer.copy(globalFilterRegionRel);
502 proxy.setOffset(globalFilterRegion.topLeft());
503 if (proxy.isNull())
504 return buffer;
505
506 QMap<QString, QImage> buffers;
507 buffers[QStringLiteral("")] = proxy;
508 buffers[QStringLiteral("SourceGraphic")] = proxy;
509
510 bool requiresSourceAlpha = false;
511
512 for (const auto &node : renderers()) {
513 const QSvgFeFilterPrimitive *filter = QSvgFeFilterPrimitive::castToFilterPrimitive(node.get());
514 if (filter && filter->requiresSourceAlpha()) {
515 requiresSourceAlpha = true;
516 break;
517 }
518 }
519
520 if (requiresSourceAlpha) {
521 QImage proxyAlpha = proxy.convertedTo(QImage::Format_Alpha8).convertedTo(proxy.format());
522 proxyAlpha.setOffset(proxy.offset());
523 if (proxyAlpha.isNull())
524 return buffer;
525 buffers[QStringLiteral("SourceAlpha")] = std::move(proxyAlpha);
526 }
527
528 QImage result;
529 for (const auto &node : renderers()) {
530 const QSvgFeFilterPrimitive *filter = QSvgFeFilterPrimitive::castToFilterPrimitive(node.get());
531 if (filter) {
532 result = filter->apply(buffers, p, bounds, localFilterRegion, m_primitiveUnits, m_filterUnits);
533 if (!result.isNull()) {
534 buffers[QStringLiteral("")] = result;
535 buffers[filter->result()] = result;
536 }
537 }
538 }
539 return result;
540}
541
542void QSvgFilterContainer::setSupported(bool supported)
543{
544 m_supported = supported;
545}
546
547bool QSvgFilterContainer::supported() const
548{
549 return m_supported;
550}
551
552QRectF QSvgFilterContainer::filterRegion(const QRectF &itemBounds) const
553{
554 return m_rect.resolveRelativeLengths(itemBounds, m_filterUnits);
555}
556
557QSvgNode::Type QSvgFilterContainer::type() const
558{
559 return Filter;
560}
561
562
563inline static bool isSupportedSvgFeature(QStringView str)
564{
565 constexpr auto prefix_1_2 = "http://www.w3.org/Graphics/SVG/feature/1.2/#"_L1;
566 if (str.startsWith(prefix_1_2)) {
567 const auto suffix = str.sliced(prefix_1_2.size());
568
569 constexpr auto features = qOffsetStringArray(
570 "Text",
571 "Shape",
572 "SVG",
573 "Structure",
574 "SolidColor",
575 "Hyperlinking",
576 "CoreAttribute",
577 "XlinkAttribute",
578 "SVG-static",
579 "OpacityAttribute",
580 "Gradient",
581 "Font",
582 "Image",
583 "ConditionalProcessing",
584 "Extensibility",
585 "GraphicsAttribute",
586 "Prefetch",
587 "PaintAttribute",
588 "ConditionalProcessingAttribute",
589 "ExternalResourcesRequiredAttribute"
590 );
591
592 // This is
593 // return features.contains(suffix);
594 // but QOffsetStringArray does't support heterogeneous contains()
595 for (int i = 0; i < features.count(); ++i)
596 if (suffix == QLatin1StringView{features.at(i)})
597 return true;
598 } // 1.2
599
600 return false;
601}
602
603static inline bool isSupportedSvgExtension(const QString &)
604{
605 return false;
606}
607
608
609QSvgSwitch::QSvgSwitch(QSvgNode *parent)
610 : QSvgStructureNode(parent)
611{
612 init();
613}
614
615QSvgSwitch::~QSvgSwitch()
616 = default;
617
618QSvgNode *QSvgSwitch::childToRender() const
619{
620 for (const auto &node : renderers()) {
621 if (node->isVisible() && (node->displayMode() != QSvgNode::NoneMode)) {
622 const QStringList &features = node->requiredFeatures();
623 const QStringList &extensions = node->requiredExtensions();
624 const QStringList &languages = node->requiredLanguages();
625 const QStringList &formats = node->requiredFormats();
626 const QStringList &fonts = node->requiredFonts();
627
628 bool okToRender = true;
629 if (!features.isEmpty()) {
630 QStringList::const_iterator sitr = features.constBegin();
631 for (; sitr != features.constEnd(); ++sitr) {
632 if (!isSupportedSvgFeature(*sitr)) {
633 okToRender = false;
634 break;
635 }
636 }
637 }
638
639 if (okToRender && !extensions.isEmpty()) {
640 QStringList::const_iterator sitr = extensions.constBegin();
641 for (; sitr != extensions.constEnd(); ++sitr) {
642 if (!isSupportedSvgExtension(*sitr)) {
643 okToRender = false;
644 break;
645 }
646 }
647 }
648
649 if (okToRender && !languages.isEmpty()) {
650 QStringList::const_iterator sitr = languages.constBegin();
651 okToRender = false;
652 for (; sitr != languages.constEnd(); ++sitr) {
653 if ((*sitr).startsWith(m_systemLanguagePrefix)) {
654 okToRender = true;
655 break;
656 }
657 }
658 }
659
660 if (okToRender && !formats.isEmpty())
661 okToRender = false;
662
663 if (okToRender && !fonts.isEmpty())
664 okToRender = false;
665
666 if (okToRender)
667 return node.get();
668 }
669 }
670
671 return nullptr;
672}
673
674void QSvgSwitch::drawCommand(QPainter *p, QSvgExtraStates &states)
675{
676 QSvgNode *node = childToRender();
677 if (node != nullptr)
678 node->draw(p, states);
679}
680
681QSvgNode::Type QSvgSwitch::type() const
682{
683 return Switch;
684}
685
686void QSvgSwitch::init()
687{
688 QLocale locale;
689 m_systemLanguage = locale.name().replace(QLatin1Char('_'), QLatin1Char('-'));
690 int idx = m_systemLanguage.indexOf(QLatin1Char('-'));
691 m_systemLanguagePrefix = m_systemLanguage.mid(0, idx);
692}
693
694QRectF QSvgStructureNode::internalBounds(QPainter *p, QSvgExtraStates &states) const
695{
696 QRectF bounds;
697 if (!m_recursing) {
698 QScopedValueRollback<bool> guard(m_recursing, true);
699 for (const auto &node : renderers())
700 bounds |= node->bounds(p, states);
701 }
702 return bounds;
703}
704
705QRectF QSvgStructureNode::decoratedInternalBounds(QPainter *p, QSvgExtraStates &states) const
706{
707 QRectF bounds;
708 if (!m_recursing) {
709 QScopedValueRollback<bool> guard(m_recursing, true);
710 for (const auto &node : renderers())
711 bounds |= node->decoratedBounds(p, states);
712 }
713 return bounds;
714}
715
716QSvgNode* QSvgStructureNode::previousSiblingNode(QSvgNode *n) const
717{
718 QSvgNode *prev = nullptr;
719 for (const auto &node : renderers()) {
720 if (node.get() == n)
721 return prev;
722 prev = node.get();
723 }
724 return prev;
725}
726
727void QSvgStructureNode::releaseDescendants()
728{
729 // This function will release the descendants of a QSvgStructureNode from bottom to top.
730 // Destructors are never called recursively in this case and stack overflow will not
731 // happen in deeply nested trees.
732 // This function does not allocate any memory at the cost of sacrificing some performance to
733 // make it safe to be called from a destructor.
734 QSvgNode *currentParent = this;
735 while (currentParent) {
736 switch (currentParent->type()) {
737 case QSvgNode::Doc:
738 case QSvgNode::Defs:
739 case QSvgNode::Group:
740 case QSvgNode::Mask:
741 case QSvgNode::Pattern:
742 case QSvgNode::Symbol:
743 case QSvgNode::Switch:
744 case QSvgNode::Filter:
745 {
746 QSvgStructureNode *currentParentSN = static_cast<QSvgStructureNode *>(currentParent);
747 if (currentParentSN->m_renderers.empty()) {
748 currentParent = currentParent->parent();
749 if (currentParent)
750 static_cast<QSvgStructureNode *>(currentParent)->m_renderers.pop_front();
751 } else {
752 Q_ASSERT(currentParentSN->m_renderers.front().get()->parent() == currentParent);
753 currentParent = currentParentSN->m_renderers.front().get();
754 }
755 }
756 break;
757 default:
758 currentParent = currentParent->parent();
759 if (currentParent)
760 static_cast<QSvgStructureNode *>(currentParent)->m_renderers.pop_front();
761 break;
762 }
763 }
764 Q_ASSERT(this->m_renderers.empty());
765}
766
767QSvgMask::QSvgMask(QSvgNode *parent, QSvgRectF bounds,
768 QtSvg::UnitTypes contentUnits)
769 : QSvgStructureNode(parent)
770 , m_rect(bounds)
771 , m_contentUnits(contentUnits)
772{
773}
774
775QSvgMask::~QSvgMask()
776 = default;
777
778bool QSvgMask::shouldDrawNode(QPainter *, QSvgExtraStates &) const
779{
780 return false;
781}
782
783QImage QSvgMask::createMask(QPainter *p, QSvgExtraStates &states, QSvgNode *targetNode, QRectF *globalRect) const
784{
785 QTransform t = p->transform();
786 p->resetTransform();
787 QRectF basicRect = targetNode->internalBounds(p, states);
788 *globalRect = t.mapRect(basicRect);
789 p->setTransform(t);
790 return createMask(p, states, basicRect, globalRect);
791}
792
793QImage QSvgMask::createMask(QPainter *p, QSvgExtraStates &states, const QRectF &localRect, QRectF *globalRect) const
794{
795 QRect imageBound = globalRect->toAlignedRect();
796 *globalRect = imageBound.toRectF();
797
798 QImage mask;
799 if (!QImageIOHandler::allocateImage(imageBound.size(), QImage::Format_RGBA8888, &mask)) {
800 qCWarning(lcSvgDraw) << "The requested mask size is too big, ignoring";
801 return mask;
802 }
803
804 if (Q_UNLIKELY(m_recursing))
805 return mask;
806 QScopedValueRollback<bool> recursingGuard(m_recursing, true);
807
808 // Chrome seems to return the mask of the mask if a mask is set on the mask
809 if (this->hasMask()) {
810 QSvgNode *referencedNode = document()->namedNode(this->maskId());
811 if (referencedNode && referencedNode->type() == QSvgNode::Mask) {
812 QSvgMask *maskNode = static_cast<QSvgMask *>(referencedNode);
813 QRectF boundsRect;
814 return maskNode->createMask(p, states, localRect, &boundsRect);
815 }
816 }
817
818 // The mask is created with other elements during rendering.
819 // Black pixels are masked out, white pixels are not masked.
820 // The strategy is to draw the elements in a buffer (QImage) and to map
821 // the white-black image into a transparent-white image that can be used
822 // with QPainters composition mode to set the mask.
823
824 mask.fill(Qt::transparent);
825 QPainter painter(&mask);
826 initPainter(&painter);
827
828 QSvgExtraStates maskNodeStates;
829 applyStyleRecursive(&painter, maskNodeStates);
830
831 // The transformation of the mask node is not relevant. What matters are the contentUnits
832 // and the position/scale of the node that the mask is applied to.
833 painter.resetTransform();
834 painter.translate(-imageBound.topLeft());
835 painter.setTransform(p->transform(), true);
836
837 QTransform oldT = painter.transform();
838 if (m_contentUnits == QtSvg::UnitTypes::objectBoundingBox){
839 painter.translate(localRect.topLeft());
840 painter.scale(localRect.width(), localRect.height());
841 }
842
843 // Draw all content items of the mask to generate the mask
844 for (const auto &node : renderers())
845 {
846 if ((node->isVisible()) && (node->displayMode() != QSvgNode::NoneMode))
847 node->draw(&painter, maskNodeStates);
848 }
849
850 for (int i=0; i < mask.height(); i++) {
851 QRgb *line = reinterpret_cast<QRgb *>(mask.scanLine(i));
852 for (int j=0; j < mask.width(); j++) {
853 const qreal rC = 0.2125, gC = 0.7154, bC = 0.0721; //luminanceToAlpha times alpha following SVG 1.1
854 int alpha = 255 - (qRed(line[j]) * rC + qGreen(line[j]) * gC + qBlue(line[j]) * bC) * qAlpha(line[j])/255.;
855 line[j] = qRgba(0, 0, 0, alpha);
856 }
857 }
858
859 // Make a path out of the clipRectangle and draw it inverted - black over all content items.
860 // This is required to apply a clip rectangle with transformations.
861 // painter.setClipRect(clipRect) sounds like the obvious thing to do but
862 // created artifacts due to antialiasing.
863 QRectF clipRect = m_rect.resolveRelativeLengths(localRect);
864 QPainterPath clipPath;
865 clipPath.setFillRule(Qt::OddEvenFill);
866 clipPath.addRect(mask.rect().adjusted(-10, -10, 20, 20));
867 clipPath.addPolygon(oldT.map(QPolygonF(clipRect)));
868 painter.resetTransform();
869 painter.fillPath(clipPath, Qt::black);
870 revertStyleRecursive(&painter, maskNodeStates);
871 return mask;
872}
873
874QSvgNode::Type QSvgMask::type() const
875{
876 return Mask;
877}
878
879QSvgPattern::QSvgPattern(QSvgNode *parent, QSvgRectF bounds, QRectF viewBox,
880 QtSvg::UnitTypes contentUnits, QTransform transform)
881 : QSvgStructureNode(parent),
882 m_rect(bounds),
883 m_viewBox(viewBox),
884 m_contentUnits(contentUnits),
885 m_isRendering(false),
886 m_transform(transform)
887
888{
889
890}
891
892QSvgPattern::~QSvgPattern()
893 = default;
894
895bool QSvgPattern::shouldDrawNode(QPainter *, QSvgExtraStates &) const
896{
897 return false;
898}
899
901{
902 static QImage checkerPattern;
903
904 if (checkerPattern.isNull()) {
905 checkerPattern = QImage(QSize(8, 8), QImage::Format_ARGB32);
906 QPainter p(&checkerPattern);
907 p.fillRect(QRect(0, 0, 4, 4), QColorConstants::Svg::white);
908 p.fillRect(QRect(4, 0, 4, 4), QColorConstants::Svg::black);
909 p.fillRect(QRect(0, 4, 4, 4), QColorConstants::Svg::black);
910 p.fillRect(QRect(4, 4, 4, 4), QColorConstants::Svg::white);
911 }
912
913 return checkerPattern;
914}
915
916QImage QSvgPattern::patternImage(QPainter *p, QSvgExtraStates &states, const QSvgNode *patternElement)
917{
918 // pe stands for Pattern Element
919 QRectF peBoundingBox;
920 QRectF peWorldBoundingBox;
921
922 QTransform t = p->transform();
923 p->resetTransform();
924 peBoundingBox = patternElement->internalBounds(p, states);
925 peWorldBoundingBox = t.mapRect(peBoundingBox);
926 p->setTransform(t);
927
928 // This function renders the pattern into an Image, so we need to apply the correct
929 // scaling values when we draw the pattern. The scaling is affected by two factors :
930 // - The "patternTransform" attribute which itself might contain a scaling
931 // - The scaling applied globally.
932 // The first is obtained from m11 and m22 matrix elements,
933 // while the second is calculated by dividing the patternElement global size
934 // by its local size.
935 qreal contentScaleFactorX = m_transform.m11();
936 qreal contentScaleFactorY = m_transform.m22();
937 if (m_contentUnits == QtSvg::UnitTypes::userSpaceOnUse) {
938 contentScaleFactorX *= t.m11();
939 contentScaleFactorY *= t.m22();
940 } else {
941 contentScaleFactorX *= peWorldBoundingBox.width();
942 contentScaleFactorY *= peWorldBoundingBox.height();
943 }
944
945 // Calculate the pattern bounding box depending on the used UnitTypes
946 QRectF patternBoundingBox = m_rect.resolveRelativeLengths(peBoundingBox);
947
948 QSize imageSize;
949 imageSize.setWidth(qCeil(patternBoundingBox.width() * t.m11() * m_transform.m11()));
950 imageSize.setHeight(qCeil(patternBoundingBox.height() * t.m22() * m_transform.m22()));
951 if (imageSize.isEmpty())
952 return QImage(); // Avoid division by zero in calculateAppliedTransform()
953
954 calculateAppliedTransform(t, peBoundingBox, imageSize);
955 if (document()->isCalculatingImplicitViewBox())
956 return QImage(imageSize, QImage::Format_ARGB32); // dummy image to avoid endless recursion
957 else
958 return renderPattern(imageSize, contentScaleFactorX, contentScaleFactorY);
959}
960
961QSvgNode::Type QSvgPattern::type() const
962{
963 return Pattern;
964}
965
966QImage QSvgPattern::renderPattern(QSize size, qreal contentScaleX, qreal contentScaleY)
967{
968 if (size.isEmpty() || !qIsFinite(contentScaleX) || !qIsFinite(contentScaleY))
969 return defaultPattern();
970
971 // Allocate a QImage to draw the pattern in with the calculated size.
972 QImage pattern;
973 if (!QImageIOHandler::allocateImage(size, QImage::Format_ARGB32, &pattern)) {
974 qCWarning(lcSvgDraw) << "The requested pattern size is too big, ignoring";
975 return defaultPattern();
976 }
977 pattern.fill(Qt::transparent);
978
979 if (m_isRendering) {
980 qCWarning(lcSvgDraw) << "The pattern is trying to render itself recursively. "
981 "Returning a transparent QImage of the right size.";
982 return pattern;
983 }
984 QScopedValueRollback<bool> guard(m_isRendering, true);
985
986 // Draw the pattern using our QPainter.
987 QPainter patternPainter(&pattern);
988 QSvgExtraStates patternStates;
989 initPainter(&patternPainter);
990 applyStyleRecursive(&patternPainter, patternStates);
991 patternPainter.resetTransform();
992
993 // According to the <pattern> definition, if viewBox exists then patternContentUnits
994 // is ignored
995 if (m_viewBox.isNull())
996 patternPainter.scale(contentScaleX, contentScaleY);
997 else
998 patternPainter.setWindow(m_viewBox.toRect());
999
1000 // Draw all this Pattern children nodes with our QPainter,
1001 // no need to use any Extra States
1002 for (const auto &node : renderers())
1003 node->draw(&patternPainter, patternStates);
1004
1005 revertStyleRecursive(&patternPainter, patternStates);
1006 return pattern;
1007}
1008
1009void QSvgPattern::calculateAppliedTransform(QTransform &worldTransform, QRectF peLocalBB, QSize imageSize)
1010{
1011 // Calculate the required transform to be applied to the QBrush used for correct
1012 // pattern drawing with the object being rendered.
1013 // Scale : Apply inverse the scale used above because QBrush uses the transform used
1014 // by the QPainter and this function has already rendered the QImage with the
1015 // correct size. Moreover, take into account the difference between the required
1016 // ideal image size in float and the QSize given to image as an integer value.
1017 //
1018 // Translate : Apply translation depending on the calculated x and y values so that the
1019 // drawn pattern can be shifted inside the object.
1020 // Pattern Transform : Apply the transform in the "patternTransform" attribute. This
1021 // transform contains everything except scaling, because it is
1022 // already applied above on the QImage and the QPainter while
1023 // drawing the pattern tile.
1024 m_appliedTransform.reset();
1025 qreal imageDownScaleFactorX = 1 / worldTransform.m11();
1026 qreal imageDownScaleFactorY = 1 / worldTransform.m22();
1027
1028 m_appliedTransform.scale(qIsFinite(imageDownScaleFactorX) ? imageDownScaleFactorX : 1.0,
1029 qIsFinite(imageDownScaleFactorY) ? imageDownScaleFactorY : 1.0);
1030
1031 QRectF p = m_rect.resolveRelativeLengths(peLocalBB);
1032 m_appliedTransform.scale((p.width() * worldTransform.m11() * m_transform.m11()) / imageSize.width(),
1033 (p.height() * worldTransform.m22() * m_transform.m22()) / imageSize.height());
1034
1035 QPointF translation = m_rect.translationRelativeToBoundingBox(peLocalBB);
1036 m_appliedTransform.translate(translation.x() * worldTransform.m11(), translation.y() * worldTransform.m22());
1037
1038 QTransform scalelessTransform = m_transform;
1039 scalelessTransform.scale(1 / m_transform.m11(), 1 / m_transform.m22());
1040
1041 m_appliedTransform = m_appliedTransform * scalelessTransform;
1042}
1043
1044QT_END_NAMESPACE
Definition qlist.h:81
friend class QPainter
The QPolygonF class provides a list of points using floating point precision.
Definition qpolygon.h:97
Combined button and popup list for selecting options.
#define QStringLiteral(str)
Definition qstring.h:1825
static QImage & defaultPattern()
static bool isSupportedSvgExtension(const QString &)
static bool isSupportedSvgFeature(QStringView str)