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

source code of qtsvg/src/svg/qsvgstructure.cpp