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