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 plugins 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 "qsvgiohandler.h"
41
42#ifndef QT_NO_SVGRENDERER
43
44#include "qsvgrenderer.h"
45#include "qimage.h"
46#include "qpixmap.h"
47#include "qpainter.h"
48#include "qvariant.h"
49#include "qbuffer.h"
50#include "qdebug.h"
51
52QT_BEGIN_NAMESPACE
53
54class QSvgIOHandlerPrivate
55{
56public:
57 QSvgIOHandlerPrivate(QSvgIOHandler *qq)
58 : q(qq), loaded(false), readDone(false), backColor(Qt::transparent)
59 {}
60
61 bool load(QIODevice *device);
62
63 QSvgIOHandler *q;
64 QSvgRenderer r;
65 QXmlStreamReader xmlReader;
66 QSize defaultSize;
67 QRect clipRect;
68 QSize scaledSize;
69 QRect scaledClipRect;
70 bool loaded;
71 bool readDone;
72 QColor backColor;
73};
74
75
76bool QSvgIOHandlerPrivate::load(QIODevice *device)
77{
78 if (loaded)
79 return true;
80 if (q->format().isEmpty())
81 q->canRead();
82
83 // # The SVG renderer doesn't handle trailing, unrelated data, so we must
84 // assume that all available data in the device is to be read.
85 bool res = false;
86 QBuffer *buf = qobject_cast<QBuffer *>(object: device);
87 if (buf) {
88 const QByteArray &ba = buf->data();
89 res = r.load(contents: QByteArray::fromRawData(ba.constData() + buf->pos(), size: ba.size() - buf->pos()));
90 buf->seek(off: ba.size());
91#ifndef QT_NO_COMPRESS
92 } else if (q->format() == "svgz") {
93 res = r.load(contents: device->readAll());
94#endif
95 } else {
96 xmlReader.setDevice(device);
97 res = r.load(contents: &xmlReader);
98 }
99
100 if (res) {
101 defaultSize = r.defaultSize();
102 loaded = true;
103 }
104
105 return loaded;
106}
107
108
109QSvgIOHandler::QSvgIOHandler()
110 : d(new QSvgIOHandlerPrivate(this))
111{
112
113}
114
115
116QSvgIOHandler::~QSvgIOHandler()
117{
118 delete d;
119}
120
121static bool isPossiblySvg(QIODevice *device, bool *isCompressed = nullptr)
122{
123 constexpr int bufSize = 64;
124 char buf[bufSize];
125 const qint64 readLen = device->peek(data: buf, maxlen: bufSize);
126 if (readLen < 8)
127 return false;
128# ifndef QT_NO_COMPRESS
129 if (quint8(buf[0]) == 0x1f && quint8(buf[1]) == 0x8b) {
130 if (isCompressed)
131 *isCompressed = true;
132 return true;
133 }
134# endif
135 QTextStream str(QByteArray::fromRawData(buf, size: readLen));
136 QByteArray ba = str.read(maxlen: 16).trimmed().toLatin1();
137 return ba.startsWith(c: "<?xml") || ba.startsWith(c: "<svg") || ba.startsWith(c: "<!--") || ba.startsWith(c: "<!DOCTYPE svg");
138}
139
140bool QSvgIOHandler::canRead() const
141{
142 if (!device())
143 return false;
144 if (d->loaded && !d->readDone)
145 return true; // Will happen if we have been asked for the size
146
147 bool isCompressed = false;
148 if (isPossiblySvg(device: device(), isCompressed: &isCompressed)) {
149 setFormat(isCompressed ? "svgz" : "svg");
150 return true;
151 }
152 return false;
153}
154
155bool QSvgIOHandler::read(QImage *image)
156{
157 if (!d->readDone && d->load(device: device())) {
158 bool xform = (d->clipRect.isValid() || d->scaledSize.isValid() || d->scaledClipRect.isValid());
159 QSize finalSize = d->defaultSize;
160 QRectF bounds;
161 if (xform && !d->defaultSize.isEmpty()) {
162 bounds = QRectF(QPointF(0,0), QSizeF(d->defaultSize));
163 QPoint tr1, tr2;
164 QSizeF sc(1, 1);
165 if (d->clipRect.isValid()) {
166 tr1 = -d->clipRect.topLeft();
167 finalSize = d->clipRect.size();
168 }
169 if (d->scaledSize.isValid()) {
170 sc = QSizeF(qreal(d->scaledSize.width()) / finalSize.width(),
171 qreal(d->scaledSize.height()) / finalSize.height());
172 finalSize = d->scaledSize;
173 }
174 if (d->scaledClipRect.isValid()) {
175 tr2 = -d->scaledClipRect.topLeft();
176 finalSize = d->scaledClipRect.size();
177 }
178 QTransform t;
179 t.translate(dx: tr2.x(), dy: tr2.y());
180 t.scale(sx: sc.width(), sy: sc.height());
181 t.translate(dx: tr1.x(), dy: tr1.y());
182 bounds = t.mapRect(bounds);
183 }
184 if (image->size() != finalSize || !image->reinterpretAsFormat(f: QImage::Format_ARGB32_Premultiplied)) {
185 if (qMax(a: finalSize.width(), b: finalSize.height()) > 0xffff)
186 return false; // Assume corrupted file
187 *image = QImage(finalSize, QImage::Format_ARGB32_Premultiplied);
188 if (!finalSize.isEmpty() && image->isNull()) {
189 qWarning(msg: "QSvgIOHandler: QImage allocation failed (size %i x %i)", finalSize.width(), finalSize.height());
190 return false;
191 }
192 }
193 if (!finalSize.isEmpty()) {
194 image->fill(pixel: d->backColor.rgba());
195 QPainter p(image);
196 d->r.render(p: &p, bounds);
197 p.end();
198 }
199 d->readDone = true;
200 return true;
201 }
202
203 return false;
204}
205
206
207QVariant QSvgIOHandler::option(ImageOption option) const
208{
209 switch(option) {
210 case ImageFormat:
211 return QImage::Format_ARGB32_Premultiplied;
212 break;
213 case Size:
214 d->load(device: device());
215 return d->defaultSize;
216 break;
217 case ClipRect:
218 return d->clipRect;
219 break;
220 case ScaledSize:
221 return d->scaledSize;
222 break;
223 case ScaledClipRect:
224 return d->scaledClipRect;
225 break;
226 case BackgroundColor:
227 return d->backColor;
228 break;
229 default:
230 break;
231 }
232 return QVariant();
233}
234
235
236void QSvgIOHandler::setOption(ImageOption option, const QVariant & value)
237{
238 switch(option) {
239 case ClipRect:
240 d->clipRect = value.toRect();
241 break;
242 case ScaledSize:
243 d->scaledSize = value.toSize();
244 break;
245 case ScaledClipRect:
246 d->scaledClipRect = value.toRect();
247 break;
248 case BackgroundColor:
249 d->backColor = value.value<QColor>();
250 break;
251 default:
252 break;
253 }
254}
255
256
257bool QSvgIOHandler::supportsOption(ImageOption option) const
258{
259 switch(option)
260 {
261 case ImageFormat:
262 case Size:
263 case ClipRect:
264 case ScaledSize:
265 case ScaledClipRect:
266 case BackgroundColor:
267 return true;
268 default:
269 break;
270 }
271 return false;
272}
273
274
275bool QSvgIOHandler::canRead(QIODevice *device)
276{
277 return isPossiblySvg(device);
278}
279
280QT_END_NAMESPACE
281
282#endif // QT_NO_SVGRENDERER
283

source code of qtsvg/src/plugins/imageformats/svg/qsvgiohandler.cpp