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