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