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