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

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