1/****************************************************************************
2**
3** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
4** Contact: http://www.qt-project.org/legal
5**
6** This file is part of the QtSvg 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 Digia. For licensing terms and
14** conditions see http://qt.digia.com/licensing. For further information
15** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 2.1 requirements
23** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24**
25** In addition, as a special exception, Digia gives you certain additional
26** rights. These rights are described in the Digia Qt LGPL Exception
27** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28**
29** GNU General Public License Usage
30** Alternatively, this file may be used under the terms of the GNU
31** General Public License version 3.0 as published by the Free Software
32** Foundation and appearing in the file LICENSE.GPL included in the
33** packaging of this file. Please review the following information to
34** ensure the GNU General Public License version 3.0 requirements will be
35** met: http://www.gnu.org/copyleft/gpl.html.
36**
37**
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qsvggenerator.h"
43
44#ifndef QT_NO_SVGGENERATOR
45
46#include "qpainterpath.h"
47
48#include "private/qpaintengine_p.h"
49#include "private/qtextengine_p.h"
50#include "private/qdrawhelper_p.h"
51
52#include "qfile.h"
53#include "qtextcodec.h"
54#include "qtextstream.h"
55#include "qbuffer.h"
56#include "qmath.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("#%1%2%3")
70 .arg(color.red(), 2, 16, QLatin1Char('0'))
71 .arg(color.green(), 2, 16, QLatin1Char('0'))
72 .arg(color.blue(), 2, 16, QLatin1Char('0'));
73 *opacity_string = QString::number(color.alphaF());
74}
75
76static void translate_dashPattern(QVector<qreal> pattern, const 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 foreach (qreal entry, pattern)
82 *pattern_string += QString::fromLatin1("%1,").arg(entry * width);
83
84 pattern_string->chop(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 QMatrix matrix;
122 QFont font;
123
124 QString generateGradientName() {
125 ++numGradients;
126 currentGradientName = QString::fromLatin1("gradient%1").arg(numGradients);
127 return currentGradientName;
128 }
129
130 QString currentGradientName;
131 int numGradients;
132
133 struct _attributes {
134 QString document_title;
135 QString document_description;
136 QString font_weight;
137 QString font_size;
138 QString font_family;
139 QString font_style;
140 QString stroke, strokeOpacity;
141 QString dashPattern, dashOffset;
142 QString fill, fillOpacity;
143 } attributes;
144};
145
146static inline QPaintEngine::PaintEngineFeatures svgEngineFeatures()
147{
148 return QPaintEngine::PaintEngineFeatures(
149 QPaintEngine::AllFeatures
150 & ~QPaintEngine::PatternBrush
151 & ~QPaintEngine::PerspectiveTransform
152 & ~QPaintEngine::ConicalGradientFill
153 & ~QPaintEngine::PorterDuff);
154}
155
156class QSvgPaintEngine : public QPaintEngine
157{
158 Q_DECLARE_PRIVATE(QSvgPaintEngine)
159public:
160
161 QSvgPaintEngine()
162 : QPaintEngine(*new QSvgPaintEnginePrivate,
163 svgEngineFeatures())
164 {
165 }
166
167 bool begin(QPaintDevice *device);
168 bool end();
169
170 void updateState(const QPaintEngineState &state);
171 void popGroup();
172
173 void drawPath(const QPainterPath &path);
174 void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr);
175 void drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode);
176 void drawTextItem(const QPointF &pt, const QTextItem &item);
177 void drawImage(const QRectF &r, const QImage &pm, const QRectF &sr,
178 Qt::ImageConversionFlag = Qt::AutoColor);
179
180 QPaintEngine::Type type() const { return QPaintEngine::SVG; }
181
182 QSize size() const { return d_func()->size; }
183 void setSize(const QSize &size) {
184 Q_ASSERT(!isActive());
185 d_func()->size = size;
186 }
187
188 QRectF viewBox() const { return d_func()->viewBox; }
189 void setViewBox(const QRectF &viewBox) {
190 Q_ASSERT(!isActive());
191 d_func()->viewBox = viewBox;
192 }
193
194 QString documentTitle() const { return d_func()->attributes.document_title; }
195 void setDocumentTitle(const QString &title) {
196 d_func()->attributes.document_title = title;
197 }
198
199 QString documentDescription() const { return d_func()->attributes.document_description; }
200 void setDocumentDescription(const QString &description) {
201 d_func()->attributes.document_description = description;
202 }
203
204 QIODevice *outputDevice() const { return d_func()->outputDevice; }
205 void setOutputDevice(QIODevice *device) {
206 Q_ASSERT(!isActive());
207 d_func()->outputDevice = device;
208 }
209
210 int resolution() { return d_func()->resolution; }
211 void setResolution(int resolution) {
212 Q_ASSERT(!isActive());
213 d_func()->resolution = resolution;
214 }
215 void saveLinearGradientBrush(const QGradient *g)
216 {
217 QTextStream str(&d_func()->defs, QIODevice::Append);
218 const QLinearGradient *grad = static_cast<const QLinearGradient*>(g);
219 str << QLatin1String("<linearGradient ");
220 saveGradientUnits(str, g);
221 if (grad) {
222 str << QLatin1String("x1=\"") <<grad->start().x()<< QLatin1String("\" ")
223 << QLatin1String("y1=\"") <<grad->start().y()<< QLatin1String("\" ")
224 << QLatin1String("x2=\"") <<grad->finalStop().x() << QLatin1String("\" ")
225 << QLatin1String("y2=\"") <<grad->finalStop().y() << QLatin1String("\" ");
226 }
227
228 str << QLatin1String("id=\"") << d_func()->generateGradientName() << QLatin1String("\">\n");
229 saveGradientStops(str, g);
230 str << QLatin1String("</linearGradient>") <<endl;
231 }
232 void saveRadialGradientBrush(const QGradient *g)
233 {
234 QTextStream str(&d_func()->defs, QIODevice::Append);
235 const QRadialGradient *grad = static_cast<const QRadialGradient*>(g);
236 str << QLatin1String("<radialGradient ");
237 saveGradientUnits(str, g);
238 if (grad) {
239 str << QLatin1String("cx=\"") <<grad->center().x()<< QLatin1String("\" ")
240 << QLatin1String("cy=\"") <<grad->center().y()<< QLatin1String("\" ")
241 << QLatin1String("r=\"") <<grad->radius() << QLatin1String("\" ")
242 << QLatin1String("fx=\"") <<grad->focalPoint().x() << QLatin1String("\" ")
243 << QLatin1String("fy=\"") <<grad->focalPoint().y() << QLatin1String("\" ");
244 }
245 str << QLatin1String("xml:id=\"") <<d_func()->generateGradientName()<< QLatin1String("\">\n");
246 saveGradientStops(str, g);
247 str << QLatin1String("</radialGradient>") << endl;
248 }
249 void saveConicalGradientBrush(const QGradient *)
250 {
251 qWarning("svg's don't support conical gradients!");
252 }
253
254 void saveGradientStops(QTextStream &str, const QGradient *g) {
255 QGradientStops stops = g->stops();
256
257 if (g->interpolationMode() == QGradient::ColorInterpolation) {
258 bool constantAlpha = true;
259 int alpha = stops.at(0).second.alpha();
260 for (int i = 1; i < stops.size(); ++i)
261 constantAlpha &= (stops.at(i).second.alpha() == alpha);
262
263 if (!constantAlpha) {
264 const qreal spacing = qreal(0.02);
265 QGradientStops newStops;
266 QRgb fromColor = PREMUL(stops.at(0).second.rgba());
267 QRgb toColor;
268 for (int i = 0; i + 1 < stops.size(); ++i) {
269 int parts = qCeil((stops.at(i + 1).first - stops.at(i).first) / spacing);
270 newStops.append(stops.at(i));
271 toColor = PREMUL(stops.at(i + 1).second.rgba());
272
273 if (parts > 1) {
274 qreal step = (stops.at(i + 1).first - stops.at(i).first) / parts;
275 for (int j = 1; j < parts; ++j) {
276 QRgb color = INV_PREMUL(INTERPOLATE_PIXEL_256(fromColor, 256 - 256 * j / parts, toColor, 256 * j / parts));
277 newStops.append(QGradientStop(stops.at(i).first + j * step, QColor::fromRgba(color)));
278 }
279 }
280 fromColor = toColor;
281 }
282 newStops.append(stops.back());
283 stops = newStops;
284 }
285 }
286
287 foreach(QGradientStop stop, stops) {
288 QString color =
289 QString::fromLatin1("#%1%2%3")
290 .arg(stop.second.red(), 2, 16, QLatin1Char('0'))
291 .arg(stop.second.green(), 2, 16, QLatin1Char('0'))
292 .arg(stop.second.blue(), 2, 16, QLatin1Char('0'));
293 str << QLatin1String(" <stop offset=\"")<< stop.first << QLatin1String("\" ")
294 << QLatin1String("stop-color=\"") << color << QLatin1String("\" ")
295 << QLatin1String("stop-opacity=\"") << stop.second.alphaF() <<QLatin1String("\" />\n");
296 }
297 }
298
299 void saveGradientUnits(QTextStream &str, const QGradient *gradient)
300 {
301 str << QLatin1String("gradientUnits=\"");
302 if (gradient && gradient->coordinateMode() == QGradient::ObjectBoundingMode)
303 str << QLatin1String("objectBoundingBox");
304 else
305 str << QLatin1String("userSpaceOnUse");
306 str << QLatin1String("\" ");
307 }
308
309 void generateQtDefaults()
310 {
311 *d_func()->stream << QLatin1String("fill=\"none\" ");
312 *d_func()->stream << QLatin1String("stroke=\"black\" ");
313 *d_func()->stream << QLatin1String("stroke-width=\"1\" ");
314 *d_func()->stream << QLatin1String("fill-rule=\"evenodd\" ");
315 *d_func()->stream << QLatin1String("stroke-linecap=\"square\" ");
316 *d_func()->stream << QLatin1String("stroke-linejoin=\"bevel\" ");
317 *d_func()->stream << QLatin1String(">\n");
318 }
319 inline QTextStream &stream()
320 {
321 return *d_func()->stream;
322 }
323
324
325 void qpenToSvg(const QPen &spen)
326 {
327 QString width;
328
329 d_func()->pen = spen;
330
331 switch (spen.style()) {
332 case Qt::NoPen:
333 stream() << QLatin1String("stroke=\"none\" ");
334
335 d_func()->attributes.stroke = QLatin1String("none");
336 d_func()->attributes.strokeOpacity = QString();
337 return;
338 break;
339 case Qt::SolidLine: {
340 QString color, colorOpacity;
341
342 translate_color(spen.color(), &color,
343 &colorOpacity);
344 d_func()->attributes.stroke = color;
345 d_func()->attributes.strokeOpacity = colorOpacity;
346
347 stream() << QLatin1String("stroke=\"")<<color<< QLatin1String("\" ");
348 stream() << QLatin1String("stroke-opacity=\"")<<colorOpacity<< QLatin1String("\" ");
349 }
350 break;
351 case Qt::DashLine:
352 case Qt::DotLine:
353 case Qt::DashDotLine:
354 case Qt::DashDotDotLine:
355 case Qt::CustomDashLine: {
356 QString color, colorOpacity, dashPattern, dashOffset;
357
358 qreal penWidth = spen.width() == 0 ? qreal(1) : spen.widthF();
359
360 translate_color(spen.color(), &color, &colorOpacity);
361 translate_dashPattern(spen.dashPattern(), penWidth, &dashPattern);
362
363 // SVG uses absolute offset
364 dashOffset = QString::number(spen.dashOffset() * penWidth);
365
366 d_func()->attributes.stroke = color;
367 d_func()->attributes.strokeOpacity = colorOpacity;
368 d_func()->attributes.dashPattern = dashPattern;
369 d_func()->attributes.dashOffset = dashOffset;
370
371 stream() << QLatin1String("stroke=\"")<<color<< QLatin1String("\" ");
372 stream() << QLatin1String("stroke-opacity=\"")<<colorOpacity<< QLatin1String("\" ");
373 stream() << QLatin1String("stroke-dasharray=\"")<<dashPattern<< QLatin1String("\" ");
374 stream() << QLatin1String("stroke-dashoffset=\"")<<dashOffset<< QLatin1String("\" ");
375 break;
376 }
377 default:
378 qWarning("Unsupported pen style");
379 break;
380 }
381
382 if (spen.widthF() == 0)
383 stream() <<"stroke-width=\"1\" ";
384 else
385 stream() <<"stroke-width=\"" << spen.widthF() << "\" ";
386
387 switch (spen.capStyle()) {
388 case Qt::FlatCap:
389 stream() << "stroke-linecap=\"butt\" ";
390 break;
391 case Qt::SquareCap:
392 stream() << "stroke-linecap=\"square\" ";
393 break;
394 case Qt::RoundCap:
395 stream() << "stroke-linecap=\"round\" ";
396 break;
397 default:
398 qWarning("Unhandled cap style");
399 }
400 switch (spen.joinStyle()) {
401 case Qt::MiterJoin:
402 stream() << "stroke-linejoin=\"miter\" "
403 "stroke-miterlimit=\""<<spen.miterLimit()<<"\" ";
404 break;
405 case Qt::BevelJoin:
406 stream() << "stroke-linejoin=\"bevel\" ";
407 break;
408 case Qt::RoundJoin:
409 stream() << "stroke-linejoin=\"round\" ";
410 break;
411 case Qt::SvgMiterJoin:
412 stream() << "stroke-linejoin=\"miter\" "
413 "stroke-miterlimit=\""<<spen.miterLimit()<<"\" ";
414 break;
415 default:
416 qWarning("Unhandled join style");
417 }
418 }
419 void qbrushToSvg(const QBrush &sbrush)
420 {
421 d_func()->brush = sbrush;
422 switch (sbrush.style()) {
423 case Qt::SolidPattern: {
424 QString color, colorOpacity;
425 translate_color(sbrush.color(), &color, &colorOpacity);
426 stream() << "fill=\"" << color << "\" "
427 "fill-opacity=\""
428 << colorOpacity << "\" ";
429 d_func()->attributes.fill = color;
430 d_func()->attributes.fillOpacity = colorOpacity;
431 }
432 break;
433 case Qt::LinearGradientPattern:
434 saveLinearGradientBrush(sbrush.gradient());
435 d_func()->attributes.fill = QString::fromLatin1("url(#%1)").arg(d_func()->currentGradientName);
436 d_func()->attributes.fillOpacity = QString();
437 stream() << QLatin1String("fill=\"url(#") << d_func()->currentGradientName << QLatin1String(")\" ");
438 break;
439 case Qt::RadialGradientPattern:
440 saveRadialGradientBrush(sbrush.gradient());
441 d_func()->attributes.fill = QString::fromLatin1("url(#%1)").arg(d_func()->currentGradientName);
442 d_func()->attributes.fillOpacity = QString();
443 stream() << QLatin1String("fill=\"url(#") << d_func()->currentGradientName << QLatin1String(")\" ");
444 break;
445 case Qt::ConicalGradientPattern:
446 saveConicalGradientBrush(sbrush.gradient());
447 d_func()->attributes.fill = QString::fromLatin1("url(#%1)").arg(d_func()->currentGradientName);
448 d_func()->attributes.fillOpacity = QString();
449 stream() << QLatin1String("fill=\"url(#") << d_func()->currentGradientName << QLatin1String(")\" ");
450 break;
451 case Qt::NoBrush:
452 stream() << QLatin1String("fill=\"none\" ");
453 d_func()->attributes.fill = QLatin1String("none");
454 d_func()->attributes.fillOpacity = QString();
455 return;
456 break;
457 default:
458 break;
459 }
460 }
461 void qfontToSvg(const QFont &sfont)
462 {
463 Q_D(QSvgPaintEngine);
464
465 d->font = sfont;
466
467 if (d->font.pixelSize() == -1)
468 d->attributes.font_size = QString::number(d->font.pointSizeF() * d->resolution / 72);
469 else
470 d->attributes.font_size = QString::number(d->font.pixelSize());
471
472 int svgWeight = d->font.weight();
473 switch (svgWeight) {
474 case QFont::Light:
475 svgWeight = 100;
476 break;
477 case QFont::Normal:
478 svgWeight = 400;
479 break;
480 case QFont::Bold:
481 svgWeight = 700;
482 break;
483 default:
484 svgWeight *= 10;
485 }
486
487 d->attributes.font_weight = QString::number(svgWeight);
488 d->attributes.font_family = d->font.family();
489 d->attributes.font_style = d->font.italic() ? QLatin1String("italic") : QLatin1String("normal");
490
491 *d->stream << "font-family=\"" << d->attributes.font_family << "\" "
492 "font-size=\"" << d->attributes.font_size << "\" "
493 "font-weight=\"" << d->attributes.font_weight << "\" "
494 "font-style=\"" << d->attributes.font_style << "\" "
495 << endl;
496 }
497};
498
499class QSvgGeneratorPrivate
500{
501public:
502 QSvgPaintEngine *engine;
503
504 uint owns_iodevice : 1;
505 QString fileName;
506};
507
508/*!
509 \class QSvgGenerator
510 \ingroup painting
511 \since 4.3
512 \brief The QSvgGenerator class provides a paint device that is used to create SVG drawings.
513 \reentrant
514
515 This paint device represents a Scalable Vector Graphics (SVG) drawing. Like QPrinter, it is
516 designed as a write-only device that generates output in a specific format.
517
518 To write an SVG file, you first need to configure the output by setting the \l fileName
519 or \l outputDevice properties. It is usually necessary to specify the size of the drawing
520 by setting the \l size property, and in some cases where the drawing will be included in
521 another, the \l viewBox property also needs to be set.
522
523 \snippet examples/painting/svggenerator/window.cpp configure SVG generator
524
525 Other meta-data can be specified by setting the \a title, \a description and \a resolution
526 properties.
527
528 As with other QPaintDevice subclasses, a QPainter object is used to paint onto an instance
529 of this class:
530
531 \snippet examples/painting/svggenerator/window.cpp begin painting
532 \dots
533 \snippet examples/painting/svggenerator/window.cpp end painting
534
535 Painting is performed in the same way as for any other paint device. However,
536 it is necessary to use the QPainter::begin() and \l{QPainter::}{end()} to
537 explicitly begin and end painting on the device.
538
539 The \l{SVG Generator Example} shows how the same painting commands can be used
540 for painting a widget and writing an SVG file.
541
542 \sa QSvgRenderer, QSvgWidget, {About SVG}
543*/
544
545/*!
546 Constructs a new generator.
547*/
548QSvgGenerator::QSvgGenerator()
549 : d_ptr(new QSvgGeneratorPrivate)
550{
551 Q_D(QSvgGenerator);
552
553 d->engine = new QSvgPaintEngine;
554 d->owns_iodevice = false;
555}
556
557/*!
558 Destroys the generator.
559*/
560QSvgGenerator::~QSvgGenerator()
561{
562 Q_D(QSvgGenerator);
563 if (d->owns_iodevice)
564 delete d->engine->outputDevice();
565 delete d->engine;
566}
567
568/*!
569 \property QSvgGenerator::title
570 \brief the title of the generated SVG drawing
571 \since 4.5
572 \sa description
573*/
574QString QSvgGenerator::title() const
575{
576 Q_D(const QSvgGenerator);
577
578 return d->engine->documentTitle();
579}
580
581void QSvgGenerator::setTitle(const QString &title)
582{
583 Q_D(QSvgGenerator);
584
585 d->engine->setDocumentTitle(title);
586}
587
588/*!
589 \property QSvgGenerator::description
590 \brief the description of the generated SVG drawing
591 \since 4.5
592 \sa title
593*/
594QString QSvgGenerator::description() const
595{
596 Q_D(const QSvgGenerator);
597
598 return d->engine->documentDescription();
599}
600
601void QSvgGenerator::setDescription(const QString &description)
602{
603 Q_D(QSvgGenerator);
604
605 d->engine->setDocumentDescription(description);
606}
607
608/*!
609 \property QSvgGenerator::size
610 \brief the size of the generated SVG drawing
611 \since 4.5
612
613 By default this property is set to \c{QSize(-1, -1)}, which
614 indicates that the generator should not output the width and
615 height attributes of the \c<svg> element.
616
617 \note It is not possible to change this property while a
618 QPainter is active on the generator.
619
620 \sa viewBox, resolution
621*/
622QSize QSvgGenerator::size() const
623{
624 Q_D(const QSvgGenerator);
625 return d->engine->size();
626}
627
628void QSvgGenerator::setSize(const QSize &size)
629{
630 Q_D(QSvgGenerator);
631 if (d->engine->isActive()) {
632 qWarning("QSvgGenerator::setSize(), cannot set size while SVG is being generated");
633 return;
634 }
635 d->engine->setSize(size);
636}
637
638/*!
639 \property QSvgGenerator::viewBox
640 \brief the viewBox of the generated SVG drawing
641 \since 4.5
642
643 By default this property is set to \c{QRect(0, 0, -1, -1)}, which
644 indicates that the generator should not output the viewBox attribute
645 of the \c<svg> element.
646
647 \note It is not possible to change this property while a
648 QPainter is active on the generator.
649
650 \sa viewBox(), size, resolution
651*/
652QRectF QSvgGenerator::viewBoxF() const
653{
654 Q_D(const QSvgGenerator);
655 return d->engine->viewBox();
656}
657
658/*!
659 \since 4.5
660
661 Returns viewBoxF().toRect().
662
663 \sa viewBoxF()
664*/
665QRect QSvgGenerator::viewBox() const
666{
667 Q_D(const QSvgGenerator);
668 return d->engine->viewBox().toRect();
669}
670
671void QSvgGenerator::setViewBox(const QRectF &viewBox)
672{
673 Q_D(QSvgGenerator);
674 if (d->engine->isActive()) {
675 qWarning("QSvgGenerator::setViewBox(), cannot set viewBox while SVG is being generated");
676 return;
677 }
678 d->engine->setViewBox(viewBox);
679}
680
681void QSvgGenerator::setViewBox(const QRect &viewBox)
682{
683 setViewBox(QRectF(viewBox));
684}
685
686/*!
687 \property QSvgGenerator::fileName
688 \brief the target filename for the generated SVG drawing
689 \since 4.5
690
691 \sa outputDevice
692*/
693QString QSvgGenerator::fileName() const
694{
695 Q_D(const QSvgGenerator);
696 return d->fileName;
697}
698
699void QSvgGenerator::setFileName(const QString &fileName)
700{
701 Q_D(QSvgGenerator);
702 if (d->engine->isActive()) {
703 qWarning("QSvgGenerator::setFileName(), cannot set file name while SVG is being generated");
704 return;
705 }
706
707 if (d->owns_iodevice)
708 delete d->engine->outputDevice();
709
710 d->owns_iodevice = true;
711
712 d->fileName = fileName;
713 QFile *file = new QFile(fileName);
714 d->engine->setOutputDevice(file);
715}
716
717/*!
718 \property QSvgGenerator::outputDevice
719 \brief the output device for the generated SVG drawing
720 \since 4.5
721
722 If both output device and file name are specified, the output device
723 will have precedence.
724
725 \sa fileName
726*/
727QIODevice *QSvgGenerator::outputDevice() const
728{
729 Q_D(const QSvgGenerator);
730 return d->engine->outputDevice();
731}
732
733void QSvgGenerator::setOutputDevice(QIODevice *outputDevice)
734{
735 Q_D(QSvgGenerator);
736 if (d->engine->isActive()) {
737 qWarning("QSvgGenerator::setOutputDevice(), cannot set output device while SVG is being generated");
738 return;
739 }
740 d->owns_iodevice = false;
741 d->engine->setOutputDevice(outputDevice);
742 d->fileName = QString();
743}
744
745/*!
746 \property QSvgGenerator::resolution
747 \brief the resolution of the generated output
748 \since 4.5
749
750 The resolution is specified in dots per inch, and is used to
751 calculate the physical size of an SVG drawing.
752
753 \sa size, viewBox
754*/
755int QSvgGenerator::resolution() const
756{
757 Q_D(const QSvgGenerator);
758 return d->engine->resolution();
759}
760
761void QSvgGenerator::setResolution(int dpi)
762{
763 Q_D(QSvgGenerator);
764 d->engine->setResolution(dpi);
765}
766
767/*!
768 Returns the paint engine used to render graphics to be converted to SVG
769 format information.
770*/
771QPaintEngine *QSvgGenerator::paintEngine() const
772{
773 Q_D(const QSvgGenerator);
774 return d->engine;
775}
776
777/*!
778 \reimp
779*/
780int QSvgGenerator::metric(QPaintDevice::PaintDeviceMetric metric) const
781{
782 Q_D(const QSvgGenerator);
783 switch (metric) {
784 case QPaintDevice::PdmDepth:
785 return 32;
786 case QPaintDevice::PdmWidth:
787 return d->engine->size().width();
788 case QPaintDevice::PdmHeight:
789 return d->engine->size().height();
790 case QPaintDevice::PdmDpiX:
791 return d->engine->resolution();
792 case QPaintDevice::PdmDpiY:
793 return d->engine->resolution();
794 case QPaintDevice::PdmHeightMM:
795 return qRound(d->engine->size().height() * 25.4 / d->engine->resolution());
796 case QPaintDevice::PdmWidthMM:
797 return qRound(d->engine->size().width() * 25.4 / d->engine->resolution());
798 case QPaintDevice::PdmNumColors:
799 return 0xffffffff;
800 case QPaintDevice::PdmPhysicalDpiX:
801 return d->engine->resolution();
802 case QPaintDevice::PdmPhysicalDpiY:
803 return d->engine->resolution();
804 default:
805 qWarning("QSvgGenerator::metric(), unhandled metric %d\n", metric);
806 break;
807 }
808 return 0;
809}
810
811/*****************************************************************************
812 * class QSvgPaintEngine
813 */
814
815bool QSvgPaintEngine::begin(QPaintDevice *)
816{
817 Q_D(QSvgPaintEngine);
818 if (!d->outputDevice) {
819 qWarning("QSvgPaintEngine::begin(), no output device");
820 return false;
821 }
822
823 if (!d->outputDevice->isOpen()) {
824 if (!d->outputDevice->open(QIODevice::WriteOnly | QIODevice::Text)) {
825 qWarning("QSvgPaintEngine::begin(), could not open output device: '%s'",
826 qPrintable(d->outputDevice->errorString()));
827 return false;
828 }
829 } else if (!d->outputDevice->isWritable()) {
830 qWarning("QSvgPaintEngine::begin(), could not write to read-only output device: '%s'",
831 qPrintable(d->outputDevice->errorString()));
832 return false;
833 }
834
835 d->stream = new QTextStream(&d->header);
836
837 // stream out the header...
838 *d->stream << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" << endl << "<svg";
839
840 if (d->size.isValid()) {
841 qreal wmm = d->size.width() * 25.4 / d->resolution;
842 qreal hmm = d->size.height() * 25.4 / d->resolution;
843 *d->stream << " width=\"" << wmm << "mm\" height=\"" << hmm << "mm\"" << endl;
844 }
845
846 if (d->viewBox.isValid()) {
847 *d->stream << " viewBox=\"" << d->viewBox.left() << ' ' << d->viewBox.top();
848 *d->stream << ' ' << d->viewBox.width() << ' ' << d->viewBox.height() << '\"' << endl;
849 }
850
851 *d->stream << " xmlns=\"http://www.w3.org/2000/svg\""
852 " xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
853 " version=\"1.2\" baseProfile=\"tiny\">" << endl;
854
855 if (!d->attributes.document_title.isEmpty()) {
856 *d->stream << "<title>" << d->attributes.document_title << "</title>" << endl;
857 }
858
859 if (!d->attributes.document_description.isEmpty()) {
860 *d->stream << "<desc>" << d->attributes.document_description << "</desc>" << endl;
861 }
862
863 d->stream->setString(&d->defs);
864 *d->stream << "<defs>\n";
865
866 d->stream->setString(&d->body);
867 // Start the initial graphics state...
868 *d->stream << "<g ";
869 generateQtDefaults();
870 *d->stream << endl;
871
872 return true;
873}
874
875bool QSvgPaintEngine::end()
876{
877 Q_D(QSvgPaintEngine);
878
879 d->stream->setString(&d->defs);
880 *d->stream << "</defs>\n";
881
882 d->stream->setDevice(d->outputDevice);
883#ifndef QT_NO_TEXTCODEC
884 d->stream->setCodec(QTextCodec::codecForName("UTF-8"));
885#endif
886
887 *d->stream << d->header;
888 *d->stream << d->defs;
889 *d->stream << d->body;
890 if (d->afterFirstUpdate)
891 *d->stream << "</g>" << endl; // close the updateState
892
893 *d->stream << "</g>" << endl // close the Qt defaults
894 << "</svg>" << endl;
895
896 delete d->stream;
897
898 return true;
899}
900
901void QSvgPaintEngine::drawPixmap(const QRectF &r, const QPixmap &pm,
902 const QRectF &sr)
903{
904 drawImage(r, pm.toImage(), sr);
905}
906
907void QSvgPaintEngine::drawImage(const QRectF &r, const QImage &image,
908 const QRectF &sr,
909 Qt::ImageConversionFlag flags)
910{
911 //Q_D(QSvgPaintEngine);
912
913 Q_UNUSED(sr);
914 Q_UNUSED(flags);
915 stream() << "<image ";
916 stream() << "x=\""<<r.x()<<"\" "
917 "y=\""<<r.y()<<"\" "
918 "width=\""<<r.width()<<"\" "
919 "height=\""<<r.height()<<"\" "
920 "preserveAspectRatio=\"none\" ";
921
922 QByteArray data;
923 QBuffer buffer(&data);
924 buffer.open(QBuffer::ReadWrite);
925 image.save(&buffer, "PNG");
926 buffer.close();
927 stream() << "xlink:href=\"data:image/png;base64,"
928 << data.toBase64()
929 <<"\" />\n";
930}
931
932void QSvgPaintEngine::updateState(const QPaintEngineState &state)
933{
934 Q_D(QSvgPaintEngine);
935 QPaintEngine::DirtyFlags flags = state.state();
936
937 // always stream full gstate, which is not required, but...
938 flags |= QPaintEngine::AllDirty;
939
940 // close old state and start a new one...
941 if (d->afterFirstUpdate)
942 *d->stream << "</g>\n\n";
943
944 *d->stream << "<g ";
945
946 if (flags & QPaintEngine::DirtyBrush) {
947 qbrushToSvg(state.brush());
948 }
949
950 if (flags & QPaintEngine::DirtyPen) {
951 qpenToSvg(state.pen());
952 }
953
954 if (flags & QPaintEngine::DirtyTransform) {
955 d->matrix = state.matrix();
956 *d->stream << "transform=\"matrix(" << d->matrix.m11() << ','
957 << d->matrix.m12() << ','
958 << d->matrix.m21() << ',' << d->matrix.m22() << ','
959 << d->matrix.dx() << ',' << d->matrix.dy()
960 << ")\""
961 << endl;
962 }
963
964 if (flags & QPaintEngine::DirtyFont) {
965 qfontToSvg(state.font());
966 }
967
968 if (flags & QPaintEngine::DirtyOpacity) {
969 if (!qFuzzyIsNull(state.opacity() - 1))
970 stream() << "opacity=\""<<state.opacity()<<"\" ";
971 }
972
973 *d->stream << '>' << endl;
974
975 d->afterFirstUpdate = true;
976}
977
978void QSvgPaintEngine::drawPath(const QPainterPath &p)
979{
980 Q_D(QSvgPaintEngine);
981
982 *d->stream << "<path vector-effect=\""
983 << (state->pen().isCosmetic() ? "non-scaling-stroke" : "none")
984 << "\" fill-rule=\""
985 << (p.fillRule() == Qt::OddEvenFill ? "evenodd" : "nonzero")
986 << "\" d=\"";
987
988 for (int i=0; i<p.elementCount(); ++i) {
989 const QPainterPath::Element &e = p.elementAt(i);
990 switch (e.type) {
991 case QPainterPath::MoveToElement:
992 *d->stream << 'M' << e.x << ',' << e.y;
993 break;
994 case QPainterPath::LineToElement:
995 *d->stream << 'L' << e.x << ',' << e.y;
996 break;
997 case QPainterPath::CurveToElement:
998 *d->stream << 'C' << e.x << ',' << e.y;
999 ++i;
1000 while (i < p.elementCount()) {
1001 const QPainterPath::Element &e = p.elementAt(i);
1002 if (e.type != QPainterPath::CurveToDataElement) {
1003 --i;
1004 break;
1005 } else
1006 *d->stream << ' ';
1007 *d->stream << e.x << ',' << e.y;
1008 ++i;
1009 }
1010 break;
1011 default:
1012 break;
1013 }
1014 if (i != p.elementCount() - 1) {
1015 *d->stream << ' ';
1016 }
1017 }
1018
1019 *d->stream << "\"/>" << endl;
1020}
1021
1022void QSvgPaintEngine::drawPolygon(const QPointF *points, int pointCount,
1023 PolygonDrawMode mode)
1024{
1025 Q_ASSERT(pointCount >= 2);
1026
1027 //Q_D(QSvgPaintEngine);
1028
1029 QPainterPath path(points[0]);
1030 for (int i=1; i<pointCount; ++i)
1031 path.lineTo(points[i]);
1032
1033 if (mode == PolylineMode) {
1034 stream() << "<polyline fill=\"none\" vector-effect=\""
1035 << (state->pen().isCosmetic() ? "non-scaling-stroke" : "none")
1036 << "\" points=\"";
1037 for (int i = 0; i < pointCount; ++i) {
1038 const QPointF &pt = points[i];
1039 stream() << pt.x() << ',' << pt.y() << ' ';
1040 }
1041 stream() << "\" />" <<endl;
1042 } else {
1043 path.closeSubpath();
1044 drawPath(path);
1045 }
1046}
1047
1048void QSvgPaintEngine::drawTextItem(const QPointF &pt, const QTextItem &textItem)
1049{
1050 Q_D(QSvgPaintEngine);
1051 if (d->pen.style() == Qt::NoPen)
1052 return;
1053
1054 const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem);
1055 QString s = QString::fromRawData(ti.chars, ti.num_chars);
1056
1057 *d->stream << "<text "
1058 "fill=\"" << d->attributes.stroke << "\" "
1059 "fill-opacity=\"" << d->attributes.strokeOpacity << "\" "
1060 "stroke=\"none\" "
1061 "xml:space=\"preserve\" "
1062 "x=\"" << pt.x() << "\" y=\"" << pt.y() << "\" ";
1063 qfontToSvg(textItem.font());
1064 *d->stream << " >"
1065 << Qt::escape(s)
1066 << "</text>"
1067 << endl;
1068}
1069
1070QT_END_NAMESPACE
1071
1072#endif // QT_NO_SVGGENERATOR
1073