|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** All rights reserved. |
|
5 ** Contact: Nokia Corporation (qt-info@nokia.com) |
|
6 ** |
|
7 ** This file is part of the QtSvg module of the Qt Toolkit. |
|
8 ** |
|
9 ** $QT_BEGIN_LICENSE:LGPL$ |
|
10 ** No Commercial Usage |
|
11 ** This file contains pre-release code and may not be distributed. |
|
12 ** You may use this file in accordance with the terms and conditions |
|
13 ** contained in the Technology Preview License Agreement accompanying |
|
14 ** this package. |
|
15 ** |
|
16 ** GNU Lesser General Public License Usage |
|
17 ** Alternatively, this file may be used under the terms of the GNU Lesser |
|
18 ** General Public License version 2.1 as published by the Free Software |
|
19 ** Foundation and appearing in the file LICENSE.LGPL included in the |
|
20 ** packaging of this file. Please review the following information to |
|
21 ** ensure the GNU Lesser General Public License version 2.1 requirements |
|
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
|
23 ** |
|
24 ** In addition, as a special exception, Nokia gives you certain additional |
|
25 ** rights. These rights are described in the Nokia Qt LGPL Exception |
|
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
|
27 ** |
|
28 ** If you have questions regarding the use of this file, please contact |
|
29 ** Nokia at qt-info@nokia.com. |
|
30 ** |
|
31 ** |
|
32 ** |
|
33 ** |
|
34 ** |
|
35 ** |
|
36 ** |
|
37 ** |
|
38 ** $QT_END_LICENSE$ |
|
39 ** |
|
40 ****************************************************************************/ |
|
41 |
|
42 #include "qsvgtinydocument_p.h" |
|
43 |
|
44 #ifndef QT_NO_SVG |
|
45 |
|
46 #include "qsvghandler_p.h" |
|
47 #include "qsvgfont_p.h" |
|
48 |
|
49 #include "qpainter.h" |
|
50 #include "qfile.h" |
|
51 #include "qbuffer.h" |
|
52 #include "qbytearray.h" |
|
53 #include "qqueue.h" |
|
54 #include "qstack.h" |
|
55 #include "qdebug.h" |
|
56 |
|
57 #ifndef QT_NO_COMPRESS |
|
58 #include <zlib.h> |
|
59 #endif |
|
60 |
|
61 QT_BEGIN_NAMESPACE |
|
62 |
|
63 QSvgTinyDocument::QSvgTinyDocument() |
|
64 : QSvgStructureNode(0) |
|
65 , m_widthPercent(false) |
|
66 , m_heightPercent(false) |
|
67 , m_animated(false) |
|
68 , m_animationDuration(0) |
|
69 , m_fps(30) |
|
70 { |
|
71 } |
|
72 |
|
73 QSvgTinyDocument::~QSvgTinyDocument() |
|
74 { |
|
75 } |
|
76 |
|
77 #ifndef QT_NO_COMPRESS |
|
78 # ifdef QT_BUILD_INTERNAL |
|
79 Q_AUTOTEST_EXPORT QByteArray qt_inflateGZipDataFrom(QIODevice *device); |
|
80 # else |
|
81 static QByteArray qt_inflateGZipDataFrom(QIODevice *device); |
|
82 # endif |
|
83 |
|
84 QByteArray qt_inflateGZipDataFrom(QIODevice *device) |
|
85 { |
|
86 if (!device) |
|
87 return QByteArray(); |
|
88 |
|
89 if (!device->isOpen()) |
|
90 device->open(QIODevice::ReadOnly); |
|
91 |
|
92 Q_ASSERT(device->isOpen() && device->isReadable()); |
|
93 |
|
94 static const int CHUNK_SIZE = 4096; |
|
95 int zlibResult = Z_OK; |
|
96 |
|
97 QByteArray source; |
|
98 QByteArray destination; |
|
99 |
|
100 // Initialize zlib stream struct |
|
101 z_stream zlibStream; |
|
102 zlibStream.next_in = Z_NULL; |
|
103 zlibStream.avail_in = 0; |
|
104 zlibStream.avail_out = 0; |
|
105 zlibStream.zalloc = Z_NULL; |
|
106 zlibStream.zfree = Z_NULL; |
|
107 zlibStream.opaque = Z_NULL; |
|
108 |
|
109 // Adding 16 to the window size gives us gzip decoding |
|
110 if (inflateInit2(&zlibStream, MAX_WBITS + 16) != Z_OK) { |
|
111 qWarning("Cannot initialize zlib, because: %s", |
|
112 (zlibStream.msg != NULL ? zlibStream.msg : "Unknown error")); |
|
113 return QByteArray(); |
|
114 } |
|
115 |
|
116 bool stillMoreWorkToDo = true; |
|
117 while (stillMoreWorkToDo) { |
|
118 |
|
119 if (!zlibStream.avail_in) { |
|
120 source = device->read(CHUNK_SIZE); |
|
121 |
|
122 if (source.isEmpty()) |
|
123 break; |
|
124 |
|
125 zlibStream.avail_in = source.size(); |
|
126 zlibStream.next_in = reinterpret_cast<Bytef*>(source.data()); |
|
127 } |
|
128 |
|
129 do { |
|
130 // Prepare the destination buffer |
|
131 int oldSize = destination.size(); |
|
132 destination.resize(oldSize + CHUNK_SIZE); |
|
133 zlibStream.next_out = reinterpret_cast<Bytef*>( |
|
134 destination.data() + oldSize - zlibStream.avail_out); |
|
135 zlibStream.avail_out += CHUNK_SIZE; |
|
136 |
|
137 zlibResult = inflate(&zlibStream, Z_NO_FLUSH); |
|
138 switch (zlibResult) { |
|
139 case Z_NEED_DICT: |
|
140 case Z_DATA_ERROR: |
|
141 case Z_STREAM_ERROR: |
|
142 case Z_MEM_ERROR: { |
|
143 inflateEnd(&zlibStream); |
|
144 qWarning("Error while inflating gzip file: %s", |
|
145 (zlibStream.msg != NULL ? zlibStream.msg : "Unknown error")); |
|
146 destination.chop(zlibStream.avail_out); |
|
147 return destination; |
|
148 } |
|
149 } |
|
150 |
|
151 // If the output buffer still has more room after calling inflate |
|
152 // it means we have to provide more data, so exit the loop here |
|
153 } while (!zlibStream.avail_out); |
|
154 |
|
155 if (zlibResult == Z_STREAM_END) { |
|
156 // Make sure there are no more members to process before exiting |
|
157 if (!(zlibStream.avail_in && inflateReset(&zlibStream) == Z_OK)) |
|
158 stillMoreWorkToDo = false; |
|
159 } |
|
160 } |
|
161 |
|
162 // Chop off trailing space in the buffer |
|
163 destination.chop(zlibStream.avail_out); |
|
164 |
|
165 inflateEnd(&zlibStream); |
|
166 return destination; |
|
167 } |
|
168 #endif |
|
169 |
|
170 QSvgTinyDocument * QSvgTinyDocument::load(const QString &fileName) |
|
171 { |
|
172 QFile file(fileName); |
|
173 if (!file.open(QFile::ReadOnly)) { |
|
174 qWarning("Cannot open file '%s', because: %s", |
|
175 qPrintable(fileName), qPrintable(file.errorString())); |
|
176 return 0; |
|
177 } |
|
178 |
|
179 #ifndef QT_NO_COMPRESS |
|
180 if (fileName.endsWith(QLatin1String(".svgz"), Qt::CaseInsensitive) |
|
181 || fileName.endsWith(QLatin1String(".svg.gz"), Qt::CaseInsensitive)) { |
|
182 return load(qt_inflateGZipDataFrom(&file)); |
|
183 } |
|
184 #endif |
|
185 |
|
186 QSvgTinyDocument *doc = 0; |
|
187 QSvgHandler handler(&file); |
|
188 if (handler.ok()) { |
|
189 doc = handler.document(); |
|
190 doc->m_animationDuration = handler.animationDuration(); |
|
191 } else { |
|
192 qWarning("Cannot read file '%s', because: %s (line %d)", |
|
193 qPrintable(fileName), qPrintable(handler.errorString()), handler.lineNumber()); |
|
194 } |
|
195 return doc; |
|
196 } |
|
197 |
|
198 QSvgTinyDocument * QSvgTinyDocument::load(const QByteArray &contents) |
|
199 { |
|
200 #ifndef QT_NO_COMPRESS |
|
201 // Check for gzip magic number and inflate if appropriate |
|
202 if (contents.startsWith("\x1f\x8b")) { |
|
203 QBuffer buffer(const_cast<QByteArray *>(&contents)); |
|
204 return load(qt_inflateGZipDataFrom(&buffer)); |
|
205 } |
|
206 #endif |
|
207 |
|
208 QSvgHandler handler(contents); |
|
209 |
|
210 QSvgTinyDocument *doc = 0; |
|
211 if (handler.ok()) { |
|
212 doc = handler.document(); |
|
213 doc->m_animationDuration = handler.animationDuration(); |
|
214 } |
|
215 return doc; |
|
216 } |
|
217 |
|
218 QSvgTinyDocument * QSvgTinyDocument::load(QXmlStreamReader *contents) |
|
219 { |
|
220 QSvgHandler handler(contents); |
|
221 |
|
222 QSvgTinyDocument *doc = 0; |
|
223 if (handler.ok()) { |
|
224 doc = handler.document(); |
|
225 doc->m_animationDuration = handler.animationDuration(); |
|
226 } |
|
227 return doc; |
|
228 } |
|
229 |
|
230 void QSvgTinyDocument::draw(QPainter *p, const QRectF &bounds) |
|
231 { |
|
232 if (m_time.isNull()) { |
|
233 m_time.start(); |
|
234 } |
|
235 |
|
236 if (displayMode() == QSvgNode::NoneMode) |
|
237 return; |
|
238 |
|
239 p->save(); |
|
240 //sets default style on the painter |
|
241 //### not the most optimal way |
|
242 mapSourceToTarget(p, bounds); |
|
243 QPen pen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); |
|
244 pen.setMiterLimit(4); |
|
245 p->setPen(pen); |
|
246 p->setBrush(Qt::black); |
|
247 p->setRenderHint(QPainter::Antialiasing); |
|
248 p->setRenderHint(QPainter::SmoothPixmapTransform); |
|
249 QList<QSvgNode*>::iterator itr = m_renderers.begin(); |
|
250 applyStyle(p, m_states); |
|
251 while (itr != m_renderers.end()) { |
|
252 QSvgNode *node = *itr; |
|
253 if ((node->isVisible()) && (node->displayMode() != QSvgNode::NoneMode)) |
|
254 node->draw(p, m_states); |
|
255 ++itr; |
|
256 } |
|
257 revertStyle(p, m_states); |
|
258 p->restore(); |
|
259 } |
|
260 |
|
261 |
|
262 void QSvgTinyDocument::draw(QPainter *p, const QString &id, |
|
263 const QRectF &bounds) |
|
264 { |
|
265 QSvgNode *node = scopeNode(id); |
|
266 |
|
267 if (!node) { |
|
268 qDebug("Couldn't find node %s. Skipping rendering.", qPrintable(id)); |
|
269 return; |
|
270 } |
|
271 if (m_time.isNull()) { |
|
272 m_time.start(); |
|
273 } |
|
274 |
|
275 if (node->displayMode() == QSvgNode::NoneMode) |
|
276 return; |
|
277 |
|
278 p->save(); |
|
279 |
|
280 const QRectF elementBounds = node->transformedBounds(QTransform()); |
|
281 |
|
282 mapSourceToTarget(p, bounds, elementBounds); |
|
283 QTransform originalTransform = p->worldTransform(); |
|
284 |
|
285 //XXX set default style on the painter |
|
286 QPen pen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); |
|
287 pen.setMiterLimit(4); |
|
288 p->setPen(pen); |
|
289 p->setBrush(Qt::black); |
|
290 p->setRenderHint(QPainter::Antialiasing); |
|
291 p->setRenderHint(QPainter::SmoothPixmapTransform); |
|
292 |
|
293 QStack<QSvgNode*> parentApplyStack; |
|
294 QSvgNode *parent = node->parent(); |
|
295 while (parent) { |
|
296 parentApplyStack.push(parent); |
|
297 parent = parent->parent(); |
|
298 } |
|
299 |
|
300 for (int i = parentApplyStack.size() - 1; i >= 0; --i) |
|
301 parentApplyStack[i]->applyStyle(p, m_states); |
|
302 |
|
303 // Reset the world transform so that our parents don't affect |
|
304 // the position |
|
305 QTransform currentTransform = p->worldTransform(); |
|
306 p->setWorldTransform(originalTransform); |
|
307 |
|
308 node->draw(p, m_states); |
|
309 |
|
310 p->setWorldTransform(currentTransform); |
|
311 |
|
312 for (int i = 0; i < parentApplyStack.size(); ++i) |
|
313 parentApplyStack[i]->revertStyle(p, m_states); |
|
314 |
|
315 //p->fillRect(bounds.adjusted(-5, -5, 5, 5), QColor(0, 0, 255, 100)); |
|
316 |
|
317 p->restore(); |
|
318 } |
|
319 |
|
320 |
|
321 QSvgNode::Type QSvgTinyDocument::type() const |
|
322 { |
|
323 return DOC; |
|
324 } |
|
325 |
|
326 void QSvgTinyDocument::setWidth(int len, bool percent) |
|
327 { |
|
328 m_size.setWidth(len); |
|
329 m_widthPercent = percent; |
|
330 } |
|
331 |
|
332 void QSvgTinyDocument::setHeight(int len, bool percent) |
|
333 { |
|
334 m_size.setHeight(len); |
|
335 m_heightPercent = percent; |
|
336 } |
|
337 |
|
338 void QSvgTinyDocument::setViewBox(const QRectF &rect) |
|
339 { |
|
340 m_viewBox = rect; |
|
341 } |
|
342 |
|
343 void QSvgTinyDocument::addSvgFont(QSvgFont *font) |
|
344 { |
|
345 m_fonts.insert(font->familyName(), font); |
|
346 } |
|
347 |
|
348 QSvgFont * QSvgTinyDocument::svgFont(const QString &family) const |
|
349 { |
|
350 return m_fonts[family]; |
|
351 } |
|
352 |
|
353 void QSvgTinyDocument::addNamedNode(const QString &id, QSvgNode *node) |
|
354 { |
|
355 m_namedNodes.insert(id, node); |
|
356 } |
|
357 |
|
358 QSvgNode *QSvgTinyDocument::namedNode(const QString &id) const |
|
359 { |
|
360 return m_namedNodes.value(id); |
|
361 } |
|
362 |
|
363 void QSvgTinyDocument::addNamedStyle(const QString &id, QSvgFillStyleProperty *style) |
|
364 { |
|
365 m_namedStyles.insert(id, style); |
|
366 } |
|
367 |
|
368 QSvgFillStyleProperty *QSvgTinyDocument::namedStyle(const QString &id) const |
|
369 { |
|
370 return m_namedStyles.value(id); |
|
371 } |
|
372 |
|
373 void QSvgTinyDocument::restartAnimation() |
|
374 { |
|
375 m_time.restart(); |
|
376 } |
|
377 |
|
378 bool QSvgTinyDocument::animated() const |
|
379 { |
|
380 return m_animated; |
|
381 } |
|
382 |
|
383 void QSvgTinyDocument::setAnimated(bool a) |
|
384 { |
|
385 m_animated = a; |
|
386 } |
|
387 |
|
388 void QSvgTinyDocument::draw(QPainter *p) |
|
389 { |
|
390 draw(p, QRectF()); |
|
391 } |
|
392 |
|
393 void QSvgTinyDocument::draw(QPainter *p, QSvgExtraStates &) |
|
394 { |
|
395 draw(p); |
|
396 } |
|
397 |
|
398 void QSvgTinyDocument::mapSourceToTarget(QPainter *p, const QRectF &targetRect, const QRectF &sourceRect) |
|
399 { |
|
400 QRectF target = targetRect; |
|
401 if (target.isNull()) { |
|
402 QPaintDevice *dev = p->device(); |
|
403 QRectF deviceRect(0, 0, dev->width(), dev->height()); |
|
404 if (deviceRect.isNull()) { |
|
405 if (sourceRect.isNull()) |
|
406 target = QRectF(QPointF(0, 0), size()); |
|
407 else |
|
408 target = QRectF(QPointF(0, 0), sourceRect.size()); |
|
409 } else { |
|
410 target = deviceRect; |
|
411 } |
|
412 } |
|
413 |
|
414 QRectF source = sourceRect; |
|
415 if (source.isNull()) |
|
416 source = viewBox(); |
|
417 |
|
418 if (source != target && !source.isNull()) { |
|
419 QTransform transform; |
|
420 transform.scale(target.width() / source.width(), |
|
421 target.height() / source.height()); |
|
422 QRectF c2 = transform.mapRect(source); |
|
423 p->translate(target.x() - c2.x(), |
|
424 target.y() - c2.y()); |
|
425 p->scale(target.width() / source.width(), |
|
426 target.height() / source.height()); |
|
427 } |
|
428 } |
|
429 |
|
430 QRectF QSvgTinyDocument::boundsOnElement(const QString &id) const |
|
431 { |
|
432 const QSvgNode *node = scopeNode(id); |
|
433 if (!node) |
|
434 node = this; |
|
435 |
|
436 return node->transformedBounds(QTransform()); |
|
437 } |
|
438 |
|
439 bool QSvgTinyDocument::elementExists(const QString &id) const |
|
440 { |
|
441 QSvgNode *node = scopeNode(id); |
|
442 |
|
443 return (node!=0); |
|
444 } |
|
445 |
|
446 QMatrix QSvgTinyDocument::matrixForElement(const QString &id) const |
|
447 { |
|
448 QSvgNode *node = scopeNode(id); |
|
449 |
|
450 if (!node) { |
|
451 qDebug("Couldn't find node %s. Skipping rendering.", qPrintable(id)); |
|
452 return QMatrix(); |
|
453 } |
|
454 |
|
455 QTransform t; |
|
456 |
|
457 node = node->parent(); |
|
458 while (node) { |
|
459 if (node->m_style.transform) |
|
460 t *= node->m_style.transform->qtransform(); |
|
461 node = node->parent(); |
|
462 } |
|
463 |
|
464 return t.toAffine(); |
|
465 } |
|
466 |
|
467 int QSvgTinyDocument::currentFrame() const |
|
468 { |
|
469 double runningPercentage = qMin(m_time.elapsed()/double(m_animationDuration), 1.); |
|
470 |
|
471 int totalFrames = m_fps * m_animationDuration; |
|
472 |
|
473 return int(runningPercentage * totalFrames); |
|
474 } |
|
475 |
|
476 void QSvgTinyDocument::setCurrentFrame(int frame) |
|
477 { |
|
478 int totalFrames = m_fps * m_animationDuration; |
|
479 double framePercentage = frame/double(totalFrames); |
|
480 double timeForFrame = m_animationDuration * framePercentage; //in S |
|
481 timeForFrame *= 1000; //in ms |
|
482 int timeToAdd = int(timeForFrame - m_time.elapsed()); |
|
483 m_time = m_time.addMSecs(timeToAdd); |
|
484 } |
|
485 |
|
486 void QSvgTinyDocument::setFramesPerSecond(int num) |
|
487 { |
|
488 m_fps = num; |
|
489 } |
|
490 |
|
491 QT_END_NAMESPACE |
|
492 |
|
493 #endif // QT_NO_SVG |