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"
31 QString *opacity_string)
33 Q_ASSERT(color_string);
34 Q_ASSERT(opacity_string);
37 QString::fromLatin1(
"#%1%2%3")
38 .arg(color.red(), 2, 16, QLatin1Char(
'0'))
39 .arg(color.green(), 2, 16, QLatin1Char(
'0'))
40 .arg(color.blue(), 2, 16, QLatin1Char(
'0'));
41 *opacity_string = QString::number(color.alphaF());
46 Q_ASSERT(pattern_string);
49 for (qreal entry : pattern)
50 *pattern_string += QString::fromLatin1(
"%1,").arg(entry * width);
52 pattern_string->chop(1);
66 attributes.document_title = QLatin1String(
"Qt SVG Document");
67 attributes.document_description = QLatin1String(
"Generated with Qt");
68 attributes.font_family = QLatin1String(
"serif");
69 attributes.font_size = QLatin1String(
"10pt");
70 attributes.font_style = QLatin1String(
"normal");
71 attributes.font_weight = QLatin1String(
"normal");
96 currentGradientName = QString::fromLatin1(
"gradient%1").arg(numGradients);
97 return currentGradientName;
120 currentClipPathName = QStringLiteral(
"clipPath%1").arg(numClipPaths);
121 return currentClipPathName;
127 return clipEnabled && clipPath.has_value();
136 return QPaintEngine::PaintEngineFeatures(
137 QPaintEngine::AllFeatures
138 & ~QPaintEngine::PerspectiveTransform
139 & ~QPaintEngine::ConicalGradientFill
140 & ~QPaintEngine::PorterDuff);
143Q_GUI_EXPORT
QImage qt_imageForBrush(
int brushStyle,
bool invert);
166 void drawPixmap(
const QRectF &r,
const QPixmap &pm,
const QRectF &sr)
override;
167 void drawPolygon(
const QPointF *points,
int pointCount, PolygonDrawMode mode)
override;
168 void drawRects(
const QRectF *rects,
int rectCount)
override;
171 void drawImage(
const QRectF &r,
const QImage &pm,
const QRectF &sr,
172 Qt::ImageConversionFlags flags =
Qt::
AutoColor)
override;
178 Q_ASSERT(!isActive());
179 d_func()->size = size;
184 Q_ASSERT(!isActive());
185 d_func()->viewBox = viewBox;
188 QString
documentTitle()
const {
return d_func()->attributes.document_title; }
190 d_func()->attributes.document_title = title;
195 d_func()->attributes.document_description = description;
200 Q_ASSERT(!isActive());
201 d_func()->outputDevice = device;
206 Q_ASSERT(!isActive());
207 d_func()->resolution = resolution;
220 str <<
"<mask id=\"" <<
maskId <<
"\" x=\"0\" y=\"0\" width=\"8\" height=\"8\" "
221 <<
"stroke=\"none\" fill=\"#ffffff\" patternUnits=\"userSpaceOnUse\" >" <<
Qt::
endl;
303 qWarning(
"svg's don't support conical gradients!");
429 stream() <<
"stroke-width=\"1\" ";
435 stream() <<
"stroke-linecap=\"butt\" ";
438 stream() <<
"stroke-linecap=\"square\" ";
441 stream() <<
"stroke-linecap=\"round\" ";
449 stream() <<
"stroke-linejoin=\"miter\" "
453 stream() <<
"stroke-linejoin=\"bevel\" ";
456 stream() <<
"stroke-linejoin=\"round\" ";
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
597
598
599
600
601
602
603
606
607
608
609
610
611
612
613
614
617
618
619QSvgGenerator::QSvgGenerator()
620 : QSvgGenerator(SvgVersion::SvgTiny12)
625
626
627
628
629QSvgGenerator::QSvgGenerator(SvgVersion version)
630 : d_ptr(
new QSvgGeneratorPrivate)
634 d->engine =
new QSvgPaintEngine(version);
635 d->owns_iodevice =
false;
639
640
641QSvgGenerator::~QSvgGenerator()
644 if (d->owns_iodevice)
645 delete d->engine->outputDevice();
650
651
652
653
654
655QString QSvgGenerator::title()
const
657 Q_D(
const QSvgGenerator);
659 return d->engine->documentTitle();
662void QSvgGenerator::setTitle(
const QString &title)
666 d->engine->setDocumentTitle(title);
670
671
672
673
674
675QString QSvgGenerator::description()
const
677 Q_D(
const QSvgGenerator);
679 return d->engine->documentDescription();
682void QSvgGenerator::setDescription(
const QString &description)
686 d->engine->setDocumentDescription(description);
690
691
692
693
694
695
696
697
698
699
700
701
702
703QSize QSvgGenerator::size()
const
705 Q_D(
const QSvgGenerator);
706 return d->engine->size();
709void QSvgGenerator::setSize(
const QSize &size)
712 if (d->engine->isActive()) {
713 qWarning(
"QSvgGenerator::setSize(), cannot set size while SVG is being generated");
716 d->engine->setSize(size);
720
721
722
723
724
725
726
727
728
729
730
731
732
733QRectF QSvgGenerator::viewBoxF()
const
735 Q_D(
const QSvgGenerator);
736 return d->engine->viewBox();
740
741
742
743
744
745
746QRect QSvgGenerator::viewBox()
const
748 Q_D(
const QSvgGenerator);
749 return d->engine->viewBox().toRect();
752void QSvgGenerator::setViewBox(
const QRectF &viewBox)
755 if (d->engine->isActive()) {
756 qWarning(
"QSvgGenerator::setViewBox(), cannot set viewBox while SVG is being generated");
759 d->engine->setViewBox(viewBox);
762void QSvgGenerator::setViewBox(
const QRect &viewBox)
764 setViewBox(QRectF(viewBox));
768
769
770
771
772
773
774QString QSvgGenerator::fileName()
const
776 Q_D(
const QSvgGenerator);
780void QSvgGenerator::setFileName(
const QString &fileName)
783 if (d->engine->isActive()) {
784 qWarning(
"QSvgGenerator::setFileName(), cannot set file name while SVG is being generated");
788 if (d->owns_iodevice)
789 delete d->engine->outputDevice();
791 d->owns_iodevice =
true;
793 d->fileName = fileName;
794 QFile *file =
new QFile(fileName);
795 d->engine->setOutputDevice(file);
799
800
801
802
803
804
805
806
807
808QIODevice *QSvgGenerator::outputDevice()
const
810 Q_D(
const QSvgGenerator);
811 return d->engine->outputDevice();
814void QSvgGenerator::setOutputDevice(QIODevice *outputDevice)
817 if (d->engine->isActive()) {
818 qWarning(
"QSvgGenerator::setOutputDevice(), cannot set output device while SVG is being generated");
821 d->owns_iodevice =
false;
822 d->engine->setOutputDevice(outputDevice);
823 d->fileName = QString();
827
828
829
830
831
832
833
834
835
836int QSvgGenerator::resolution()
const
838 Q_D(
const QSvgGenerator);
839 return d->engine->resolution();
842void QSvgGenerator::setResolution(
int dpi)
845 d->engine->setResolution(dpi);
849
850
851
852
853
854QSvgGenerator::SvgVersion QSvgGenerator::svgVersion()
const
856 Q_D(
const QSvgGenerator);
857 return d->engine->svgVersion();
861
862
863
864QPaintEngine *QSvgGenerator::paintEngine()
const
866 Q_D(
const QSvgGenerator);
871
872
873int QSvgGenerator::metric(QPaintDevice::PaintDeviceMetric metric)
const
875 Q_D(
const QSvgGenerator);
877 case QPaintDevice::PdmDepth:
879 case QPaintDevice::PdmWidth:
880 return d->engine->size().width();
881 case QPaintDevice::PdmHeight:
882 return d->engine->size().height();
883 case QPaintDevice::PdmDpiX:
884 return d->engine->resolution();
885 case QPaintDevice::PdmDpiY:
886 return d->engine->resolution();
887 case QPaintDevice::PdmHeightMM:
888 return qRound(d->engine->size().height() * 25.4 / d->engine->resolution());
889 case QPaintDevice::PdmWidthMM:
890 return qRound(d->engine->size().width() * 25.4 / d->engine->resolution());
891 case QPaintDevice::PdmNumColors:
893 case QPaintDevice::PdmPhysicalDpiX:
894 return d->engine->resolution();
895 case QPaintDevice::PdmPhysicalDpiY:
896 return d->engine->resolution();
897 case QPaintDevice::PdmDevicePixelRatio:
899 case QPaintDevice::PdmDevicePixelRatioScaled:
900 return 1 * QPaintDevice::devicePixelRatioFScale();
902 qWarning(
"QSvgGenerator::metric(), unhandled metric %d\n", metric);
909
910
911
912void QSvgGenerator::initPainter(QPainter *painter)
const
914 QPainterPrivate *painterPrivate = QPainterPrivate::get(painter);
916 for (QFont *font : { &painterPrivate->state->deviceFont, &painterPrivate->state->font }) {
917 if (font->hintingPreference() == QFont::PreferDefaultHinting)
918 font->setHintingPreference(QFont::PreferNoHinting);
920 painterPrivate->setEngineDirtyFlags({ QPaintEngine::DirtyFont });
924
925
929 Q_D(QSvgPaintEngine);
930 if (!d->outputDevice) {
931 qWarning(
"QSvgPaintEngine::begin(), no output device");
935 if (!d->outputDevice->isOpen()) {
936 if (!d->outputDevice->open(QIODevice::WriteOnly | QIODevice::Text)) {
937 qWarning(
"QSvgPaintEngine::begin(), could not open output device: '%s'",
938 qPrintable(d->outputDevice->errorString()));
941 }
else if (!d->outputDevice->isWritable()) {
942 qWarning(
"QSvgPaintEngine::begin(), could not write to read-only output device: '%s'",
943 qPrintable(d->outputDevice->errorString()));
947 d->stream =
new QTextStream(&d->header);
950 *d->stream <<
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" << Qt::endl <<
"<svg";
952 if (d->size.isValid()) {
953 qreal wmm = d->size.width() * 25.4 / d->resolution;
954 qreal hmm = d->size.height() * 25.4 / d->resolution;
955 *d->stream <<
" width=\"" << wmm <<
"mm\" height=\"" << hmm <<
"mm\"" << Qt::endl;
958 if (d->viewBox.isValid()) {
959 *d->stream <<
" viewBox=\"" << d->viewBox.left() <<
' ' << d->viewBox.top();
960 *d->stream <<
' ' << d->viewBox.width() <<
' ' << d->viewBox.height() <<
'\"' << Qt::endl;
963 *d->stream <<
" xmlns=\"http://www.w3.org/2000/svg\""
964 " xmlns:xlink=\"http://www.w3.org/1999/xlink\"";
965 switch (d->svgVersion) {
966 case QSvgGenerator::SvgVersion::SvgTiny12:
967 *d->stream <<
" version=\"1.2\" baseProfile=\"tiny\">";
969 case QSvgGenerator::SvgVersion::Svg11:
970 *d->stream <<
" version=\"1.1\">";
973 *d->stream << Qt::endl;
975 if (!d->attributes.document_title.isEmpty()) {
976 *d->stream <<
"<title>" << d->attributes.document_title.toHtmlEscaped() <<
"</title>" << Qt::endl;
979 if (!d->attributes.document_description.isEmpty()) {
980 *d->stream <<
"<desc>" << d->attributes.document_description.toHtmlEscaped() <<
"</desc>" << Qt::endl;
983 d->stream->setString(&d->defs);
984 *d->stream <<
"<defs>\n";
986 d->stream->setString(&d->body);
989 generateQtDefaults();
990 *d->stream << Qt::endl;
997 Q_D(QSvgPaintEngine);
999 d->stream->setString(&d->defs);
1000 *d->stream <<
"</defs>\n";
1002 d->stream->setDevice(d->outputDevice);
1004 *d->stream << d->header;
1005 *d->stream << d->defs;
1006 *d->stream << d->body;
1007 if (d->hasEmittedClipGroup)
1008 *d->stream <<
"</g>";
1009 if (d->afterFirstUpdate)
1010 *d->stream <<
"</g>" << Qt::endl;
1012 *d->stream <<
"</g>" << Qt::endl
1013 <<
"</svg>" << Qt::endl;
1023 drawImage(r, pm.toImage(), sr);
1026void QSvgPaintEngine::generateImage(QTextStream &stream,
const QRectF &r,
const QImage &image)
1029 if (state->renderHints() & QPainter::SmoothPixmapTransform) {
1030 quality = QLatin1String(
"optimizeQuality");
1032 quality = QLatin1String(
"optimizeSpeed");
1034 stream <<
"<image ";
1035 stream <<
"x=\""<<r.x()<<
"\" "
1036 "y=\""<<r.y()<<
"\" "
1037 "width=\""<<r.width()<<
"\" "
1038 "height=\""<<r.height()<<
"\" "
1039 "preserveAspectRatio=\"none\" "
1040 "image-rendering=\""<<quality<<
"\" ";
1043 QBuffer buffer(&data);
1044 buffer.open(QBuffer::ReadWrite);
1045 image.save(&buffer,
"PNG");
1047 stream <<
"xlink:href=\"data:image/png;base64,"
1054 Qt::ImageConversionFlags flags)
1058 generateImage(stream(), r, image);
1063 Q_D(QSvgPaintEngine);
1067 if (d->hasEmittedClipGroup)
1068 *d->stream <<
"</g>\n";
1069 if (d->afterFirstUpdate)
1070 *d->stream <<
"</g>\n\n";
1072 updateClipState(state);
1074 if (d->isClippingEffective()) {
1075 *d->stream << QStringLiteral(
"<g clip-path=\"url(#%1)\">").arg(d->currentClipPathName);
1076 d->hasEmittedClipGroup =
true;
1078 d->hasEmittedClipGroup =
false;
1081 *d->stream <<
"<g ";
1083 qbrushToSvg(state.brush());
1084 qpenToSvg(state.pen());
1086 d->matrix = state.transform();
1087 *d->stream <<
"transform=\"matrix(" << d->matrix.m11() <<
','
1088 << d->matrix.m12() <<
','
1089 << d->matrix.m21() <<
',' << d->matrix.m22() <<
','
1090 << d->matrix.dx() <<
',' << d->matrix.dy()
1094 qfontToSvg(state.font());
1096 if (!qFuzzyIsNull(state.opacity() - 1))
1097 stream() <<
"opacity=\""<<state.opacity()<<
"\" ";
1099 *d->stream <<
'>' << Qt::endl;
1101 d->afterFirstUpdate =
true;
1106 Q_D(QSvgPaintEngine);
1107 switch (d->svgVersion) {
1108 case QSvgGenerator::SvgVersion::SvgTiny12:
1111 case QSvgGenerator::SvgVersion::Svg11:
1115 const QPaintEngine::DirtyFlags flags = state.state();
1117 const bool clippingChanged = flags.testAnyFlags(DirtyClipPath | DirtyClipRegion);
1118 if (clippingChanged) {
1119 switch (state.clipOperation()) {
1121 d->clipEnabled =
false;
1122 d->clipPath.reset();
1124 case Qt::ReplaceClip:
1125 case Qt::IntersectClip:
1126 d->clipPath = painter()->transform().map(painter()->clipPath());
1131 if (flags & DirtyClipEnabled)
1132 d->clipEnabled = state.isClipEnabled();
1134 if (d->isClippingEffective() && clippingChanged) {
1135 d->stream->setString(&d->defs);
1136 *d->stream << QLatin1String(
"<clipPath id=\"%1\">\n").arg(d->generateClipPathName());
1138 *d->stream <<
"</clipPath>\n";
1139 d->stream->setString(&d->body);
1145 Q_D(QSvgPaintEngine);
1147 const bool isCircle = r.width() == r.height();
1148 *d->stream <<
'<' << (isCircle ?
"circle" :
"ellipse");
1149 if (state->pen().isCosmetic())
1150 *d->stream <<
" vector-effect=\"non-scaling-stroke\"";
1151 const QPointF c = r.center();
1152 *d->stream <<
" cx=\"" << c.x() <<
"\" cy=\"" << c.y();
1154 *d->stream <<
"\" r=\"" << r.width() / qreal(2.0);
1156 *d->stream <<
"\" rx=\"" << r.width() / qreal(2.0) <<
"\" ry=\"" << r.height() / qreal(2.0);
1157 *d->stream <<
"\"/>" << Qt::endl;
1162 Q_D(QSvgPaintEngine);
1164 *d->stream <<
"<path vector-effect=\""
1165 << (state->pen().isCosmetic() ?
"non-scaling-stroke" :
"none")
1166 <<
"\" fill-rule=\""
1167 << (p.fillRule() == Qt::OddEvenFill ?
"evenodd" :
"nonzero")
1171 QPointF subPathStart;
1172 auto endSubPath = [&]() {
1174 const QPointF subPathEnd = p.elementAt(i - 1);
1175 if (subPathEnd == subPathStart)
1180 for (i = 0; i < p.elementCount(); ++i) {
1181 const QPainterPath::Element &e = p.elementAt(i);
1183 case QPainterPath::MoveToElement:
1186 *d->stream <<
'M' << e.x <<
',' << e.y;
1188 case QPainterPath::LineToElement:
1189 *d->stream <<
'L' << e.x <<
',' << e.y;
1191 case QPainterPath::CurveToElement:
1192 *d->stream <<
'C' << e.x <<
',' << e.y;
1194 while (i < p.elementCount()) {
1195 const QPainterPath::Element &e = p.elementAt(i);
1196 if (e.type != QPainterPath::CurveToDataElement) {
1201 *d->stream << e.x <<
',' << e.y;
1212 *d->stream <<
"\"/>" << Qt::endl;
1216 PolygonDrawMode mode)
1218 Q_ASSERT(pointCount >= 2);
1222 if (mode == PolylineMode)
1223 stream() <<
"<polyline fill=\"none\"";
1224 else if (mode == OddEvenMode)
1225 stream() <<
"<polygon fill-rule=\"evenodd\"";
1226 else if (mode == WindingMode || mode == ConvexMode)
1227 stream() <<
"<polygon fill-rule=\"nonzero\"";
1229 stream() <<
" vector-effect=\""
1230 << (state->pen().isCosmetic() ?
"non-scaling-stroke" :
"none")
1232 for (
int i = 0; i < pointCount; ++i) {
1233 const QPointF &pt = points[i];
1234 stream() << pt.x() <<
',' << pt.y() <<
' ';
1236 stream() <<
"\" />" <<Qt::endl;
1242 Q_D(QSvgPaintEngine);
1244 for (
int i=0; i < rectCount; ++i) {
1245 const QRectF &rect = rects[i].normalized();
1246 *d->stream <<
"<rect";
1247 if (state->pen().isCosmetic())
1248 *d->stream <<
" vector-effect=\"non-scaling-stroke\"";
1249 *d->stream <<
" x=\"" << rect.x() <<
"\" y=\"" << rect.y()
1250 <<
"\" width=\"" << rect.width() <<
"\" height=\"" << rect.height()
1251 <<
"\"/>" << Qt::endl;
1257 Q_D(QSvgPaintEngine);
1258 if (d->pen.style() == Qt::NoPen)
1261 const QTextItemInt &ti =
static_cast<
const QTextItemInt &>(textItem);
1263 QPaintEngine::drawTextItem(pt, ti);
1264 QString s = QString::fromRawData(ti.chars, ti.num_chars);
1266 *d->stream <<
"<text "
1267 "fill=\"" << d->attributes.stroke <<
"\" "
1268 "fill-opacity=\"" << d->attributes.strokeOpacity <<
"\" "
1270 "xml:space=\"preserve\" "
1271 "x=\"" << pt.x() <<
"\" y=\"" << pt.y() <<
"\" ";
1272 qfontToSvg(textItem.font());
1274 << 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)
QString document_description