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 test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
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 General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | |
30 | #include <QtTest/QtTest> |
31 | |
32 | #include <qapplication.h> |
33 | #include <qdebug.h> |
34 | #include <qsvgrenderer.h> |
35 | #include <qsvggenerator.h> |
36 | #include <QPainter> |
37 | #include <QPen> |
38 | #include <QPicture> |
39 | #include <QXmlStreamReader> |
40 | |
41 | #ifndef SRCDIR |
42 | #define SRCDIR |
43 | #endif |
44 | |
45 | class tst_QSvgRenderer : public QObject |
46 | { |
47 | Q_OBJECT |
48 | |
49 | public: |
50 | tst_QSvgRenderer(); |
51 | virtual ~tst_QSvgRenderer(); |
52 | |
53 | private slots: |
54 | void getSetCheck(); |
55 | void inexistentUrl(); |
56 | void emptyUrl(); |
57 | void invalidUrl_data(); |
58 | void invalidUrl(); |
59 | void testStrokeWidth(); |
60 | void testMapViewBoxToTarget(); |
61 | void testRenderElement(); |
62 | void testRenderElementToBounds(); |
63 | void testRenderDocumentWithSizeToBounds(); |
64 | void constructorQXmlStreamReader() const; |
65 | void loadQXmlStreamReader() const; |
66 | void nestedQXmlStreamReader() const; |
67 | void stylePropagation() const; |
68 | void matrixForElement() const; |
69 | void boundsOnElement() const; |
70 | void gradientStops() const; |
71 | void gradientRefs(); |
72 | void recursiveRefs_data(); |
73 | void recursiveRefs(); |
74 | void fillRule(); |
75 | void opacity(); |
76 | void paths(); |
77 | void paths2(); |
78 | void displayMode(); |
79 | void strokeInherit(); |
80 | void testFillInheritance(); |
81 | void testStopOffsetOpacity(); |
82 | void testUseElement(); |
83 | void smallFont(); |
84 | void styleSheet(); |
85 | void duplicateStyleId(); |
86 | void oss_fuzz_23731(); |
87 | void oss_fuzz_24131(); |
88 | void oss_fuzz_24738(); |
89 | |
90 | #ifndef QT_NO_COMPRESS |
91 | void testGzLoading(); |
92 | void testGzHelper_data(); |
93 | void testGzHelper(); |
94 | #endif |
95 | |
96 | private: |
97 | static const char *const src; |
98 | }; |
99 | |
100 | const char *const tst_QSvgRenderer::src = "<svg><g><rect x='250' y='250' width='500' height='500'/>" |
101 | "<rect id='foo' x='400' y='400' width='100' height='100'/></g></svg>" ; |
102 | |
103 | tst_QSvgRenderer::tst_QSvgRenderer() |
104 | { |
105 | } |
106 | |
107 | tst_QSvgRenderer::~tst_QSvgRenderer() |
108 | { |
109 | } |
110 | |
111 | // Testing get/set functions |
112 | void tst_QSvgRenderer::getSetCheck() |
113 | { |
114 | QSvgRenderer obj1; |
115 | // int QSvgRenderer::framesPerSecond() |
116 | // void QSvgRenderer::setFramesPerSecond(int) |
117 | obj1.setFramesPerSecond(20); |
118 | QCOMPARE(20, obj1.framesPerSecond()); |
119 | obj1.setFramesPerSecond(0); |
120 | QCOMPARE(0, obj1.framesPerSecond()); |
121 | obj1.setFramesPerSecond(INT_MIN); |
122 | QCOMPARE(0, obj1.framesPerSecond()); // Can't have a negative framerate |
123 | obj1.setFramesPerSecond(INT_MAX); |
124 | QCOMPARE(INT_MAX, obj1.framesPerSecond()); |
125 | } |
126 | |
127 | void tst_QSvgRenderer::inexistentUrl() |
128 | { |
129 | const char *src = "<svg><g><path d=\"\" style=\"stroke:url(#inexistent)\"/></g></svg>" ; |
130 | |
131 | QByteArray data(src); |
132 | QSvgRenderer renderer(data); |
133 | |
134 | QVERIFY(renderer.isValid()); |
135 | } |
136 | |
137 | void tst_QSvgRenderer::emptyUrl() |
138 | { |
139 | const char *src = "<svg><text fill=\"url()\" /></svg>" ; |
140 | |
141 | QByteArray data(src); |
142 | QSvgRenderer renderer(data); |
143 | |
144 | QVERIFY(renderer.isValid()); |
145 | } |
146 | |
147 | void tst_QSvgRenderer::invalidUrl_data() |
148 | { |
149 | QTest::addColumn<QByteArray>(name: "svg" ); |
150 | |
151 | QTest::newRow(dataTag: "01" ) << QByteArray("<svg><linearGradient id=\"0\"/><circle fill=\"url0\" /></svg>" ); |
152 | QTest::newRow(dataTag: "02" ) << QByteArray("<svg><linearGradient id=\"0\"/><circle fill=\"url(0\" /></svg>" ); |
153 | QTest::newRow(dataTag: "03" ) << QByteArray("<svg><linearGradient id=\"0\"/><circle fill=\"url (0\" /></svg>" ); |
154 | QTest::newRow(dataTag: "04" ) << QByteArray("<svg><linearGradient id=\"0\"/><circle fill=\"url ( 0\" /></svg>" ); |
155 | QTest::newRow(dataTag: "05" ) << QByteArray("<svg><linearGradient id=\"0\"/><circle fill=\"url#\" /></svg>" ); |
156 | QTest::newRow(dataTag: "06" ) << QByteArray("<svg><linearGradient id=\"0\"/><circle fill=\"url#(\" /></svg>" ); |
157 | QTest::newRow(dataTag: "07" ) << QByteArray("<svg><linearGradient id=\"0\"/><circle fill=\"url(#\" /></svg>" ); |
158 | QTest::newRow(dataTag: "08" ) << QByteArray("<svg><linearGradient id=\"0\"/><circle fill=\"url(# \" /></svg>" ); |
159 | QTest::newRow(dataTag: "09" ) << QByteArray("<svg><linearGradient id=\"0\"/><circle fill=\"url(# 0\" /></svg>" ); |
160 | QTest::newRow(dataTag: "10" ) << QByteArray("<svg><linearGradient id=\"blabla\"/><circle fill=\"urlblabla\" /></svg>" ); |
161 | QTest::newRow(dataTag: "11" ) << QByteArray("<svg><linearGradient id=\"blabla\"/><circle fill=\"url(blabla\" /></svg>" ); |
162 | QTest::newRow(dataTag: "12" ) << QByteArray("<svg><linearGradient id=\"blabla\"/><circle fill=\"url(blabla)\" /></svg>" ); |
163 | QTest::newRow(dataTag: "13" ) << QByteArray("<svg><linearGradient id=\"blabla\"/><circle fill=\"url(#blabla\" /></svg>" ); |
164 | } |
165 | |
166 | void tst_QSvgRenderer::invalidUrl() |
167 | { |
168 | QFETCH(QByteArray, svg); |
169 | |
170 | #if QT_CONFIG(regularexpression) |
171 | QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression("Could not resolve property" )); |
172 | #endif |
173 | QSvgRenderer renderer(svg); |
174 | QVERIFY(renderer.isValid()); |
175 | } |
176 | |
177 | void tst_QSvgRenderer::testStrokeWidth() |
178 | { |
179 | qreal squareSize = 30.0; |
180 | qreal strokeWidth = 1.0; |
181 | qreal topLeft = 100.0; |
182 | |
183 | QSvgGenerator generator; |
184 | |
185 | QBuffer buffer; |
186 | QByteArray byteArray; |
187 | buffer.setBuffer(&byteArray); |
188 | generator.setOutputDevice(&buffer); |
189 | |
190 | QPainter painter(&generator); |
191 | painter.setBrush(Qt::blue); |
192 | |
193 | // Draw a rect with stroke |
194 | painter.setPen(QPen(Qt::black, strokeWidth)); |
195 | painter.drawRect(x: topLeft, y: topLeft, w: squareSize, h: squareSize); |
196 | |
197 | // Draw a rect without stroke |
198 | painter.setPen(Qt::NoPen); |
199 | painter.drawRect(x: topLeft, y: topLeft, w: squareSize, h: squareSize); |
200 | painter.end(); |
201 | |
202 | // Insert ID tags into the document |
203 | byteArray.insert(i: byteArray.indexOf(c: "stroke=\"#000000\"" ), s: "id=\"SquareStroke\" " ); |
204 | byteArray.insert(i: byteArray.indexOf(c: "stroke=\"none\"" ), s: "id=\"SquareNoStroke\" " ); |
205 | |
206 | QSvgRenderer renderer(byteArray); |
207 | |
208 | QRectF noStrokeRect = renderer.boundsOnElement(id: "SquareNoStroke" ); |
209 | QCOMPARE(noStrokeRect.width(), squareSize); |
210 | QCOMPARE(noStrokeRect.height(), squareSize); |
211 | QCOMPARE(noStrokeRect.x(), topLeft); |
212 | QCOMPARE(noStrokeRect.y(), topLeft); |
213 | |
214 | QRectF strokeRect = renderer.boundsOnElement(id: "SquareStroke" ); |
215 | QCOMPARE(strokeRect.width(), squareSize + strokeWidth); |
216 | QCOMPARE(strokeRect.height(), squareSize + strokeWidth); |
217 | QCOMPARE(strokeRect.x(), topLeft - (strokeWidth / 2)); |
218 | QCOMPARE(strokeRect.y(), topLeft - (strokeWidth / 2)); |
219 | } |
220 | |
221 | void tst_QSvgRenderer::testMapViewBoxToTarget() |
222 | { |
223 | const char *src = "<svg><g><rect x=\"250\" y=\"250\" width=\"500\" height=\"500\" /></g></svg>" ; |
224 | QByteArray data(src); |
225 | |
226 | { // No viewport, viewBox, targetRect, or deviceRect -> boundingRect |
227 | QPicture picture; |
228 | QPainter painter(&picture); |
229 | QSvgRenderer rend(data); |
230 | rend.render(p: &painter); |
231 | painter.end(); |
232 | QCOMPARE(picture.boundingRect(), QRect(0, 0, 500, 500)); |
233 | } |
234 | |
235 | { // No viewport, viewBox, targetRect -> deviceRect |
236 | QPicture picture; |
237 | picture.setBoundingRect(QRect(100, 100, 200, 200)); |
238 | QPainter painter(&picture); |
239 | QSvgRenderer rend(data); |
240 | rend.render(p: &painter); |
241 | painter.end(); |
242 | QCOMPARE(picture.boundingRect(), QRect(100, 100, 200, 200)); |
243 | } |
244 | |
245 | { // No viewport, viewBox -> targetRect |
246 | QPicture picture; |
247 | QPainter painter(&picture); |
248 | QSvgRenderer rend(data); |
249 | rend.render(p: &painter, bounds: QRectF(50, 50, 250, 250)); |
250 | painter.end(); |
251 | QCOMPARE(picture.boundingRect(), QRect(50, 50, 250, 250)); |
252 | |
253 | } |
254 | |
255 | data.replace(before: "<svg>" , after: "<svg viewBox=\"0 0 1000 1000\">" ); |
256 | |
257 | { // No viewport, no targetRect -> viewBox |
258 | QPicture picture; |
259 | QPainter painter(&picture); |
260 | QSvgRenderer rend(data); |
261 | rend.render(p: &painter); |
262 | painter.end(); |
263 | QCOMPARE(picture.boundingRect(), QRect(250, 250, 500, 500)); |
264 | } |
265 | |
266 | data.replace(before: "<svg" , after: "<svg width=\"500\" height=\"500\"" ); |
267 | |
268 | { // Viewport |
269 | QPicture picture; |
270 | QPainter painter(&picture); |
271 | QSvgRenderer rend(data); |
272 | rend.render(p: &painter); |
273 | painter.end(); |
274 | QCOMPARE(picture.boundingRect(), QRect(125, 125, 250, 250)); |
275 | } |
276 | |
277 | // Requires keep-aspectratio feature |
278 | { // Viewport and viewBox specified -> scale 500x500 square to 1000x750 while preserving aspect ratio gives 750x750 |
279 | data = "<svg width=\"1000\" height=\"750\" viewBox=\"-250 -250 500 500\"><g><rect x=\"0\" y=\"0\" width=\"500\" height=\"500\" /></g></svg>" ; |
280 | QPicture picture; |
281 | QPainter painter(&picture); |
282 | QSvgRenderer rend(data); |
283 | rend.setAspectRatioMode(Qt::KeepAspectRatio); |
284 | rend.render(p: &painter); |
285 | painter.end(); |
286 | QCOMPARE(picture.boundingRect(), QRect(500, 375, 750, 750)); |
287 | } |
288 | } |
289 | |
290 | void tst_QSvgRenderer::testRenderElement() |
291 | { |
292 | QByteArray data(src); |
293 | |
294 | { // No viewport, viewBox, targetRect, or deviceRect -> boundingRect |
295 | QPicture picture; |
296 | QPainter painter(&picture); |
297 | QSvgRenderer rend(data); |
298 | rend.render(p: &painter, elementId: QLatin1String("foo" )); |
299 | painter.end(); |
300 | QCOMPARE(picture.boundingRect(), QRect(0, 0, 100, 100)); |
301 | } |
302 | |
303 | { // No viewport, viewBox, targetRect -> deviceRect |
304 | QPicture picture; |
305 | picture.setBoundingRect(QRect(100, 100, 200, 200)); |
306 | QPainter painter(&picture); |
307 | QSvgRenderer rend(data); |
308 | rend.render(p: &painter, elementId: QLatin1String("foo" )); |
309 | painter.end(); |
310 | QCOMPARE(picture.boundingRect(), QRect(100, 100, 200, 200)); |
311 | } |
312 | |
313 | { // No viewport, viewBox -> targetRect |
314 | QPicture picture; |
315 | QPainter painter(&picture); |
316 | QSvgRenderer rend(data); |
317 | rend.render(p: &painter, elementId: QLatin1String("foo" ), bounds: QRectF(50, 50, 250, 250)); |
318 | painter.end(); |
319 | QCOMPARE(picture.boundingRect(), QRect(50, 50, 250, 250)); |
320 | |
321 | } |
322 | |
323 | data.replace(before: "<svg>" , after: "<svg viewBox=\"0 0 1000 1000\">" ); |
324 | |
325 | { // No viewport, no targetRect -> view box size |
326 | QPicture picture; |
327 | QPainter painter(&picture); |
328 | QSvgRenderer rend(data); |
329 | rend.render(p: &painter, elementId: QLatin1String("foo" )); |
330 | painter.end(); |
331 | QCOMPARE(picture.boundingRect(), QRect(0, 0, 100, 100)); |
332 | } |
333 | |
334 | data.replace(before: "<svg" , after: "<svg width=\"500\" height=\"500\"" ); |
335 | |
336 | { // Viewport |
337 | QPicture picture; |
338 | QPainter painter(&picture); |
339 | QSvgRenderer rend(data); |
340 | rend.render(p: &painter, elementId: QLatin1String("foo" )); |
341 | painter.end(); |
342 | QCOMPARE(picture.boundingRect(), QRect(0, 0, 100, 100)); |
343 | } |
344 | |
345 | } |
346 | |
347 | void tst_QSvgRenderer::testRenderElementToBounds() |
348 | { |
349 | // QTBUG-79933 |
350 | QImage reference(400, 200, QImage::Format_ARGB32); |
351 | { |
352 | reference.fill(color: Qt::transparent); |
353 | QPainter p(&reference); |
354 | p.fillRect( x: 0, y: 0, w: 200, h: 100, c: Qt::blue); |
355 | p.fillRect(x: 200, y: 100, w: 200, h: 100, c: Qt::blue); |
356 | p.fillRect( x: 0, y: 0, w: 100, h: 50, c: Qt::red); |
357 | p.fillRect(x: 100, y: 50, w: 100, h: 50, c: Qt::red); |
358 | p.fillRect(x: 200, y: 100, w: 100, h: 50, c: Qt::red); |
359 | p.fillRect(x: 300, y: 150, w: 100, h: 50, c: Qt::red); |
360 | } |
361 | |
362 | QImage rendering(400, 200, QImage::Format_ARGB32); |
363 | { |
364 | const char *const src = |
365 | "<svg viewBox=\"0 0 100 100\">" // Presence of a viewBox triggered QTBUG-79933 |
366 | "<path id=\"el1\" d=\"M 80,10 H 0 V 0 h 40 v 20 h 40 z\" fill=\"red\" />" |
367 | "<path id=\"el2\" d=\"M 90,100 V 20 h 10 V 60 H 80 v 40 z\" fill=\"blue\" />" |
368 | "</svg>" ; |
369 | const QByteArray data(src); |
370 | QSvgRenderer rend(data); |
371 | rendering.fill(color: Qt::transparent); |
372 | QPainter p(&rendering); |
373 | rend.render(p: &p, elementId: "el1" , bounds: QRectF( 0, 0, 200, 100)); |
374 | rend.render(p: &p, elementId: "el2" , bounds: QRectF( 0, 0, 200, 100)); |
375 | rend.render(p: &p, elementId: "el1" , bounds: QRectF(200, 100, 200, 100)); |
376 | rend.render(p: &p, elementId: "el2" , bounds: QRectF(200, 100, 200, 100)); |
377 | } |
378 | |
379 | QCOMPARE(reference, rendering); |
380 | } |
381 | |
382 | void tst_QSvgRenderer::testRenderDocumentWithSizeToBounds() |
383 | { |
384 | // QTBUG-80888 |
385 | QImage reference(400, 200, QImage::Format_ARGB32); |
386 | { |
387 | reference.fill(color: Qt::transparent); |
388 | QPainter p(&reference); |
389 | p.fillRect(x: 100, y: 100, w: 100, h: 50, c: Qt::blue); |
390 | p.fillRect(x: 200, y: 50, w: 100, h: 50, c: Qt::blue); |
391 | } |
392 | |
393 | QImage rendering(400, 200, QImage::Format_ARGB32); |
394 | { |
395 | const char *const src = R"src( |
396 | <svg width="20" height="80"> |
397 | <g transform="translate(-100,-100)"> |
398 | <path d="m 110,180 v -80 h 10 v 40 h -20 v 40 z" fill="blue" /> |
399 | </g> |
400 | </svg> |
401 | )src" ; |
402 | const QByteArray data(src); |
403 | QSvgRenderer rend(data); |
404 | rendering.fill(color: Qt::transparent); |
405 | QPainter p(&rendering); |
406 | rend.render(p: &p, bounds: QRectF(100, 50, 200, 100)); |
407 | } |
408 | |
409 | QCOMPARE(reference, rendering); |
410 | } |
411 | |
412 | void tst_QSvgRenderer::constructorQXmlStreamReader() const |
413 | { |
414 | const QByteArray data(src); |
415 | |
416 | QXmlStreamReader reader(data); |
417 | |
418 | QPicture picture; |
419 | QPainter painter(&picture); |
420 | QSvgRenderer rend(&reader); |
421 | rend.render(p: &painter, elementId: QLatin1String("foo" )); |
422 | painter.end(); |
423 | QCOMPARE(picture.boundingRect(), QRect(0, 0, 100, 100)); |
424 | } |
425 | |
426 | void tst_QSvgRenderer::loadQXmlStreamReader() const |
427 | { |
428 | const QByteArray data(src); |
429 | |
430 | QXmlStreamReader reader(data); |
431 | QPicture picture; |
432 | QPainter painter(&picture); |
433 | QSvgRenderer rend; |
434 | rend.load(contents: &reader); |
435 | rend.render(p: &painter, elementId: QLatin1String("foo" )); |
436 | painter.end(); |
437 | QCOMPARE(picture.boundingRect(), QRect(0, 0, 100, 100)); |
438 | } |
439 | |
440 | |
441 | void tst_QSvgRenderer::nestedQXmlStreamReader() const |
442 | { |
443 | const QByteArray data(QByteArray("<bar>" ) + QByteArray(src) + QByteArray("</bar>" )); |
444 | |
445 | QXmlStreamReader reader(data); |
446 | |
447 | QCOMPARE(reader.readNext(), QXmlStreamReader::StartDocument); |
448 | QCOMPARE(reader.readNext(), QXmlStreamReader::StartElement); |
449 | QCOMPARE(reader.name().toString(), QLatin1String("bar" )); |
450 | |
451 | QPicture picture; |
452 | QPainter painter(&picture); |
453 | QSvgRenderer renderer(&reader); |
454 | renderer.render(p: &painter, elementId: QLatin1String("foo" )); |
455 | painter.end(); |
456 | QCOMPARE(picture.boundingRect(), QRect(0, 0, 100, 100)); |
457 | |
458 | QCOMPARE(reader.readNext(), QXmlStreamReader::EndElement); |
459 | QCOMPARE(reader.name().toString(), QLatin1String("bar" )); |
460 | QCOMPARE(reader.readNext(), QXmlStreamReader::EndDocument); |
461 | |
462 | QVERIFY(reader.atEnd()); |
463 | QVERIFY(!reader.hasError()); |
464 | } |
465 | |
466 | void tst_QSvgRenderer::stylePropagation() const |
467 | { |
468 | QByteArray data("<svg>" |
469 | "<g id='foo' style='fill:#ffff00;'>" |
470 | "<g id='bar' style='fill:#ff00ff;'>" |
471 | "<g id='baz' style='fill:#00ffff;'>" |
472 | "<rect id='alpha' x='0' y='0' width='100' height='100'/>" |
473 | "</g>" |
474 | "<rect id='beta' x='100' y='0' width='100' height='100'/>" |
475 | "</g>" |
476 | "<rect id='gamma' x='0' y='100' width='100' height='100'/>" |
477 | "</g>" |
478 | "<rect id='delta' x='100' y='100' width='100' height='100'/>" |
479 | "</svg>" ); // alpha=cyan, beta=magenta, gamma=yellow, delta=black |
480 | |
481 | QImage image1(200, 200, QImage::Format_RGB32); |
482 | QImage image2(200, 200, QImage::Format_RGB32); |
483 | QImage image3(200, 200, QImage::Format_RGB32); |
484 | QPainter painter; |
485 | QSvgRenderer renderer(data); |
486 | QLatin1String parts[4] = {QLatin1String("alpha" ), QLatin1String("beta" ), QLatin1String("gamma" ), QLatin1String("delta" )}; |
487 | |
488 | QVERIFY(painter.begin(&image1)); |
489 | for (int i = 0; i < 4; ++i) |
490 | renderer.render(p: &painter, elementId: parts[i], bounds: QRectF(renderer.boundsOnElement(id: parts[i]))); |
491 | painter.end(); |
492 | |
493 | QVERIFY(painter.begin(&image2)); |
494 | renderer.render(p: &painter, bounds: renderer.viewBoxF()); |
495 | painter.end(); |
496 | |
497 | QVERIFY(painter.begin(&image3)); |
498 | painter.setPen(Qt::NoPen); |
499 | painter.setBrush(QBrush(Qt::cyan)); |
500 | painter.drawRect(x: 0, y: 0, w: 100, h: 100); |
501 | painter.setBrush(QBrush(Qt::magenta)); |
502 | painter.drawRect(x: 100, y: 0, w: 100, h: 100); |
503 | painter.setBrush(QBrush(Qt::yellow)); |
504 | painter.drawRect(x: 0, y: 100, w: 100, h: 100); |
505 | painter.setBrush(QBrush(Qt::black)); |
506 | painter.drawRect(x: 100, y: 100, w: 100, h: 100); |
507 | painter.end(); |
508 | |
509 | QCOMPARE(image1, image2); |
510 | QCOMPARE(image1, image3); |
511 | } |
512 | |
513 | static qreal transformNorm(const QTransform &m) |
514 | { |
515 | return qSqrt(v: m.m11() * m.m11() |
516 | + m.m12() * m.m12() |
517 | + m.m13() * m.m13() |
518 | + m.m21() * m.m21() |
519 | + m.m22() * m.m22() |
520 | + m.m23() * m.m23() |
521 | + m.m31() * m.m31() |
522 | + m.m32() * m.m32() |
523 | + m.m33() * m.m33()); |
524 | } |
525 | |
526 | static bool diffIsSmallEnough(double diff, double norm) |
527 | { |
528 | return diff <= 1e-12 * norm; |
529 | } |
530 | |
531 | static inline bool diffIsSmallEnough(float diff, float norm) |
532 | { |
533 | return diff <= 1e-5 * norm; |
534 | } |
535 | |
536 | static void compareTransforms(const QTransform &m1, const QTransform &m2) |
537 | { |
538 | qreal norm1 = transformNorm(m: m1); |
539 | qreal norm2 = transformNorm(m: m2); |
540 | qreal diffNorm = transformNorm(m: QTransform(m1.m11() - m2.m11(), |
541 | m1.m12() - m2.m12(), |
542 | m1.m13() - m2.m13(), |
543 | m1.m21() - m2.m21(), |
544 | m1.m22() - m2.m22(), |
545 | m1.m23() - m2.m23(), |
546 | m1.m31() - m2.m31(), |
547 | m1.m32() - m2.m32(), |
548 | m1.m33() - m2.m33())); |
549 | QVERIFY(diffIsSmallEnough(diffNorm, qMin(norm1, norm2))); |
550 | } |
551 | |
552 | void tst_QSvgRenderer::matrixForElement() const |
553 | { |
554 | QByteArray data("<svg>" |
555 | "<g id='ichi' transform='translate(-3,1)'>" |
556 | "<g id='ni' transform='rotate(45)'>" |
557 | "<g id='san' transform='scale(4,2)'>" |
558 | "<g id='yon' transform='matrix(1,2,3,4,5,6)'>" |
559 | "<rect id='firkant' x='-1' y='-1' width='2' height='2'/>" |
560 | "</g>" |
561 | "</g>" |
562 | "</g>" |
563 | "</g>" |
564 | "</svg>" ); |
565 | |
566 | QImage image(13, 37, QImage::Format_RGB32); |
567 | QPainter painter(&image); |
568 | QSvgRenderer renderer(data); |
569 | |
570 | compareTransforms(m1: painter.worldTransform(), m2: renderer.transformForElement(id: QLatin1String("ichi" ))); |
571 | painter.translate(dx: -3, dy: 1); |
572 | compareTransforms(m1: painter.worldTransform(), m2: renderer.transformForElement(id: QLatin1String("ni" ))); |
573 | painter.rotate(a: 45); |
574 | compareTransforms(m1: painter.worldTransform(), m2: renderer.transformForElement(id: QLatin1String("san" ))); |
575 | painter.scale(sx: 4, sy: 2); |
576 | compareTransforms(m1: painter.worldTransform(), m2: renderer.transformForElement(id: QLatin1String("yon" ))); |
577 | painter.setWorldTransform(matrix: QTransform(1, 2, 3, 4, 5, 6), combine: true); |
578 | compareTransforms(m1: painter.worldTransform(), m2: renderer.transformForElement(id: QLatin1String("firkant" ))); |
579 | } |
580 | |
581 | void tst_QSvgRenderer::boundsOnElement() const |
582 | { |
583 | QByteArray data("<svg>" |
584 | "<g id=\"prim\" transform=\"translate(-3,1)\">" |
585 | "<g id=\"sjokade\" stroke=\"none\" stroke-width=\"10\">" |
586 | "<rect id=\"kaviar\" transform=\"rotate(45)\" x=\"-10\" y=\"-10\" width=\"20\" height=\"20\"/>" |
587 | "</g>" |
588 | "<g id=\"nugatti\" stroke=\"black\" stroke-width=\"2\">" |
589 | "<use x=\"0\" y=\"0\" transform=\"rotate(45)\" xlink:href=\"#kaviar\"/>" |
590 | "</g>" |
591 | "<g id=\"nutella\" stroke=\"none\" stroke-width=\"10\">" |
592 | "<path id=\"baconost\" transform=\"rotate(45)\" d=\"M-10 -10 L10 -10 L10 10 L-10 10 Z\"/>" |
593 | "</g>" |
594 | "<g id=\"hapaa\" transform=\"translate(-2,2)\" stroke=\"black\" stroke-width=\"2\">" |
595 | "<use x=\"0\" y=\"0\" transform=\"rotate(45)\" xlink:href=\"#baconost\"/>" |
596 | "</g>" |
597 | "</g>" |
598 | "</svg>" ); |
599 | |
600 | qreal sqrt2 = qSqrt(v: 2); |
601 | QSvgRenderer renderer(data); |
602 | QCOMPARE(renderer.boundsOnElement(QLatin1String("sjokade" )), QRectF(-10 * sqrt2, -10 * sqrt2, 20 * sqrt2, 20 * sqrt2)); |
603 | QCOMPARE(renderer.boundsOnElement(QLatin1String("kaviar" )), QRectF(-10 * sqrt2, -10 * sqrt2, 20 * sqrt2, 20 * sqrt2)); |
604 | QCOMPARE(renderer.boundsOnElement(QLatin1String("nugatti" )), QRectF(-11, -11, 22, 22)); |
605 | QCOMPARE(renderer.boundsOnElement(QLatin1String("nutella" )), QRectF(-10 * sqrt2, -10 * sqrt2, 20 * sqrt2, 20 * sqrt2)); |
606 | QCOMPARE(renderer.boundsOnElement(QLatin1String("baconost" )), QRectF(-10 * sqrt2, -10 * sqrt2, 20 * sqrt2, 20 * sqrt2)); |
607 | QCOMPARE(renderer.boundsOnElement(QLatin1String("hapaa" )), QRectF(-13, -9, 22, 22)); |
608 | QCOMPARE(renderer.boundsOnElement(QLatin1String("prim" )), QRectF(-10 * sqrt2 - 3, -10 * sqrt2 + 1, 20 * sqrt2, 20 * sqrt2)); |
609 | } |
610 | |
611 | void tst_QSvgRenderer::gradientStops() const |
612 | { |
613 | { |
614 | QByteArray data("<svg>" |
615 | "<defs>" |
616 | "<linearGradient id=\"gradient\">" |
617 | "</linearGradient>" |
618 | "</defs>" |
619 | "<rect fill=\"url(#gradient)\" height=\"64\" width=\"64\" x=\"0\" y=\"0\"/>" |
620 | "</svg>" ); |
621 | QSvgRenderer renderer(data); |
622 | |
623 | QImage image(64, 64, QImage::Format_ARGB32_Premultiplied), refImage(64, 64, QImage::Format_ARGB32_Premultiplied); |
624 | image.fill(pixel: 0x87654321); |
625 | refImage.fill(pixel: 0x87654321); |
626 | |
627 | QPainter painter(&image); |
628 | renderer.render(p: &painter); |
629 | QCOMPARE(image, refImage); |
630 | } |
631 | |
632 | { |
633 | QByteArray data("<svg>" |
634 | "<defs>" |
635 | "<linearGradient id=\"gradient\">" |
636 | "<stop offset=\"1\" stop-color=\"cyan\"/>" |
637 | "</linearGradient>" |
638 | "</defs>" |
639 | "<rect fill=\"url(#gradient)\" height=\"64\" width=\"64\" x=\"0\" y=\"0\"/>" |
640 | "</svg>" ); |
641 | QSvgRenderer renderer(data); |
642 | |
643 | QImage image(64, 64, QImage::Format_ARGB32_Premultiplied), refImage(64, 64, QImage::Format_ARGB32_Premultiplied); |
644 | refImage.fill(pixel: 0xff00ffff); |
645 | |
646 | QPainter painter(&image); |
647 | renderer.render(p: &painter); |
648 | QCOMPARE(image, refImage); |
649 | } |
650 | |
651 | { |
652 | QByteArray data("<svg>" |
653 | "<defs>" |
654 | "<linearGradient id=\"gradient\">" |
655 | "<stop offset=\"0\" stop-color=\"red\"/>" |
656 | "<stop offset=\"0\" stop-color=\"cyan\"/>" |
657 | "<stop offset=\"0.5\" stop-color=\"cyan\"/>" |
658 | "<stop offset=\"0.5\" stop-color=\"magenta\"/>" |
659 | "<stop offset=\"0.5\" stop-color=\"yellow\"/>" |
660 | "<stop offset=\"1\" stop-color=\"yellow\"/>" |
661 | "<stop offset=\"1\" stop-color=\"blue\"/>" |
662 | "</linearGradient>" |
663 | "</defs>" |
664 | "<rect fill=\"url(#gradient)\" height=\"64\" width=\"64\" x=\"0\" y=\"0\"/>" |
665 | "</svg>" ); |
666 | QSvgRenderer renderer(data); |
667 | |
668 | QImage image(64, 64, QImage::Format_ARGB32_Premultiplied), refImage(64, 64, QImage::Format_ARGB32_Premultiplied); |
669 | |
670 | QPainter painter; |
671 | painter.begin(&refImage); |
672 | painter.fillRect(r: QRectF(0, 0, 32, 64), c: Qt::cyan); |
673 | painter.fillRect(r: QRectF(32, 0, 32, 64), c: Qt::yellow); |
674 | painter.end(); |
675 | |
676 | painter.begin(&image); |
677 | renderer.render(p: &painter); |
678 | painter.end(); |
679 | |
680 | QCOMPARE(image, refImage); |
681 | } |
682 | } |
683 | |
684 | void tst_QSvgRenderer::gradientRefs() |
685 | { |
686 | const char *svgs[] = { |
687 | "<svg>" |
688 | "<defs>" |
689 | "<linearGradient id=\"gradient\">" |
690 | "<stop offset=\"0\" stop-color=\"red\" stop-opacity=\"0\"/>" |
691 | "<stop offset=\"1\" stop-color=\"blue\"/>" |
692 | "</linearGradient>" |
693 | "</defs>" |
694 | "<rect fill=\"url(#gradient)\" height=\"8\" width=\"256\" x=\"0\" y=\"0\"/>" |
695 | "</svg>" , |
696 | "<svg>" |
697 | "<defs>" |
698 | "<linearGradient id=\"gradient\" xlink:href=\"#gradient0\">" |
699 | "</linearGradient>" |
700 | "<linearGradient id=\"gradient0\">" |
701 | "<stop offset=\"0\" stop-color=\"red\" stop-opacity=\"0\"/>" |
702 | "<stop offset=\"1\" stop-color=\"blue\"/>" |
703 | "</linearGradient>" |
704 | "</defs>" |
705 | "<rect fill=\"url(#gradient)\" height=\"8\" width=\"256\" x=\"0\" y=\"0\"/>" |
706 | "</svg>" , |
707 | "<svg>" |
708 | "<defs>" |
709 | "<linearGradient id=\"gradient0\">" |
710 | "<stop offset=\"0\" stop-color=\"red\" stop-opacity=\"0\"/>" |
711 | "<stop offset=\"1\" stop-color=\"blue\"/>" |
712 | "</linearGradient>" |
713 | "<linearGradient id=\"gradient\" xlink:href=\"#gradient0\">" |
714 | "</linearGradient>" |
715 | "</defs>" |
716 | "<rect fill=\"url(#gradient)\" height=\"8\" width=\"256\" x=\"0\" y=\"0\"/>" |
717 | "</svg>" , |
718 | "<svg>" |
719 | "<rect fill=\"url(#gradient)\" height=\"8\" width=\"256\" x=\"0\" y=\"0\"/>" |
720 | "<defs>" |
721 | "<linearGradient id=\"gradient0\">" |
722 | "<stop offset=\"0\" stop-color=\"red\" stop-opacity=\"0\"/>" |
723 | "<stop offset=\"1\" stop-color=\"blue\"/>" |
724 | "</linearGradient>" |
725 | "<linearGradient id=\"gradient\" xlink:href=\"#gradient0\">" |
726 | "</linearGradient>" |
727 | "</defs>" |
728 | "</svg>" , |
729 | "<svg>" |
730 | "<defs>" |
731 | "<linearGradient xlink:href=\"#0\" id=\"0\">" |
732 | "<stop offset=\"0\" stop-color=\"red\" stop-opacity=\"0\"/>" |
733 | "<stop offset=\"1\" stop-color=\"blue\"/>" |
734 | "</linearGradient>" |
735 | "</defs>" |
736 | "<rect fill=\"url(#0)\" height=\"8\" width=\"256\" x=\"0\" y=\"0\"/>" |
737 | "</svg>" |
738 | }; |
739 | for (size_t i = 0 ; i < sizeof(svgs) / sizeof(svgs[0]) ; ++i) |
740 | { |
741 | QByteArray data = svgs[i]; |
742 | QSvgRenderer renderer(data); |
743 | |
744 | QImage image(256, 8, QImage::Format_ARGB32_Premultiplied); |
745 | image.fill(pixel: 0); |
746 | |
747 | QPainter painter(&image); |
748 | renderer.render(p: &painter); |
749 | |
750 | const QRgb *line = reinterpret_cast<QRgb *>(image.scanLine(3)); |
751 | QRgb left = line[0]; // transparent black |
752 | QRgb mid = line[127]; // semi transparent magenta |
753 | QRgb right = line[255]; // opaque blue |
754 | |
755 | QVERIFY((qAlpha(left) < 3) && (qRed(left) < 3) && (qGreen(left) == 0) && (qBlue(left) < 3)); |
756 | QVERIFY((qAbs(qAlpha(mid) - 127) < 3) && (qAbs(qRed(mid) - 63) < 4) && (qGreen(mid) == 0) && (qAbs(qBlue(mid) - 63) < 4)); |
757 | QVERIFY((qAlpha(right) > 253) && (qRed(right) < 3) && (qGreen(right) == 0) && (qBlue(right) > 251)); |
758 | } |
759 | } |
760 | |
761 | void tst_QSvgRenderer::recursiveRefs_data() |
762 | { |
763 | QTest::addColumn<QByteArray>(name: "svg" ); |
764 | |
765 | QTest::newRow(dataTag: "single" ) << QByteArray("<svg>" |
766 | "<linearGradient id='0' xlink:href='#0'/>" |
767 | "<rect x='0' y='0' width='20' height='20' fill='url(#0)'/>" |
768 | "</svg>" ); |
769 | |
770 | QTest::newRow(dataTag: "double" ) << QByteArray("<svg>" |
771 | "<linearGradient id='0' xlink:href='#1'/>" |
772 | "<linearGradient id='1' xlink:href='#0'/>" |
773 | "<rect x='0' y='0' width='20' height='20' fill='url(#0)'/>" |
774 | "</svg>" ); |
775 | |
776 | QTest::newRow(dataTag: "triple" ) << QByteArray("<svg>" |
777 | "<linearGradient id='0' xlink:href='#1'/>" |
778 | "<linearGradient id='1' xlink:href='#2'/>" |
779 | "<linearGradient id='2' xlink:href='#0'/>" |
780 | "<rect x='0' y='0' width='20' height='20' fill='url(#0)'/>" |
781 | "</svg>" ); |
782 | } |
783 | |
784 | void tst_QSvgRenderer::recursiveRefs() |
785 | { |
786 | QFETCH(QByteArray, svg); |
787 | |
788 | QImage image(20, 20, QImage::Format_ARGB32_Premultiplied); |
789 | image.fill(color: Qt::green); |
790 | QImage refImage = image.copy(); |
791 | |
792 | QSvgRenderer renderer(svg); |
793 | QPainter painter(&image); |
794 | renderer.render(p: &painter); |
795 | QCOMPARE(image, refImage); |
796 | } |
797 | |
798 | |
799 | #ifndef QT_NO_COMPRESS |
800 | void tst_QSvgRenderer::testGzLoading() |
801 | { |
802 | QSvgRenderer renderer(QLatin1String(SRCDIR "heart.svgz" )); |
803 | QVERIFY(renderer.isValid()); |
804 | |
805 | QSvgRenderer resourceRenderer(QLatin1String(":/heart.svgz" )); |
806 | QVERIFY(resourceRenderer.isValid()); |
807 | |
808 | QFile largeFileGz(SRCDIR "large.svgz" ); |
809 | largeFileGz.open(flags: QIODevice::ReadOnly); |
810 | QByteArray data = largeFileGz.readAll(); |
811 | QSvgRenderer autoDetectGzData(data); |
812 | QVERIFY(autoDetectGzData.isValid()); |
813 | } |
814 | |
815 | #ifdef QT_BUILD_INTERNAL |
816 | QT_BEGIN_NAMESPACE |
817 | QByteArray qt_inflateGZipDataFrom(QIODevice *device); |
818 | QT_END_NAMESPACE |
819 | #endif |
820 | |
821 | void tst_QSvgRenderer::testGzHelper_data() |
822 | { |
823 | QTest::addColumn<QByteArray>(name: "in" ); |
824 | QTest::addColumn<QByteArray>(name: "out" ); |
825 | |
826 | QTest::newRow(dataTag: "empty" ) << QByteArray() << QByteArray(); |
827 | QTest::newRow(dataTag: "small" ) << QByteArray::fromHex(hexEncoded: QByteArray("1f8b08005819934800034b" |
828 | "cbcfe70200a865327e04000000" )) << QByteArray("foo\n" ); |
829 | |
830 | QFile largeFileGz("large.svgz" ); |
831 | largeFileGz.open(flags: QIODevice::ReadOnly); |
832 | QFile largeFile("large.svg" ); |
833 | largeFile.open(flags: QIODevice::ReadOnly); |
834 | QTest::newRow(dataTag: "large" ) << largeFileGz.readAll() << largeFile.readAll(); |
835 | |
836 | QTest::newRow(dataTag: "zeroes" ) << QByteArray::fromHex(hexEncoded: QByteArray("1f8b0800131f9348000333" |
837 | "301805a360148c54c00500d266708601040000" )) << QByteArray(1024, '0').append(c: '\n'); |
838 | |
839 | QTest::newRow(dataTag: "twoMembers" ) << QByteArray::fromHex(hexEncoded: QByteArray("1f8b08001c2a934800034b" |
840 | "cbcfe70200a865327e040000001f8b08001c2a934800034b4a2ce20200e9b3a20404000000" )) |
841 | << QByteArray("foo\nbar\n" ); |
842 | |
843 | QTest::newRow(dataTag: "corruptedSecondMember" ) << QByteArray::fromHex(hexEncoded: QByteArray("1f8b08001c2a934800034b" |
844 | "cbcfe70200a865327e040000001f8c08001c2a934800034b4a2ce20200e9b3a20404000000" )) |
845 | << QByteArray(); |
846 | |
847 | } |
848 | |
849 | void tst_QSvgRenderer::testGzHelper() |
850 | { |
851 | #ifdef QT_BUILD_INTERNAL |
852 | QFETCH(QByteArray, in); |
853 | QFETCH(QByteArray, out); |
854 | |
855 | QBuffer buffer(&in); |
856 | buffer.open(openMode: QIODevice::ReadOnly); |
857 | QVERIFY(buffer.isReadable()); |
858 | QByteArray result = qt_inflateGZipDataFrom(device: &buffer); |
859 | QCOMPARE(result, out); |
860 | #endif |
861 | } |
862 | #endif |
863 | |
864 | void tst_QSvgRenderer::fillRule() |
865 | { |
866 | static const char *svgs[] = { |
867 | // Paths |
868 | // Default fill-rule (nonzero) |
869 | "<svg>" |
870 | " <rect x=\"0\" y=\"0\" height=\"30\" width=\"30\" fill=\"blue\" />" |
871 | " <path d=\"M10 0 L30 0 L30 30 L0 30 L0 10 L20 10 L20 20 L10 20 Z\" fill=\"red\" />" |
872 | "</svg>" , |
873 | // nonzero |
874 | "<svg>" |
875 | " <rect x=\"0\" y=\"0\" height=\"30\" width=\"30\" fill=\"blue\" />" |
876 | " <path d=\"M10 0 L30 0 L30 30 L0 30 L0 10 L20 10 L20 20 L10 20 Z\" fill=\"red\" fill-rule=\"nonzero\" />" |
877 | "</svg>" , |
878 | // evenodd |
879 | "<svg>" |
880 | " <rect x=\"0\" y=\"0\" height=\"30\" width=\"30\" fill=\"blue\" />" |
881 | " <path d=\"M10 0 L30 0 L30 30 L0 30 L0 10 L20 10 L20 20 L10 20 Z\" fill=\"red\" fill-rule=\"evenodd\" />" |
882 | "</svg>" , |
883 | |
884 | // Polygons |
885 | // Default fill-rule (nonzero) |
886 | "<svg>" |
887 | " <rect x=\"0\" y=\"0\" height=\"30\" width=\"30\" fill=\"blue\" />" |
888 | " <polygon points=\"10 0 30 0 30 30 0 30 0 10 20 10 20 20 10 20\" fill=\"red\" />" |
889 | "</svg>" , |
890 | // nonzero |
891 | "<svg>" |
892 | " <rect x=\"0\" y=\"0\" height=\"30\" width=\"30\" fill=\"blue\" />" |
893 | " <polygon points=\"10 0 30 0 30 30 0 30 0 10 20 10 20 20 10 20\" fill=\"red\" fill-rule=\"nonzero\" />" |
894 | "</svg>" , |
895 | // evenodd |
896 | "<svg>" |
897 | " <rect x=\"0\" y=\"0\" height=\"30\" width=\"30\" fill=\"blue\" />" |
898 | " <polygon points=\"10 0 30 0 30 30 0 30 0 10 20 10 20 20 10 20\" fill=\"red\" fill-rule=\"evenodd\" />" |
899 | "</svg>" |
900 | }; |
901 | |
902 | const int COUNT = sizeof(svgs) / sizeof(svgs[0]); |
903 | QImage refImageNonZero(30, 30, QImage::Format_ARGB32_Premultiplied); |
904 | QImage refImageEvenOdd(30, 30, QImage::Format_ARGB32_Premultiplied); |
905 | refImageNonZero.fill(pixel: 0xffff0000); |
906 | refImageEvenOdd.fill(pixel: 0xffff0000); |
907 | QPainter p; |
908 | p.begin(&refImageNonZero); |
909 | p.fillRect(r: QRectF(0, 0, 10, 10), c: Qt::blue); |
910 | p.end(); |
911 | p.begin(&refImageEvenOdd); |
912 | p.fillRect(r: QRectF(0, 0, 10, 10), c: Qt::blue); |
913 | p.fillRect(r: QRectF(10, 10, 10, 10), c: Qt::blue); |
914 | p.end(); |
915 | |
916 | for (int i = 0; i < COUNT; ++i) { |
917 | QByteArray data(svgs[i]); |
918 | QSvgRenderer renderer(data); |
919 | QImage image(30, 30, QImage::Format_ARGB32_Premultiplied); |
920 | image.fill(pixel: 0); |
921 | p.begin(&image); |
922 | renderer.render(p: &p); |
923 | p.end(); |
924 | QCOMPARE(image, i % 3 == 2 ? refImageEvenOdd : refImageNonZero); |
925 | } |
926 | } |
927 | |
928 | static void opacity_drawSvgAndVerify(const QByteArray &data) |
929 | { |
930 | QSvgRenderer renderer(data); |
931 | QVERIFY(renderer.isValid()); |
932 | QImage image(10, 10, QImage::Format_ARGB32_Premultiplied); |
933 | image.fill(pixel: 0xffff00ff); |
934 | QPainter painter(&image); |
935 | renderer.render(p: &painter); |
936 | painter.end(); |
937 | QCOMPARE(image.pixel(5, 5), 0xff7f7f7f); |
938 | } |
939 | |
940 | void tst_QSvgRenderer::opacity() |
941 | { |
942 | static const char *opacities[] = {"-1.4641" , "0" , "0.5" , "1" , "1.337" }; |
943 | static const char *firstColors[] = {"#7f7f7f" , "#7f7f7f" , "#402051" , "blue" , "#123456" }; |
944 | static const char *secondColors[] = {"red" , "#bad" , "#bedead" , "#7f7f7f" , "#7f7f7f" }; |
945 | |
946 | // Fill-opacity |
947 | for (int i = 0; i < 5; ++i) { |
948 | QByteArray data("<svg><rect x=\"0\" y=\"0\" height=\"10\" width=\"10\" fill=\"" ); |
949 | data.append(s: firstColors[i]); |
950 | data.append(s: "\"/><rect x=\"0\" y=\"0\" height=\"10\" width=\"10\" fill=\"" ); |
951 | data.append(s: secondColors[i]); |
952 | data.append(s: "\" fill-opacity=\"" ); |
953 | data.append(s: opacities[i]); |
954 | data.append(s: "\"/></svg>" ); |
955 | opacity_drawSvgAndVerify(data); |
956 | } |
957 | // Stroke-opacity |
958 | for (int i = 0; i < 5; ++i) { |
959 | QByteArray data("<svg viewBox=\"0 0 10 10\"><polyline points=\"0 5 10 5\" fill=\"none\" stroke=\"" ); |
960 | data.append(s: firstColors[i]); |
961 | data.append(s: "\" stroke-width=\"10\"/><line x1=\"5\" y1=\"0\" x2=\"5\" y2=\"10\" fill=\"none\" stroke=\"" ); |
962 | data.append(s: secondColors[i]); |
963 | data.append(s: "\" stroke-width=\"10\" stroke-opacity=\"" ); |
964 | data.append(s: opacities[i]); |
965 | data.append(s: "\"/></svg>" ); |
966 | opacity_drawSvgAndVerify(data); |
967 | } |
968 | // As gradients: |
969 | // Fill-opacity |
970 | for (int i = 0; i < 5; ++i) { |
971 | QByteArray data("<svg><defs><linearGradient id=\"gradient\"><stop offset=\"0\" stop-color=\"" ); |
972 | data.append(s: secondColors[i]); |
973 | data.append(s: "\"/><stop offset=\"1\" stop-color=\"" ); |
974 | data.append(s: secondColors[i]); |
975 | data.append(s: "\"/></linearGradient></defs><rect x=\"0\" y=\"0\" height=\"10\" width=\"10\" fill=\"" ); |
976 | data.append(s: firstColors[i]); |
977 | data.append(s: "\"/><rect x=\"0\" y=\"0\" height=\"10\" width=\"10\" fill=\"url(#gradient)\" fill-opacity=\"" ); |
978 | data.append(s: opacities[i]); |
979 | data.append(s: "\"/></svg>" ); |
980 | opacity_drawSvgAndVerify(data); |
981 | } |
982 | // Stroke-opacity |
983 | for (int i = 0; i < 5; ++i) { |
984 | QByteArray data("<svg viewBox=\"0 0 10 10\"><defs><linearGradient id=\"grad\"><stop offset=\"0\" stop-color=\"" ); |
985 | data.append(s: secondColors[i]); |
986 | data.append(s: "\"/><stop offset=\"1\" stop-color=\"" ); |
987 | data.append(s: secondColors[i]); |
988 | data.append(s: "\"/></linearGradient></defs><polyline points=\"0 5 10 5\" fill=\"none\" stroke=\"" ); |
989 | data.append(s: firstColors[i]); |
990 | data.append(s: "\" stroke-width=\"10\" /><line x1=\"5\" y1=\"0\" x2=\"5\" y2=\"10\" fill=\"none\" stroke=\"url(#grad)\" stroke-width=\"10\" stroke-opacity=\"" ); |
991 | data.append(s: opacities[i]); |
992 | data.append(s: "\" /></svg>" ); |
993 | opacity_drawSvgAndVerify(data); |
994 | } |
995 | } |
996 | |
997 | void tst_QSvgRenderer::paths() |
998 | { |
999 | static const char *svgs[] = { |
1000 | // Absolute coordinates, explicit commands. |
1001 | "<svg>" |
1002 | " <rect x=\"0\" y=\"0\" height=\"50\" width=\"50\" fill=\"blue\" />" |
1003 | " <path d=\"M50 0 V50 H0 Q0 25 25 25 T50 0 C25 0 50 50 25 50 S25 0 0 0 Z\" fill=\"red\" fill-rule=\"evenodd\"/>" |
1004 | "</svg>" , |
1005 | // Absolute coordinates, implicit commands. |
1006 | "<svg>" |
1007 | " <rect x=\"0\" y=\"0\" height=\"50\" width=\"50\" fill=\"blue\" />" |
1008 | " <path d=\"M50 0 50 50 0 50 Q0 25 25 25 Q50 25 50 0 C25 0 50 50 25 50 C0 50 25 0 0 0 Z\" fill=\"red\" fill-rule=\"evenodd\" />" |
1009 | "</svg>" , |
1010 | // Relative coordinates, explicit commands. |
1011 | "<svg>" |
1012 | " <rect x=\"0\" y=\"0\" height=\"50\" width=\"50\" fill=\"blue\" />" |
1013 | " <path d=\"m50 0 v50 h-50 q0 -25 25 -25 t25 -25 c-25 0 0 50 -25 50 s0 -50 -25 -50 z\" fill=\"red\" fill-rule=\"evenodd\" />" |
1014 | "</svg>" , |
1015 | // Relative coordinates, implicit commands. |
1016 | "<svg>" |
1017 | " <rect x=\"0\" y=\"0\" height=\"50\" width=\"50\" fill=\"blue\" />" |
1018 | " <path d=\"m50 0 0 50 -50 0 q0 -25 25 -25 25 0 25 -25 c-25 0 0 50 -25 50 -25 0 0 -50 -25 -50 z\" fill=\"red\" fill-rule=\"evenodd\" />" |
1019 | "</svg>" , |
1020 | // Absolute coordinates, explicit commands, minimal whitespace. |
1021 | "<svg>" |
1022 | " <rect x=\"0\" y=\"0\" height=\"50\" width=\"50\" fill=\"blue\" />" |
1023 | " <path d=\"m50 0v50h-50q0-25 25-25t25-25c-25 0 0 50-25 50s0-50-25-50z\" fill=\"red\" fill-rule=\"evenodd\" />" |
1024 | "</svg>" , |
1025 | // Absolute coordinates, explicit commands, extra whitespace. |
1026 | "<svg>" |
1027 | " <rect x=\"0\" y=\"0\" height=\"50\" width=\"50\" fill=\"blue\" />" |
1028 | " <path d=\" M 50 0 V 50 H 0 Q 0 25 25 25 T 50 0 C 25 0 50 50 25 50 S 25 0 0 0 Z \" fill=\"red\" fill-rule=\"evenodd\" />" |
1029 | "</svg>" |
1030 | }; |
1031 | |
1032 | const int COUNT = sizeof(svgs) / sizeof(svgs[0]); |
1033 | QImage images[COUNT]; |
1034 | QPainter p; |
1035 | |
1036 | for (int i = 0; i < COUNT; ++i) { |
1037 | QByteArray data(svgs[i]); |
1038 | QSvgRenderer renderer(data); |
1039 | QVERIFY(renderer.isValid()); |
1040 | images[i] = QImage(50, 50, QImage::Format_ARGB32_Premultiplied); |
1041 | images[i].fill(pixel: 0); |
1042 | p.begin(&images[i]); |
1043 | renderer.render(p: &p); |
1044 | p.end(); |
1045 | if (i != 0) { |
1046 | QCOMPARE(images[i], images[0]); |
1047 | } |
1048 | } |
1049 | } |
1050 | |
1051 | void tst_QSvgRenderer::paths2() |
1052 | { |
1053 | const char *svg = |
1054 | "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\">" |
1055 | "<path d=\"M 3 8 A 5 5 0 1013 8\" id=\"path1\"/>" |
1056 | "</svg>" ; |
1057 | |
1058 | QByteArray data(svg); |
1059 | QSvgRenderer renderer(data); |
1060 | QVERIFY(renderer.isValid()); |
1061 | QCOMPARE(renderer.boundsOnElement(QLatin1String("path1" )).toRect(), QRect(3, 8, 10, 5)); |
1062 | } |
1063 | |
1064 | void tst_QSvgRenderer::displayMode() |
1065 | { |
1066 | static const char *svgs[] = { |
1067 | // All visible. |
1068 | "<svg>" |
1069 | " <g>" |
1070 | " <rect x=\"0\" y=\"0\" height=\"10\" width=\"10\" fill=\"red\" />" |
1071 | " <rect x=\"0\" y=\"0\" height=\"10\" width=\"10\" fill=\"blue\" />" |
1072 | " </g>" |
1073 | "</svg>" , |
1074 | // Don't display svg element. |
1075 | "<svg display=\"none\">" |
1076 | " <g>" |
1077 | " <rect x=\"0\" y=\"0\" height=\"10\" width=\"10\" fill=\"red\" />" |
1078 | " <rect x=\"0\" y=\"0\" height=\"10\" width=\"10\" fill=\"blue\" />" |
1079 | " </g>" |
1080 | "</svg>" , |
1081 | // Don't display g element. |
1082 | "<svg>" |
1083 | " <g display=\"none\">" |
1084 | " <rect x=\"0\" y=\"0\" height=\"10\" width=\"10\" fill=\"red\" />" |
1085 | " <rect x=\"0\" y=\"0\" height=\"10\" width=\"10\" fill=\"blue\" />" |
1086 | " </g>" |
1087 | "</svg>" , |
1088 | // Don't display first rect element. |
1089 | "<svg>" |
1090 | " <g>" |
1091 | " <rect x=\"0\" y=\"0\" height=\"10\" width=\"10\" fill=\"red\" display=\"none\" />" |
1092 | " <rect x=\"0\" y=\"0\" height=\"10\" width=\"10\" fill=\"blue\" />" |
1093 | " </g>" |
1094 | "</svg>" , |
1095 | // Don't display second rect element. |
1096 | "<svg>" |
1097 | " <g>" |
1098 | " <rect x=\"0\" y=\"0\" height=\"10\" width=\"10\" fill=\"red\" />" |
1099 | " <rect x=\"0\" y=\"0\" height=\"10\" width=\"10\" fill=\"blue\" display=\"none\" />" |
1100 | " </g>" |
1101 | "</svg>" , |
1102 | // Don't display svg element, but set display mode to "inline" for other elements. |
1103 | "<svg display=\"none\">" |
1104 | " <g display=\"inline\">" |
1105 | " <rect x=\"0\" y=\"0\" height=\"10\" width=\"10\" fill=\"red\" display=\"inline\" />" |
1106 | " <rect x=\"0\" y=\"0\" height=\"10\" width=\"10\" fill=\"blue\" display=\"inline\" />" |
1107 | " </g>" |
1108 | "</svg>" |
1109 | }; |
1110 | |
1111 | QRgb expectedColors[] = {0xff0000ff, 0xff00ff00, 0xff00ff00, 0xff0000ff, 0xffff0000, 0xff00ff00}; |
1112 | |
1113 | const int COUNT = sizeof(svgs) / sizeof(svgs[0]); |
1114 | QPainter p; |
1115 | |
1116 | for (int i = 0; i < COUNT; ++i) { |
1117 | QByteArray data(svgs[i]); |
1118 | QSvgRenderer renderer(data); |
1119 | QVERIFY(renderer.isValid()); |
1120 | QImage image(10, 10, QImage::Format_ARGB32_Premultiplied); |
1121 | image.fill(pixel: 0xff00ff00); |
1122 | p.begin(&image); |
1123 | renderer.render(p: &p); |
1124 | p.end(); |
1125 | QCOMPARE(image.pixel(5, 5), expectedColors[i]); |
1126 | } |
1127 | } |
1128 | |
1129 | void tst_QSvgRenderer::strokeInherit() |
1130 | { |
1131 | static const char *svgs[] = { |
1132 | // Reference. |
1133 | "<svg viewBox=\"0 0 200 30\">" |
1134 | " <g stroke=\"blue\" stroke-width=\"20\" stroke-linecap=\"butt\"" |
1135 | " stroke-linejoin=\"miter\" stroke-miterlimit=\"1\" stroke-dasharray=\"20,10\"" |
1136 | " stroke-dashoffset=\"10\" stroke-opacity=\"0.5\">" |
1137 | " <polyline fill=\"none\" points=\"10 10 100 10 100 20 190 20\"/>" |
1138 | " </g>" |
1139 | " <g stroke=\"green\" stroke-width=\"0\" stroke-dasharray=\"3,3,1\" stroke-dashoffset=\"4.5\">" |
1140 | " <polyline fill=\"none\" points=\"10 25 80 25\"/>" |
1141 | " </g>" |
1142 | "</svg>" , |
1143 | // stroke |
1144 | "<svg viewBox=\"0 0 200 30\">" |
1145 | " <g stroke=\"none\" stroke-width=\"20\" stroke-linecap=\"butt\"" |
1146 | " stroke-linejoin=\"miter\" stroke-miterlimit=\"1\" stroke-dasharray=\"20,10\"" |
1147 | " stroke-dashoffset=\"10\" stroke-opacity=\"0.5\">" |
1148 | " <polyline fill=\"none\" points=\"10 10 100 10 100 20 190 20\" stroke=\"blue\"/>" |
1149 | " </g>" |
1150 | " <g stroke=\"yellow\" stroke-width=\"0\" stroke-dasharray=\"3,3,1\" stroke-dashoffset=\"4.5\">" |
1151 | " <polyline fill=\"none\" points=\"10 25 80 25\" stroke=\"green\"/>" |
1152 | " </g>" |
1153 | "</svg>" , |
1154 | // stroke-width |
1155 | "<svg viewBox=\"0 0 200 30\">" |
1156 | " <g stroke=\"blue\" stroke-width=\"0\" stroke-linecap=\"butt\"" |
1157 | " stroke-linejoin=\"miter\" stroke-miterlimit=\"1\" stroke-dasharray=\"20,10\"" |
1158 | " stroke-dashoffset=\"10\" stroke-opacity=\"0.5\">" |
1159 | " <polyline fill=\"none\" points=\"10 10 100 10 100 20 190 20\" stroke-width=\"20\"/>" |
1160 | " </g>" |
1161 | " <g stroke=\"green\" stroke-width=\"10\" stroke-dasharray=\"3,3,1\" stroke-dashoffset=\"4.5\">" |
1162 | " <polyline fill=\"none\" points=\"10 25 80 25\" stroke-width=\"0\"/>" |
1163 | " </g>" |
1164 | "</svg>" , |
1165 | // stroke-linecap |
1166 | "<svg viewBox=\"0 0 200 30\">" |
1167 | " <g stroke=\"blue\" stroke-width=\"20\" stroke-linecap=\"round\"" |
1168 | " stroke-linejoin=\"miter\" stroke-miterlimit=\"1\" stroke-dasharray=\"20,10\"" |
1169 | " stroke-dashoffset=\"10\" stroke-opacity=\"0.5\">" |
1170 | " <polyline fill=\"none\" points=\"10 10 100 10 100 20 190 20\" stroke-linecap=\"butt\"/>" |
1171 | " </g>" |
1172 | " <g stroke=\"green\" stroke-width=\"0\" stroke-dasharray=\"3,3,1\" stroke-dashoffset=\"4.5\">" |
1173 | " <polyline fill=\"none\" points=\"10 25 80 25\"/>" |
1174 | " </g>" |
1175 | "</svg>" , |
1176 | // stroke-linejoin |
1177 | "<svg viewBox=\"0 0 200 30\">" |
1178 | " <g stroke=\"blue\" stroke-width=\"20\" stroke-linecap=\"butt\"" |
1179 | " stroke-linejoin=\"round\" stroke-miterlimit=\"1\" stroke-dasharray=\"20,10\"" |
1180 | " stroke-dashoffset=\"10\" stroke-opacity=\"0.5\">" |
1181 | " <polyline fill=\"none\" points=\"10 10 100 10 100 20 190 20\" stroke-linejoin=\"miter\"/>" |
1182 | " </g>" |
1183 | " <g stroke=\"green\" stroke-width=\"0\" stroke-dasharray=\"3,3,1\" stroke-dashoffset=\"4.5\">" |
1184 | " <polyline fill=\"none\" points=\"10 25 80 25\"/>" |
1185 | " </g>" |
1186 | "</svg>" , |
1187 | // stroke-miterlimit |
1188 | "<svg viewBox=\"0 0 200 30\">" |
1189 | " <g stroke=\"blue\" stroke-width=\"20\" stroke-linecap=\"butt\"" |
1190 | " stroke-linejoin=\"miter\" stroke-miterlimit=\"2\" stroke-dasharray=\"20,10\"" |
1191 | " stroke-dashoffset=\"10\" stroke-opacity=\"0.5\">" |
1192 | " <polyline fill=\"none\" points=\"10 10 100 10 100 20 190 20\" stroke-miterlimit=\"1\"/>" |
1193 | " </g>" |
1194 | " <g stroke=\"green\" stroke-width=\"0\" stroke-dasharray=\"3,3,1\" stroke-dashoffset=\"4.5\">" |
1195 | " <polyline fill=\"none\" points=\"10 25 80 25\"/>" |
1196 | " </g>" |
1197 | "</svg>" , |
1198 | // stroke-dasharray |
1199 | "<svg viewBox=\"0 0 200 30\">" |
1200 | " <g stroke=\"blue\" stroke-width=\"20\" stroke-linecap=\"butt\"" |
1201 | " stroke-linejoin=\"miter\" stroke-miterlimit=\"1\" stroke-dasharray=\"1,1,1,1,1,1,3,1,3,1,3,1,1,1,1,1,1,3\"" |
1202 | " stroke-dashoffset=\"10\" stroke-opacity=\"0.5\">" |
1203 | " <polyline fill=\"none\" points=\"10 10 100 10 100 20 190 20\" stroke-dasharray=\"20,10\"/>" |
1204 | " </g>" |
1205 | " <g stroke=\"green\" stroke-width=\"0\" stroke-dasharray=\"none\" stroke-dashoffset=\"4.5\">" |
1206 | " <polyline fill=\"none\" points=\"10 25 80 25\" stroke-dasharray=\"3,3,1\"/>" |
1207 | " </g>" |
1208 | "</svg>" , |
1209 | // stroke-dashoffset |
1210 | "<svg viewBox=\"0 0 200 30\">" |
1211 | " <g stroke=\"blue\" stroke-width=\"20\" stroke-linecap=\"butt\"" |
1212 | " stroke-linejoin=\"miter\" stroke-miterlimit=\"1\" stroke-dasharray=\"20,10\"" |
1213 | " stroke-dashoffset=\"0\" stroke-opacity=\"0.5\">" |
1214 | " <polyline fill=\"none\" points=\"10 10 100 10 100 20 190 20\" stroke-dashoffset=\"10\"/>" |
1215 | " </g>" |
1216 | " <g stroke=\"green\" stroke-width=\"0\" stroke-dasharray=\"3,3,1\" stroke-dashoffset=\"0\">" |
1217 | " <polyline fill=\"none\" points=\"10 25 80 25\" stroke-dashoffset=\"4.5\"/>" |
1218 | " </g>" |
1219 | "</svg>" , |
1220 | // stroke-opacity |
1221 | "<svg viewBox=\"0 0 200 30\">" |
1222 | " <g stroke=\"blue\" stroke-width=\"20\" stroke-linecap=\"butt\"" |
1223 | " stroke-linejoin=\"miter\" stroke-miterlimit=\"1\" stroke-dasharray=\"20,10\"" |
1224 | " stroke-dashoffset=\"10\" stroke-opacity=\"0\">" |
1225 | " <polyline fill=\"none\" points=\"10 10 100 10 100 20 190 20\" stroke-opacity=\"0.5\"/>" |
1226 | " </g>" |
1227 | " <g stroke=\"green\" stroke-width=\"0\" stroke-dasharray=\"3,3,1\" stroke-dashoffset=\"4.5\">" |
1228 | " <polyline fill=\"none\" points=\"10 25 80 25\"/>" |
1229 | " </g>" |
1230 | "</svg>" |
1231 | }; |
1232 | |
1233 | const int COUNT = sizeof(svgs) / sizeof(svgs[0]); |
1234 | QImage images[COUNT]; |
1235 | QPainter p; |
1236 | |
1237 | for (int i = 0; i < COUNT; ++i) { |
1238 | QByteArray data(svgs[i]); |
1239 | QSvgRenderer renderer(data); |
1240 | QVERIFY(renderer.isValid()); |
1241 | images[i] = QImage(200, 30, QImage::Format_ARGB32_Premultiplied); |
1242 | images[i].fill(pixel: -1); |
1243 | p.begin(&images[i]); |
1244 | renderer.render(p: &p); |
1245 | p.end(); |
1246 | if (i != 0) { |
1247 | QCOMPARE(images[0], images[i]); |
1248 | } |
1249 | } |
1250 | } |
1251 | |
1252 | void tst_QSvgRenderer::testFillInheritance() |
1253 | { |
1254 | static const char *svgs[] = { |
1255 | //reference |
1256 | "<svg viewBox = \"0 0 200 200\">" |
1257 | " <polygon points=\"20,20 50,120 100,10 40,80 50,80\" fill= \"red\" stroke = \"blue\" fill-opacity = \"0.5\" fill-rule = \"evenodd\"/>" |
1258 | "</svg>" , |
1259 | "<svg viewBox = \"0 0 200 200\">" |
1260 | " <polygon points=\"20,20 50,120 100,10 40,80 50,80\" fill= \"red\" stroke = \"blue\" fill-opacity = \"0.5\" fill-rule = \"evenodd\"/>" |
1261 | " <rect x = \"40\" y = \"40\" width = \"70\" height =\"20\" fill = \"green\" fill-opacity = \"0\"/>" |
1262 | "</svg>" , |
1263 | "<svg viewBox = \"0 0 200 200\">" |
1264 | " <g fill = \"red\" fill-opacity = \"0.5\" fill-rule = \"evenodd\">" |
1265 | " <polygon points=\"20,20 50,120 100,10 40,80 50,80\" stroke = \"blue\"/>" |
1266 | " </g>" |
1267 | " <rect x = \"40\" y = \"40\" width = \"70\" height =\"20\" fill = \"green\" fill-opacity = \"0\"/>" |
1268 | "</svg>" , |
1269 | "<svg viewBox = \"0 0 200 200\">" |
1270 | " <g fill = \"green\" fill-rule = \"nonzero\">" |
1271 | " <polygon points=\"20,20 50,120 100,10 40,80 50,80\" stroke = \"blue\" fill = \"red\" fill-opacity = \"0.5\" fill-rule = \"evenodd\"/>" |
1272 | " </g>" |
1273 | " <g fill-opacity = \"0.8\" fill = \"red\">" |
1274 | " <rect x = \"40\" y = \"40\" width = \"70\" height =\"20\" fill = \"green\" fill-opacity = \"0\"/>" |
1275 | " </g>" |
1276 | "</svg>" , |
1277 | "<svg viewBox = \"0 0 200 200\">" |
1278 | " <g fill = \"red\" >" |
1279 | " <g fill-opacity = \"0.5\">" |
1280 | " <g fill-rule = \"evenodd\">" |
1281 | " <g>" |
1282 | " <polygon points=\"20,20 50,120 100,10 40,80 50,80\" stroke = \"blue\"/>" |
1283 | " </g>" |
1284 | " </g>" |
1285 | " </g>" |
1286 | " </g>" |
1287 | " <g fill-opacity = \"0.8\" >" |
1288 | " <rect x = \"40\" y = \"40\" width = \"70\" height =\"20\" fill = \"none\"/>" |
1289 | " </g>" |
1290 | "</svg>" , |
1291 | "<svg viewBox = \"0 0 200 200\">" |
1292 | " <g fill = \"none\" fill-opacity = \"0\">" |
1293 | " <polygon points=\"20,20 50,120 100,10 40,80 50,80\" stroke = \"blue\" fill = \"red\" fill-opacity = \"0.5\" fill-rule = \"evenodd\"/>" |
1294 | " </g>" |
1295 | " <g fill-opacity = \"0\" >" |
1296 | " <rect x = \"40\" y = \"40\" width = \"70\" height =\"20\" fill = \"green\"/>" |
1297 | " </g>" |
1298 | "</svg>" |
1299 | }; |
1300 | |
1301 | const int COUNT = sizeof(svgs) / sizeof(svgs[0]); |
1302 | QImage images[COUNT]; |
1303 | QPainter p; |
1304 | |
1305 | for (int i = 0; i < COUNT; ++i) { |
1306 | QByteArray data(svgs[i]); |
1307 | QSvgRenderer renderer(data); |
1308 | QVERIFY(renderer.isValid()); |
1309 | images[i] = QImage(200, 200, QImage::Format_ARGB32_Premultiplied); |
1310 | images[i].fill(pixel: -1); |
1311 | p.begin(&images[i]); |
1312 | renderer.render(p: &p); |
1313 | p.end(); |
1314 | if (i != 0) { |
1315 | QCOMPARE(images[0], images[i]); |
1316 | } |
1317 | } |
1318 | } |
1319 | void tst_QSvgRenderer::testStopOffsetOpacity() |
1320 | { |
1321 | static const char *svgs[] = { |
1322 | //reference |
1323 | "<svg viewBox=\"0 0 64 64\">" |
1324 | "<radialGradient id=\"MyGradient1\" gradientUnits=\"userSpaceOnUse\" cx=\"50\" cy=\"50\" r=\"30\" fx=\"20\" fy=\"20\">" |
1325 | "<stop offset=\"0.0\" style=\"stop-color:red\" stop-opacity=\"0.3\"/>" |
1326 | "<stop offset=\"0.5\" style=\"stop-color:green\" stop-opacity=\"1\"/>" |
1327 | "<stop offset=\"1\" style=\"stop-color:yellow\" stop-opacity=\"1\"/>" |
1328 | "</radialGradient>" |
1329 | "<radialGradient id=\"MyGradient2\" gradientUnits=\"userSpaceOnUse\" cx=\"50\" cy=\"70\" r=\"70\" fx=\"20\" fy=\"20\">" |
1330 | "<stop offset=\"0.0\" style=\"stop-color:blue\" stop-opacity=\"0.3\"/>" |
1331 | "<stop offset=\"0.5\" style=\"stop-color:violet\" stop-opacity=\"1\"/>" |
1332 | "<stop offset=\"1\" style=\"stop-color:orange\" stop-opacity=\"1\"/>" |
1333 | "</radialGradient>" |
1334 | "<rect x=\"5\" y=\"5\" width=\"55\" height=\"55\" fill=\"url(#MyGradient1)\" stroke=\"black\" />" |
1335 | "<rect x=\"20\" y=\"20\" width=\"35\" height=\"35\" fill=\"url(#MyGradient2)\"/>" |
1336 | "</svg>" , |
1337 | //Stop Offset |
1338 | "<svg viewBox=\"0 0 64 64\">" |
1339 | "<radialGradient id=\"MyGradient1\" gradientUnits=\"userSpaceOnUse\" cx=\"50\" cy=\"50\" r=\"30\" fx=\"20\" fy=\"20\">" |
1340 | "<stop offset=\"abc\" style=\"stop-color:red\" stop-opacity=\"0.3\"/>" |
1341 | "<stop offset=\"0.5\" style=\"stop-color:green\" stop-opacity=\"1\"/>" |
1342 | "<stop offset=\"1\" style=\"stop-color:yellow\" stop-opacity=\"1\"/>" |
1343 | "</radialGradient>" |
1344 | "<radialGradient id=\"MyGradient2\" gradientUnits=\"userSpaceOnUse\" cx=\"50\" cy=\"70\" r=\"70\" fx=\"20\" fy=\"20\">" |
1345 | "<stop offset=\"-3.bc\" style=\"stop-color:blue\" stop-opacity=\"0.3\"/>" |
1346 | "<stop offset=\"0.5\" style=\"stop-color:violet\" stop-opacity=\"1\"/>" |
1347 | "<stop offset=\"1\" style=\"stop-color:orange\" stop-opacity=\"1\"/>" |
1348 | "</radialGradient>" |
1349 | "<rect x=\"5\" y=\"5\" width=\"55\" height=\"55\" fill=\"url(#MyGradient1)\" stroke=\"black\" />" |
1350 | "<rect x=\"20\" y=\"20\" width=\"35\" height=\"35\" fill=\"url(#MyGradient2)\"/>" |
1351 | "</svg>" , |
1352 | //Stop Opacity |
1353 | "<svg viewBox=\"0 0 64 64\">" |
1354 | "<radialGradient id=\"MyGradient1\" gradientUnits=\"userSpaceOnUse\" cx=\"50\" cy=\"50\" r=\"30\" fx=\"20\" fy=\"20\">" |
1355 | "<stop offset=\"0.0\" style=\"stop-color:red\" stop-opacity=\"0.3\"/>" |
1356 | "<stop offset=\"0.5\" style=\"stop-color:green\" stop-opacity=\"x.45\"/>" |
1357 | "<stop offset=\"1\" style=\"stop-color:yellow\" stop-opacity=\"-3.abc\"/>" |
1358 | "</radialGradient>" |
1359 | "<radialGradient id=\"MyGradient2\" gradientUnits=\"userSpaceOnUse\" cx=\"50\" cy=\"70\" r=\"70\" fx=\"20\" fy=\"20\">" |
1360 | "<stop offset=\"0.0\" style=\"stop-color:blue\" stop-opacity=\"0.3\"/>" |
1361 | "<stop offset=\"0.5\" style=\"stop-color:violet\" stop-opacity=\"-0.xy\"/>" |
1362 | "<stop offset=\"1\" style=\"stop-color:orange\" stop-opacity=\"z.5\"/>" |
1363 | "</radialGradient>" |
1364 | "<rect x=\"5\" y=\"5\" width=\"55\" height=\"55\" fill=\"url(#MyGradient1)\" stroke=\"black\" />" |
1365 | "<rect x=\"20\" y=\"20\" width=\"35\" height=\"35\" fill=\"url(#MyGradient2)\"/>" |
1366 | "</svg>" , |
1367 | //Stop offset and Stop opacity |
1368 | "<svg viewBox=\"0 0 64 64\">" |
1369 | "<radialGradient id=\"MyGradient1\" gradientUnits=\"userSpaceOnUse\" cx=\"50\" cy=\"50\" r=\"30\" fx=\"20\" fy=\"20\">" |
1370 | "<stop offset=\"abc\" style=\"stop-color:red\" stop-opacity=\"0.3\"/>" |
1371 | "<stop offset=\"0.5\" style=\"stop-color:green\" stop-opacity=\"x.45\"/>" |
1372 | "<stop offset=\"1\" style=\"stop-color:yellow\" stop-opacity=\"-3.abc\"/>" |
1373 | "</radialGradient>" |
1374 | "<radialGradient id=\"MyGradient2\" gradientUnits=\"userSpaceOnUse\" cx=\"50\" cy=\"70\" r=\"70\" fx=\"20\" fy=\"20\">" |
1375 | "<stop offset=\"-3.bc\" style=\"stop-color:blue\" stop-opacity=\"0.3\"/>" |
1376 | "<stop offset=\"0.5\" style=\"stop-color:violet\" stop-opacity=\"-0.xy\"/>" |
1377 | "<stop offset=\"1\" style=\"stop-color:orange\" stop-opacity=\"z.5\"/>" |
1378 | "</radialGradient>" |
1379 | "<rect x=\"5\" y=\"5\" width=\"55\" height=\"55\" fill=\"url(#MyGradient1)\" stroke=\"black\" />" |
1380 | "<rect x=\"20\" y=\"20\" width=\"35\" height=\"35\" fill=\"url(#MyGradient2)\"/>" |
1381 | "</svg>" |
1382 | }; |
1383 | |
1384 | QImage images[4]; |
1385 | QPainter p; |
1386 | |
1387 | for (int i = 0; i < 4; ++i) { |
1388 | QByteArray data(svgs[i]); |
1389 | QSvgRenderer renderer(data); |
1390 | QVERIFY(renderer.isValid()); |
1391 | images[i] = QImage(64, 64, QImage::Format_ARGB32_Premultiplied); |
1392 | images[i].fill(pixel: -1); |
1393 | p.begin(&images[i]); |
1394 | renderer.render(p: &p); |
1395 | p.end(); |
1396 | } |
1397 | QCOMPARE(images[0], images[1]); |
1398 | QCOMPARE(images[0], images[2]); |
1399 | QCOMPARE(images[0], images[3]); |
1400 | } |
1401 | |
1402 | void tst_QSvgRenderer::testUseElement() |
1403 | { |
1404 | static const char *svgs[] = { |
1405 | // 0 - Use referring to non group node (1) |
1406 | "<svg viewBox = \"0 0 200 200\">" |
1407 | " <polygon points=\"20,20 50,120 100,10 40,80 50,80\"/>" |
1408 | " <polygon points=\"20,80 50,180 100,70 40,140 50,140\" fill= \"red\" stroke = \"blue\" fill-opacity = \"0.7\" fill-rule = \"evenodd\" stroke-width = \"3\"/>" |
1409 | "</svg>" , |
1410 | // 1 |
1411 | "<svg viewBox = \"0 0 200 200\">" |
1412 | " <polygon id = \"usedPolyline\" points=\"20,20 50,120 100,10 40,80 50,80\"/>" |
1413 | " <use y = \"60\" xlink:href = \"#usedPolyline\" fill= \"red\" stroke = \"blue\" fill-opacity = \"0.7\" fill-rule = \"evenodd\" stroke-width = \"3\"/>" |
1414 | "</svg>" , |
1415 | // 2 |
1416 | "<svg viewBox = \"0 0 200 200\">" |
1417 | " <polygon id = \"usedPolyline\" points=\"20,20 50,120 100,10 40,80 50,80\"/>" |
1418 | " <g fill = \" red\" fill-opacity =\"0.2\">" |
1419 | "<use y = \"60\" xlink:href = \"#usedPolyline\" stroke = \"blue\" fill-opacity = \"0.7\" fill-rule = \"evenodd\" stroke-width = \"3\"/>" |
1420 | "</g>" |
1421 | "</svg>" , |
1422 | // 3 |
1423 | "<svg viewBox = \"0 0 200 200\">" |
1424 | " <polygon id = \"usedPolyline\" points=\"20,20 50,120 100,10 40,80 50,80\"/>" |
1425 | " <g stroke-width = \"3\" stroke = \"yellow\">" |
1426 | " <use y = \"60\" xlink:href = \"#usedPolyline\" fill = \" red\" stroke = \"blue\" fill-opacity = \"0.7\" fill-rule = \"evenodd\"/>" |
1427 | " </g>" |
1428 | "</svg>" , |
1429 | // 4 - Use referring to non group node (2) |
1430 | "<svg viewBox = \"0 0 200 200\">" |
1431 | " <polygon points=\"20,20 50,120 100,10 40,80 50,80\" fill = \"green\" fill-rule = \"nonzero\" stroke = \"purple\" stroke-width = \"4\" stroke-dasharray = \"1,1,3,1\" stroke-offset = \"3\" stroke-miterlimit = \"6\" stroke-linecap = \"butt\" stroke-linejoin = \"round\"/>" |
1432 | " <polygon points=\"20,80 50,180 100,70 40,140 50,140\" fill= \"red\" stroke = \"blue\" fill-opacity = \"0.7\" fill-rule = \"evenodd\" stroke-width = \"3\" stroke-dasharray = \"1,1,1,1\" stroke-offset = \"5\" stroke-miterlimit = \"3\" stroke-linecap = \"butt\" stroke-linejoin = \"square\"/>" |
1433 | "</svg>" , |
1434 | // 5 |
1435 | "<svg viewBox = \"0 0 200 200\">" |
1436 | " <g fill = \"green\" fill-rule = \"nonzero\" stroke = \"purple\" stroke-width = \"4\" stroke-dasharray = \"1,1,3,1\" stroke-offset = \"3\" stroke-miterlimit = \"6\" stroke-linecap = \"butt\" stroke-linejoin = \"round\">" |
1437 | " <polygon id = \"usedPolyline\" points=\"20,20 50,120 100,10 40,80 50,80\" />" |
1438 | " </g>" |
1439 | " <g stroke = \"blue\" stroke-width = \"3\" stroke-dasharray = \"1,1,1,1\" stroke-offset = \"5\" stroke-miterlimit = \"3\" stroke-linecap = \"butt\" stroke-linejoin = \"square\">" |
1440 | " <use y = \"60\" xlink:href = \"#usedPolyline\" fill-opacity = \"0.7\" fill= \"red\" stroke = \"blue\" fill-rule = \"evenodd\"/>" |
1441 | " </g>" |
1442 | "</svg>" , |
1443 | // 6 |
1444 | "<svg viewBox = \"0 0 200 200\">" |
1445 | " <g fill = \"green\" fill-rule = \"nonzero\" stroke = \"purple\" stroke-width = \"4\" stroke-dasharray = \"1,1,3,1\" stroke-offset = \"3\" stroke-miterlimit = \"6\" stroke-linecap = \"butt\" stroke-linejoin = \"round\">" |
1446 | " <polygon id = \"usedPolyline\" points=\"20,20 50,120 100,10 40,80 50,80\" />" |
1447 | " </g>" |
1448 | " <g stroke-width = \"3\" stroke-dasharray = \"1,1,1,1\" stroke-offset = \"5\" stroke-miterlimit = \"3\" stroke-linecap = \"butt\" stroke-linejoin = \"square\" >" |
1449 | " <use y = \"60\" xlink:href = \"#usedPolyline\" fill= \"red\" stroke = \"blue\" fill-opacity = \"0.7\" fill-rule = \"evenodd\" />" |
1450 | " </g>" |
1451 | "</svg>" , |
1452 | // 7 - Use referring to group node |
1453 | "<svg viewBox = \"0 0 200 200\">" |
1454 | " <g>" |
1455 | " <circle cx=\"0\" cy=\"0\" r=\"100\" fill = \"red\" fill-opacity = \"0.6\"/>" |
1456 | " <rect x = \"10\" y = \"10\" width = \"30\" height = \"30\" fill = \"red\" fill-opacity = \"0.5\"/>" |
1457 | " <circle fill=\"#a6ce39\" cx=\"0\" cy=\"0\" r=\"33\" fill-opacity = \"0.5\"/>" |
1458 | " </g>" |
1459 | "</svg>" , |
1460 | // 8 |
1461 | "<svg viewBox = \"0 0 200 200\">" |
1462 | " <defs>" |
1463 | " <g id=\"usedG\">" |
1464 | " <circle cx=\"0\" cy=\"0\" r=\"100\" fill-opacity = \"0.6\"/>" |
1465 | " <rect x = \"10\" y = \"10\" width = \"30\" height = \"30\"/>" |
1466 | " <circle fill=\"#a6ce39\" cx=\"0\" cy=\"0\" r=\"33\" />" |
1467 | " </g>" |
1468 | " </defs>" |
1469 | " <use xlink:href =\"#usedG\" fill = \"red\" fill-opacity =\"0.5\"/>" |
1470 | "</svg>" , |
1471 | // 9 |
1472 | "<svg viewBox = \"0 0 200 200\">" |
1473 | " <defs>" |
1474 | " <g fill = \"blue\" fill-opacity = \"0.3\">" |
1475 | " <g id=\"usedG\">" |
1476 | " <circle cx=\"0\" cy=\"0\" r=\"100\" fill-opacity = \"0.6\"/>" |
1477 | " <rect x = \"10\" y = \"10\" width = \"30\" height = \"30\"/>" |
1478 | " <circle fill=\"#a6ce39\" cx=\"0\" cy=\"0\" r=\"33\" />" |
1479 | " </g>" |
1480 | " </g>" |
1481 | " </defs>" |
1482 | " <g fill = \"red\" fill-opacity =\"0.5\">" |
1483 | " <use xlink:href =\"#usedG\" />" |
1484 | " </g>" |
1485 | "</svg>" , |
1486 | // 10 - Self referral, should be ignored |
1487 | "<svg><g id=\"0\"><use xlink:href=\"#0\" /></g></svg>" , |
1488 | // 11 |
1489 | "<svg width=\"200\" height=\"200\">" |
1490 | " <rect width=\"100\" height=\"50\"/>" |
1491 | "</svg>" , |
1492 | // 12 |
1493 | "<svg width=\"200\" height=\"200\">" |
1494 | " <g id=\"0\"><use xlink:href=\"#0\" /><rect width=\"100\" height=\"50\"/></g>" |
1495 | "</svg>" , |
1496 | // 13 |
1497 | "<svg width=\"200\" height=\"200\">" |
1498 | " <g id=\"0\"><g><use xlink:href=\"#0\" /><rect width=\"100\" height=\"50\"/></g></g>" |
1499 | "</svg>" , |
1500 | // 14 (undefined) |
1501 | "<svg width=\"200\" height=\"200\">" |
1502 | " <rect width=\"100\" height=\"50\"/>" |
1503 | " <use x=\"100\" y=\"100\" opacity=\"0.5\" xlink:href=\"#nosuch\" />" |
1504 | "</svg>" , |
1505 | // 15 - Forward references |
1506 | "<svg viewBox = \"0 0 200 200\">" |
1507 | " <use y = \"60\" xlink:href = \"#usedPolyline\" fill= \"red\" stroke = \"blue\" fill-opacity = \"0.7\" fill-rule = \"evenodd\" stroke-width = \"3\"/>" |
1508 | " <polygon id = \"usedPolyline\" points=\"20,20 50,120 100,10 40,80 50,80\"/>" |
1509 | "</svg>" , |
1510 | // 16 |
1511 | "<svg viewBox = \"0 0 200 200\">" |
1512 | " <use xlink:href =\"#usedG\" fill = \"red\" fill-opacity =\"0.5\"/>" |
1513 | " <defs>" |
1514 | " <g id=\"usedG\">" |
1515 | " <circle cx=\"0\" cy=\"0\" r=\"100\" fill-opacity = \"0.6\"/>" |
1516 | " <rect x = \"10\" y = \"10\" width = \"30\" height = \"30\"/>" |
1517 | " <circle fill=\"#a6ce39\" cx=\"0\" cy=\"0\" r=\"33\" />" |
1518 | " </g>" |
1519 | " </defs>" |
1520 | "</svg>" , |
1521 | // 17 - Indirect self referral |
1522 | "<svg>" |
1523 | " <defs>" |
1524 | " <g id=\"g0\">" |
1525 | " <g id=\"g1\"><use href=\"#g2\"/></g>" |
1526 | " <g id=\"g2\"><use href=\"#g1\"/></g>" |
1527 | " </g>" |
1528 | " </defs>" |
1529 | " <use xlink:href=\"#g0\" fill=\"black\"/>" |
1530 | "</svg>" |
1531 | }; |
1532 | |
1533 | const int COUNT = sizeof(svgs) / sizeof(svgs[0]); |
1534 | QImage images[COUNT]; |
1535 | QPainter p; |
1536 | |
1537 | for (int i = 0; i < COUNT; ++i) { |
1538 | QByteArray data(svgs[i]); |
1539 | QSvgRenderer renderer(data); |
1540 | images[i] = QImage(200, 200, QImage::Format_ARGB32_Premultiplied); |
1541 | images[i].fill(pixel: -1); |
1542 | p.begin(&images[i]); |
1543 | renderer.render(p: &p); |
1544 | p.end(); |
1545 | |
1546 | if (i < 4 && i != 0) { |
1547 | QCOMPARE(images[0], images[i]); |
1548 | } else if (i > 4 && i < 7) { |
1549 | if (sizeof(qreal) != sizeof(float)) |
1550 | { |
1551 | // These images use blending functions which due to numerical |
1552 | // issues on Windows CE and likes differ in very few pixels. |
1553 | // For this reason an exact comparison will fail. |
1554 | QCOMPARE(images[4], images[i]); |
1555 | } |
1556 | } else if (i > 7 && i < 10) { |
1557 | QCOMPARE(images[8], images[i]); |
1558 | } else if (i > 11 && i < 15) { |
1559 | QCOMPARE(images[11], images[i]); |
1560 | } else if (i == 15) { |
1561 | QCOMPARE(images[0], images[i]); |
1562 | } else if (i == 16) { |
1563 | QCOMPARE(images[8], images[i]); |
1564 | } |
1565 | } |
1566 | } |
1567 | |
1568 | void tst_QSvgRenderer::smallFont() |
1569 | { |
1570 | static const char *svgs[] = { "<svg width=\"50px\" height=\"50px\"><text x=\"10\" y=\"10\" font-size=\"0\">Hello world</text></svg>" , |
1571 | "<svg width=\"50px\" height=\"50px\"><text x=\"10\" y=\"10\" font-size=\"0.5\">Hello world</text></svg>" |
1572 | }; |
1573 | const int COUNT = sizeof(svgs) / sizeof(svgs[0]); |
1574 | QImage images[COUNT]; |
1575 | QPainter p; |
1576 | |
1577 | for (int i = 0; i < COUNT; ++i) { |
1578 | QByteArray data(svgs[i]); |
1579 | if (i == 0) |
1580 | QTest::ignoreMessage(type: QtWarningMsg, message: "QFont::setPointSizeF: Point size <= 0 (0.000000), must be greater than 0" ); |
1581 | QSvgRenderer renderer(data); |
1582 | images[i] = QImage(50, 50, QImage::Format_ARGB32_Premultiplied); |
1583 | images[i].fill(pixel: -1); |
1584 | p.begin(&images[i]); |
1585 | renderer.render(p: &p); |
1586 | p.end(); |
1587 | } |
1588 | QVERIFY(images[0] != images[1]); |
1589 | } |
1590 | |
1591 | void tst_QSvgRenderer::styleSheet() |
1592 | { |
1593 | static const char *svgs[] = { "<svg><style type=\"text/css\">.cls {fill:#ff0000;}</style><rect class=\"cls\" x = \"10\" y = \"10\" width = \"30\" height = \"30\"/></svg>" , |
1594 | "<svg><style>.cls {fill:#ff0000;}</style><rect class=\"cls\" x = \"10\" y = \"10\" width = \"30\" height = \"30\"/></svg>" , |
1595 | }; |
1596 | const int COUNT = sizeof(svgs) / sizeof(svgs[0]); |
1597 | QImage images[COUNT]; |
1598 | QPainter p; |
1599 | |
1600 | for (int i = 0; i < COUNT; ++i) { |
1601 | QByteArray data(svgs[i]); |
1602 | QSvgRenderer renderer(data); |
1603 | images[i] = QImage(50, 50, QImage::Format_ARGB32_Premultiplied); |
1604 | images[i].fill(pixel: -1); |
1605 | p.begin(&images[i]); |
1606 | renderer.render(p: &p); |
1607 | p.end(); |
1608 | } |
1609 | QCOMPARE(images[0], images[1]); |
1610 | } |
1611 | |
1612 | void tst_QSvgRenderer::duplicateStyleId() |
1613 | { |
1614 | QByteArray svg = QByteArrayLiteral("<svg><linearGradient id=\"a\"/>" |
1615 | "<rect style=\"fill:url(#a)\"/>" |
1616 | "<linearGradient id=\"a\"/></svg>" ); |
1617 | QTest::ignoreMessage(type: QtWarningMsg, message: "Duplicate unique style id: \"a\"" ); |
1618 | QImage image(200, 200, QImage::Format_RGB32); |
1619 | QPainter painter(&image); |
1620 | QSvgRenderer renderer(svg); |
1621 | renderer.render(p: &painter); |
1622 | } |
1623 | |
1624 | void tst_QSvgRenderer::oss_fuzz_23731() |
1625 | { |
1626 | // when configured with "-sanitize undefined", this resulted in: |
1627 | // "runtime error: division by zero" |
1628 | QSvgRenderer().load(contents: QByteArray("<svg><path d=\"A4------\">" )); |
1629 | } |
1630 | |
1631 | void tst_QSvgRenderer::oss_fuzz_24131() |
1632 | { |
1633 | // when configured with "-sanitize undefined", this resulted in: |
1634 | // "runtime error: -nan is outside the range of representable values of type 'int'" |
1635 | // runtime error: signed integer overflow: -2147483648 + -2147483648 cannot be represented in type 'int' |
1636 | QImage image(377, 233, QImage::Format_RGB32); |
1637 | QPainter painter(&image); |
1638 | QSvgRenderer renderer(QByteArray("<svg><path d=\"M- 4 44044404444E-334-\"/></svg>" )); |
1639 | renderer.render(p: &painter); |
1640 | } |
1641 | |
1642 | void tst_QSvgRenderer::oss_fuzz_24738() |
1643 | { |
1644 | // when configured with "-sanitize undefined", this resulted in: |
1645 | // "runtime error: division by zero" |
1646 | QSvgRenderer().load(contents: QByteArray("<svg><path d=\"a 2 1e-212.....\">" )); |
1647 | } |
1648 | |
1649 | QTEST_MAIN(tst_QSvgRenderer) |
1650 | #include "tst_qsvgrenderer.moc" |
1651 | |