Skip to content

Commit c6bb0b9

Browse files
committed
Add unit-tests to avoid regression in Server printing to PDF output format
In QGIS 3.4, Selection can be printed in Image output and not in PDF or SVG output. A fix has been done 2752f83 to fix inconsistent use of layout render context flags, and draw selection is activated with a flag.
1 parent de9ca74 commit c6bb0b9

File tree

5 files changed

+167
-1
lines changed

5 files changed

+167
-1
lines changed

tests/src/python/test_qgsserver_wms_getprint.py

Lines changed: 167 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,18 @@
2828

2929
from qgis.testing import unittest
3030
from qgis.PyQt.QtCore import QSize
31+
from qgis.PyQt.QtGui import QImage, QPainter
32+
from qgis.PyQt.QtSvg import QSvgRenderer, QSvgGenerator
3133

3234
import osgeo.gdal # NOQA
35+
import tempfile
36+
import base64
37+
import subprocess
3338

3439
from test_qgsserver import QgsServerTestBase
35-
from qgis.core import QgsProject
40+
from qgis.core import QgsProject, QgsRenderChecker
3641
from qgis.server import QgsServerRequest
42+
from utilities import getExecutablePath, unitTestDataPath
3743

3844
# Strip path and content length because path may vary
3945
RE_STRIP_UNCHECKABLE = b'MAP=[^"]+|Content-Length: \d+'
@@ -42,6 +48,132 @@
4248

4349
class TestQgsServerWMSGetPrint(QgsServerTestBase):
4450

51+
def _pdf_to_png(self, pdf_file_path, rendered_file_path, page, dpi=96):
52+
53+
# PDF-to-image utility
54+
# look for Poppler w/ Cairo, then muPDF
55+
# * Poppler w/ Cairo renders correctly
56+
# * Poppler w/o Cairo does not always correctly render vectors in PDF to image
57+
# * muPDF renders correctly, but slightly shifts colors
58+
for util in [
59+
'pdftocairo',
60+
# 'mudraw',
61+
]:
62+
PDFUTIL = getExecutablePath(util)
63+
if PDFUTIL:
64+
break
65+
66+
# noinspection PyUnboundLocalVariable
67+
if not PDFUTIL:
68+
assert False, ('PDF-to-image utility not found on PATH: '
69+
'install Poppler (with Cairo)')
70+
71+
if PDFUTIL.strip().endswith('pdftocairo'):
72+
filebase = os.path.join(
73+
os.path.dirname(rendered_file_path),
74+
os.path.splitext(os.path.basename(rendered_file_path))[0]
75+
)
76+
call = [
77+
PDFUTIL, '-png', '-singlefile', '-r', str(dpi),
78+
'-x', '0', '-y', '0', '-f', str(page), '-l', str(page),
79+
pdf_file_path, filebase
80+
]
81+
elif PDFUTIL.strip().endswith('mudraw'):
82+
call = [
83+
PDFUTIL, '-c', 'rgba',
84+
'-r', str(dpi), '-f', str(page), '-l', str(page),
85+
# '-b', '8',
86+
'-o', rendered_file_path, pdf_file_path
87+
]
88+
else:
89+
return False, ''
90+
91+
print("exportToPdf call: {0}".format(' '.join(call)))
92+
try:
93+
subprocess.check_call(call)
94+
except subprocess.CalledProcessError as e:
95+
assert False, ("exportToPdf failed!\n"
96+
"cmd: {0}\n"
97+
"returncode: {1}\n"
98+
"message: {2}".format(e.cmd, e.returncode, e.message))
99+
100+
def _pdf_diff(self, pdf, control_image, max_diff, max_size_diff=QSize(), dpi=96):
101+
102+
temp_pdf = os.path.join(tempfile.gettempdir(), "%s_result.pdf" % control_image)
103+
104+
with open(temp_pdf, "wb") as f:
105+
f.write(pdf)
106+
107+
temp_image = os.path.join(tempfile.gettempdir(), "%s_result.png" % control_image)
108+
self._pdf_to_png(temp_pdf, temp_image, dpi=dpi, page=1)
109+
110+
control = QgsRenderChecker()
111+
control.setControlPathPrefix("qgis_server")
112+
control.setControlName(control_image)
113+
control.setRenderedImage(temp_image)
114+
if max_size_diff.isValid():
115+
control.setSizeTolerance(max_size_diff.width(), max_size_diff.height())
116+
return control.compareImages(control_image, max_diff), control.report()
117+
118+
def _pdf_diff_error(self, response, headers, image, max_diff=100, max_size_diff=QSize(), unittest_data_path='control_images', dpi=96):
119+
120+
reference_path = unitTestDataPath(unittest_data_path) + '/qgis_server/' + image + '/' + image + '.pdf'
121+
self.store_reference(reference_path, response)
122+
123+
self.assertEqual(
124+
headers.get("Content-Type"), 'application/pdf',
125+
"Content type is wrong: %s instead of %s\n%s" % (headers.get("Content-Type"), 'application/pdf', response))
126+
127+
test, report = self._pdf_diff(response, image, max_diff, max_size_diff, dpi)
128+
129+
with open(os.path.join(tempfile.gettempdir(), image + "_result.pdf"), "rb") as rendered_file:
130+
if not os.environ.get('ENCODED_OUTPUT'):
131+
message = "PDF is wrong\: rendered file %s/%s_result.%s" % (tempfile.gettempdir(), image, 'pdf')
132+
else:
133+
encoded_rendered_file = base64.b64encode(rendered_file.read())
134+
message = "PDF is wrong\n%s\File:\necho '%s' | base64 -d >%s/%s_result.%s" % (
135+
report, encoded_rendered_file.strip().decode('utf8'), tempfile.gettempdir(), image, 'pdf'
136+
)
137+
138+
with open(os.path.join(tempfile.gettempdir(), image + "_result.png"), "rb") as rendered_file:
139+
if not os.environ.get('ENCODED_OUTPUT'):
140+
message = "Image is wrong\: rendered file %s/%s_result.%s" % (tempfile.gettempdir(), image, 'png')
141+
else:
142+
encoded_rendered_file = base64.b64encode(rendered_file.read())
143+
message = "Image is wrong\n%s\nImage:\necho '%s' | base64 -d >%s/%s_result.%s" % (
144+
report, encoded_rendered_file.strip().decode('utf8'), tempfile.gettempdir(), image, 'png'
145+
)
146+
147+
# If the failure is in image sizes the diff file will not exists.
148+
if os.path.exists(os.path.join(tempfile.gettempdir(), image + "_result_diff.png")):
149+
with open(os.path.join(tempfile.gettempdir(), image + "_result_diff.png"), "rb") as diff_file:
150+
if not os.environ.get('ENCODED_OUTPUT'):
151+
message = "Image is wrong\: diff file %s/%s_result_diff.%s" % (tempfile.gettempdir(), image, 'png')
152+
else:
153+
encoded_diff_file = base64.b64encode(diff_file.read())
154+
message += "\nDiff:\necho '%s' | base64 -d > %s/%s_result_diff.%s" % (
155+
encoded_diff_file.strip().decode('utf8'), tempfile.gettempdir(), image, 'png'
156+
)
157+
158+
self.assertTrue(test, message)
159+
160+
def _svg_to_png(svg_file_path, rendered_file_path, width):
161+
svgr = QSvgRenderer(svg_file_path)
162+
163+
height = width / svgr.viewBoxF().width() * svgr.viewBoxF().height()
164+
165+
image = QImage(width, height, QImage.Format_ARGB32)
166+
image.fill(Qt.transparent)
167+
168+
p = QPainter(image)
169+
p.setRenderHint(QPainter.Antialiasing, False)
170+
svgr.render(p)
171+
p.end()
172+
173+
res = image.save(rendered_file_path, 'png')
174+
if not res:
175+
os.unlink(rendered_file_path)
176+
45177
"""QGIS Server WMS Tests for GetPrint request"""
46178

47179
def test_wms_getprint_basic(self):
@@ -107,6 +239,22 @@ def test_wms_getprint_basic(self):
107239
r, h = self._result(self._execute_request(qs))
108240
self._img_diff_error(r, h, "WMS_GetPrint_Basic", outputJpg=True)
109241

242+
# Output PDF
243+
qs = "?" + "&".join(["%s=%s" % i for i in list({
244+
"MAP": urllib.parse.quote(self.projectPath),
245+
"SERVICE": "WMS",
246+
"VERSION": "1.1.1",
247+
"REQUEST": "GetPrint",
248+
"TEMPLATE": "layoutA4",
249+
"FORMAT": "pdf",
250+
"map0:EXTENT": "-33626185.498,-13032965.185,33978427.737,16020257.031",
251+
"LAYERS": "Country,Hello",
252+
"CRS": "EPSG:3857"
253+
}.items())])
254+
255+
r, h = self._result(self._execute_request(qs))
256+
self._pdf_diff_error(r, h, "WMS_GetPrint_Basic_Pdf", dpi=300)
257+
110258
def test_wms_getprint_style(self):
111259
# default style
112260
qs = "?" + "&".join(["%s=%s" % i for i in list({
@@ -312,6 +460,24 @@ def test_wms_getprint_selection(self):
312460
r, h = self._result(self._execute_request(qs))
313461
self._img_diff_error(r, h, "WMS_GetPrint_Selection")
314462

463+
# Output PDF
464+
qs = "?" + "&".join(["%s=%s" % i for i in list({
465+
"MAP": urllib.parse.quote(self.projectPath),
466+
"SERVICE": "WMS",
467+
"VERSION": "1.1.1",
468+
"REQUEST": "GetPrint",
469+
"TEMPLATE": "layoutA4",
470+
"FORMAT": "pdf",
471+
"LAYERS": "Country,Hello",
472+
"map0:EXTENT": "-33626185.498,-13032965.185,33978427.737,16020257.031",
473+
"map0:LAYERS": "Country,Hello",
474+
"CRS": "EPSG:3857",
475+
"SELECTION": "Country: 4"
476+
}.items())])
477+
478+
r, h = self._result(self._execute_request(qs))
479+
self._pdf_diff_error(r, h, "WMS_GetPrint_Selection_Pdf", dpi=300)
480+
315481
def test_wms_getprint_opacity(self):
316482
qs = "?" + "&".join(["%s=%s" % i for i in list({
317483
"MAP": urllib.parse.quote(self.projectPath),
Loading

0 commit comments

Comments
 (0)