6#ifndef QT_NO_SVGGENERATOR
10#include "private/qdrawhelper_p.h"
11#include "private/qpaintengine_p.h"
12#include "private/qpainter_p.h"
13#include "private/qtextengine_p.h"
29 QString *opacity_string)
31 Q_ASSERT(color_string);
32 Q_ASSERT(opacity_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());
44 Q_ASSERT(pattern_string);
47 for (qreal entry : pattern)
48 *pattern_string += QString::fromLatin1(
"%1,").arg(entry * width);
50 pattern_string->chop(1);
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");
94 currentGradientName = QString::fromLatin1(
"gradient%1").arg(numGradients);
95 return currentGradientName;
118 currentClipPathName = QStringLiteral(
"clipPath%1").arg(numClipPaths);
119 return currentClipPathName;
125 return clipEnabled && clipPath.has_value();
134 return QPaintEngine::PaintEngineFeatures(
135 QPaintEngine::AllFeatures
136 & ~QPaintEngine::PerspectiveTransform
137 & ~QPaintEngine::ConicalGradientFill
138 & ~QPaintEngine::PorterDuff);
141Q_GUI_EXPORT
QImage qt_imageForBrush(
int brushStyle,
bool invert);
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;
169 void drawImage(
const QRectF &r,
const QImage &pm,
const QRectF &sr,
170 Qt::ImageConversionFlags flags =
Qt::
AutoColor)
override;
176 Q_ASSERT(!isActive());
177 d_func()->size = size;
182 Q_ASSERT(!isActive());
183 d_func()->viewBox = viewBox;
186 QString
documentTitle()
const {
return d_func()->attributes.document_title; }
188 d_func()->attributes.document_title = title;
193 d_func()->attributes.document_description = description;
198 Q_ASSERT(!isActive());
199 d_func()->outputDevice = device;
204 Q_ASSERT(!isActive());
205 d_func()->resolution = resolution;
218 str <<
"<mask id=\"" <<
maskId <<
"\" x=\"0\" y=\"0\" width=\"8\" height=\"8\" "
219 <<
"stroke=\"none\" fill=\"#ffffff\" patternUnits=\"userSpaceOnUse\" >" <<
Qt::
endl;
301 qWarning(
"svg's don't support conical gradients!");
427 stream() <<
"stroke-width=\"1\" ";
433 stream() <<
"stroke-linecap=\"butt\" ";
436 stream() <<
"stroke-linecap=\"square\" ";
439 stream() <<
"stroke-linecap=\"round\" ";
447 stream() <<
"stroke-linejoin=\"miter\" "
451 stream() <<
"stroke-linejoin=\"bevel\" ";
454 stream() <<
"stroke-linejoin=\"round\" ";
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
594
595
596
597
598
599
600
601
602
605
606
607QSvgGenerator::QSvgGenerator()
608 : QSvgGenerator(SvgVersion::SvgTiny12)
613
614
615
616
617QSvgGenerator::QSvgGenerator(SvgVersion version)
618 : d_ptr(
new QSvgGeneratorPrivate)
622 d->engine =
new QSvgPaintEngine(version);
623 d->owns_iodevice =
false;
627
628
629QSvgGenerator::~QSvgGenerator()
632 if (d->owns_iodevice)
633 delete d->engine->outputDevice();
638
639
640
641
642
643QString QSvgGenerator::title()
const
645 Q_D(
const QSvgGenerator);
647 return d->engine->documentTitle();
650void QSvgGenerator::setTitle(
const QString &title)
654 d->engine->setDocumentTitle(title);
658
659
660
661
662
663QString QSvgGenerator::description()
const
665 Q_D(
const QSvgGenerator);
667 return d->engine->documentDescription();
670void QSvgGenerator::setDescription(
const QString &description)
674 d->engine->setDocumentDescription(description);
678
679
680
681
682
683
684
685
686
687
688
689
690
691QSize QSvgGenerator::size()
const
693 Q_D(
const QSvgGenerator);
694 return d->engine->size();
697void QSvgGenerator::setSize(
const QSize &size)
700 if (d->engine->isActive()) {
701 qWarning(
"QSvgGenerator::setSize(), cannot set size while SVG is being generated");
704 d->engine->setSize(size);
708
709
710
711
712
713
714
715
716
717
718
719
720
721QRectF QSvgGenerator::viewBoxF()
const
723 Q_D(
const QSvgGenerator);
724 return d->engine->viewBox();
728
729
730
731
732
733
734QRect QSvgGenerator::viewBox()
const
736 Q_D(
const QSvgGenerator);
737 return d->engine->viewBox().toRect();
740void QSvgGenerator::setViewBox(
const QRectF &viewBox)
743 if (d->engine->isActive()) {
744 qWarning(
"QSvgGenerator::setViewBox(), cannot set viewBox while SVG is being generated");
747 d->engine->setViewBox(viewBox);
750void QSvgGenerator::setViewBox(
const QRect &viewBox)
752 setViewBox(QRectF(viewBox));
756
757
758
759
760
761
762QString QSvgGenerator::fileName()
const
764 Q_D(
const QSvgGenerator);
768void QSvgGenerator::setFileName(
const QString &fileName)
771 if (d->engine->isActive()) {
772 qWarning(
"QSvgGenerator::setFileName(), cannot set file name while SVG is being generated");
776 if (d->owns_iodevice)
777 delete d->engine->outputDevice();
779 d->owns_iodevice =
true;
781 d->fileName = fileName;
782 QFile *file =
new QFile(fileName);
783 d->engine->setOutputDevice(file);
787
788
789
790
791
792
793
794
795
796QIODevice *QSvgGenerator::outputDevice()
const
798 Q_D(
const QSvgGenerator);
799 return d->engine->outputDevice();
802void QSvgGenerator::setOutputDevice(QIODevice *outputDevice)
805 if (d->engine->isActive()) {
806 qWarning(
"QSvgGenerator::setOutputDevice(), cannot set output device while SVG is being generated");
809 d->owns_iodevice =
false;
810 d->engine->setOutputDevice(outputDevice);
811 d->fileName = QString();
815
816
817
818
819
820
821
822
823
824int QSvgGenerator::resolution()
const
826 Q_D(
const QSvgGenerator);
827 return d->engine->resolution();
830void QSvgGenerator::setResolution(
int dpi)
833 d->engine->setResolution(dpi);
837
838
839
840
841
842QSvgGenerator::SvgVersion QSvgGenerator::svgVersion()
const
844 Q_D(
const QSvgGenerator);
845 return d->engine->svgVersion();
849
850
851
852QPaintEngine *QSvgGenerator::paintEngine()
const
854 Q_D(
const QSvgGenerator);
859
860
861int QSvgGenerator::metric(QPaintDevice::PaintDeviceMetric metric)
const
863 Q_D(
const QSvgGenerator);
865 case QPaintDevice::PdmDepth:
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:
881 case QPaintDevice::PdmPhysicalDpiX:
882 return d->engine->resolution();
883 case QPaintDevice::PdmPhysicalDpiY:
884 return d->engine->resolution();
885 case QPaintDevice::PdmDevicePixelRatio:
887 case QPaintDevice::PdmDevicePixelRatioScaled:
888 return 1 * QPaintDevice::devicePixelRatioFScale();
890 qWarning(
"QSvgGenerator::metric(), unhandled metric %d\n", metric);
896void QSvgGenerator::initPainter(QPainter *painter)
const
898 QPainterPrivate *painterPrivate = QPainterPrivate::get(painter);
900 for (QFont *font : { &painterPrivate->state->deviceFont, &painterPrivate->state->font }) {
901 if (font->hintingPreference() == QFont::PreferDefaultHinting)
902 font->setHintingPreference(QFont::PreferNoHinting);
904 painterPrivate->setEngineDirtyFlags({ QPaintEngine::DirtyFont });
908
909
913 Q_D(QSvgPaintEngine);
914 if (!d->outputDevice) {
915 qWarning(
"QSvgPaintEngine::begin(), no output device");
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()));
925 }
else if (!d->outputDevice->isWritable()) {
926 qWarning(
"QSvgPaintEngine::begin(), could not write to read-only output device: '%s'",
927 qPrintable(d->outputDevice->errorString()));
931 d->stream =
new QTextStream(&d->header);
934 *d->stream <<
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" << Qt::endl <<
"<svg";
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;
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;
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\">";
953 case QSvgGenerator::SvgVersion::Svg11:
954 *d->stream <<
" version=\"1.1\">";
957 *d->stream << Qt::endl;
959 if (!d->attributes.document_title.isEmpty()) {
960 *d->stream <<
"<title>" << d->attributes.document_title.toHtmlEscaped() <<
"</title>" << Qt::endl;
963 if (!d->attributes.document_description.isEmpty()) {
964 *d->stream <<
"<desc>" << d->attributes.document_description.toHtmlEscaped() <<
"</desc>" << Qt::endl;
967 d->stream->setString(&d->defs);
968 *d->stream <<
"<defs>\n";
970 d->stream->setString(&d->body);
973 generateQtDefaults();
974 *d->stream << Qt::endl;
981 Q_D(QSvgPaintEngine);
983 d->stream->setString(&d->defs);
984 *d->stream <<
"</defs>\n";
986 d->stream->setDevice(d->outputDevice);
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;
996 *d->stream <<
"</g>" << Qt::endl
997 <<
"</svg>" << Qt::endl;
1007 drawImage(r, pm.toImage(), sr);
1010void QSvgPaintEngine::generateImage(QTextStream &stream,
const QRectF &r,
const QImage &image)
1013 if (state->renderHints() & QPainter::SmoothPixmapTransform) {
1014 quality = QLatin1String(
"optimizeQuality");
1016 quality = QLatin1String(
"optimizeSpeed");
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<<
"\" ";
1027 QBuffer buffer(&data);
1028 buffer.open(QBuffer::ReadWrite);
1029 image.save(&buffer,
"PNG");
1031 stream <<
"xlink:href=\"data:image/png;base64,"
1038 Qt::ImageConversionFlags flags)
1042 generateImage(stream(), r, image);
1047 Q_D(QSvgPaintEngine);
1051 if (d->hasEmittedClipGroup)
1052 *d->stream <<
"</g>\n";
1053 if (d->afterFirstUpdate)
1054 *d->stream <<
"</g>\n\n";
1056 updateClipState(state);
1058 if (d->isClippingEffective()) {
1059 *d->stream << QStringLiteral(
"<g clip-path=\"url(#%1)\">").arg(d->currentClipPathName);
1060 d->hasEmittedClipGroup =
true;
1062 d->hasEmittedClipGroup =
false;
1065 *d->stream <<
"<g ";
1067 qbrushToSvg(state.brush());
1068 qpenToSvg(state.pen());
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()
1078 qfontToSvg(state.font());
1080 if (!qFuzzyIsNull(state.opacity() - 1))
1081 stream() <<
"opacity=\""<<state.opacity()<<
"\" ";
1083 *d->stream <<
'>' << Qt::endl;
1085 d->afterFirstUpdate =
true;
1090 Q_D(QSvgPaintEngine);
1091 switch (d->svgVersion) {
1092 case QSvgGenerator::SvgVersion::SvgTiny12:
1095 case QSvgGenerator::SvgVersion::Svg11:
1099 const QPaintEngine::DirtyFlags flags = state.state();
1101 const bool clippingChanged = flags.testAnyFlags(DirtyClipPath | DirtyClipRegion);
1102 if (clippingChanged) {
1103 switch (state.clipOperation()) {
1105 d->clipEnabled =
false;
1106 d->clipPath.reset();
1108 case Qt::ReplaceClip:
1109 case Qt::IntersectClip:
1110 d->clipPath = painter()->transform().map(painter()->clipPath());
1115 if (flags & DirtyClipEnabled)
1116 d->clipEnabled = state.isClipEnabled();
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);
1129 Q_D(QSvgPaintEngine);
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();
1138 *d->stream <<
"\" r=\"" << r.width() / qreal(2.0);
1140 *d->stream <<
"\" rx=\"" << r.width() / qreal(2.0) <<
"\" ry=\"" << r.height() / qreal(2.0);
1141 *d->stream <<
"\"/>" << Qt::endl;
1146 Q_D(QSvgPaintEngine);
1148 *d->stream <<
"<path vector-effect=\""
1149 << (state->pen().isCosmetic() ?
"non-scaling-stroke" :
"none")
1150 <<
"\" fill-rule=\""
1151 << (p.fillRule() == Qt::OddEvenFill ?
"evenodd" :
"nonzero")
1154 for (
int i=0; i<p.elementCount(); ++i) {
1155 const QPainterPath::Element &e = p.elementAt(i);
1157 case QPainterPath::MoveToElement:
1158 *d->stream <<
'M' << e.x <<
',' << e.y;
1160 case QPainterPath::LineToElement:
1161 *d->stream <<
'L' << e.x <<
',' << e.y;
1163 case QPainterPath::CurveToElement:
1164 *d->stream <<
'C' << e.x <<
',' << e.y;
1166 while (i < p.elementCount()) {
1167 const QPainterPath::Element &e = p.elementAt(i);
1168 if (e.type != QPainterPath::CurveToDataElement) {
1173 *d->stream << e.x <<
',' << e.y;
1180 if (i != p.elementCount() - 1) {
1185 *d->stream <<
"\"/>" << Qt::endl;
1189 PolygonDrawMode mode)
1191 Q_ASSERT(pointCount >= 2);
1195 QPainterPath path(points[0]);
1196 for (
int i=1; i<pointCount; ++i)
1197 path.lineTo(points[i]);
1199 if (mode == PolylineMode) {
1200 stream() <<
"<polyline fill=\"none\" vector-effect=\""
1201 << (state->pen().isCosmetic() ?
"non-scaling-stroke" :
"none")
1203 for (
int i = 0; i < pointCount; ++i) {
1204 const QPointF &pt = points[i];
1205 stream() << pt.x() <<
',' << pt.y() <<
' ';
1207 stream() <<
"\" />" <<Qt::endl;
1209 path.closeSubpath();
1216 Q_D(QSvgPaintEngine);
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;
1231 Q_D(QSvgPaintEngine);
1232 if (d->pen.style() == Qt::NoPen)
1235 const QTextItemInt &ti =
static_cast<
const QTextItemInt &>(textItem);
1237 QPaintEngine::drawTextItem(pt, ti);
1238 QString s = QString::fromRawData(ti.chars, ti.num_chars);
1240 *d->stream <<
"<text "
1241 "fill=\"" << d->attributes.stroke <<
"\" "
1242 "fill-opacity=\"" << d->attributes.strokeOpacity <<
"\" "
1244 "xml:space=\"preserve\" "
1245 "x=\"" << pt.x() <<
"\" y=\"" << pt.y() <<
"\" ";
1246 qfontToSvg(textItem.font());
1248 << 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