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

source code of qtsvg/src/svg/qsvggenerator.cpp