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 <QDomDocument>
33#include <QDomElement>
34#include <QDomNode>
35
36#include <qapplication.h>
37#include <qdebug.h>
38#include <qpainter.h>
39#include <qsvggenerator.h>
40#include <qsvgrenderer.h>
41
42class tst_QSvgGenerator : public QObject
43{
44Q_OBJECT
45
46public:
47 tst_QSvgGenerator();
48 virtual ~tst_QSvgGenerator();
49
50private slots:
51 void construction();
52 void fileName();
53 void outputDevice();
54 void sizeAndViewBox();
55 void metric();
56 void radialGradient();
57 void fileEncoding();
58 void fractionalFontSize();
59 void titleAndDescription();
60 void gradientInterpolation();
61 void patternBrush();
62};
63
64tst_QSvgGenerator::tst_QSvgGenerator()
65{
66}
67
68tst_QSvgGenerator::~tst_QSvgGenerator()
69{
70 QFile::remove(fileName: QLatin1String("fileName_output.svg"));
71 QFile::remove(fileName: QLatin1String("outputDevice_output.svg"));
72 QFile::remove(fileName: QLatin1String("radial_gradient.svg"));
73}
74
75void tst_QSvgGenerator::construction()
76{
77 QSvgGenerator generator;
78 QCOMPARE(generator.fileName(), QString());
79 QCOMPARE(generator.outputDevice(), (QIODevice *)0);
80 QCOMPARE(generator.resolution(), 72);
81 QCOMPARE(generator.size(), QSize());
82}
83
84static void removeAttribute(const QDomNode &node, const QString &attribute)
85{
86 if (node.isNull())
87 return;
88
89 node.toElement().removeAttribute(name: attribute);
90
91 removeAttribute(node: node.firstChild(), attribute);
92 removeAttribute(node: node.nextSibling(), attribute);
93}
94
95static void compareWithoutFontInfo(const QByteArray &source, const QByteArray &reference)
96{
97 QDomDocument sourceDoc;
98 sourceDoc.setContent(text: source);
99
100 QDomDocument referenceDoc;
101 referenceDoc.setContent(text: reference);
102
103 const QString fontAttributes[] = {
104 "font-family",
105 "font-size",
106 "font-weight",
107 "font-style",
108 };
109
110 for (const QString &attribute : fontAttributes) {
111 removeAttribute(node: sourceDoc, attribute);
112 removeAttribute(node: referenceDoc, attribute);
113 }
114
115 QCOMPARE(sourceDoc.toByteArray(), referenceDoc.toByteArray());
116}
117
118static void checkFile(const QString &fileName)
119{
120 QVERIFY(QFile::exists(fileName));;
121
122 QFile file(fileName);
123 QVERIFY(file.open(QIODevice::ReadOnly));
124
125 QFile referenceFile(QFINDTESTDATA("referenceSvgs/" + fileName));
126 QVERIFY(referenceFile.open(QIODevice::ReadOnly));
127
128 compareWithoutFontInfo(source: file.readAll(), reference: referenceFile.readAll());
129}
130
131void tst_QSvgGenerator::fileName()
132{
133 QString fileName = "fileName_output.svg";
134 QFile::remove(fileName);
135
136 QSvgGenerator generator;
137 generator.setFileName(fileName);
138 QCOMPARE(generator.fileName(), fileName);
139
140 QPainter painter(&generator);
141 painter.fillRect(x: 0, y: 0, w: 100, h: 100, c: Qt::red);
142 painter.end();
143
144 checkFile(fileName);
145}
146
147void tst_QSvgGenerator::outputDevice()
148{
149 QString fileName = "outputDevice_output.svg";
150 QFile::remove(fileName);
151
152 QFile file(fileName);
153
154 {
155 // Device is not open
156 QSvgGenerator generator;
157 generator.setOutputDevice(&file);
158 QCOMPARE(generator.outputDevice(), (QIODevice *)&file);
159
160 QPainter painter;
161 QVERIFY(painter.begin(&generator));
162 QCOMPARE(file.openMode(), QIODevice::OpenMode(QIODevice::Text | QIODevice::WriteOnly));
163 file.close();
164 }
165 {
166 // Device is not open, WriteOnly
167 file.open(flags: QIODevice::WriteOnly);
168
169 QSvgGenerator generator;
170 generator.setOutputDevice(&file);
171 QCOMPARE(generator.outputDevice(), (QIODevice *)&file);
172
173 QPainter painter;
174 QVERIFY(painter.begin(&generator));
175 QCOMPARE(file.openMode(), QIODevice::OpenMode(QIODevice::WriteOnly));
176 file.close();
177 }
178 {
179 // Device is not open, ReadOnly
180 file.open(flags: QIODevice::ReadOnly);
181
182 QSvgGenerator generator;
183 generator.setOutputDevice(&file);
184 QCOMPARE(generator.outputDevice(), (QIODevice *)&file);
185
186 QPainter painter;
187 QTest::ignoreMessage(type: QtWarningMsg, message: "QSvgPaintEngine::begin(), could not write to read-only output device: 'Unknown error'");
188 QVERIFY(!painter.begin(&generator));
189 QCOMPARE(file.openMode(), QIODevice::OpenMode(QIODevice::ReadOnly));
190 file.close();
191 }
192}
193
194void tst_QSvgGenerator::sizeAndViewBox()
195{
196 { // Setting neither properties should result in
197 // none of the attributes written to the SVG
198 QSvgGenerator generator;
199 QByteArray byteArray;
200 QBuffer buffer(&byteArray);
201 generator.setOutputDevice(&buffer);
202 QPainter painter(&generator);
203 painter.end();
204
205 QVERIFY(!byteArray.contains("<svg width=\""));
206 QVERIFY(!byteArray.contains("viewBox=\""));
207 }
208
209 { // Setting size only should write size only
210 QSvgGenerator generator;
211 QByteArray byteArray;
212 QBuffer buffer(&byteArray);
213 generator.setOutputDevice(&buffer);
214 generator.setResolution(254);
215 generator.setSize(QSize(100, 100));
216 QPainter painter(&generator);
217 painter.end();
218
219 QVERIFY(byteArray.contains("<svg width=\"10mm\" height=\"10mm\""));
220 QVERIFY(!byteArray.contains("viewBox=\""));
221 }
222
223 { // Setting viewBox only should write viewBox only
224 QSvgGenerator generator;
225 QByteArray byteArray;
226 QBuffer buffer(&byteArray);
227 generator.setOutputDevice(&buffer);
228 generator.setViewBox(QRectF(20, 20, 50.666, 50.666));
229 QPainter painter(&generator);
230 painter.end();
231
232 QVERIFY(!byteArray.contains("<svg width=\""));
233 QVERIFY(byteArray.contains("<svg viewBox=\"20 20 50.666 50.666\""));
234 }
235
236 { // Setting both properties should result in
237 // both of the attributes written to the SVG
238 QSvgGenerator generator;
239 QByteArray byteArray;
240 QBuffer buffer(&byteArray);
241 generator.setOutputDevice(&buffer);
242 generator.setResolution(254);
243 generator.setSize(QSize(500, 500));
244 generator.setViewBox(QRectF(20.666, 20.666, 50, 50));
245 QPainter painter(&generator);
246 painter.end();
247
248 QVERIFY(byteArray.contains("<svg width=\"50mm\" height=\"50mm\""));
249 QVERIFY(byteArray.contains("viewBox=\"20.666 20.666 50 50\""));
250 }
251}
252
253void tst_QSvgGenerator::metric()
254{
255 QSvgGenerator generator;
256 generator.setSize(QSize(100, 100));
257 generator.setResolution(254); // 254 dots per inch == 10 dots per mm
258
259 QCOMPARE(generator.widthMM(), 10);
260 QCOMPARE(generator.heightMM(), 10);
261}
262
263void tst_QSvgGenerator::radialGradient()
264{
265 QString fileName = "radial_gradient.svg";
266 QFile::remove(fileName);
267
268 QSvgGenerator generator;
269 generator.setSize(QSize(200, 100));
270 generator.setFileName(fileName);
271 QCOMPARE(generator.fileName(), fileName);
272
273 QRadialGradient gradient(QPointF(0.5, 0.5), 0.5, QPointF(0.5, 0.5));
274 gradient.setInterpolationMode(QGradient::ComponentInterpolation);
275 gradient.setColorAt(pos: 0, color: Qt::red);
276 gradient.setColorAt(pos: 1, color: Qt::blue);
277 gradient.setCoordinateMode(QGradient::ObjectBoundingMode);
278
279 QPainter painter(&generator);
280 painter.fillRect(x: 0, y: 0, w: 100, h: 100, b: gradient);
281
282 gradient = QRadialGradient(QPointF(150, 50), 50, QPointF(150, 50));
283 gradient.setInterpolationMode(QGradient::ComponentInterpolation);
284 gradient.setColorAt(pos: 0, color: Qt::red);
285 gradient.setColorAt(pos: 1, color: Qt::blue);
286 painter.fillRect(x: 100, y: 0, w: 100, h: 100, b: gradient);
287 painter.end();
288
289 checkFile(fileName);
290}
291
292void tst_QSvgGenerator::fileEncoding()
293{
294 QTextCodec::setCodecForLocale(QTextCodec::codecForName(name: "ISO-8859-1"));
295
296 QByteArray byteArray;
297 QBuffer buffer(&byteArray);
298
299 QSvgGenerator generator;
300 generator.setOutputDevice(&buffer);
301
302 static const QChar unicode[] = { 'f', 'o', 'o',
303 0x00F8, 'b', 'a', 'r'};
304
305 int size = sizeof(unicode) / sizeof(QChar);
306 QString unicodeString = QString::fromRawData(unicode, size);
307
308 QPainter painter(&generator);
309 painter.drawText(x: 100, y: 100, s: unicodeString);
310 painter.end();
311
312 QVERIFY(byteArray.contains(unicodeString.toUtf8()));
313}
314
315void tst_QSvgGenerator::fractionalFontSize()
316{
317 QByteArray byteArray;
318 QBuffer buffer(&byteArray);
319
320 QSvgGenerator generator;
321 generator.setResolution(72);
322 generator.setOutputDevice(&buffer);
323
324 QPainter painter(&generator);
325 QFont fractionalFont = painter.font();
326 fractionalFont.setPointSizeF(7.5);
327 painter.setFont(fractionalFont);
328 painter.drawText(x: 100, y: 100, s: "foo");
329 painter.end();
330
331 QVERIFY(byteArray.contains("7.5"));
332}
333
334void tst_QSvgGenerator::titleAndDescription()
335{
336 QByteArray byteArray;
337 QBuffer buffer(&byteArray);
338
339 QSvgGenerator generator;
340 generator.setTitle("foo");
341 QCOMPARE(generator.title(), QString("foo"));
342 generator.setDescription("bar");
343 QCOMPARE(generator.description(), QString("bar"));
344 generator.setOutputDevice(&buffer);
345
346 QPainter painter(&generator);
347 painter.end();
348
349 QVERIFY(byteArray.contains("<title>foo</title>"));
350 QVERIFY(byteArray.contains("<desc>bar</desc>"));
351}
352
353static void drawTestGradients(QPainter &painter)
354{
355 int w = painter.device()->width();
356 int h = painter.device()->height();
357 if (w <= 0 || h <= 0)
358 h = w = 72;
359
360 QLinearGradient gradient(QPoint(0, 0), QPoint(1, 1));
361 gradient.setCoordinateMode(QGradient::ObjectBoundingMode);
362 gradient.setColorAt(pos: 0, color: QColor(255, 0, 0, 0));
363 gradient.setColorAt(pos: 1, color: QColor(0, 0, 255, 255));
364 painter.fillRect(QRectF(0, 0, w/2, h/2), gradient);
365
366 gradient.setInterpolationMode(QGradient::ComponentInterpolation);
367 painter.fillRect(QRectF(0, h/2, w/2, h - h/2), gradient);
368
369 gradient.setInterpolationMode(QGradient::ColorInterpolation);
370 gradient.setColorAt(pos: 0, color: QColor(255, 0, 0, 123));
371 gradient.setColorAt(pos: 1, color: QColor(0, 0, 255, 123));
372 painter.fillRect(QRectF(w/2, 0, w - w/2, h/2), gradient);
373
374 gradient.setInterpolationMode(QGradient::ComponentInterpolation);
375 painter.fillRect(QRectF(w/2, h/2, w - w/2, h - h/2), gradient);
376}
377
378static qreal sqrImageDiff(const QImage &image1, const QImage &image2)
379{
380 if (image1.size() != image2.size())
381 return 1e30;
382 quint64 sum = 0;
383 for (int y = 0; y < image1.height(); ++y) {
384 const quint8 *line1 = reinterpret_cast<const quint8 *>(image1.scanLine(y));
385 const quint8 *line2 = reinterpret_cast<const quint8 *>(image2.scanLine(y));
386 for (int x = 0; x < image1.width() * 4; ++x)
387 sum += quint64((int(line1[x]) - int(line2[x])) * (int(line1[x]) - int(line2[x])));
388 }
389 return qreal(sum) / qreal(image1.width() * image1.height());
390}
391
392void tst_QSvgGenerator::gradientInterpolation()
393{
394 QByteArray byteArray;
395 QPainter painter;
396 QImage image(576, 576, QImage::Format_ARGB32_Premultiplied);
397 QImage refImage(576, 576, QImage::Format_ARGB32_Premultiplied);
398 image.fill(pixel: 0x80208050);
399 refImage.fill(pixel: 0x80208050);
400
401 {
402 QSvgGenerator generator;
403 QBuffer buffer(&byteArray);
404 generator.setOutputDevice(&buffer);
405
406 QVERIFY(painter.begin(&generator));
407 drawTestGradients(painter);
408 painter.end();
409 }
410
411 {
412 QVERIFY(painter.begin(&image));
413 QSvgRenderer renderer(byteArray);
414 renderer.render(p: &painter, bounds: image.rect());
415 painter.end();
416 }
417
418 {
419 QVERIFY(painter.begin(&refImage));
420 drawTestGradients(painter);
421 painter.end();
422 }
423
424 QVERIFY(sqrImageDiff(image, refImage) < 2); // pixel error < 1.41 (L2-norm)
425}
426
427void tst_QSvgGenerator::patternBrush()
428{
429 { // Pattern brush should create mask and pattern used as fill
430 QSvgGenerator generator;
431 QByteArray byteArray;
432 QBuffer buffer(&byteArray);
433 generator.setOutputDevice(&buffer);
434 QPainter painter(&generator);
435 painter.setBrush(Qt::CrossPattern);
436 painter.drawRect(x: 0, y: 0, w: 100, h: 100);
437 painter.end();
438
439 QVERIFY(byteArray.contains("<mask id=\"patternmask"));
440 QVERIFY(byteArray.contains("<pattern id=\"fillpattern"));
441 QVERIFY(byteArray.contains("<g fill=\"url(#fillpattern"));
442 }
443
444 { // Masks and patterns should be reused, not regenerated
445 QSvgGenerator generator;
446 QByteArray byteArray;
447 QBuffer buffer(&byteArray);
448 generator.setOutputDevice(&buffer);
449 QPainter painter(&generator);
450 painter.setBrush(QBrush(Qt::red, Qt::Dense3Pattern));
451 painter.drawRect(x: 0, y: 0, w: 100, h: 100);
452 painter.drawEllipse(x: 200, y: 50, w: 50, h: 50);
453 painter.setBrush(QBrush(Qt::green, Qt::Dense3Pattern));
454 painter.drawRoundedRect(x: 0, y: 200, w: 100, h: 100, xRadius: 10, yRadius: 10);
455 painter.setBrush(QBrush(Qt::blue, Qt::Dense4Pattern));
456 painter.drawRect(x: 200, y: 200, w: 100, h: 100);
457 painter.setBrush(QBrush(Qt::red, Qt::Dense3Pattern));
458 painter.drawRoundedRect(x: 120, y: 120, w: 60, h: 60, xRadius: 5, yRadius: 5);
459 painter.end();
460
461 QCOMPARE(byteArray.count("<mask id=\"patternmask"), 2);
462 QCOMPARE(byteArray.count("<pattern id=\"fillpattern"), 3);
463 QVERIFY(byteArray.count("<g fill=\"url(#fillpattern") >= 4);
464 }
465
466}
467
468QTEST_MAIN(tst_QSvgGenerator)
469#include "tst_qsvggenerator.moc"
470

source code of qtsvg/tests/auto/qsvggenerator/tst_qsvggenerator.cpp