8#ifndef QT_NO_SVGGENERATOR
12#include "private/qdrawhelper_p.h"
13#include "private/qpaintengine_p.h"
14#include "private/qpainter_p.h"
15#include "private/qtextengine_p.h"
32 QString *opacity_string)
34 Q_ASSERT(color_string);
35 Q_ASSERT(opacity_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());
47 Q_ASSERT(pattern_string);
50 for (qreal entry : pattern)
51 *pattern_string += QString::fromLatin1(
"%1,").arg(entry * width);
53 pattern_string->chop(1);
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");
89 currentGradientName = QString::fromLatin1(
"gradient%1").arg(numGradients);
90 return currentGradientName;
113 currentClipPathName = QStringLiteral(
"clipPath%1").arg(numClipPaths);
114 return currentClipPathName;
120 return clipEnabled && clipPath.has_value();
129 return QPaintEngine::PaintEngineFeatures(
130 QPaintEngine::AllFeatures
131 & ~QPaintEngine::PerspectiveTransform
132 & ~QPaintEngine::ConicalGradientFill
133 & ~QPaintEngine::PorterDuff);
136Q_GUI_EXPORT
QImage qt_imageForBrush(
int brushStyle,
bool invert);
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;
164 void drawImage(
const QRectF &r,
const QImage &pm,
const QRectF &sr,
165 Qt::ImageConversionFlags flags =
Qt::
AutoColor)
override;
171 Q_ASSERT(!isActive());
172 d_func()->size = size;
177 Q_ASSERT(!isActive());
178 d_func()->viewBox = viewBox;
181 QString
documentTitle()
const {
return d_func()->attributes.document_title; }
183 d_func()->attributes.document_title = title;
188 d_func()->attributes.document_description = description;
193 Q_ASSERT(!isActive());
194 d_func()->outputDevice = device;
199 Q_ASSERT(!isActive());
200 d_func()->resolution = resolution;
213 str <<
"<mask id=\"" <<
maskId <<
"\" x=\"0\" y=\"0\" width=\"8\" height=\"8\" "
214 <<
"stroke=\"none\" fill=\"#ffffff\" patternUnits=\"userSpaceOnUse\" >" <<
'\n';
296 qWarning(
"svg's don't support conical gradients!");
422 stream() <<
"stroke-width=\"1\" ";
428 stream() <<
"stroke-linecap=\"butt\" ";
431 stream() <<
"stroke-linecap=\"square\" ";
434 stream() <<
"stroke-linecap=\"round\" ";
442 stream() <<
"stroke-linejoin=\"miter\" "
446 stream() <<
"stroke-linejoin=\"bevel\" ";
449 stream() <<
"stroke-linejoin=\"round\" ";
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
599
600
601
602
603
604
605
606
607
610
611
612QSvgGenerator::QSvgGenerator()
613 : QSvgGenerator(SvgVersion::SvgTiny12)
618
619
620
621
622QSvgGenerator::QSvgGenerator(SvgVersion version)
623 : d_ptr(
new QSvgGeneratorPrivate)
627 d->engine =
new QSvgPaintEngine(version);
628 d->owns_iodevice =
false;
632
633
634QSvgGenerator::~QSvgGenerator()
637 if (d->owns_iodevice)
638 delete d->engine->outputDevice();
643
644
645
646
647
648QString QSvgGenerator::title()
const
650 Q_D(
const QSvgGenerator);
652 return d->engine->documentTitle();
655void QSvgGenerator::setTitle(
const QString &title)
659 d->engine->setDocumentTitle(title);
663
664
665
666
667
668QString QSvgGenerator::description()
const
670 Q_D(
const QSvgGenerator);
672 return d->engine->documentDescription();
675void QSvgGenerator::setDescription(
const QString &description)
679 d->engine->setDocumentDescription(description);
683
684
685
686
687
688
689
690
691
692
693
694
695
696QSize QSvgGenerator::size()
const
698 Q_D(
const QSvgGenerator);
699 return d->engine->size();
702void QSvgGenerator::setSize(
const QSize &size)
705 if (d->engine->isActive()) {
706 qWarning(
"QSvgGenerator::setSize(), cannot set size while SVG is being generated");
709 d->engine->setSize(size);
713
714
715
716
717
718
719
720
721
722
723
724
725
726QRectF QSvgGenerator::viewBoxF()
const
728 Q_D(
const QSvgGenerator);
729 return d->engine->viewBox();
733
734
735
736
737
738
739QRect QSvgGenerator::viewBox()
const
741 Q_D(
const QSvgGenerator);
742 return d->engine->viewBox().toRect();
745void QSvgGenerator::setViewBox(
const QRectF &viewBox)
748 if (d->engine->isActive()) {
749 qWarning(
"QSvgGenerator::setViewBox(), cannot set viewBox while SVG is being generated");
752 d->engine->setViewBox(viewBox);
755void QSvgGenerator::setViewBox(
const QRect &viewBox)
757 setViewBox(QRectF(viewBox));
761
762
763
764
765
766
767QString QSvgGenerator::fileName()
const
769 Q_D(
const QSvgGenerator);
773void QSvgGenerator::setFileName(
const QString &fileName)
776 if (d->engine->isActive()) {
777 qWarning(
"QSvgGenerator::setFileName(), cannot set file name while SVG is being generated");
781 if (d->owns_iodevice)
782 delete d->engine->outputDevice();
784 d->owns_iodevice =
true;
786 d->fileName = fileName;
787 QFile *file =
new QFile(fileName);
788 d->engine->setOutputDevice(file);
792
793
794
795
796
797
798
799
800
801QIODevice *QSvgGenerator::outputDevice()
const
803 Q_D(
const QSvgGenerator);
804 return d->engine->outputDevice();
807void QSvgGenerator::setOutputDevice(QIODevice *outputDevice)
810 if (d->engine->isActive()) {
811 qWarning(
"QSvgGenerator::setOutputDevice(), cannot set output device while SVG is being generated");
814 d->owns_iodevice =
false;
815 d->engine->setOutputDevice(outputDevice);
816 d->fileName = QString();
820
821
822
823
824
825
826
827
828
829int QSvgGenerator::resolution()
const
831 Q_D(
const QSvgGenerator);
832 return d->engine->resolution();
835void QSvgGenerator::setResolution(
int dpi)
838 d->engine->setResolution(dpi);
842
843
844
845
846
847QSvgGenerator::SvgVersion QSvgGenerator::svgVersion()
const
849 Q_D(
const QSvgGenerator);
850 return d->engine->svgVersion();
854
855
856
857QPaintEngine *QSvgGenerator::paintEngine()
const
859 Q_D(
const QSvgGenerator);
864
865
866int QSvgGenerator::metric(QPaintDevice::PaintDeviceMetric metric)
const
868 Q_D(
const QSvgGenerator);
870 case QPaintDevice::PdmDepth:
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:
886 case QPaintDevice::PdmPhysicalDpiX:
887 return d->engine->resolution();
888 case QPaintDevice::PdmPhysicalDpiY:
889 return d->engine->resolution();
890 case QPaintDevice::PdmDevicePixelRatio:
892 case QPaintDevice::PdmDevicePixelRatioScaled:
893 return 1 * QPaintDevice::devicePixelRatioFScale();
895 qWarning(
"QSvgGenerator::metric(), unhandled metric %d\n", metric);
902
903
904
905void QSvgGenerator::initPainter(QPainter *painter)
const
907 QPainterPrivate *painterPrivate = QPainterPrivate::get(painter);
909 for (QFont *font : { &painterPrivate->state->deviceFont, &painterPrivate->state->font }) {
910 if (font->hintingPreference() == QFont::PreferDefaultHinting)
911 font->setHintingPreference(QFont::PreferNoHinting);
913 painterPrivate->setEngineDirtyFlags({ QPaintEngine::DirtyFont });
917
918
922 Q_D(QSvgPaintEngine);
923 if (!d->outputDevice) {
924 qWarning(
"QSvgPaintEngine::begin(), no output device");
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()));
934 }
else if (!d->outputDevice->isWritable()) {
935 qWarning(
"QSvgPaintEngine::begin(), could not write to read-only output device: '%s'",
936 qPrintable(d->outputDevice->errorString()));
940 d->stream =
new QTextStream(&d->header);
943 *d->stream <<
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" <<
'\n' <<
"<svg";
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';
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';
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\">";
962 case QSvgGenerator::SvgVersion::Svg11:
963 *d->stream <<
" version=\"1.1\">";
968 if (!d->attributes.document_title.isEmpty()) {
969 *d->stream <<
"<title>" << d->attributes.document_title.toHtmlEscaped() <<
"</title>" <<
'\n';
972 if (!d->attributes.document_description.isEmpty()) {
973 *d->stream <<
"<desc>" << d->attributes.document_description.toHtmlEscaped() <<
"</desc>" <<
'\n';
976 d->stream->setString(&d->defs);
977 *d->stream <<
"<defs>\n";
979 d->stream->setString(&d->body);
982 generateQtDefaults();
990 Q_D(QSvgPaintEngine);
992 d->stream->setString(&d->defs);
993 *d->stream <<
"</defs>\n";
995 d->stream->setDevice(d->outputDevice);
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';
1005 *d->stream <<
"</g>" <<
'\n'
1006 <<
"</svg>" <<
'\n';
1016 drawImage(r, pm.toImage(), sr);
1019void QSvgPaintEngine::generateImage(QTextStream &stream,
const QRectF &r,
const QImage &image)
1022 if (state->renderHints() & QPainter::SmoothPixmapTransform) {
1023 quality = QLatin1String(
"optimizeQuality");
1025 quality = QLatin1String(
"optimizeSpeed");
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<<
"\" ";
1036 QBuffer buffer(&data);
1037 buffer.open(QBuffer::ReadWrite);
1038 image.save(&buffer,
"PNG");
1040 stream <<
"xlink:href=\"data:image/png;base64,"
1047 Qt::ImageConversionFlags flags)
1051 generateImage(stream(), r, image);
1056 Q_D(QSvgPaintEngine);
1060 if (d->hasEmittedClipGroup)
1061 *d->stream <<
"</g>\n";
1062 if (d->afterFirstUpdate)
1063 *d->stream <<
"</g>\n\n";
1065 updateClipState(state);
1067 if (d->isClippingEffective()) {
1068 *d->stream << QStringLiteral(
"<g clip-path=\"url(#%1)\">").arg(d->currentClipPathName);
1069 d->hasEmittedClipGroup =
true;
1071 d->hasEmittedClipGroup =
false;
1074 *d->stream <<
"<g ";
1076 qbrushToSvg(state.brush());
1077 qpenToSvg(state.pen());
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()
1087 qfontToSvg(state.font());
1089 if (!qFuzzyIsNull(state.opacity() - 1))
1090 stream() <<
"opacity=\""<<state.opacity()<<
"\" ";
1092 *d->stream <<
'>' <<
'\n';
1094 d->afterFirstUpdate =
true;
1099 Q_D(QSvgPaintEngine);
1100 switch (d->svgVersion) {
1101 case QSvgGenerator::SvgVersion::SvgTiny12:
1104 case QSvgGenerator::SvgVersion::Svg11:
1108 const QPaintEngine::DirtyFlags flags = state.state();
1110 const bool clippingChanged = flags.testAnyFlags(DirtyClipPath | DirtyClipRegion);
1111 if (clippingChanged) {
1112 switch (state.clipOperation()) {
1114 d->clipEnabled =
false;
1115 d->clipPath.reset();
1117 case Qt::ReplaceClip:
1118 case Qt::IntersectClip:
1119 d->clipPath = painter()->transform().map(painter()->clipPath());
1124 if (flags & DirtyClipEnabled)
1125 d->clipEnabled = state.isClipEnabled();
1127 if (d->isClippingEffective() && clippingChanged) {
1128 d->stream->setString(&d->defs);
1129 *d->stream << QLatin1String(
"<clipPath id=\"%1\">\n").arg(d->generateClipPathName());
1131 *d->stream <<
"</clipPath>\n";
1132 d->stream->setString(&d->body);
1138 Q_D(QSvgPaintEngine);
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();
1147 *d->stream <<
"\" r=\"" << r.width() / qreal(2.0);
1149 *d->stream <<
"\" rx=\"" << r.width() / qreal(2.0) <<
"\" ry=\"" << r.height() / qreal(2.0);
1150 *d->stream <<
"\"/>" <<
'\n';
1155 Q_D(QSvgPaintEngine);
1157 *d->stream <<
"<path vector-effect=\""
1158 << (state->pen().isCosmetic() ?
"non-scaling-stroke" :
"none")
1159 <<
"\" fill-rule=\""
1160 << (p.fillRule() == Qt::OddEvenFill ?
"evenodd" :
"nonzero")
1164 QPointF subPathStart;
1165 auto endSubPath = [&]() {
1167 const QPointF subPathEnd = p.elementAt(i - 1);
1168 if (subPathEnd == subPathStart)
1173 bool inCurveToElement =
false;
1174 for (i = 0; i < p.elementCount(); ++i) {
1175 const QPainterPath::Element &e = p.elementAt(i);
1177 case QPainterPath::MoveToElement:
1180 *d->stream <<
'M' << e.x <<
',' << e.y;
1182 case QPainterPath::LineToElement:
1183 *d->stream <<
'L' << e.x <<
',' << e.y;
1185 case QPainterPath::CurveToElement:
1186 *d->stream <<
'C' << e.x <<
',' << e.y;
1188 case QPainterPath::CurveToDataElement:
1190 *d->stream << e.x <<
',' << e.y;
1196 if (e.type != QPainterPath::CurveToDataElement)
1197 inCurveToElement = (e.type == QPainterPath::CurveToElement);
1201 *d->stream <<
"\"/>" <<
'\n';
1205 PolygonDrawMode mode)
1207 Q_ASSERT(pointCount >= 2);
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\"";
1218 stream() <<
" vector-effect=\""
1219 << (state->pen().isCosmetic() ?
"non-scaling-stroke" :
"none")
1221 for (
int i = 0; i < pointCount; ++i) {
1222 const QPointF &pt = points[i];
1223 stream() << pt.x() <<
',' << pt.y() <<
' ';
1225 stream() <<
"\" />" <<
'\n';
1231 Q_D(QSvgPaintEngine);
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()
1246 Q_D(QSvgPaintEngine);
1247 if (d->pen.style() == Qt::NoPen)
1250 const QTextItemInt &ti =
static_cast<
const QTextItemInt &>(textItem);
1252 QPaintEngine::drawTextItem(pt, ti);
1253 QString s = QString::fromRawData(ti.chars, ti.num_chars);
1255 *d->stream <<
"<text "
1256 "fill=\"" << d->attributes.stroke <<
"\" "
1257 "fill-opacity=\"" << d->attributes.strokeOpacity <<
"\" "
1259 "xml:space=\"preserve\" "
1260 "x=\"" << pt.x() <<
"\" y=\"" << pt.y() <<
"\" ";
1261 qfontToSvg(textItem.font());
1263 << s.toHtmlEscaped()
QString generateClipPathName()
bool isClippingEffective() const
QString currentGradientName
QStringList savedPatternMasks
QStringList savedPatternBrushes
QString currentClipPathName
QSvgPaintEnginePrivate(QSvgGenerator::SvgVersion version)
QString generateGradientName()
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
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)
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.
Combined button and popup list for selecting options.
bool qHasPixmapTexture(const QBrush &)
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)
QStringList font_families
QString document_description