1// Copyright (C) 2024 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 "qsvgvisitorimpl_p.h"
5#include "qquickgenerator_p.h"
6#include "qquicknodeinfo_p.h"
7
8#include <private/qsvgvisitor_p.h>
9
10#include <QString>
11#include <QPainter>
12#include <QTextDocument>
13#include <QTextLayout>
14#include <QMatrix4x4>
15#include <QQuickItem>
16
17#include <private/qquickshape_p.h>
18#include <private/qquicktext_p.h>
19#include <private/qquicktranslate_p.h>
20#include <private/qquickitem_p.h>
21
22#include <private/qquickimagebase_p_p.h>
23#include <private/qquickimage_p.h>
24#include <private/qsgcurveprocessor_p.h>
25
26#include <private/qquadpath_p.h>
27
28#include <QtCore/private/qstringiterator_p.h>
29
30#include "utils_p.h"
31#include <QtCore/qloggingcategory.h>
32
33#include <QtSvg/private/qsvgstyle_p.h>
34
35QT_BEGIN_NAMESPACE
36
37using namespace Qt::StringLiterals;
38
39Q_DECLARE_LOGGING_CATEGORY(lcQuickVectorImage)
40
41class QSvgStyleResolver
42{
43public:
44 QSvgStyleResolver()
45 {
46 m_dummyImage = QImage(1, 1, QImage::Format_RGB32);
47 m_dummyPainter.begin(&m_dummyImage);
48 QPen defaultPen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin);
49 defaultPen.setMiterLimit(4);
50 m_dummyPainter.setPen(defaultPen);
51 m_dummyPainter.setBrush(Qt::black);
52 }
53
54 ~QSvgStyleResolver()
55 {
56 m_dummyPainter.end();
57 }
58
59 QPainter& painter() { return m_dummyPainter; }
60 QSvgExtraStates& states() { return m_svgState; }
61
62 QColor currentFillColor() const
63 {
64 if (m_dummyPainter.brush().style() == Qt::NoBrush ||
65 m_dummyPainter.brush().color() == QColorConstants::Transparent) {
66 return QColor(QColorConstants::Transparent);
67 }
68
69 QColor fillColor;
70 fillColor = m_dummyPainter.brush().color();
71 fillColor.setAlphaF(m_svgState.fillOpacity);
72
73 return fillColor;
74 }
75
76 qreal currentFillOpacity() const
77 {
78 return m_svgState.fillOpacity;
79 }
80
81 const QGradient *currentStrokeGradient() const
82 {
83 QBrush brush = m_dummyPainter.pen().brush();
84 if (brush.style() == Qt::LinearGradientPattern
85 || brush.style() == Qt::RadialGradientPattern
86 || brush.style() == Qt::ConicalGradientPattern) {
87 return brush.gradient();
88 }
89 return nullptr;
90 }
91
92 const QGradient *currentFillGradient() const
93 {
94 if (m_dummyPainter.brush().style() == Qt::LinearGradientPattern || m_dummyPainter.brush().style() == Qt::RadialGradientPattern || m_dummyPainter.brush().style() == Qt::ConicalGradientPattern )
95 return m_dummyPainter.brush().gradient();
96 return nullptr;
97 }
98
99 QTransform currentFillTransform() const
100 {
101 return m_dummyPainter.brush().transform();
102 }
103
104 QColor currentStrokeColor() const
105 {
106 if (m_dummyPainter.pen().brush().style() == Qt::NoBrush ||
107 m_dummyPainter.pen().brush().color() == QColorConstants::Transparent) {
108 return QColor(QColorConstants::Transparent);
109 }
110
111 QColor strokeColor;
112 strokeColor = m_dummyPainter.pen().brush().color();
113 strokeColor.setAlphaF(m_svgState.strokeOpacity);
114
115 return strokeColor;
116 }
117
118 static QGradient applyOpacityToGradient(const QGradient &gradient, float opacity)
119 {
120 QGradient grad = gradient;
121 QGradientStops stops;
122 for (auto &stop : grad.stops()) {
123 stop.second.setAlphaF(stop.second.alphaF() * opacity);
124 stops.append(t: stop);
125 }
126
127 grad.setStops(stops);
128
129 return grad;
130 }
131
132 float currentStrokeWidth() const
133 {
134 float penWidth = m_dummyPainter.pen().widthF();
135 return penWidth ? penWidth : 1;
136 }
137
138 QPen currentStroke() const
139 {
140 return m_dummyPainter.pen();
141 }
142
143protected:
144 QPainter m_dummyPainter;
145 QImage m_dummyImage;
146 QSvgExtraStates m_svgState;
147};
148
149Q_GLOBAL_STATIC(QSvgStyleResolver, styleResolver)
150
151namespace {
152inline bool isPathContainer(const QSvgStructureNode *node)
153{
154 bool foundPath = false;
155 for (const auto *child : node->renderers()) {
156 switch (child->type()) {
157 // nodes that shouldn't go inside Shape{}
158 case QSvgNode::Switch:
159 case QSvgNode::Doc:
160 case QSvgNode::Group:
161 case QSvgNode::Animation:
162 case QSvgNode::Use:
163 case QSvgNode::Video:
164 case QSvgNode::Image:
165 case QSvgNode::Textarea:
166 case QSvgNode::Text:
167 case QSvgNode::Tspan:
168 //qCDebug(lcQuickVectorGraphics) << "NOT path container because" << node->typeName() ;
169 return false;
170
171 // nodes that could go inside Shape{}
172 case QSvgNode::Defs:
173 break;
174
175 // nodes that are done as pure ShapePath{}
176 case QSvgNode::Rect:
177 case QSvgNode::Circle:
178 case QSvgNode::Ellipse:
179 case QSvgNode::Line:
180 case QSvgNode::Path:
181 case QSvgNode::Polygon:
182 case QSvgNode::Polyline:
183 if (!child->style().transform.isDefault()) {
184 //qCDebug(lcQuickVectorGraphics) << "NOT path container because local transform";
185 return false;
186 }
187 foundPath = true;
188 break;
189 default:
190 qCDebug(lcQuickVectorImage) << "Unhandled type in switch" << child->type();
191 break;
192 }
193 }
194 //qCDebug(lcQuickVectorGraphics) << "Container" << node->nodeId() << node->typeName() << "is" << foundPath;
195 return foundPath;
196}
197
198static QString capStyleName(Qt::PenCapStyle style)
199{
200 QString styleName;
201
202 switch (style) {
203 case Qt::SquareCap:
204 styleName = QStringLiteral("squarecap");
205 break;
206 case Qt::FlatCap:
207 styleName = QStringLiteral("flatcap");
208 break;
209 case Qt::RoundCap:
210 styleName = QStringLiteral("roundcap");
211 break;
212 default:
213 break;
214 }
215
216 return styleName;
217}
218
219static QString joinStyleName(Qt::PenJoinStyle style)
220{
221 QString styleName;
222
223 switch (style) {
224 case Qt::MiterJoin:
225 styleName = QStringLiteral("miterjoin");
226 break;
227 case Qt::BevelJoin:
228 styleName = QStringLiteral("beveljoin");
229 break;
230 case Qt::RoundJoin:
231 styleName = QStringLiteral("roundjoin");
232 break;
233 case Qt::SvgMiterJoin:
234 styleName = QStringLiteral("svgmiterjoin");
235 break;
236 default:
237 break;
238 }
239
240 return styleName;
241}
242
243static QString dashArrayString(QList<qreal> dashArray)
244{
245 if (dashArray.isEmpty())
246 return QString();
247
248 QString dashArrayString;
249 QTextStream stream(&dashArrayString);
250
251 for (int i = 0; i < dashArray.length() - 1; i++) {
252 qreal value = dashArray[i];
253 stream << value << ", ";
254 }
255
256 stream << dashArray.last();
257
258 return dashArrayString;
259}
260};
261
262QSvgVisitorImpl::QSvgVisitorImpl(const QString svgFileName, QQuickGenerator *generator)
263 : m_svgFileName(svgFileName)
264 , m_generator(generator)
265{
266}
267
268bool QSvgVisitorImpl::traverse()
269{
270 if (!m_generator) {
271 qCDebug(lcQuickVectorImage) << "No valid QQuickGenerator is set. Genration will stop";
272 return false;
273 }
274
275 auto *doc = QSvgTinyDocument::load(file: m_svgFileName);
276 if (!doc) {
277 qCDebug(lcQuickVectorImage) << "Not a valid Svg File : " << m_svgFileName;
278 return false;
279 }
280
281 QSvgVisitor::traverse(node: doc);
282 return true;
283}
284
285void QSvgVisitorImpl::visitNode(const QSvgNode *node)
286{
287 handleBaseNodeSetup(node);
288
289 NodeInfo info;
290 fillCommonNodeInfo(node, info);
291
292 m_generator->generateNode(info);
293
294 handleBaseNodeEnd(node);
295}
296
297void QSvgVisitorImpl::visitImageNode(const QSvgImage *node)
298{
299 // TODO: this requires proper asset management.
300 handleBaseNodeSetup(node);
301
302 ImageNodeInfo info;
303 fillCommonNodeInfo(node, info);
304 info.image = node->image();
305 info.rect = node->rect();
306 info.externalFileReference = node->filename();
307
308 m_generator->generateImageNode(info);
309
310 handleBaseNodeEnd(node);
311}
312
313void QSvgVisitorImpl::visitRectNode(const QSvgRect *node)
314{
315 QRectF rect = node->rect();
316 QPointF rads = node->radius();
317 // This is using Qt::RelativeSize semantics: percentage of half rect size
318 qreal x1 = rect.left();
319 qreal x2 = rect.right();
320 qreal y1 = rect.top();
321 qreal y2 = rect.bottom();
322
323 qreal rx = rads.x() * rect.width() / 200;
324 qreal ry = rads.y() * rect.height() / 200;
325 QPainterPath p;
326
327 p.moveTo(x: x1 + rx, y: y1);
328 p.lineTo(x: x2 - rx, y: y1);
329 // qCDebug(lcQuickVectorGraphics) << "Line1" << x2 - rx << y1;
330 p.arcTo(x: x2 - rx * 2, y: y1, w: rx * 2, h: ry * 2, startAngle: 90, arcLength: -90); // ARC to x2, y1 + ry
331 // qCDebug(lcQuickVectorGraphics) << "p1" << p;
332
333 p.lineTo(x: x2, y: y2 - ry);
334 p.arcTo(x: x2 - rx * 2, y: y2 - ry * 2, w: rx * 2, h: ry * 2, startAngle: 0, arcLength: -90); // ARC to x2 - rx, y2
335
336 p.lineTo(x: x1 + rx, y: y2);
337 p.arcTo(x: x1, y: y2 - ry * 2, w: rx * 2, h: ry * 2, startAngle: 270, arcLength: -90); // ARC to x1, y2 - ry
338
339 p.lineTo(x: x1, y: y1 + ry);
340 p.arcTo(x: x1, y: y1, w: rx * 2, h: ry * 2, startAngle: 180, arcLength: -90); // ARC to x1 + rx, y1
341
342 handlePathNode(node, path: p);
343}
344
345void QSvgVisitorImpl::visitEllipseNode(const QSvgEllipse *node)
346{
347 QRectF rect = node->rect();
348
349 QPainterPath p;
350 p.addEllipse(rect);
351
352 handlePathNode(node, path: p);
353}
354
355void QSvgVisitorImpl::visitPathNode(const QSvgPath *node)
356{
357 handlePathNode(node, path: node->path());
358}
359
360void QSvgVisitorImpl::visitLineNode(const QSvgLine *node)
361{
362 QPainterPath p;
363 p.moveTo(p: node->line().p1());
364 p.lineTo(p: node->line().p2());
365 handlePathNode(node, path: p);
366}
367
368void QSvgVisitorImpl::visitPolygonNode(const QSvgPolygon *node)
369{
370 QPainterPath p = QQuickVectorImageGenerator::Utils::polygonToPath(poly: node->polygon(), closed: true);
371 handlePathNode(node, path: p);
372}
373
374void QSvgVisitorImpl::visitPolylineNode(const QSvgPolyline *node)
375{
376 QPainterPath p = QQuickVectorImageGenerator::Utils::polygonToPath(poly: node->polygon(), closed: false);
377 handlePathNode(node, path: p);
378}
379
380QString QSvgVisitorImpl::gradientCssDescription(const QGradient *gradient)
381{
382 QString cssDescription;
383 if (gradient->type() == QGradient::LinearGradient) {
384 const QLinearGradient *linearGradient = static_cast<const QLinearGradient *>(gradient);
385
386 cssDescription += " -qt-foreground: qlineargradient("_L1;
387 cssDescription += "x1:"_L1 + QString::number(linearGradient->start().x()) + u',';
388 cssDescription += "y1:"_L1 + QString::number(linearGradient->start().y()) + u',';
389 cssDescription += "x2:"_L1 + QString::number(linearGradient->finalStop().x()) + u',';
390 cssDescription += "y2:"_L1 + QString::number(linearGradient->finalStop().y()) + u',';
391 } else if (gradient->type() == QGradient::RadialGradient) {
392 const QRadialGradient *radialGradient = static_cast<const QRadialGradient *>(gradient);
393
394 cssDescription += " -qt-foreground: qradialgradient("_L1;
395 cssDescription += "cx:"_L1 + QString::number(radialGradient->center().x()) + u',';
396 cssDescription += "cy:"_L1 + QString::number(radialGradient->center().y()) + u',';
397 cssDescription += "fx:"_L1 + QString::number(radialGradient->focalPoint().x()) + u',';
398 cssDescription += "fy:"_L1 + QString::number(radialGradient->focalPoint().y()) + u',';
399 cssDescription += "radius:"_L1 + QString::number(radialGradient->radius()) + u',';
400 } else {
401 const QConicalGradient *conicalGradient = static_cast<const QConicalGradient *>(gradient);
402
403 cssDescription += " -qt-foreground: qconicalgradient("_L1;
404 cssDescription += "cx:"_L1 + QString::number(conicalGradient->center().x()) + u',';
405 cssDescription += "cy:"_L1 + QString::number(conicalGradient->center().y()) + u',';
406 cssDescription += "angle:"_L1 + QString::number(conicalGradient->angle()) + u',';
407 }
408
409 const QStringList coordinateModes = { "logical"_L1, "stretchtodevice"_L1, "objectbounding"_L1, "object"_L1 };
410 cssDescription += "coordinatemode:"_L1;
411 cssDescription += coordinateModes.at(i: int(gradient->coordinateMode()));
412 cssDescription += u',';
413
414 const QStringList spreads = { "pad"_L1, "reflect"_L1, "repeat"_L1 };
415 cssDescription += "spread:"_L1;
416 cssDescription += spreads.at(i: int(gradient->spread()));
417
418 for (const QGradientStop &stop : gradient->stops()) {
419 cssDescription += ",stop:"_L1;
420 cssDescription += QString::number(stop.first);
421 cssDescription += u' ';
422 cssDescription += stop.second.name(format: QColor::HexArgb);
423 }
424
425 cssDescription += ");"_L1;
426
427 return cssDescription;
428}
429
430QString QSvgVisitorImpl::colorCssDescription(QColor color)
431{
432 QString cssDescription;
433 cssDescription += QStringLiteral("rgba(");
434 cssDescription += QString::number(color.red()) + QStringLiteral(",");
435 cssDescription += QString::number(color.green()) + QStringLiteral(",");
436 cssDescription += QString::number(color.blue()) + QStringLiteral(",");
437 cssDescription += QString::number(color.alphaF()) + QStringLiteral(")");
438
439 return cssDescription;
440}
441
442namespace {
443
444 // Simple class for representing the SVG font as a font engine
445 // We use the Proxy font engine type, which is currently unused and does not map to
446 // any specific font engine
447 // (The QSvgFont object must outlive the engine.)
448 class QSvgFontEngine : public QFontEngine
449 {
450 public:
451 QSvgFontEngine(const QSvgFont *font, qreal size);
452
453 QFontEngine *cloneWithSize(qreal size) const override;
454
455 glyph_t glyphIndex(uint ucs4) const override;
456 int stringToCMap(const QChar *str,
457 int len,
458 QGlyphLayout *glyphs,
459 int *nglyphs,
460 ShaperFlags flags) const override;
461
462 void addGlyphsToPath(glyph_t *glyphs,
463 QFixedPoint *positions,
464 int nGlyphs,
465 QPainterPath *path,
466 QTextItem::RenderFlags flags) override;
467
468 glyph_metrics_t boundingBox(glyph_t glyph) override;
469
470 void recalcAdvances(QGlyphLayout *, ShaperFlags) const override;
471 QFixed ascent() const override;
472 QFixed capHeight() const override;
473 QFixed descent() const override;
474 QFixed leading() const override;
475 qreal maxCharWidth() const override;
476 qreal minLeftBearing() const override;
477 qreal minRightBearing() const override;
478
479 QFixed emSquareSize() const override;
480
481 private:
482 const QSvgFont *m_font;
483 };
484
485 QSvgFontEngine::QSvgFontEngine(const QSvgFont *font, qreal size)
486 : QFontEngine(Proxy)
487 , m_font(font)
488 {
489 fontDef.pixelSize = size;
490 fontDef.families = QStringList(m_font->m_familyName);
491 }
492
493 QFixed QSvgFontEngine::emSquareSize() const
494 {
495 return QFixed::fromReal(r: m_font->m_unitsPerEm);
496 }
497
498 glyph_t QSvgFontEngine::glyphIndex(uint ucs4) const
499 {
500 if (ucs4 < USHRT_MAX && m_font->m_glyphs.contains(key: QChar(ushort(ucs4))))
501 return glyph_t(ucs4);
502
503 return 0;
504 }
505
506 int QSvgFontEngine::stringToCMap(const QChar *str,
507 int len,
508 QGlyphLayout *glyphs,
509 int *nglyphs,
510 ShaperFlags flags) const
511 {
512 Q_ASSERT(glyphs->numGlyphs >= *nglyphs);
513 if (*nglyphs < len) {
514 *nglyphs = len;
515 return -1;
516 }
517
518 int ucs4Length = 0;
519 QStringIterator it(str, str + len);
520 while (it.hasNext()) {
521 char32_t ucs4 = it.next();
522 glyph_t index = glyphIndex(ucs4);
523 glyphs->glyphs[ucs4Length++] = index;
524 }
525
526 *nglyphs = ucs4Length;
527 glyphs->numGlyphs = ucs4Length;
528
529 if (!(flags & GlyphIndicesOnly))
530 recalcAdvances(glyphs, flags);
531
532 return *nglyphs;
533 }
534
535 void QSvgFontEngine::addGlyphsToPath(glyph_t *glyphs,
536 QFixedPoint *positions,
537 int nGlyphs,
538 QPainterPath *path,
539 QTextItem::RenderFlags flags)
540 {
541 Q_UNUSED(flags);
542 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
543 for (int i = 0; i < nGlyphs; ++i) {
544 glyph_t index = glyphs[i];
545 if (index > 0) {
546 QPointF position = positions[i].toPointF();
547 QPainterPath glyphPath = m_font->m_glyphs.value(key: QChar(ushort(index))).m_path;
548
549 QTransform xform;
550 xform.translate(dx: position.x(), dy: position.y());
551 xform.scale(sx: scale, sy: -scale);
552 glyphPath = xform.map(p: glyphPath);
553 path->addPath(path: glyphPath);
554 }
555 }
556 }
557
558 glyph_metrics_t QSvgFontEngine::boundingBox(glyph_t glyph)
559 {
560 glyph_metrics_t ret;
561 ret.x = 0; // left bearing
562 ret.y = -ascent();
563 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
564 const QSvgGlyph &svgGlyph = m_font->m_glyphs.value(key: QChar(ushort(glyph)));
565 ret.width = QFixed::fromReal(r: svgGlyph.m_horizAdvX * scale);
566 ret.height = ascent() + descent();
567 return ret;
568 }
569
570 QFontEngine *QSvgFontEngine::cloneWithSize(qreal size) const
571 {
572 QSvgFontEngine *otherEngine = new QSvgFontEngine(m_font, size);
573 return otherEngine;
574 }
575
576 void QSvgFontEngine::recalcAdvances(QGlyphLayout *glyphLayout, ShaperFlags) const
577 {
578 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
579 for (int i = 0; i < glyphLayout->numGlyphs; i++) {
580 glyph_t glyph = glyphLayout->glyphs[i];
581 const QSvgGlyph &svgGlyph = m_font->m_glyphs.value(key: QChar(ushort(glyph)));
582 glyphLayout->advances[i] = QFixed::fromReal(r: svgGlyph.m_horizAdvX * scale);
583 }
584 }
585
586 QFixed QSvgFontEngine::ascent() const
587 {
588 return QFixed::fromReal(r: fontDef.pixelSize);
589 }
590
591 QFixed QSvgFontEngine::capHeight() const
592 {
593 return ascent();
594 }
595 QFixed QSvgFontEngine::descent() const
596 {
597 return QFixed{};
598 }
599
600 QFixed QSvgFontEngine::leading() const
601 {
602 return QFixed{};
603 }
604
605 qreal QSvgFontEngine::maxCharWidth() const
606 {
607 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
608 return m_font->m_horizAdvX * scale;
609 }
610
611 qreal QSvgFontEngine::minLeftBearing() const
612 {
613 return 0.0;
614 }
615
616 qreal QSvgFontEngine::minRightBearing() const
617 {
618 return 0.0;
619 }
620}
621
622void QSvgVisitorImpl::visitTextNode(const QSvgText *node)
623{
624 handleBaseNodeSetup(node);
625 const bool isTextArea = node->type() == QSvgNode::Textarea;
626
627 QString text;
628 const QSvgFont *svgFont = styleResolver->states().svgFont;
629 bool needsRichText = false;
630 bool preserveWhiteSpace = node->whitespaceMode() == QSvgText::Preserve;
631 const QGradient *mainGradient = styleResolver->currentFillGradient();
632
633 QFontEngine *fontEngine = nullptr;
634 if (svgFont != nullptr) {
635 fontEngine = new QSvgFontEngine(svgFont, styleResolver->painter().font().pointSize());
636 fontEngine->ref.ref();
637 }
638
639#if QT_CONFIG(texthtmlparser)
640 bool needsPathNode = mainGradient != nullptr
641 || svgFont != nullptr
642 || styleResolver->currentStrokeGradient() != nullptr;
643#endif
644 for (const auto *tspan : node->tspans()) {
645 if (!tspan) {
646 text += QStringLiteral("<br>");
647 continue;
648 }
649
650 // Note: We cannot get the font directly from the style, since this does
651 // not apply the weight, since this is relative and depends on current state.
652 handleBaseNodeSetup(node: tspan);
653 QFont font = styleResolver->painter().font();
654
655 QString styleTagContent;
656
657 if ((font.resolveMask() & QFont::FamilyResolved)
658 || (font.resolveMask() & QFont::FamiliesResolved)) {
659 styleTagContent += QStringLiteral("font-family: %1;").arg(a: font.family());
660 }
661
662 if (font.resolveMask() & QFont::WeightResolved
663 && font.weight() != QFont::Normal
664 && font.weight() != QFont::Bold) {
665 styleTagContent += QStringLiteral("font-weight: %1;").arg(a: int(font.weight()));
666 }
667
668 if (font.resolveMask() & QFont::SizeResolved) {
669 // Pixel size stored as point size in SVG parser
670 styleTagContent += QStringLiteral("font-size: %1px;").arg(a: int(font.pointSizeF()));
671 }
672
673 if (font.resolveMask() & QFont::CapitalizationResolved
674 && font.capitalization() == QFont::SmallCaps) {
675 styleTagContent += QStringLiteral("font-variant: small-caps;");
676 }
677
678 if (styleResolver->currentFillGradient() != nullptr
679 && styleResolver->currentFillGradient() != mainGradient) {
680 const QGradient grad = styleResolver->applyOpacityToGradient(gradient: *styleResolver->currentFillGradient(), opacity: styleResolver->currentFillOpacity());
681 styleTagContent += gradientCssDescription(gradient: &grad) + u';';
682#if QT_CONFIG(texthtmlparser)
683 needsPathNode = true;
684#endif
685 }
686
687 const QColor currentStrokeColor = styleResolver->currentStrokeColor();
688 if (currentStrokeColor.alpha() > 0) {
689 QString strokeColor = colorCssDescription(color: currentStrokeColor);
690 styleTagContent += QStringLiteral("-qt-stroke-color:%1;").arg(a: strokeColor);
691 styleTagContent += QStringLiteral("-qt-stroke-width:%1px;").arg(a: styleResolver->currentStrokeWidth());
692 styleTagContent += QStringLiteral("-qt-stroke-dasharray:%1;").arg(a: dashArrayString(dashArray: styleResolver->currentStroke().dashPattern()));
693 styleTagContent += QStringLiteral("-qt-stroke-dashoffset:%1;").arg(a: styleResolver->currentStroke().dashOffset());
694 styleTagContent += QStringLiteral("-qt-stroke-lineCap:%1;").arg(a: capStyleName(style: styleResolver->currentStroke().capStyle()));
695 styleTagContent += QStringLiteral("-qt-stroke-lineJoin:%1;").arg(a: joinStyleName(style: styleResolver->currentStroke().joinStyle()));
696 if (styleResolver->currentStroke().joinStyle() == Qt::MiterJoin || styleResolver->currentStroke().joinStyle() == Qt::SvgMiterJoin)
697 styleTagContent += QStringLiteral("-qt-stroke-miterlimit:%1;").arg(a: styleResolver->currentStroke().miterLimit());
698#if QT_CONFIG(texthtmlparser)
699 needsPathNode = true;
700#endif
701 }
702
703 if (tspan->whitespaceMode() == QSvgText::Preserve && !preserveWhiteSpace)
704 styleTagContent += QStringLiteral("white-space: pre-wrap;");
705
706 QString content = tspan->text().toHtmlEscaped();
707 content.replace(before: QLatin1Char('\t'), after: QLatin1Char(' '));
708 content.replace(before: QLatin1Char('\n'), after: QLatin1Char(' '));
709
710 bool fontTag = false;
711 if (!tspan->style().fill.isDefault()) {
712 auto &b = tspan->style().fill->qbrush();
713 qCDebug(lcQuickVectorImage) << "tspan FILL:" << b;
714 if (b.style() != Qt::NoBrush)
715 {
716 if (qFuzzyCompare(p1: b.color().alphaF() + 1.0, p2: 2.0))
717 {
718 QString spanColor = b.color().name();
719 fontTag = !spanColor.isEmpty();
720 if (fontTag)
721 text += QStringLiteral("<font color=\"%1\">").arg(a: spanColor);
722 } else {
723 QString spanColor = colorCssDescription(color: b.color());
724 styleTagContent += QStringLiteral("color:%1").arg(a: spanColor);
725 }
726 }
727 }
728
729 needsRichText = needsRichText || !styleTagContent.isEmpty();
730 if (!styleTagContent.isEmpty())
731 text += QStringLiteral("<span style=\"%1\">").arg(a: styleTagContent);
732
733 if (font.resolveMask() & QFont::WeightResolved && font.bold())
734 text += QStringLiteral("<b>");
735
736 if (font.resolveMask() & QFont::StyleResolved && font.italic())
737 text += QStringLiteral("<i>");
738
739 if (font.resolveMask() & QFont::CapitalizationResolved) {
740 switch (font.capitalization()) {
741 case QFont::AllLowercase:
742 content = content.toLower();
743 break;
744 case QFont::AllUppercase:
745 content = content.toUpper();
746 break;
747 case QFont::Capitalize:
748 // ### We need to iterate over the string and do the title case conversion,
749 // since this is not part of QString.
750 qCWarning(lcQuickVectorImage) << "Title case not implemented for tspan";
751 break;
752 default:
753 break;
754 }
755 }
756 text += content;
757 if (fontTag)
758 text += QStringLiteral("</font>");
759
760 if (font.resolveMask() & QFont::StyleResolved && font.italic())
761 text += QStringLiteral("</i>");
762
763 if (font.resolveMask() & QFont::WeightResolved && font.bold())
764 text += QStringLiteral("</b>");
765
766 if (!styleTagContent.isEmpty())
767 text += QStringLiteral("</span>");
768
769 handleBaseNodeEnd(node: tspan);
770 }
771
772 if (preserveWhiteSpace && (needsRichText || styleResolver->currentFillGradient() != nullptr))
773 text = QStringLiteral("<span style=\"white-space: pre-wrap\">") + text + QStringLiteral("</span>");
774
775 QFont font = styleResolver->painter().font();
776 if (font.pixelSize() <= 0 && font.pointSize() > 0)
777 font.setPixelSize(font.pointSize()); // Pixel size stored as point size by SVG parser
778
779 font.setHintingPreference(QFont::PreferNoHinting);
780
781#if QT_CONFIG(texthtmlparser)
782 if (needsPathNode) {
783 QTextDocument document;
784 document.setHtml(text);
785 if (isTextArea && node->size().width() > 0)
786 document.setTextWidth(node->size().width());
787 document.setDefaultFont(font);
788 document.pageCount(); // Force layout
789
790 QTextBlock block = document.firstBlock();
791 while (block.isValid()) {
792 QTextLayout *lout = block.layout();
793
794 if (lout != nullptr) {
795 QRectF boundingRect = lout->boundingRect();
796
797 // If this block has requested the current SVG font, we override it
798 // (note that this limits the text to one svg font, but this is also the case
799 // in the QPainter at the moment, and needs a more centralized solution in Qt Svg
800 // first)
801 QFont blockFont = block.charFormat().font();
802 if (svgFont != nullptr
803 && blockFont.family() == svgFont->m_familyName) {
804 QRawFont rawFont;
805 QRawFontPrivate *rawFontD = QRawFontPrivate::get(font: rawFont);
806 rawFontD->setFontEngine(fontEngine->cloneWithSize(blockFont.pixelSize()));
807
808 lout->setRawFont(rawFont);
809 }
810
811 auto addPathForFormat = [&](QPainterPath p, QTextCharFormat fmt) {
812 PathNodeInfo info;
813 fillCommonNodeInfo(node, info);
814 auto fillStyle = node->style().fill;
815 if (fillStyle)
816 info.fillRule = fillStyle->fillRule();
817
818 if (fmt.hasProperty(propertyId: QTextCharFormat::ForegroundBrush)) {
819 info.fillColor = fmt.foreground().color();
820 if (fmt.foreground().gradient() != nullptr && fmt.foreground().gradient()->type() != QGradient::NoGradient)
821 info.grad = *fmt.foreground().gradient();
822 } else {
823 info.fillColor = styleResolver->currentFillColor();
824 }
825
826 info.painterPath = p;
827
828 const QGradient *strokeGradient = styleResolver->currentStrokeGradient();
829 QPen pen;
830 if (fmt.hasProperty(propertyId: QTextCharFormat::TextOutline)) {
831 pen = fmt.textOutline();
832 if (strokeGradient == nullptr) {
833 info.strokeStyle = StrokeStyle::fromPen(p: pen);
834 info.strokeStyle.color = pen.color();
835 }
836 } else {
837 pen = styleResolver->currentStroke();
838 if (strokeGradient == nullptr) {
839 info.strokeStyle = StrokeStyle::fromPen(p: pen);
840 info.strokeStyle.color = styleResolver->currentStrokeColor();
841 }
842 }
843
844 if (info.grad.type() == QGradient::NoGradient && styleResolver->currentFillGradient() != nullptr)
845 info.grad = styleResolver->applyOpacityToGradient(gradient: *styleResolver->currentFillGradient(), opacity: styleResolver->currentFillOpacity());
846
847 info.fillTransform = styleResolver->currentFillTransform();
848
849 m_generator->generatePath(info, overrideBoundingRect: boundingRect);
850
851 if (strokeGradient != nullptr) {
852 PathNodeInfo strokeInfo;
853 fillCommonNodeInfo(node, info&: strokeInfo);
854
855 strokeInfo.grad = *strokeGradient;
856
857 QPainterPathStroker stroker(pen);
858 strokeInfo.painterPath = stroker.createStroke(path: p);
859 m_generator->generatePath(info: strokeInfo, overrideBoundingRect: boundingRect);
860 }
861 };
862
863 qreal baselineOffset = -QFontMetricsF(font).ascent();
864 if (lout->lineCount() > 0 && lout->lineAt(i: 0).isValid())
865 baselineOffset = -lout->lineAt(i: 0).ascent();
866
867 const QPointF baselineTranslation(0.0, baselineOffset);
868 auto glyphsToPath = [&](QList<QGlyphRun> glyphRuns, qreal width) {
869 QList<QPainterPath> paths;
870 for (const QGlyphRun &glyphRun : glyphRuns) {
871 QRawFont font = glyphRun.rawFont();
872 QList<quint32> glyphIndexes = glyphRun.glyphIndexes();
873 QList<QPointF> positions = glyphRun.positions();
874
875 for (qsizetype j = 0; j < glyphIndexes.size(); ++j) {
876 quint32 glyphIndex = glyphIndexes.at(i: j);
877 const QPointF &pos = positions.at(i: j);
878
879 QPainterPath p = font.pathForGlyph(glyphIndex);
880 p.translate(offset: pos + node->position() + baselineTranslation);
881 if (styleResolver->states().textAnchor == Qt::AlignHCenter)
882 p.translate(offset: QPointF(-0.5 * width, 0));
883 else if (styleResolver->states().textAnchor == Qt::AlignRight)
884 p.translate(offset: QPointF(-width, 0));
885 paths.append(t: p);
886 }
887 }
888
889 return paths;
890 };
891
892 QList<QTextLayout::FormatRange> formats = block.textFormats();
893 for (int i = 0; i < formats.size(); ++i) {
894 QTextLayout::FormatRange range = formats.at(i);
895
896 // If we hit a "multi" anchor, it means we have additional formats to apply
897 // for both this and the subsequent range, so we merge them.
898 if (!range.format.anchorNames().isEmpty()
899 && range.format.anchorNames().first().startsWith(QStringLiteral("multi"))
900 && i < formats.size() - 1) {
901 QTextLayout::FormatRange nextRange = formats.at(i: ++i);
902 range.length += nextRange.length;
903 range.format.merge(other: nextRange.format);
904 }
905 QList<QGlyphRun> glyphRuns = lout->glyphRuns(from: range.start, length: range.length);
906 QList<QPainterPath> paths = glyphsToPath(glyphRuns, lout->minimumWidth());
907 for (const QPainterPath &path : paths)
908 addPathForFormat(path, range.format);
909 }
910 }
911
912 block = block.next();
913 }
914 } else
915#endif
916 {
917 TextNodeInfo info;
918 fillCommonNodeInfo(node, info);
919
920 info.position = node->position();
921 info.size = node->size();
922 info.font = font;
923 info.text = text;
924 info.isTextArea = isTextArea;
925 info.needsRichText = needsRichText;
926 info.fillColor = styleResolver->currentFillColor();
927 info.alignment = styleResolver->states().textAnchor;
928 info.strokeColor = styleResolver->currentStrokeColor();
929
930 m_generator->generateTextNode(info);
931 }
932
933 handleBaseNodeEnd(node);
934
935 if (fontEngine != nullptr) {
936 fontEngine->ref.deref();
937 Q_ASSERT(fontEngine->ref.loadRelaxed() == 0);
938 delete fontEngine;
939 }
940}
941
942void QSvgVisitorImpl::visitUseNode(const QSvgUse *node)
943{
944 QSvgNode *link = node->link();
945 if (!link)
946 return;
947
948 handleBaseNodeSetup(node);
949 UseNodeInfo info;
950 fillCommonNodeInfo(node, info);
951
952 info.stage = StructureNodeStage::Start;
953 info.startPos = node->start();
954
955 m_generator->generateUseNode(info);
956
957 QSvgVisitor::traverse(node: link);
958
959 info.stage = StructureNodeStage::End;
960 m_generator->generateUseNode(info);
961 handleBaseNodeEnd(node);
962}
963
964bool QSvgVisitorImpl::visitSwitchNodeStart(const QSvgSwitch *node)
965{
966 QSvgNode *link = node->childToRender();
967 if (!link)
968 return false;
969
970 QSvgVisitor::traverse(node: link);
971
972 return false;
973}
974
975void QSvgVisitorImpl::visitSwitchNodeEnd(const QSvgSwitch *node)
976{
977 Q_UNUSED(node);
978}
979
980bool QSvgVisitorImpl::visitDefsNodeStart(const QSvgDefs *node)
981{
982 Q_UNUSED(node)
983
984 return m_generator->generateDefsNode(info: NodeInfo{});
985}
986
987bool QSvgVisitorImpl::visitStructureNodeStart(const QSvgStructureNode *node)
988{
989 constexpr bool forceSeparatePaths = false;
990 handleBaseNodeSetup(node);
991
992 StructureNodeInfo info;
993
994 fillCommonNodeInfo(node, info);
995 info.forceSeparatePaths = forceSeparatePaths;
996 info.isPathContainer = isPathContainer(node);
997 info.stage = StructureNodeStage::Start;
998
999 return m_generator->generateStructureNode(info);
1000}
1001
1002void QSvgVisitorImpl::visitStructureNodeEnd(const QSvgStructureNode *node)
1003{
1004 handleBaseNodeEnd(node);
1005 // qCDebug(lcQuickVectorGraphics) << "REVERT" << node->nodeId() << node->type() << (m_styleResolver->painter().pen().style() != Qt::NoPen) << m_styleResolver->painter().pen().color().name()
1006 // << (m_styleResolver->painter().pen().brush().style() != Qt::NoBrush) << m_styleResolver->painter().pen().brush().color().name();
1007
1008 StructureNodeInfo info;
1009 fillCommonNodeInfo(node, info);
1010 info.stage = StructureNodeStage::End;
1011
1012 m_generator->generateStructureNode(info);
1013}
1014
1015bool QSvgVisitorImpl::visitDocumentNodeStart(const QSvgTinyDocument *node)
1016{
1017 handleBaseNodeSetup(node);
1018
1019 StructureNodeInfo info;
1020 fillCommonNodeInfo(node, info);
1021
1022 const QSvgTinyDocument *doc = static_cast<const QSvgTinyDocument *>(node);
1023 info.size = doc->size();
1024 info.viewBox = doc->viewBox();
1025 info.isPathContainer = isPathContainer(node);
1026 info.forceSeparatePaths = false;
1027 info.stage = StructureNodeStage::Start;
1028
1029 return m_generator->generateRootNode(info);
1030}
1031
1032void QSvgVisitorImpl::visitDocumentNodeEnd(const QSvgTinyDocument *node)
1033{
1034 handleBaseNodeEnd(node);
1035 qCDebug(lcQuickVectorImage) << "REVERT" << node->nodeId() << node->type() << (styleResolver->painter().pen().style() != Qt::NoPen)
1036 << styleResolver->painter().pen().color().name() << (styleResolver->painter().pen().brush().style() != Qt::NoBrush)
1037 << styleResolver->painter().pen().brush().color().name();
1038
1039 StructureNodeInfo info;
1040 fillCommonNodeInfo(node, info);
1041 info.stage = StructureNodeStage::End;
1042
1043 m_generator->generateRootNode(info);
1044}
1045
1046void QSvgVisitorImpl::fillCommonNodeInfo(const QSvgNode *node, NodeInfo &info)
1047{
1048 info.nodeId = node->nodeId();
1049 info.typeName = node->typeName();
1050 info.isDefaultTransform = node->style().transform.isDefault();
1051 info.transform = !info.isDefaultTransform ? node->style().transform->qtransform() : QTransform();
1052 info.isDefaultOpacity = node->style().opacity.isDefault();
1053 info.opacity = !info.isDefaultOpacity ? node->style().opacity->opacity() : 1.0;
1054 info.isVisible = node->isVisible();
1055 info.isDisplayed = node->displayMode() != QSvgNode::DisplayMode::NoneMode;
1056}
1057
1058void QSvgVisitorImpl::handleBaseNodeSetup(const QSvgNode *node)
1059{
1060 qCDebug(lcQuickVectorImage) << "Before SETUP" << node << "fill" << styleResolver->currentFillColor()
1061 << "stroke" << styleResolver->currentStrokeColor() << styleResolver->currentStrokeWidth()
1062 << node->nodeId() << " type: " << node->typeName() << " " << node->type();
1063
1064 node->applyStyle(p: &styleResolver->painter(), states&: styleResolver->states());
1065
1066 qCDebug(lcQuickVectorImage) << "After SETUP" << node << "fill" << styleResolver->currentFillColor()
1067 << "stroke" << styleResolver->currentStrokeColor()
1068 << styleResolver->currentStrokeWidth() << node->nodeId();
1069}
1070
1071void QSvgVisitorImpl::handleBaseNode(const QSvgNode *node)
1072{
1073 NodeInfo info;
1074 fillCommonNodeInfo(node, info);
1075
1076 m_generator->generateNodeBase(info);
1077}
1078
1079void QSvgVisitorImpl::handleBaseNodeEnd(const QSvgNode *node)
1080{
1081 node->revertStyle(p: &styleResolver->painter(), states&: styleResolver->states());
1082
1083 qCDebug(lcQuickVectorImage) << "After END" << node << "fill" << styleResolver->currentFillColor()
1084 << "stroke" << styleResolver->currentStrokeColor() << styleResolver->currentStrokeWidth()
1085 << node->nodeId();
1086}
1087
1088void QSvgVisitorImpl::handlePathNode(const QSvgNode *node, const QPainterPath &path)
1089{
1090 handleBaseNodeSetup(node);
1091
1092 PathNodeInfo info;
1093 fillCommonNodeInfo(node, info);
1094 auto fillStyle = node->style().fill;
1095 if (fillStyle)
1096 info.fillRule = fillStyle->fillRule();
1097
1098 const QGradient *strokeGradient = styleResolver->currentStrokeGradient();
1099
1100 info.painterPath = path;
1101 info.fillColor = styleResolver->currentFillColor();
1102 if (strokeGradient == nullptr) {
1103 info.strokeStyle = StrokeStyle::fromPen(p: styleResolver->currentStroke());
1104 info.strokeStyle.color = styleResolver->currentStrokeColor();
1105 }
1106 if (styleResolver->currentFillGradient() != nullptr)
1107 info.grad = styleResolver->applyOpacityToGradient(gradient: *styleResolver->currentFillGradient(), opacity: styleResolver->currentFillOpacity());
1108 info.fillTransform = styleResolver->currentFillTransform();
1109
1110 m_generator->generatePath(info);
1111
1112 if (strokeGradient != nullptr) {
1113 PathNodeInfo strokeInfo;
1114 fillCommonNodeInfo(node, info&: strokeInfo);
1115
1116 strokeInfo.grad = *strokeGradient;
1117
1118 QPainterPathStroker stroker(styleResolver->currentStroke());
1119 strokeInfo.painterPath = stroker.createStroke(path);
1120 m_generator->generatePath(info: strokeInfo);
1121 }
1122
1123 handleBaseNodeEnd(node);
1124}
1125
1126QT_END_NAMESPACE
1127

source code of qtdeclarative/src/quickvectorimage/generator/qsvgvisitorimpl.cpp