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 "qsvgrenderer.h" |
41 | |
42 | #ifndef QT_NO_SVGRENDERER |
43 | |
44 | #include "qsvgtinydocument_p.h" |
45 | |
46 | #include "qbytearray.h" |
47 | #include "qtimer.h" |
48 | #include "qtransform.h" |
49 | #include "qdebug.h" |
50 | #include "private/qobject_p.h" |
51 | |
52 | |
53 | QT_BEGIN_NAMESPACE |
54 | |
55 | /*! |
56 | \class QSvgRenderer |
57 | \inmodule QtSvg |
58 | \ingroup painting |
59 | |
60 | \brief The QSvgRenderer class is used to draw the contents of SVG files onto paint devices. |
61 | \since 4.1 |
62 | \reentrant |
63 | |
64 | Using QSvgRenderer, Scalable Vector Graphics (SVG) can be rendered onto any QPaintDevice |
65 | subclass, including QWidget, QImage, and QGLWidget. |
66 | |
67 | QSvgRenderer provides an API that supports basic features of SVG rendering, such as loading |
68 | and rendering of static drawings, and more interactive features like animation. Since the |
69 | rendering is performed using QPainter, SVG drawings can be rendered on any subclass of |
70 | QPaintDevice. |
71 | |
72 | SVG drawings are either loaded when an QSvgRenderer is constructed, or loaded later |
73 | using the load() functions. Data is either supplied directly as serialized XML, or |
74 | indirectly using a file name. If a valid file has been loaded, either when the renderer |
75 | is constructed or at some later time, isValid() returns true; otherwise it returns false. |
76 | QSvgRenderer provides the render() slot to render the current document, or the current |
77 | frame of an animated document, using a given painter. |
78 | |
79 | The defaultSize() function provides information about the amount of space that is required |
80 | to render the currently loaded SVG file. This is useful for paint devices, such as QWidget, |
81 | that often need to supply a size hint to their parent layout. |
82 | The default size of a drawing may differ from its visible area, found using the \l viewBox |
83 | property. |
84 | |
85 | Animated SVG drawings are supported, and can be controlled with a simple collection of |
86 | functions and properties: |
87 | |
88 | \list |
89 | \li The animated() function indicates whether a drawing contains animation information. |
90 | \omit |
91 | \li The animationDuration() function provides the duration in milliseconds of the |
92 | animation, without taking any looping into account. |
93 | \li The \l currentFrame property contains the current frame of the animation. |
94 | \endomit |
95 | \li The \l framesPerSecond property contains the rate at which the animation plays. |
96 | \endlist |
97 | |
98 | Finally, the QSvgRenderer class provides the repaintNeeded() signal which is emitted |
99 | whenever the rendering of the document needs to be updated. |
100 | |
101 | \sa QSvgWidget, {Qt SVG C++ Classes}, {SVG Viewer Example}, QPicture |
102 | */ |
103 | |
104 | class QSvgRendererPrivate : public QObjectPrivate |
105 | { |
106 | Q_DECLARE_PUBLIC(QSvgRenderer) |
107 | public: |
108 | explicit QSvgRendererPrivate() |
109 | : QObjectPrivate(), |
110 | render(0), timer(0), |
111 | fps(30) |
112 | {} |
113 | ~QSvgRendererPrivate() |
114 | { |
115 | delete render; |
116 | } |
117 | |
118 | static void callRepaintNeeded(QSvgRenderer *const q); |
119 | |
120 | QSvgTinyDocument *render; |
121 | QTimer *timer; |
122 | int fps; |
123 | }; |
124 | |
125 | /*! |
126 | Constructs a new renderer with the given \a parent. |
127 | */ |
128 | QSvgRenderer::QSvgRenderer(QObject *parent) |
129 | : QObject(*(new QSvgRendererPrivate), parent) |
130 | { |
131 | } |
132 | |
133 | /*! |
134 | Constructs a new renderer with the given \a parent and loads the contents of the |
135 | SVG file with the specified \a filename. |
136 | */ |
137 | QSvgRenderer::QSvgRenderer(const QString &filename, QObject *parent) |
138 | : QObject(*new QSvgRendererPrivate, parent) |
139 | { |
140 | load(filename); |
141 | } |
142 | |
143 | /*! |
144 | Constructs a new renderer with the given \a parent and loads the SVG data |
145 | from the byte array specified by \a contents. |
146 | */ |
147 | QSvgRenderer::QSvgRenderer(const QByteArray &contents, QObject *parent) |
148 | : QObject(*new QSvgRendererPrivate, parent) |
149 | { |
150 | load(contents); |
151 | } |
152 | |
153 | /*! |
154 | \since 4.5 |
155 | |
156 | Constructs a new renderer with the given \a parent and loads the SVG data |
157 | using the stream reader specified by \a contents. |
158 | */ |
159 | QSvgRenderer::QSvgRenderer(QXmlStreamReader *contents, QObject *parent) |
160 | : QObject(*new QSvgRendererPrivate, parent) |
161 | { |
162 | load(contents); |
163 | } |
164 | |
165 | /*! |
166 | Destroys the renderer. |
167 | */ |
168 | QSvgRenderer::~QSvgRenderer() |
169 | { |
170 | |
171 | } |
172 | |
173 | /*! |
174 | Returns true if there is a valid current document; otherwise returns false. |
175 | */ |
176 | bool QSvgRenderer::isValid() const |
177 | { |
178 | Q_D(const QSvgRenderer); |
179 | return d->render; |
180 | } |
181 | |
182 | /*! |
183 | Returns the default size of the document contents. |
184 | */ |
185 | QSize QSvgRenderer::defaultSize() const |
186 | { |
187 | Q_D(const QSvgRenderer); |
188 | if (d->render) |
189 | return d->render->size(); |
190 | else |
191 | return QSize(); |
192 | } |
193 | |
194 | /*! |
195 | Returns viewBoxF().toRect(). |
196 | |
197 | \sa viewBoxF() |
198 | */ |
199 | QRect QSvgRenderer::viewBox() const |
200 | { |
201 | Q_D(const QSvgRenderer); |
202 | if (d->render) |
203 | return d->render->viewBox().toRect(); |
204 | else |
205 | return QRect(); |
206 | } |
207 | |
208 | /*! |
209 | \property QSvgRenderer::viewBox |
210 | \brief the rectangle specifying the visible area of the document in logical coordinates |
211 | \since 4.2 |
212 | */ |
213 | void QSvgRenderer::setViewBox(const QRect &viewbox) |
214 | { |
215 | Q_D(QSvgRenderer); |
216 | if (d->render) |
217 | d->render->setViewBox(viewbox); |
218 | } |
219 | |
220 | /*! |
221 | Returns true if the current document contains animated elements; otherwise |
222 | returns false. |
223 | |
224 | \sa framesPerSecond() |
225 | */ |
226 | bool QSvgRenderer::animated() const |
227 | { |
228 | Q_D(const QSvgRenderer); |
229 | if (d->render) |
230 | return d->render->animated(); |
231 | else |
232 | return false; |
233 | } |
234 | |
235 | /*! |
236 | \property QSvgRenderer::framesPerSecond |
237 | \brief the number of frames per second to be shown |
238 | |
239 | The number of frames per second is 0 if the current document is not animated. |
240 | |
241 | \sa animated() |
242 | */ |
243 | int QSvgRenderer::framesPerSecond() const |
244 | { |
245 | Q_D(const QSvgRenderer); |
246 | return d->fps; |
247 | } |
248 | |
249 | void QSvgRenderer::setFramesPerSecond(int num) |
250 | { |
251 | Q_D(QSvgRenderer); |
252 | if (num < 0) { |
253 | qWarning(msg: "QSvgRenderer::setFramesPerSecond: Cannot set negative value %d" , num); |
254 | return; |
255 | } |
256 | d->fps = num; |
257 | } |
258 | |
259 | /*! |
260 | \property QSvgRenderer::aspectRatioMode |
261 | |
262 | \brief how rendering adheres to the SVG view box aspect ratio |
263 | |
264 | The accepted modes are: |
265 | \list |
266 | \li Qt::IgnoreAspectRatio (the default): the aspect ratio is ignored and the |
267 | rendering is stretched to the target bounds. |
268 | \li Qt::KeepAspectRatio: rendering is centered and scaled as large as possible |
269 | within the target bounds while preserving aspect ratio. |
270 | \endlist |
271 | |
272 | \since 5.15 |
273 | */ |
274 | |
275 | Qt::AspectRatioMode QSvgRenderer::aspectRatioMode() const |
276 | { |
277 | Q_D(const QSvgRenderer); |
278 | if (d->render && d->render->preserveAspectRatio()) |
279 | return Qt::KeepAspectRatio; |
280 | return Qt::IgnoreAspectRatio; |
281 | } |
282 | |
283 | void QSvgRenderer::setAspectRatioMode(Qt::AspectRatioMode mode) |
284 | { |
285 | Q_D(QSvgRenderer); |
286 | if (d->render) { |
287 | if (mode == Qt::KeepAspectRatio) |
288 | d->render->setPreserveAspectRatio(true); |
289 | else if (mode == Qt::IgnoreAspectRatio) |
290 | d->render->setPreserveAspectRatio(false); |
291 | } |
292 | } |
293 | |
294 | /*! |
295 | \property QSvgRenderer::currentFrame |
296 | \brief the current frame of the document's animation, or 0 if the document is not animated |
297 | \internal |
298 | |
299 | \sa animationDuration(), framesPerSecond, animated() |
300 | */ |
301 | |
302 | /*! |
303 | \internal |
304 | */ |
305 | int QSvgRenderer::currentFrame() const |
306 | { |
307 | Q_D(const QSvgRenderer); |
308 | return d->render->currentFrame(); |
309 | } |
310 | |
311 | /*! |
312 | \internal |
313 | */ |
314 | void QSvgRenderer::setCurrentFrame(int frame) |
315 | { |
316 | Q_D(QSvgRenderer); |
317 | d->render->setCurrentFrame(frame); |
318 | } |
319 | |
320 | /*! |
321 | \internal |
322 | |
323 | Returns the number of frames in the animation, or 0 if the current document is not |
324 | animated. |
325 | |
326 | \sa animated(), framesPerSecond |
327 | */ |
328 | int QSvgRenderer::animationDuration() const |
329 | { |
330 | Q_D(const QSvgRenderer); |
331 | return d->render->animationDuration(); |
332 | } |
333 | |
334 | /*! |
335 | \internal |
336 | \since 4.5 |
337 | |
338 | We can't have template functions, that's loadDocument(), as friends, for this |
339 | code, so we let this function be a friend of QSvgRenderer instead. |
340 | */ |
341 | void QSvgRendererPrivate::callRepaintNeeded(QSvgRenderer *const q) |
342 | { |
343 | emit q->repaintNeeded(); |
344 | } |
345 | |
346 | template<typename TInputType> |
347 | static bool loadDocument(QSvgRenderer *const q, |
348 | QSvgRendererPrivate *const d, |
349 | const TInputType &in) |
350 | { |
351 | delete d->render; |
352 | d->render = QSvgTinyDocument::load(in); |
353 | if (d->render && !d->render->size().isValid()) { |
354 | delete d->render; |
355 | d->render = nullptr; |
356 | } |
357 | if (d->render && d->render->animated() && d->fps > 0) { |
358 | if (!d->timer) |
359 | d->timer = new QTimer(q); |
360 | else |
361 | d->timer->stop(); |
362 | q->connect(sender: d->timer, SIGNAL(timeout()), |
363 | receiver: q, SIGNAL(repaintNeeded())); |
364 | d->timer->start(msec: 1000/d->fps); |
365 | } else if (d->timer) { |
366 | d->timer->stop(); |
367 | } |
368 | |
369 | //force first update |
370 | QSvgRendererPrivate::callRepaintNeeded(q); |
371 | |
372 | return d->render; |
373 | } |
374 | |
375 | /*! |
376 | Loads the SVG file specified by \a filename, returning true if the content |
377 | was successfully parsed; otherwise returns false. |
378 | */ |
379 | bool QSvgRenderer::load(const QString &filename) |
380 | { |
381 | Q_D(QSvgRenderer); |
382 | return loadDocument(q: this, d, in: filename); |
383 | } |
384 | |
385 | /*! |
386 | Loads the specified SVG format \a contents, returning true if the content |
387 | was successfully parsed; otherwise returns false. |
388 | */ |
389 | bool QSvgRenderer::load(const QByteArray &contents) |
390 | { |
391 | Q_D(QSvgRenderer); |
392 | return loadDocument(q: this, d, in: contents); |
393 | } |
394 | |
395 | /*! |
396 | Loads the specified SVG in \a contents, returning true if the content |
397 | was successfully parsed; otherwise returns false. |
398 | |
399 | The reader will be used from where it currently is positioned. If \a contents |
400 | is \c null, behavior is undefined. |
401 | |
402 | \since 4.5 |
403 | */ |
404 | bool QSvgRenderer::load(QXmlStreamReader *contents) |
405 | { |
406 | Q_D(QSvgRenderer); |
407 | return loadDocument(q: this, d, in: contents); |
408 | } |
409 | |
410 | /*! |
411 | Renders the current document, or the current frame of an animated |
412 | document, using the given \a painter. |
413 | */ |
414 | void QSvgRenderer::render(QPainter *painter) |
415 | { |
416 | Q_D(QSvgRenderer); |
417 | if (d->render) { |
418 | d->render->draw(p: painter); |
419 | } |
420 | } |
421 | |
422 | /*! |
423 | \fn void QSvgRenderer::repaintNeeded() |
424 | |
425 | This signal is emitted whenever the rendering of the document |
426 | needs to be updated, usually for the purposes of animation. |
427 | */ |
428 | |
429 | /*! |
430 | Renders the given element with \a elementId using the given \a painter |
431 | on the specified \a bounds. If the bounding rectangle is not specified |
432 | the SVG element is mapped to the whole paint device. |
433 | */ |
434 | void QSvgRenderer::render(QPainter *painter, const QString &elementId, |
435 | const QRectF &bounds) |
436 | { |
437 | Q_D(QSvgRenderer); |
438 | if (d->render) { |
439 | d->render->draw(p: painter, id: elementId, bounds); |
440 | } |
441 | } |
442 | |
443 | /*! |
444 | Renders the current document, or the current frame of an animated document, |
445 | using the given \a painter on the specified \a bounds within the painter. |
446 | If \a bounds is not empty, the output will be scaled to fill it, ignoring |
447 | any aspect ratio implied by the SVG. |
448 | */ |
449 | void QSvgRenderer::render(QPainter *painter, const QRectF &bounds) |
450 | { |
451 | Q_D(QSvgRenderer); |
452 | if (d->render) { |
453 | d->render->draw(p: painter, bounds); |
454 | } |
455 | } |
456 | |
457 | QRectF QSvgRenderer::viewBoxF() const |
458 | { |
459 | Q_D(const QSvgRenderer); |
460 | if (d->render) |
461 | return d->render->viewBox(); |
462 | else |
463 | return QRect(); |
464 | } |
465 | |
466 | void QSvgRenderer::setViewBox(const QRectF &viewbox) |
467 | { |
468 | Q_D(QSvgRenderer); |
469 | if (d->render) |
470 | d->render->setViewBox(viewbox); |
471 | } |
472 | |
473 | /*! |
474 | \since 4.2 |
475 | |
476 | Returns bounding rectangle of the item with the given \a id. |
477 | The transformation matrix of parent elements is not affecting |
478 | the bounds of the element. |
479 | |
480 | \sa transformForElement() |
481 | */ |
482 | QRectF QSvgRenderer::boundsOnElement(const QString &id) const |
483 | { |
484 | Q_D(const QSvgRenderer); |
485 | QRectF bounds; |
486 | if (d->render) |
487 | bounds = d->render->boundsOnElement(id); |
488 | return bounds; |
489 | } |
490 | |
491 | |
492 | /*! |
493 | \since 4.2 |
494 | |
495 | Returns true if the element with the given \a id exists |
496 | in the currently parsed SVG file and is a renderable |
497 | element. |
498 | |
499 | Note: this method returns true only for elements that |
500 | can be rendered. Which implies that elements that are considered |
501 | part of the fill/stroke style properties, e.g. radialGradients |
502 | even tough marked with "id" attributes will not be found by this |
503 | method. |
504 | */ |
505 | bool QSvgRenderer::elementExists(const QString &id) const |
506 | { |
507 | Q_D(const QSvgRenderer); |
508 | bool exists = false; |
509 | if (d->render) |
510 | exists = d->render->elementExists(id); |
511 | return exists; |
512 | } |
513 | |
514 | #if QT_DEPRECATED_SINCE(5, 15) |
515 | QT_WARNING_PUSH |
516 | QT_WARNING_DISABLE_DEPRECATED |
517 | /*! |
518 | \since 4.2 |
519 | \deprecated |
520 | |
521 | Use transformForElement() instead. |
522 | |
523 | |
524 | Returns the transformation matrix for the element |
525 | with the given \a id. The matrix is a product of |
526 | the transformation of the element's parents. The transformation of |
527 | the element itself is not included. |
528 | |
529 | To find the bounding rectangle of the element in logical coordinates, |
530 | you can apply the matrix on the rectangle returned from boundsOnElement(). |
531 | |
532 | \sa boundsOnElement() |
533 | */ |
534 | QMatrix QSvgRenderer::matrixForElement(const QString &id) const |
535 | { |
536 | return transformForElement(id).toAffine(); |
537 | } |
538 | QT_WARNING_POP |
539 | #endif // QT_DEPRECATED_SINCE(5, 15) |
540 | |
541 | /*! |
542 | \since 5.15 |
543 | |
544 | Returns the transformation matrix for the element |
545 | with the given \a id. The matrix is a product of |
546 | the transformation of the element's parents. The transformation of |
547 | the element itself is not included. |
548 | |
549 | To find the bounding rectangle of the element in logical coordinates, |
550 | you can apply the matrix on the rectangle returned from boundsOnElement(). |
551 | |
552 | \sa boundsOnElement() |
553 | */ |
554 | QTransform QSvgRenderer::transformForElement(const QString &id) const |
555 | { |
556 | Q_D(const QSvgRenderer); |
557 | QTransform trans; |
558 | if (d->render) |
559 | trans = d->render->transformForElement(id); |
560 | return trans; |
561 | } |
562 | |
563 | QT_END_NAMESPACE |
564 | |
565 | #include "moc_qsvgrenderer.cpp" |
566 | |
567 | #endif // QT_NO_SVGRENDERER |
568 | |