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
17QT_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
68class QSvgRendererPrivate : public QObjectPrivate
69{
70 Q_DECLARE_PUBLIC(QSvgRenderer)
71public:
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*/
92QSvgRenderer::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*/
101QSvgRenderer::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*/
111QSvgRenderer::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*/
123QSvgRenderer::QSvgRenderer(QXmlStreamReader *contents, QObject *parent)
124 : QObject(*new QSvgRendererPrivate, parent)
125{
126 load(contents);
127}
128
129/*!
130 Destroys the renderer.
131*/
132QSvgRenderer::~QSvgRenderer()
133{
134
135}
136
137/*!
138 Returns true if there is a valid current document; otherwise returns false.
139*/
140bool 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*/
149QSize 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*/
163QRect 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*/
177void 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*/
190bool 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*/
207int QSvgRenderer::framesPerSecond() const
208{
209 Q_D(const QSvgRenderer);
210 return d->fps;
211}
212
213void 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
239Qt::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
247void 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*/
269int QSvgRenderer::currentFrame() const
270{
271 Q_D(const QSvgRenderer);
272 return d->render->currentFrame();
273}
274
275/*!
276 \internal
277*/
278void 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*/
292int 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 */
305void QSvgRendererPrivate::callRepaintNeeded(QSvgRenderer *const q)
306{
307 emit q->repaintNeeded();
308}
309
310template<typename TInputType>
311static 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*/
343bool 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*/
353bool 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*/
368bool 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*/
378void 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*/
398void 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*/
413void 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
421QRectF 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
430void 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*/
446QRectF 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*/
469bool 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*/
491QTransform 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
500QT_END_NAMESPACE
501
502#include "moc_qsvgrenderer.cpp"
503
504#endif // QT_NO_SVGRENDERER
505

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