Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qsvggenerator.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5
7
8#ifndef QT_NO_SVGGENERATOR
9
10#include "qpainterpath.h"
11
12#include "private/qdrawhelper_p.h"
13#include "private/qpaintengine_p.h"
14#include "private/qpainter_p.h"
15#include "private/qtextengine_p.h"
16
17#include "qfile.h"
18#include "qtextstream.h"
19#include "qbuffer.h"
20#include "qmath.h"
21#include "qbitmap.h"
22#include "qtransform.h"
23
24#include "qdebug.h"
25
26#include <optional>
27
29
30static void translate_color(const QColor &color, QString *color_string,
31 QString *opacity_string)
32{
33 Q_ASSERT(color_string);
34 Q_ASSERT(opacity_string);
35
36 *color_string =
37 QString::fromLatin1("#%1%2%3")
38 .arg(color.red(), 2, 16, QLatin1Char('0'))
39 .arg(color.green(), 2, 16, QLatin1Char('0'))
40 .arg(color.blue(), 2, 16, QLatin1Char('0'));
41 *opacity_string = QString::number(color.alphaF());
42}
43
44static void translate_dashPattern(const QList<qreal> &pattern, qreal width, QString *pattern_string)
45{
46 Q_ASSERT(pattern_string);
47
48 // Note that SVG operates in absolute lengths, whereas Qt uses a length/width ratio.
49 for (qreal entry : pattern)
50 *pattern_string += QString::fromLatin1("%1,").arg(entry * width);
51
52 pattern_string->chop(1);
53}
54
56{
57public:
60 {
61 size = QSize();
62 viewBox = QRectF();
63 outputDevice = nullptr;
64 resolution = 72;
65
66 attributes.document_title = QLatin1String("Qt SVG Document");
67 attributes.document_description = QLatin1String("Generated with Qt");
68 attributes.font_family = QLatin1String("serif");
69 attributes.font_size = QLatin1String("10pt");
70 attributes.font_style = QLatin1String("normal");
71 attributes.font_weight = QLatin1String("normal");
72
73 afterFirstUpdate = false;
74 numGradients = 0;
75 }
76
80 QIODevice *outputDevice;
83
84 QString header;
85 QString defs;
86 QString body;
88
90 QPen pen;
91 QTransform matrix;
92 QFont font;
93
96 currentGradientName = QString::fromLatin1("gradient%1").arg(numGradients);
97 return currentGradientName;
98 }
99
102
105
117
119 ++numClipPaths;
120 currentClipPathName = QStringLiteral("clipPath%1").arg(numClipPaths);
121 return currentClipPathName;
122 }
123
124 std::optional<QPainterPath> clipPath;
125 bool clipEnabled = false;
126 bool isClippingEffective() const {
127 return clipEnabled && clipPath.has_value();
128 }
132};
133
135{
136 return QPaintEngine::PaintEngineFeatures(
137 QPaintEngine::AllFeatures
138 & ~QPaintEngine::PerspectiveTransform
139 & ~QPaintEngine::ConicalGradientFill
140 & ~QPaintEngine::PorterDuff);
141}
142
143Q_GUI_EXPORT QImage qt_imageForBrush(int brushStyle, bool invert);
144Q_GUI_EXPORT bool qHasPixmapTexture(const QBrush& brush);
145
147{
149public:
150
156
158 bool end() override;
159
160 void updateState(const QPaintEngineState &state) override;
161 void updateClipState(const QPaintEngineState &state);
162 void popGroup();
163
164 void drawEllipse(const QRectF &r) override;
165 void drawPath(const QPainterPath &path) override;
166 void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) override;
167 void drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) override;
168 void drawRects(const QRectF *rects, int rectCount) override;
169 void drawTextItem(const QPointF &pt, const QTextItem &item) override;
170 void generateImage(QTextStream &stream, const QRectF &r, const QImage &pm);
171 void drawImage(const QRectF &r, const QImage &pm, const QRectF &sr,
172 Qt::ImageConversionFlags flags = Qt::AutoColor) override;
173
174 QPaintEngine::Type type() const override { return QPaintEngine::SVG; }
175
176 QSize size() const { return d_func()->size; }
177 void setSize(const QSize &size) {
178 Q_ASSERT(!isActive());
179 d_func()->size = size;
180 }
181
182 QRectF viewBox() const { return d_func()->viewBox; }
183 void setViewBox(const QRectF &viewBox) {
184 Q_ASSERT(!isActive());
185 d_func()->viewBox = viewBox;
186 }
187
188 QString documentTitle() const { return d_func()->attributes.document_title; }
189 void setDocumentTitle(const QString &title) {
190 d_func()->attributes.document_title = title;
191 }
192
193 QString documentDescription() const { return d_func()->attributes.document_description; }
194 void setDocumentDescription(const QString &description) {
195 d_func()->attributes.document_description = description;
196 }
197
198 QIODevice *outputDevice() const { return d_func()->outputDevice; }
199 void setOutputDevice(QIODevice *device) {
200 Q_ASSERT(!isActive());
201 d_func()->outputDevice = device;
202 }
203
204 int resolution() const { return d_func()->resolution; }
205 void setResolution(int resolution) {
206 Q_ASSERT(!isActive());
207 d_func()->resolution = resolution;
208 }
209
211
213 {
214 QString maskId = QStringLiteral("patternmask%1").arg(style);
218 QString rct(QStringLiteral("<rect x=\"%1\" y=\"%2\" width=\"%3\" height=\"%4\" />"));
220 str << "<mask id=\"" << maskId << "\" x=\"0\" y=\"0\" width=\"8\" height=\"8\" "
221 << "stroke=\"none\" fill=\"#ffffff\" patternUnits=\"userSpaceOnUse\" >" << Qt::endl;
222 for (QRect r : reg)
223 str << rct.arg(r.x()).arg(r.y()).arg(r.width()).arg(r.height()) << Qt::endl;
224 str << QStringLiteral("</mask>") << Qt::endl << Qt::endl;
226 }
227 return maskId;
228 }
229
231 {
232 QString patternId = QStringLiteral("fillpattern%1_").arg(brush.style()) + QStringView{color}.mid(1);
235 QString geo(QStringLiteral("x=\"0\" y=\"0\" width=\"8\" height=\"8\""));
237 str << QStringLiteral("<pattern id=\"%1\" %2 patternUnits=\"userSpaceOnUse\" >").arg(patternId, geo) << Qt::endl;
238 str << QStringLiteral("<rect %1 stroke=\"none\" fill=\"%2\" mask=\"url(#%3)\" />").arg(geo, color, maskId) << Qt::endl;
239 str << QStringLiteral("</pattern>") << Qt::endl << Qt::endl;
241 }
242 return patternId;
243 }
244
246 {
248 QString patternId = QStringLiteral("texpattern_%1").arg(QString::number(tex.cacheKey(), 16));
252 tex.setColor(0, qRgba(0, 0, 0, 0));
253 tex.setColor(1, brush.color().rgba());
255 }
257 QString geo = QStringLiteral("x=\"0\" y=\"0\" width=\"%1\" height=\"%2\"").arg(tex.width()).arg(tex.height());
259 str << QStringLiteral("<pattern id=\"%1\" %2 patternUnits=\"userSpaceOnUse\" >").arg(patternId, geo) << Qt::endl;
261 str << QStringLiteral("</pattern>") << Qt::endl << Qt::endl;
263 }
264 return patternId;
265 }
266
268 {
270 const QLinearGradient *grad = static_cast<const QLinearGradient*>(g);
271 str << QLatin1String("<linearGradient ");
273 if (grad) {
274 str << QLatin1String("x1=\"") <<grad->start().x()<< QLatin1String("\" ")
275 << QLatin1String("y1=\"") <<grad->start().y()<< QLatin1String("\" ")
276 << QLatin1String("x2=\"") <<grad->finalStop().x() << QLatin1String("\" ")
277 << QLatin1String("y2=\"") <<grad->finalStop().y() << QLatin1String("\" ");
278 }
279
280 str << QLatin1String("id=\"") << d_func()->generateGradientName() << QLatin1String("\">\n");
282 str << QLatin1String("</linearGradient>") <<Qt::endl;
283 }
285 {
287 const QRadialGradient *grad = static_cast<const QRadialGradient*>(g);
288 str << QLatin1String("<radialGradient ");
290 if (grad) {
291 str << QLatin1String("cx=\"") <<grad->center().x()<< QLatin1String("\" ")
292 << QLatin1String("cy=\"") <<grad->center().y()<< QLatin1String("\" ")
293 << QLatin1String("r=\"") <<grad->radius() << QLatin1String("\" ")
294 << QLatin1String("fx=\"") <<grad->focalPoint().x() << QLatin1String("\" ")
295 << QLatin1String("fy=\"") <<grad->focalPoint().y() << QLatin1String("\" ");
296 }
297 str << QLatin1String("id=\"") <<d_func()->generateGradientName()<< QLatin1String("\">\n");
299 str << QLatin1String("</radialGradient>") << Qt::endl;
300 }
302 {
303 qWarning("svg's don't support conical gradients!");
304 }
305
308
310 bool constantAlpha = true;
311 int alpha = stops.at(0).second.alpha();
312 for (int i = 1; i < stops.size(); ++i)
314
315 if (!constantAlpha) {
316 const qreal spacing = qreal(0.02);
320 for (int i = 0; i + 1 < stops.size(); ++i) {
321 int parts = qCeil((stops.at(i + 1).first - stops.at(i).first) / spacing);
324
325 if (parts > 1) {
326 qreal step = (stops.at(i + 1).first - stops.at(i).first) / parts;
327 for (int j = 1; j < parts; ++j) {
330 }
331 }
333 }
335 stops = newStops;
336 }
337 }
338
339 for (const QGradientStop &stop : std::as_const(stops)) {
341 str << QLatin1String(" <stop offset=\"")<< stop.first << QLatin1String("\" ")
342 << QLatin1String("stop-color=\"") << color << QLatin1String("\" ")
343 << QLatin1String("stop-opacity=\"") << stop.second.alphaF() <<QLatin1String("\" />\n");
344 }
345 }
346
348 {
349 str << QLatin1String("gradientUnits=\"");
351 str << QLatin1String("objectBoundingBox");
352 else
353 str << QLatin1String("userSpaceOnUse");
354 str << QLatin1String("\" ");
355 }
356
358 {
359 *d_func()->stream << QLatin1String("fill=\"none\" ");
360 *d_func()->stream << QLatin1String("stroke=\"black\" ");
361 *d_func()->stream << QLatin1String("stroke-width=\"1\" ");
362 *d_func()->stream << QLatin1String("fill-rule=\"evenodd\" ");
363 *d_func()->stream << QLatin1String("stroke-linecap=\"square\" ");
364 *d_func()->stream << QLatin1String("stroke-linejoin=\"bevel\" ");
365 *d_func()->stream << QLatin1String(">\n");
366 }
368 {
369 return *d_func()->stream;
370 }
371
372
373 void qpenToSvg(const QPen &spen)
374 {
375 d_func()->pen = spen;
376
377 switch (spen.style()) {
378 case Qt::NoPen:
379 stream() << QLatin1String("stroke=\"none\" ");
380
383 return;
384 break;
385 case Qt::SolidLine: {
387
389 &colorOpacity);
392
393 stream() << QLatin1String("stroke=\"")<<color<< QLatin1String("\" ");
394 stream() << QLatin1String("stroke-opacity=\"")<<colorOpacity<< QLatin1String("\" ");
395 }
396 break;
397 case Qt::DashLine:
398 case Qt::DotLine:
399 case Qt::DashDotLine:
400 case Qt::DashDotDotLine:
401 case Qt::CustomDashLine: {
403
404 qreal penWidth = spen.width() == 0 ? qreal(1) : spen.widthF();
405
408
409 // SVG uses absolute offset
411
416
417 stream() << QLatin1String("stroke=\"")<<color<< QLatin1String("\" ");
418 stream() << QLatin1String("stroke-opacity=\"")<<colorOpacity<< QLatin1String("\" ");
419 stream() << QLatin1String("stroke-dasharray=\"")<<dashPattern<< QLatin1String("\" ");
420 stream() << QLatin1String("stroke-dashoffset=\"")<<dashOffset<< QLatin1String("\" ");
421 break;
422 }
423 default:
424 qWarning("Unsupported pen style");
425 break;
426 }
427
428 if (spen.widthF() == 0)
429 stream() <<"stroke-width=\"1\" ";
430 else
431 stream() <<"stroke-width=\"" << spen.widthF() << "\" ";
432
433 switch (spen.capStyle()) {
434 case Qt::FlatCap:
435 stream() << "stroke-linecap=\"butt\" ";
436 break;
437 case Qt::SquareCap:
438 stream() << "stroke-linecap=\"square\" ";
439 break;
440 case Qt::RoundCap:
441 stream() << "stroke-linecap=\"round\" ";
442 break;
443 default:
444 qWarning("Unhandled cap style");
445 }
446 switch (spen.joinStyle()) {
447 case Qt::SvgMiterJoin:
448 case Qt::MiterJoin:
449 stream() << "stroke-linejoin=\"miter\" "
450 "stroke-miterlimit=\""<<spen.miterLimit()<<"\" ";
451 break;
452 case Qt::BevelJoin:
453 stream() << "stroke-linejoin=\"bevel\" ";
454 break;
455 case Qt::RoundJoin:
456 stream() << "stroke-linejoin=\"round\" ";
457 break;
458 default:
459 qWarning("Unhandled join style");
460 }
461 }
463 {
464 d_func()->brush = sbrush;
465 switch (sbrush.style()) {
466 case Qt::SolidPattern: {
469 stream() << "fill=\"" << color << "\" "
470 "fill-opacity=\""
471 << colorOpacity << "\" ";
474 }
475 break;
476 case Qt::Dense1Pattern:
477 case Qt::Dense2Pattern:
478 case Qt::Dense3Pattern:
479 case Qt::Dense4Pattern:
480 case Qt::Dense5Pattern:
481 case Qt::Dense6Pattern:
482 case Qt::Dense7Pattern:
483 case Qt::HorPattern:
484 case Qt::VerPattern:
485 case Qt::CrossPattern:
486 case Qt::BDiagPattern:
487 case Qt::FDiagPattern:
488 case Qt::DiagCrossPattern:
489 case Qt::TexturePattern: {
495 stream() << "fill=\"" << patternRef << "\" fill-opacity=\"" << colorOpacity << "\" ";
498 break;
499 }
504 stream() << QLatin1String("fill=\"url(#") << d_func()->currentGradientName << QLatin1String(")\" ");
505 break;
510 stream() << QLatin1String("fill=\"url(#") << d_func()->currentGradientName << QLatin1String(")\" ");
511 break;
516 stream() << QLatin1String("fill=\"url(#") << d_func()->currentGradientName << QLatin1String(")\" ");
517 break;
518 case Qt::NoBrush:
519 stream() << QLatin1String("fill=\"none\" ");
520 d_func()->attributes.fill = QLatin1String("none");
522 return;
523 break;
524 default:
525 break;
526 }
527 }
528 void qfontToSvg(const QFont &sfont)
529 {
531
532 d->font = sfont;
533
534 if (d->font.pixelSize() == -1)
536 else
538
541 d->attributes.font_style = d->font.italic() ? QLatin1String("italic") : QLatin1String("normal");
542
543 *d->stream << "font-family=\"" << d->attributes.font_family << "\" "
544 "font-size=\"" << d->attributes.font_size << "\" "
545 "font-weight=\"" << d->attributes.font_weight << "\" "
546 "font-style=\"" << d->attributes.font_style << "\" "
547 << Qt::endl;
548 }
549};
550
552{
553public:
555
557 QString fileName;
558};
559
560/*!
561 \class QSvgGenerator
562 \ingroup painting
563 \inmodule QtSvg
564 \since 4.3
565 \brief The QSvgGenerator class provides a paint device that is used to create SVG drawings.
566 \reentrant
567
568 This paint device represents a Scalable Vector Graphics (SVG) drawing. Like QPrinter, it is
569 designed as a write-only device that generates output in a specific format.
570
571 To write an SVG file, you first need to configure the output by setting the \l fileName
572 or \l outputDevice properties. It is usually necessary to specify the size of the drawing
573 by setting the \l size property, and in some cases where the drawing will be included in
574 another, the \l viewBox property also needs to be set.
575
576 \code
577 QSvgGenerator generator;
578 generator.setFileName(path);
579 generator.setSize(QSize(200, 200));
580 generator.setViewBox(QRect(0, 0, 200, 200));
581 generator.setTitle(tr("SVG Generator Drawing"));
582 generator.setDescription(tr("An SVG drawing created by the SVG Generator"));
583 \endcode
584
585 Other meta-data can be specified by setting the \a title, \a description and \a resolution
586 properties.
587
588 As with other QPaintDevice subclasses, a QPainter object is used to paint onto an instance
589 of this class:
590
591 \code
592 QPainter painter;
593 painter.begin(&generator);
594 ...
595 painter.end();
596 \endcode
597
598 Painting is performed in the same way as for any other paint device. However,
599 it is necessary to use the QPainter::begin() and \l{QPainter::}{end()} to
600 explicitly begin and end painting on the device.
601
602 \sa QSvgRenderer, QSvgWidget, {Qt SVG C++ Classes}
603*/
604
605/*!
606 \enum QSvgGenerator::SvgVersion
607 \since 6.5
608
609 This enumeration describes the version of the SVG output of the
610 generator.
611
612 \value SvgTiny12 The generated document follows the SVG Tiny 1.2 specification.
613 \value Svg11 The generated document follows the SVG 1.1 specification.
614*/
615
616/*!
617 Constructs a new generator using the SVG Tiny 1.2 profile.
618*/
619QSvgGenerator::QSvgGenerator() // ### Qt 7: inline
620 : QSvgGenerator(SvgVersion::SvgTiny12)
621{
622}
623
624/*!
625 \since 6.5
626
627 Constructs a new generator that uses the SVG version \a version.
628*/
629QSvgGenerator::QSvgGenerator(SvgVersion version)
630 : d_ptr(new QSvgGeneratorPrivate)
631{
632 Q_D(QSvgGenerator);
633
634 d->engine = new QSvgPaintEngine(version);
635 d->owns_iodevice = false;
636}
637
638/*!
639 Destroys the generator.
640*/
641QSvgGenerator::~QSvgGenerator()
642{
643 Q_D(QSvgGenerator);
644 if (d->owns_iodevice)
645 delete d->engine->outputDevice();
646 delete d->engine;
647}
648
649/*!
650 \property QSvgGenerator::title
651 \brief the title of the generated SVG drawing
652 \since 4.5
653 \sa description
654*/
655QString QSvgGenerator::title() const
656{
657 Q_D(const QSvgGenerator);
658
659 return d->engine->documentTitle();
660}
661
662void QSvgGenerator::setTitle(const QString &title)
663{
664 Q_D(QSvgGenerator);
665
666 d->engine->setDocumentTitle(title);
667}
668
669/*!
670 \property QSvgGenerator::description
671 \brief the description of the generated SVG drawing
672 \since 4.5
673 \sa title
674*/
675QString QSvgGenerator::description() const
676{
677 Q_D(const QSvgGenerator);
678
679 return d->engine->documentDescription();
680}
681
682void QSvgGenerator::setDescription(const QString &description)
683{
684 Q_D(QSvgGenerator);
685
686 d->engine->setDocumentDescription(description);
687}
688
689/*!
690 \property QSvgGenerator::size
691 \brief the size of the generated SVG drawing
692 \since 4.5
693
694 By default this property is set to \c{QSize(-1, -1)}, which
695 indicates that the generator should not output the width and
696 height attributes of the \c<svg> element.
697
698 \note It is not possible to change this property while a
699 QPainter is active on the generator.
700
701 \sa viewBox, resolution
702*/
703QSize QSvgGenerator::size() const
704{
705 Q_D(const QSvgGenerator);
706 return d->engine->size();
707}
708
709void QSvgGenerator::setSize(const QSize &size)
710{
711 Q_D(QSvgGenerator);
712 if (d->engine->isActive()) {
713 qWarning("QSvgGenerator::setSize(), cannot set size while SVG is being generated");
714 return;
715 }
716 d->engine->setSize(size);
717}
718
719/*!
720 \property QSvgGenerator::viewBox
721 \brief the viewBox of the generated SVG drawing
722 \since 4.5
723
724 By default this property is set to \c{QRect(0, 0, -1, -1)}, which
725 indicates that the generator should not output the viewBox attribute
726 of the \c<svg> element.
727
728 \note It is not possible to change this property while a
729 QPainter is active on the generator.
730
731 \sa viewBox(), size, resolution
732*/
733QRectF QSvgGenerator::viewBoxF() const
734{
735 Q_D(const QSvgGenerator);
736 return d->engine->viewBox();
737}
738
739/*!
740 \since 4.5
741
742 Returns viewBoxF().toRect().
743
744 \sa viewBoxF()
745*/
746QRect QSvgGenerator::viewBox() const
747{
748 Q_D(const QSvgGenerator);
749 return d->engine->viewBox().toRect();
750}
751
752void QSvgGenerator::setViewBox(const QRectF &viewBox)
753{
754 Q_D(QSvgGenerator);
755 if (d->engine->isActive()) {
756 qWarning("QSvgGenerator::setViewBox(), cannot set viewBox while SVG is being generated");
757 return;
758 }
759 d->engine->setViewBox(viewBox);
760}
761
762void QSvgGenerator::setViewBox(const QRect &viewBox)
763{
764 setViewBox(QRectF(viewBox));
765}
766
767/*!
768 \property QSvgGenerator::fileName
769 \brief the target filename for the generated SVG drawing
770 \since 4.5
771
772 \sa outputDevice
773*/
774QString QSvgGenerator::fileName() const
775{
776 Q_D(const QSvgGenerator);
777 return d->fileName;
778}
779
780void QSvgGenerator::setFileName(const QString &fileName)
781{
782 Q_D(QSvgGenerator);
783 if (d->engine->isActive()) {
784 qWarning("QSvgGenerator::setFileName(), cannot set file name while SVG is being generated");
785 return;
786 }
787
788 if (d->owns_iodevice)
789 delete d->engine->outputDevice();
790
791 d->owns_iodevice = true;
792
793 d->fileName = fileName;
794 QFile *file = new QFile(fileName);
795 d->engine->setOutputDevice(file);
796}
797
798/*!
799 \property QSvgGenerator::outputDevice
800 \brief the output device for the generated SVG drawing
801 \since 4.5
802
803 If both output device and file name are specified, the output device
804 will have precedence.
805
806 \sa fileName
807*/
808QIODevice *QSvgGenerator::outputDevice() const
809{
810 Q_D(const QSvgGenerator);
811 return d->engine->outputDevice();
812}
813
814void QSvgGenerator::setOutputDevice(QIODevice *outputDevice)
815{
816 Q_D(QSvgGenerator);
817 if (d->engine->isActive()) {
818 qWarning("QSvgGenerator::setOutputDevice(), cannot set output device while SVG is being generated");
819 return;
820 }
821 d->owns_iodevice = false;
822 d->engine->setOutputDevice(outputDevice);
823 d->fileName = QString();
824}
825
826/*!
827 \property QSvgGenerator::resolution
828 \brief the resolution of the generated output
829 \since 4.5
830
831 The resolution is specified in dots per inch, and is used to
832 calculate the physical size of an SVG drawing.
833
834 \sa size, viewBox
835*/
836int QSvgGenerator::resolution() const
837{
838 Q_D(const QSvgGenerator);
839 return d->engine->resolution();
840}
841
842void QSvgGenerator::setResolution(int dpi)
843{
844 Q_D(QSvgGenerator);
845 d->engine->setResolution(dpi);
846}
847
848/*!
849 \since 6.5
850
851 Returns the version of the SVG document that this generator is
852 producing.
853*/
854QSvgGenerator::SvgVersion QSvgGenerator::svgVersion() const
855{
856 Q_D(const QSvgGenerator);
857 return d->engine->svgVersion();
858}
859
860/*!
861 Returns the paint engine used to render graphics to be converted to SVG
862 format information.
863*/
864QPaintEngine *QSvgGenerator::paintEngine() const
865{
866 Q_D(const QSvgGenerator);
867 return d->engine;
868}
869
870/*!
871 \reimp
872*/
873int QSvgGenerator::metric(QPaintDevice::PaintDeviceMetric metric) const
874{
875 Q_D(const QSvgGenerator);
876 switch (metric) {
877 case QPaintDevice::PdmDepth:
878 return 32;
879 case QPaintDevice::PdmWidth:
880 return d->engine->size().width();
881 case QPaintDevice::PdmHeight:
882 return d->engine->size().height();
883 case QPaintDevice::PdmDpiX:
884 return d->engine->resolution();
885 case QPaintDevice::PdmDpiY:
886 return d->engine->resolution();
887 case QPaintDevice::PdmHeightMM:
888 return qRound(d->engine->size().height() * 25.4 / d->engine->resolution());
889 case QPaintDevice::PdmWidthMM:
890 return qRound(d->engine->size().width() * 25.4 / d->engine->resolution());
891 case QPaintDevice::PdmNumColors:
892 return 0xffffffff;
893 case QPaintDevice::PdmPhysicalDpiX:
894 return d->engine->resolution();
895 case QPaintDevice::PdmPhysicalDpiY:
896 return d->engine->resolution();
897 case QPaintDevice::PdmDevicePixelRatio:
898 return 1;
899 case QPaintDevice::PdmDevicePixelRatioScaled:
900 return 1 * QPaintDevice::devicePixelRatioFScale();
901 default:
902 qWarning("QSvgGenerator::metric(), unhandled metric %d\n", metric);
903 break;
904 }
905 return 0;
906}
907
908/*!
909 \since 6.11
910 \reimp
911*/
912void QSvgGenerator::initPainter(QPainter *painter) const
913{
914 QPainterPrivate *painterPrivate = QPainterPrivate::get(painter);
915
916 for (QFont *font : { &painterPrivate->state->deviceFont, &painterPrivate->state->font }) {
917 if (font->hintingPreference() == QFont::PreferDefaultHinting)
918 font->setHintingPreference(QFont::PreferNoHinting);
919 }
920 painterPrivate->setEngineDirtyFlags({ QPaintEngine::DirtyFont });
921}
922
923/*****************************************************************************
924 * class QSvgPaintEngine
925 */
926
927bool QSvgPaintEngine::begin(QPaintDevice *)
928{
929 Q_D(QSvgPaintEngine);
930 if (!d->outputDevice) {
931 qWarning("QSvgPaintEngine::begin(), no output device");
932 return false;
933 }
934
935 if (!d->outputDevice->isOpen()) {
936 if (!d->outputDevice->open(QIODevice::WriteOnly | QIODevice::Text)) {
937 qWarning("QSvgPaintEngine::begin(), could not open output device: '%s'",
938 qPrintable(d->outputDevice->errorString()));
939 return false;
940 }
941 } else if (!d->outputDevice->isWritable()) {
942 qWarning("QSvgPaintEngine::begin(), could not write to read-only output device: '%s'",
943 qPrintable(d->outputDevice->errorString()));
944 return false;
945 }
946
947 d->stream = new QTextStream(&d->header);
948
949 // stream out the header...
950 *d->stream << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" << Qt::endl << "<svg";
951
952 if (d->size.isValid()) {
953 qreal wmm = d->size.width() * 25.4 / d->resolution;
954 qreal hmm = d->size.height() * 25.4 / d->resolution;
955 *d->stream << " width=\"" << wmm << "mm\" height=\"" << hmm << "mm\"" << Qt::endl;
956 }
957
958 if (d->viewBox.isValid()) {
959 *d->stream << " viewBox=\"" << d->viewBox.left() << ' ' << d->viewBox.top();
960 *d->stream << ' ' << d->viewBox.width() << ' ' << d->viewBox.height() << '\"' << Qt::endl;
961 }
962
963 *d->stream << " xmlns=\"http://www.w3.org/2000/svg\""
964 " xmlns:xlink=\"http://www.w3.org/1999/xlink\"";
965 switch (d->svgVersion) {
966 case QSvgGenerator::SvgVersion::SvgTiny12:
967 *d->stream << " version=\"1.2\" baseProfile=\"tiny\">";
968 break;
969 case QSvgGenerator::SvgVersion::Svg11:
970 *d->stream << " version=\"1.1\">";
971 break;
972 }
973 *d->stream << Qt::endl;
974
975 if (!d->attributes.document_title.isEmpty()) {
976 *d->stream << "<title>" << d->attributes.document_title.toHtmlEscaped() << "</title>" << Qt::endl;
977 }
978
979 if (!d->attributes.document_description.isEmpty()) {
980 *d->stream << "<desc>" << d->attributes.document_description.toHtmlEscaped() << "</desc>" << Qt::endl;
981 }
982
983 d->stream->setString(&d->defs);
984 *d->stream << "<defs>\n";
985
986 d->stream->setString(&d->body);
987 // Start the initial graphics state...
988 *d->stream << "<g ";
989 generateQtDefaults();
990 *d->stream << Qt::endl;
991
992 return true;
993}
994
996{
997 Q_D(QSvgPaintEngine);
998
999 d->stream->setString(&d->defs);
1000 *d->stream << "</defs>\n";
1001
1002 d->stream->setDevice(d->outputDevice);
1003
1004 *d->stream << d->header;
1005 *d->stream << d->defs;
1006 *d->stream << d->body;
1007 if (d->hasEmittedClipGroup)
1008 *d->stream << "</g>";
1009 if (d->afterFirstUpdate)
1010 *d->stream << "</g>" << Qt::endl; // close the updateState
1011
1012 *d->stream << "</g>" << Qt::endl // close the Qt defaults
1013 << "</svg>" << Qt::endl;
1014
1015 delete d->stream;
1016
1017 return true;
1018}
1019
1020void QSvgPaintEngine::drawPixmap(const QRectF &r, const QPixmap &pm,
1021 const QRectF &sr)
1022{
1023 drawImage(r, pm.toImage(), sr);
1024}
1025
1026void QSvgPaintEngine::generateImage(QTextStream &stream, const QRectF &r, const QImage &image)
1027{
1028 QString quality;
1029 if (state->renderHints() & QPainter::SmoothPixmapTransform) {
1030 quality = QLatin1String("optimizeQuality");
1031 } else {
1032 quality = QLatin1String("optimizeSpeed");
1033 }
1034 stream << "<image ";
1035 stream << "x=\""<<r.x()<<"\" "
1036 "y=\""<<r.y()<<"\" "
1037 "width=\""<<r.width()<<"\" "
1038 "height=\""<<r.height()<<"\" "
1039 "preserveAspectRatio=\"none\" "
1040 "image-rendering=\""<<quality<<"\" ";
1041
1042 QByteArray data;
1043 QBuffer buffer(&data);
1044 buffer.open(QBuffer::ReadWrite);
1045 image.save(&buffer, "PNG");
1046 buffer.close();
1047 stream << "xlink:href=\"data:image/png;base64,"
1048 << data.toBase64()
1049 <<"\" />\n";
1050}
1051
1052void QSvgPaintEngine::drawImage(const QRectF &r, const QImage &image,
1053 const QRectF &sr,
1054 Qt::ImageConversionFlags flags)
1055{
1056 Q_UNUSED(sr);
1057 Q_UNUSED(flags);
1058 generateImage(stream(), r, image);
1059}
1060
1061void QSvgPaintEngine::updateState(const QPaintEngineState &state)
1062{
1063 Q_D(QSvgPaintEngine);
1064 // always stream full gstate, which is not required, but...
1065
1066 // close old state and start a new one...
1067 if (d->hasEmittedClipGroup)
1068 *d->stream << "</g>\n";
1069 if (d->afterFirstUpdate)
1070 *d->stream << "</g>\n\n";
1071
1072 updateClipState(state);
1073
1074 if (d->isClippingEffective()) {
1075 *d->stream << QStringLiteral("<g clip-path=\"url(#%1)\">").arg(d->currentClipPathName);
1076 d->hasEmittedClipGroup = true;
1077 } else {
1078 d->hasEmittedClipGroup = false;
1079 }
1080
1081 *d->stream << "<g ";
1082
1083 qbrushToSvg(state.brush());
1084 qpenToSvg(state.pen());
1085
1086 d->matrix = state.transform();
1087 *d->stream << "transform=\"matrix(" << d->matrix.m11() << ','
1088 << d->matrix.m12() << ','
1089 << d->matrix.m21() << ',' << d->matrix.m22() << ','
1090 << d->matrix.dx() << ',' << d->matrix.dy()
1091 << ")\""
1092 << Qt::endl;
1093
1094 qfontToSvg(state.font());
1095
1096 if (!qFuzzyIsNull(state.opacity() - 1))
1097 stream() << "opacity=\""<<state.opacity()<<"\" ";
1098
1099 *d->stream << '>' << Qt::endl;
1100
1101 d->afterFirstUpdate = true;
1102}
1103
1104void QSvgPaintEngine::updateClipState(const QPaintEngineState &state)
1105{
1106 Q_D(QSvgPaintEngine);
1107 switch (d->svgVersion) {
1108 case QSvgGenerator::SvgVersion::SvgTiny12:
1109 // no clip handling in Tiny 1.2
1110 return;
1111 case QSvgGenerator::SvgVersion::Svg11:
1112 break;
1113 }
1114
1115 const QPaintEngine::DirtyFlags flags = state.state();
1116
1117 const bool clippingChanged = flags.testAnyFlags(DirtyClipPath | DirtyClipRegion);
1118 if (clippingChanged) {
1119 switch (state.clipOperation()) {
1120 case Qt::NoClip:
1121 d->clipEnabled = false;
1122 d->clipPath.reset();
1123 break;
1124 case Qt::ReplaceClip:
1125 case Qt::IntersectClip:
1126 d->clipPath = painter()->transform().map(painter()->clipPath());
1127 break;
1128 }
1129 }
1130
1131 if (flags & DirtyClipEnabled)
1132 d->clipEnabled = state.isClipEnabled();
1133
1134 if (d->isClippingEffective() && clippingChanged) {
1135 d->stream->setString(&d->defs);
1136 *d->stream << QLatin1String("<clipPath id=\"%1\">\n").arg(d->generateClipPathName());
1137 drawPath(*d->clipPath);
1138 *d->stream << "</clipPath>\n";
1139 d->stream->setString(&d->body);
1140 }
1141}
1142
1143void QSvgPaintEngine::drawEllipse(const QRectF &r)
1144{
1145 Q_D(QSvgPaintEngine);
1146
1147 const bool isCircle = r.width() == r.height();
1148 *d->stream << '<' << (isCircle ? "circle" : "ellipse");
1149 if (state->pen().isCosmetic())
1150 *d->stream << " vector-effect=\"non-scaling-stroke\"";
1151 const QPointF c = r.center();
1152 *d->stream << " cx=\"" << c.x() << "\" cy=\"" << c.y();
1153 if (isCircle)
1154 *d->stream << "\" r=\"" << r.width() / qreal(2.0);
1155 else
1156 *d->stream << "\" rx=\"" << r.width() / qreal(2.0) << "\" ry=\"" << r.height() / qreal(2.0);
1157 *d->stream << "\"/>" << Qt::endl;
1158}
1159
1160void QSvgPaintEngine::drawPath(const QPainterPath &p)
1161{
1162 Q_D(QSvgPaintEngine);
1163
1164 *d->stream << "<path vector-effect=\""
1165 << (state->pen().isCosmetic() ? "non-scaling-stroke" : "none")
1166 << "\" fill-rule=\""
1167 << (p.fillRule() == Qt::OddEvenFill ? "evenodd" : "nonzero")
1168 << "\" d=\"";
1169
1170 int i = 0;
1171 QPointF subPathStart;
1172 auto endSubPath = [&]() {
1173 if (i > 0) {
1174 const QPointF subPathEnd = p.elementAt(i - 1);
1175 if (subPathEnd == subPathStart)
1176 *d->stream << "Z ";
1177 }
1178 };
1179
1180 for (i = 0; i < p.elementCount(); ++i) {
1181 const QPainterPath::Element &e = p.elementAt(i);
1182 switch (e.type) {
1183 case QPainterPath::MoveToElement:
1184 endSubPath();
1185 subPathStart = e;
1186 *d->stream << 'M' << e.x << ',' << e.y;
1187 break;
1188 case QPainterPath::LineToElement:
1189 *d->stream << 'L' << e.x << ',' << e.y;
1190 break;
1191 case QPainterPath::CurveToElement:
1192 *d->stream << 'C' << e.x << ',' << e.y;
1193 ++i;
1194 while (i < p.elementCount()) {
1195 const QPainterPath::Element &e = p.elementAt(i);
1196 if (e.type != QPainterPath::CurveToDataElement) {
1197 --i;
1198 break;
1199 } else
1200 *d->stream << ' ';
1201 *d->stream << e.x << ',' << e.y;
1202 ++i;
1203 }
1204 break;
1205 default:
1206 break;
1207 }
1208 *d->stream << ' ';
1209 }
1210 endSubPath();
1211
1212 *d->stream << "\"/>" << Qt::endl;
1213}
1214
1215void QSvgPaintEngine::drawPolygon(const QPointF *points, int pointCount,
1216 PolygonDrawMode mode)
1217{
1218 Q_ASSERT(pointCount >= 2);
1219
1220 //Q_D(QSvgPaintEngine);
1221
1222 if (mode == PolylineMode)
1223 stream() << "<polyline fill=\"none\"";
1224 else if (mode == OddEvenMode)
1225 stream() << "<polygon fill-rule=\"evenodd\"";
1226 else if (mode == WindingMode || mode == ConvexMode)
1227 stream() << "<polygon fill-rule=\"nonzero\"";
1228
1229 stream() << " vector-effect=\""
1230 << (state->pen().isCosmetic() ? "non-scaling-stroke" : "none")
1231 << "\" points=\"";
1232 for (int i = 0; i < pointCount; ++i) {
1233 const QPointF &pt = points[i];
1234 stream() << pt.x() << ',' << pt.y() << ' ';
1235 }
1236 stream() << "\" />" <<Qt::endl;
1237
1238}
1239
1240void QSvgPaintEngine::drawRects(const QRectF *rects, int rectCount)
1241{
1242 Q_D(QSvgPaintEngine);
1243
1244 for (int i=0; i < rectCount; ++i) {
1245 const QRectF &rect = rects[i].normalized();
1246 *d->stream << "<rect";
1247 if (state->pen().isCosmetic())
1248 *d->stream << " vector-effect=\"non-scaling-stroke\"";
1249 *d->stream << " x=\"" << rect.x() << "\" y=\"" << rect.y()
1250 << "\" width=\"" << rect.width() << "\" height=\"" << rect.height()
1251 << "\"/>" << Qt::endl;
1252 }
1253}
1254
1255void QSvgPaintEngine::drawTextItem(const QPointF &pt, const QTextItem &textItem)
1256{
1257 Q_D(QSvgPaintEngine);
1258 if (d->pen.style() == Qt::NoPen)
1259 return;
1260
1261 const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem);
1262 if (ti.chars == 0)
1263 QPaintEngine::drawTextItem(pt, ti); // Draw as path
1264 QString s = QString::fromRawData(ti.chars, ti.num_chars);
1265
1266 *d->stream << "<text "
1267 "fill=\"" << d->attributes.stroke << "\" "
1268 "fill-opacity=\"" << d->attributes.strokeOpacity << "\" "
1269 "stroke=\"none\" "
1270 "xml:space=\"preserve\" "
1271 "x=\"" << pt.x() << "\" y=\"" << pt.y() << "\" ";
1272 qfontToSvg(textItem.font());
1273 *d->stream << " >"
1274 << s.toHtmlEscaped()
1275 << "</text>"
1276 << Qt::endl;
1277}
1278
1279QT_END_NAMESPACE
1280
1281#endif // QT_NO_SVGGENERATOR
\inmodule QtGui
Definition qimage.h:37
QSvgPaintEngine * engine
bool isClippingEffective() const
QSvgPaintEnginePrivate(QSvgGenerator::SvgVersion version)
std::optional< QPainterPath > clipPath
void drawImage(const QRectF &r, const QImage &pm, const QRectF &sr, Qt::ImageConversionFlags flags=Qt::AutoColor) override
Reimplement this function to draw the part of the image specified by the sr rectangle in the given re...
bool end() override
Reimplement this function to finish painting on the current paint device.
void generateImage(QTextStream &stream, const QRectF &r, const QImage &pm)
void setSize(const QSize &size)
QIODevice * outputDevice() const
int resolution() const
void drawEllipse(const QRectF &r) override
Reimplement this function to draw the largest ellipse that can be contained within rectangle rect.
void setDocumentTitle(const QString &title)
QString documentDescription() const
void drawPath(const QPainterPath &path) override
The default implementation ignores the path and does nothing.
void updateClipState(const QPaintEngineState &state)
QString documentTitle() const
void drawTextItem(const QPointF &pt, const QTextItem &item) override
This function draws the text item textItem at position p.
void setViewBox(const QRectF &viewBox)
void setResolution(int resolution)
void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) override
Reimplement this function to draw the part of the pm specified by the sr rectangle in the given r.
void setDocumentDescription(const QString &description)
QRectF viewBox() const
void setOutputDevice(QIODevice *device)
void drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) override
Reimplement this virtual function to draw the polygon defined by the pointCount first points in point...
void updateState(const QPaintEngineState &state) override
Reimplement this function to update the state of a paint engine.
QPaintEngine::Type type() const override
Reimplement this function to return the paint engine \l{Type}.
void drawRects(const QRectF *rects, int rectCount) override
Draws the first rectCount rectangles in the buffer rects.
QSize size() const
Combined button and popup list for selecting options.
bool qHasPixmapTexture(const QBrush &)
Definition qbrush.cpp:206
static void translate_dashPattern(const QList< qreal > &pattern, qreal width, QString *pattern_string)
static QPaintEngine::PaintEngineFeatures svgEngineFeatures()
static QT_BEGIN_NAMESPACE void translate_color(const QColor &color, QString *color_string, QString *opacity_string)