|
28 | 28 |
|
29 | 29 | from qgis.testing import unittest
|
30 | 30 | from qgis.PyQt.QtCore import QSize
|
| 31 | +from qgis.PyQt.QtGui import QImage, QPainter |
| 32 | +from qgis.PyQt.QtSvg import QSvgRenderer, QSvgGenerator |
31 | 33 |
|
32 | 34 | import osgeo.gdal # NOQA
|
| 35 | +import tempfile |
| 36 | +import base64 |
| 37 | +import subprocess |
33 | 38 |
|
34 | 39 | from test_qgsserver import QgsServerTestBase
|
35 |
| -from qgis.core import QgsProject |
| 40 | +from qgis.core import QgsProject, QgsRenderChecker |
36 | 41 | from qgis.server import QgsServerRequest
|
| 42 | +from utilities import getExecutablePath, unitTestDataPath |
37 | 43 |
|
38 | 44 | # Strip path and content length because path may vary
|
39 | 45 | RE_STRIP_UNCHECKABLE = b'MAP=[^"]+|Content-Length: \d+'
|
|
42 | 48 |
|
43 | 49 | class TestQgsServerWMSGetPrint(QgsServerTestBase):
|
44 | 50 |
|
| 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 | + |
45 | 177 | """QGIS Server WMS Tests for GetPrint request"""
|
46 | 178 |
|
47 | 179 | def test_wms_getprint_basic(self):
|
@@ -107,6 +239,22 @@ def test_wms_getprint_basic(self):
|
107 | 239 | r, h = self._result(self._execute_request(qs))
|
108 | 240 | self._img_diff_error(r, h, "WMS_GetPrint_Basic", outputJpg=True)
|
109 | 241 |
|
| 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 | + |
110 | 258 | def test_wms_getprint_style(self):
|
111 | 259 | # default style
|
112 | 260 | qs = "?" + "&".join(["%s=%s" % i for i in list({
|
@@ -312,6 +460,24 @@ def test_wms_getprint_selection(self):
|
312 | 460 | r, h = self._result(self._execute_request(qs))
|
313 | 461 | self._img_diff_error(r, h, "WMS_GetPrint_Selection")
|
314 | 462 |
|
| 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 | + |
315 | 481 | def test_wms_getprint_opacity(self):
|
316 | 482 | qs = "?" + "&".join(["%s=%s" % i for i in list({
|
317 | 483 | "MAP": urllib.parse.quote(self.projectPath),
|
|
0 commit comments