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