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