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

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