Skip to content

Commit 0052eb8

Browse files
committedDec 18, 2017
Add unit test for pdf exports
1 parent b4829a7 commit 0052eb8

File tree

6 files changed

+127
-1
lines changed

6 files changed

+127
-1
lines changed
 

‎python/core/qgsmultirenderchecker.sip

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,16 @@ Default value is 0.
8484

8585
:param colorTolerance: The maximum difference for each color component
8686
including alpha to be considered correct.
87+
%End
88+
89+
void setSizeTolerance( int xTolerance, int yTolerance );
90+
%Docstring
91+
Sets the largest allowable difference in size between the rendered and the expected image.
92+
93+
:param xTolerance: x tolerance in pixels
94+
:param yTolerance: y tolerance in pixels
95+
96+
.. versionadded:: 3.0
8797
%End
8898

8999
bool runTest( const QString &testName, unsigned int mismatchCount = 0 );

‎src/core/qgsmultirenderchecker.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ bool QgsMultiRenderChecker::runTest( const QString &testName, unsigned int misma
5555
QgsRenderChecker checker;
5656
checker.enableDashBuffering( true );
5757
checker.setColorTolerance( mColorTolerance );
58+
checker.setSizeTolerance( mMaxSizeDifferenceX, mMaxSizeDifferenceY );
5859
checker.setControlPathPrefix( mControlPathPrefix );
5960
checker.setControlPathSuffix( suffix );
6061
checker.setControlName( mControlName );

‎src/core/qgsmultirenderchecker.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,14 @@ class CORE_EXPORT QgsMultiRenderChecker
9494
*/
9595
void setColorTolerance( unsigned int colorTolerance ) { mColorTolerance = colorTolerance; }
9696

97+
/**
98+
* Sets the largest allowable difference in size between the rendered and the expected image.
99+
* \param xTolerance x tolerance in pixels
100+
* \param yTolerance y tolerance in pixels
101+
* \since QGIS 3.0
102+
*/
103+
void setSizeTolerance( int xTolerance, int yTolerance ) { mMaxSizeDifferenceX = xTolerance; mMaxSizeDifferenceY = yTolerance; }
104+
97105
/**
98106
* Test using renderer to generate the image to be compared.
99107
*
@@ -134,6 +142,8 @@ class CORE_EXPORT QgsMultiRenderChecker
134142
QString mControlName;
135143
QString mControlPathPrefix;
136144
unsigned int mColorTolerance = 0;
145+
int mMaxSizeDifferenceX = 0;
146+
int mMaxSizeDifferenceY = 0;
137147
QgsMapSettings mMapSettings;
138148
};
139149

‎tests/src/python/test_qgslayoutexporter.py

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import tempfile
1818
import shutil
1919
import os
20+
import subprocess
2021

2122
from qgis.core import (QgsMultiRenderChecker,
2223
QgsLayoutExporter,
@@ -35,6 +36,58 @@
3536

3637
from qgis.testing import start_app, unittest
3738

39+
from utilities import getExecutablePath
40+
41+
# PDF-to-image utility
42+
# look for Poppler w/ Cairo, then muPDF
43+
# * Poppler w/ Cairo renders correctly
44+
# * Poppler w/o Cairo does not always correctly render vectors in PDF to image
45+
# * muPDF renders correctly, but sightly shifts colors
46+
for util in [
47+
'pdftocairo',
48+
# 'mudraw',
49+
]:
50+
PDFUTIL = getExecutablePath(util)
51+
if PDFUTIL:
52+
break
53+
54+
# noinspection PyUnboundLocalVariable
55+
if not PDFUTIL:
56+
raise Exception('PDF-to-image utility not found on PATH: '
57+
'install Poppler (with Cairo)')
58+
59+
60+
def pdfToPng(pdf_file_path, rendered_file_path, page, dpi=96):
61+
if PDFUTIL.strip().endswith('pdftocairo'):
62+
filebase = os.path.join(
63+
os.path.dirname(rendered_file_path),
64+
os.path.splitext(os.path.basename(rendered_file_path))[0]
65+
)
66+
call = [
67+
PDFUTIL, '-png', '-singlefile', '-r', str(dpi),
68+
'-x', '0', '-y', '0', '-f', str(page), '-l', str(page),
69+
pdf_file_path, filebase
70+
]
71+
elif PDFUTIL.strip().endswith('mudraw'):
72+
call = [
73+
PDFUTIL, '-c', 'rgba',
74+
'-r', str(dpi), '-f', str(page), '-l', str(page),
75+
# '-b', '8',
76+
'-o', rendered_file_path, pdf_file_path
77+
]
78+
else:
79+
return False, ''
80+
81+
print("exportToPdf call: {0}".format(' '.join(call)))
82+
try:
83+
subprocess.check_call(call)
84+
except subprocess.CalledProcessError as e:
85+
assert False, ("exportToPdf failed!\n"
86+
"cmd: {0}\n"
87+
"returncode: {1}\n"
88+
"message: {2}".format(e.cmd, e.returncode, e.message))
89+
90+
3891
start_app()
3992

4093

@@ -54,12 +107,13 @@ def tearDown(self):
54107
with open(report_file_path, 'a') as report_file:
55108
report_file.write(self.report)
56109

57-
def checkImage(self, name, reference_image, rendered_image):
110+
def checkImage(self, name, reference_image, rendered_image, size_tolerance=0):
58111
checker = QgsMultiRenderChecker()
59112
checker.setControlPathPrefix("layout_exporter")
60113
checker.setControlName("expected_layoutexporter_" + reference_image)
61114
checker.setRenderedImage(rendered_image)
62115
checker.setColorTolerance(2)
116+
checker.setSizeTolerance(size_tolerance, size_tolerance)
63117
result = checker.runTest(name, 20)
64118
self.report += checker.report()
65119
print((self.report))
@@ -278,6 +332,57 @@ def testExportToImage(self):
278332
page2_path = os.path.join(self.basetestpath, 'test_exporttoimagesize_2.png')
279333
self.assertTrue(self.checkImage('exporttoimagesize_page2', 'exporttoimagesize_page2', page2_path))
280334

335+
def testExportToPdf(self):
336+
l = QgsLayout(QgsProject.instance())
337+
l.initializeDefaults()
338+
339+
# add a second page
340+
page2 = QgsLayoutItemPage(l)
341+
page2.setPageSize('A5')
342+
l.pageCollection().addPage(page2)
343+
344+
# add some items
345+
item1 = QgsLayoutItemShape(l)
346+
item1.attemptSetSceneRect(QRectF(10, 20, 100, 150))
347+
fill = QgsSimpleFillSymbolLayer()
348+
fill_symbol = QgsFillSymbol()
349+
fill_symbol.changeSymbolLayer(0, fill)
350+
fill.setColor(Qt.green)
351+
fill.setStrokeStyle(Qt.NoPen)
352+
item1.setSymbol(fill_symbol)
353+
l.addItem(item1)
354+
355+
item2 = QgsLayoutItemShape(l)
356+
item2.attemptSetSceneRect(QRectF(10, 20, 100, 150))
357+
item2.attemptMove(QgsLayoutPoint(10, 20), page=1)
358+
fill = QgsSimpleFillSymbolLayer()
359+
fill_symbol = QgsFillSymbol()
360+
fill_symbol.changeSymbolLayer(0, fill)
361+
fill.setColor(Qt.cyan)
362+
fill.setStrokeStyle(Qt.NoPen)
363+
item2.setSymbol(fill_symbol)
364+
l.addItem(item2)
365+
366+
exporter = QgsLayoutExporter(l)
367+
# setup settings
368+
settings = QgsLayoutExporter.PdfExportSettings()
369+
settings.dpi = 80
370+
settings.rasterizeWholeImage = False
371+
settings.forceVectorOutput = False
372+
373+
pdf_file_path = os.path.join(self.basetestpath, 'test_exporttopdfdpi.pdf')
374+
self.assertEqual(exporter.exportToPdf(pdf_file_path, settings), QgsLayoutExporter.Success)
375+
self.assertTrue(os.path.exists(pdf_file_path))
376+
377+
rendered_page_1 = os.path.join(self.basetestpath, 'test_exporttopdfdpi.png')
378+
dpi = 80
379+
pdfToPng(pdf_file_path, rendered_page_1, dpi=dpi, page=1)
380+
rendered_page_2 = os.path.join(self.basetestpath, 'test_exporttopdfdpi2.png')
381+
pdfToPng(pdf_file_path, rendered_page_2, dpi=dpi, page=2)
382+
383+
self.assertTrue(self.checkImage('exporttopdfdpi_page1', 'exporttopdfdpi_page1', rendered_page_1, size_tolerance=1))
384+
self.assertTrue(self.checkImage('exporttopdfdpi_page2', 'exporttopdfdpi_page2', rendered_page_2, size_tolerance=1))
385+
281386
def testExportWorldFile(self):
282387
l = QgsLayout(QgsProject.instance())
283388
l.initializeDefaults()

0 commit comments

Comments
 (0)
Please sign in to comment.