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 | |
60 | QT_BEGIN_NAMESPACE |
61 | |
62 | static 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 | |
76 | static 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 | |
87 | class QSvgPaintEnginePrivate : public QPaintEnginePrivate |
88 | { |
89 | public: |
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 ; |
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 | |
146 | static 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 | |
156 | class QSvgPaintEngine : public QPaintEngine |
157 | { |
158 | Q_DECLARE_PRIVATE(QSvgPaintEngine) |
159 | public: |
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 | |
499 | class QSvgGeneratorPrivate |
500 | { |
501 | public: |
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 | */ |
548 | QSvgGenerator::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 | */ |
560 | QSvgGenerator::~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 | */ |
574 | QString QSvgGenerator::title() const |
575 | { |
576 | Q_D(const QSvgGenerator); |
577 | |
578 | return d->engine->documentTitle(); |
579 | } |
580 | |
581 | void 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 | */ |
594 | QString QSvgGenerator::description() const |
595 | { |
596 | Q_D(const QSvgGenerator); |
597 | |
598 | return d->engine->documentDescription(); |
599 | } |
600 | |
601 | void 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 | */ |
622 | QSize QSvgGenerator::size() const |
623 | { |
624 | Q_D(const QSvgGenerator); |
625 | return d->engine->size(); |
626 | } |
627 | |
628 | void 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 | */ |
652 | QRectF 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 | */ |
665 | QRect QSvgGenerator::viewBox() const |
666 | { |
667 | Q_D(const QSvgGenerator); |
668 | return d->engine->viewBox().toRect(); |
669 | } |
670 | |
671 | void 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 | |
681 | void 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 | */ |
693 | QString QSvgGenerator::fileName() const |
694 | { |
695 | Q_D(const QSvgGenerator); |
696 | return d->fileName; |
697 | } |
698 | |
699 | void 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 | */ |
727 | QIODevice *QSvgGenerator::outputDevice() const |
728 | { |
729 | Q_D(const QSvgGenerator); |
730 | return d->engine->outputDevice(); |
731 | } |
732 | |
733 | void 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 | */ |
755 | int QSvgGenerator::resolution() const |
756 | { |
757 | Q_D(const QSvgGenerator); |
758 | return d->engine->resolution(); |
759 | } |
760 | |
761 | void 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 | */ |
771 | QPaintEngine *QSvgGenerator::paintEngine() const |
772 | { |
773 | Q_D(const QSvgGenerator); |
774 | return d->engine; |
775 | } |
776 | |
777 | /*! |
778 | \reimp |
779 | */ |
780 | int 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 | |
815 | bool 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 | |
875 | bool 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 | |
901 | void QSvgPaintEngine::drawPixmap(const QRectF &r, const QPixmap &pm, |
902 | const QRectF &sr) |
903 | { |
904 | drawImage(r, pm.toImage(), sr); |
905 | } |
906 | |
907 | void 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 | |
932 | void 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 | |
978 | void 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 | |
1022 | void 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 | |
1048 | void 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 | |
1070 | QT_END_NAMESPACE |
1071 | |
1072 | #endif // QT_NO_SVGGENERATOR |
1073 | |