1# -*- coding: utf-8 -*- 2"""QGIS Unit tests for QgsServer WMS GetPrint. 3 4From build dir, run: ctest -R PyQgsServerWMSGetPrintOutputs -V 5 6 7.. note:: This program is free software; you can redistribute it and/or modify 8it under the terms of the GNU General Public License as published by 9the Free Software Foundation; either version 2 of the License, or 10(at your option) any later version. 11 12""" 13__author__ = 'René-Luc DHONT' 14__date__ = '24/06/2020' 15__copyright__ = 'Copyright 2020, The QGIS Project' 16 17import os 18 19# Needed on Qt 5 so that the serialization of XML is consistent among all executions 20os.environ['QT_HASH_SEED'] = '1' 21 22import urllib.parse 23 24from qgis.testing import unittest 25from qgis.PyQt.QtCore import QSize, Qt 26from qgis.PyQt.QtGui import QImage, QPainter 27from qgis.PyQt.QtSvg import QSvgRenderer, QSvgGenerator 28 29import osgeo.gdal # NOQA 30import tempfile 31import base64 32import subprocess 33 34from test_qgsserver import QgsServerTestBase 35from qgis.core import QgsMultiRenderChecker 36from utilities import getExecutablePath, unitTestDataPath 37 38 39class PyQgsServerWMSGetPrintOutputs(QgsServerTestBase): 40 41 def _pdf_to_png(self, pdf_file_path, rendered_file_path, page, dpi=96): 42 43 # PDF-to-image utility 44 # look for Poppler w/ Cairo, then muPDF 45 # * Poppler w/ Cairo renders correctly 46 # * Poppler w/o Cairo does not always correctly render vectors in PDF to image 47 # * muPDF renders correctly, but slightly shifts colors 48 for util in [ 49 'pdftocairo', 50 # 'mudraw', 51 ]: 52 PDFUTIL = getExecutablePath(util) 53 if PDFUTIL: 54 break 55 56 # noinspection PyUnboundLocalVariable 57 if not PDFUTIL: 58 assert False, ('PDF-to-image utility not found on PATH: ' 59 'install Poppler (with Cairo)') 60 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 def _pdf_diff(self, pdf, control_image, max_diff, max_size_diff=QSize(), dpi=96): 91 92 temp_pdf = os.path.join(tempfile.gettempdir(), "%s_result.pdf" % control_image) 93 94 with open(temp_pdf, "wb") as f: 95 f.write(pdf) 96 97 temp_image = os.path.join(tempfile.gettempdir(), "%s_result.png" % control_image) 98 self._pdf_to_png(temp_pdf, temp_image, dpi=dpi, page=1) 99 100 control = QgsMultiRenderChecker() 101 control.setControlPathPrefix("qgis_server") 102 control.setControlName(control_image) 103 control.setRenderedImage(temp_image) 104 if max_size_diff.isValid(): 105 control.setSizeTolerance(max_size_diff.width(), max_size_diff.height()) 106 return control.runTest(control_image, max_diff), control.report() 107 108 def _pdf_diff_error(self, response, headers, image, max_diff=100, max_size_diff=QSize(), 109 unittest_data_path='control_images', dpi=96): 110 111 reference_path = unitTestDataPath(unittest_data_path) + '/qgis_server/' + image + '/' + image + '.pdf' 112 self.store_reference(reference_path, response) 113 114 self.assertEqual( 115 headers.get("Content-Type"), 'application/pdf', 116 "Content type is wrong: %s instead of %s\n%s" % (headers.get("Content-Type"), 'application/pdf', response)) 117 118 test, report = self._pdf_diff(response, image, max_diff, max_size_diff, dpi) 119 120 with open(os.path.join(tempfile.gettempdir(), image + "_result.pdf"), "rb") as rendered_file: 121 if not os.environ.get('ENCODED_OUTPUT'): 122 message = "PDF is wrong: rendered file %s/%s_result.%s" % (tempfile.gettempdir(), image, 'pdf') 123 else: 124 encoded_rendered_file = base64.b64encode(rendered_file.read()) 125 message = "PDF is wrong\n%sFile:\necho '%s' | base64 -d >%s/%s_result.%s" % ( 126 report, encoded_rendered_file.strip().decode('utf8'), tempfile.gettempdir(), image, 'pdf' 127 ) 128 129 with open(os.path.join(tempfile.gettempdir(), image + "_result.png"), "rb") as rendered_file: 130 if not os.environ.get('ENCODED_OUTPUT'): 131 message = "Image is wrong: rendered file %s/%s_result.%s" % (tempfile.gettempdir(), image, 'png') 132 else: 133 encoded_rendered_file = base64.b64encode(rendered_file.read()) 134 message = "Image is wrong\n%s\nImage:\necho '%s' | base64 -d >%s/%s_result.%s" % ( 135 report, encoded_rendered_file.strip().decode('utf8'), tempfile.gettempdir(), image, 'png' 136 ) 137 138 # If the failure is in image sizes the diff file will not exists. 139 if os.path.exists(os.path.join(tempfile.gettempdir(), image + "_result_diff.png")): 140 with open(os.path.join(tempfile.gettempdir(), image + "_result_diff.png"), "rb") as diff_file: 141 if not os.environ.get('ENCODED_OUTPUT'): 142 message = "Image is wrong: diff file %s/%s_result_diff.%s" % (tempfile.gettempdir(), image, 'png') 143 else: 144 encoded_diff_file = base64.b64encode(diff_file.read()) 145 message += "\nDiff:\necho '%s' | base64 -d > %s/%s_result_diff.%s" % ( 146 encoded_diff_file.strip().decode('utf8'), tempfile.gettempdir(), image, 'png' 147 ) 148 149 self.assertTrue(test, message) 150 151 def _svg_to_png(svg_file_path, rendered_file_path, width): 152 svgr = QSvgRenderer(svg_file_path) 153 154 height = width / svgr.viewBoxF().width() * svgr.viewBoxF().height() 155 156 image = QImage(width, height, QImage.Format_ARGB32) 157 image.fill(Qt.transparent) 158 159 p = QPainter(image) 160 p.setRenderHint(QPainter.Antialiasing, False) 161 svgr.render(p) 162 p.end() 163 164 res = image.save(rendered_file_path, 'png') 165 if not res: 166 os.unlink(rendered_file_path) 167 168 """QGIS Server WMS Tests for GetPrint request with non default outputs""" 169 170 def test_wms_getprint_basic(self): 171 172 # Output JPEG 173 qs = "?" + "&".join(["%s=%s" % i for i in list({ 174 "MAP": urllib.parse.quote(self.projectPath), 175 "SERVICE": "WMS", 176 "VERSION": "1.1.1", 177 "REQUEST": "GetPrint", 178 "TEMPLATE": "layoutA4", 179 "FORMAT": "jpeg", 180 "map0:EXTENT": "-33626185.498,-13032965.185,33978427.737,16020257.031", 181 "LAYERS": "Country,Hello", 182 "CRS": "EPSG:3857" 183 }.items())]) 184 185 r, h = self._result(self._execute_request(qs)) 186 self._img_diff_error(r, h, "WMS_GetPrint_Basic", outputFormat='JPG') 187 188 # Output PDF 189 qs = "?" + "&".join(["%s=%s" % i for i in list({ 190 "MAP": urllib.parse.quote(self.projectPath), 191 "SERVICE": "WMS", 192 "VERSION": "1.1.1", 193 "REQUEST": "GetPrint", 194 "TEMPLATE": "layoutA4", 195 "FORMAT": "pdf", 196 "map0:EXTENT": "-33626185.498,-13032965.185,33978427.737,16020257.031", 197 "LAYERS": "Country,Hello", 198 "CRS": "EPSG:3857" 199 }.items())]) 200 201 r, h = self._result(self._execute_request(qs)) 202 self._pdf_diff_error(r, h, "WMS_GetPrint_Basic_Pdf", dpi=300) 203 204 def test_wms_getprint_selection(self): 205 206 # Output PDF 207 qs = "?" + "&".join(["%s=%s" % i for i in list({ 208 "MAP": urllib.parse.quote(self.projectPath), 209 "SERVICE": "WMS", 210 "VERSION": "1.1.1", 211 "REQUEST": "GetPrint", 212 "TEMPLATE": "layoutA4", 213 "FORMAT": "pdf", 214 "LAYERS": "Country,Hello", 215 "map0:EXTENT": "-33626185.498,-13032965.185,33978427.737,16020257.031", 216 "map0:LAYERS": "Country,Hello", 217 "CRS": "EPSG:3857", 218 "SELECTION": "Country: 4" 219 }.items())]) 220 221 r, h = self._result(self._execute_request(qs)) 222 self._pdf_diff_error(r, h, "WMS_GetPrint_Selection_Pdf", dpi=300) 223 224 225if __name__ == '__main__': 226 unittest.main() 227