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