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
45class tst_QSvgRenderer : public QObject
46{
47Q_OBJECT
48
49public:
50 tst_QSvgRenderer();
51 virtual ~tst_QSvgRenderer();
52
53private 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
96private:
97 static const char *const src;
98};
99
100const 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
103tst_QSvgRenderer::tst_QSvgRenderer()
104{
105}
106
107tst_QSvgRenderer::~tst_QSvgRenderer()
108{
109}
110
111// Testing get/set functions
112void 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
127void 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
137void 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
147void 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
166void 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
177void 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
221void 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
290void 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
347void 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
382void 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
412void 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
426void 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
441void 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
466void 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
513static 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
526static bool diffIsSmallEnough(double diff, double norm)
527{
528 return diff <= 1e-12 * norm;
529}
530
531static inline bool diffIsSmallEnough(float diff, float norm)
532{
533 return diff <= 1e-5 * norm;
534}
535
536static 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
552void 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
581void 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
611void 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
684void 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
761void 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
784void 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
800void 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
816QT_BEGIN_NAMESPACE
817QByteArray qt_inflateGZipDataFrom(QIODevice *device);
818QT_END_NAMESPACE
819#endif
820
821void 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
849void 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
864void 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
928static 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
940void 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
997void 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
1051void 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
1064void 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
1129void 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
1252void 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}
1319void 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
1402void 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
1568void 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
1591void 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
1612void 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
1624void 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
1631void 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
1642void 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
1649QTEST_MAIN(tst_QSvgRenderer)
1650#include "tst_qsvgrenderer.moc"
1651

source code of qtsvg/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp