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