diff --git a/src/core/layout/qgslayoutexporter.cpp b/src/core/layout/qgslayoutexporter.cpp index 3a3ff8a78fa2..be32d98820cd 100644 --- a/src/core/layout/qgslayoutexporter.cpp +++ b/src/core/layout/qgslayoutexporter.cpp @@ -490,6 +490,8 @@ QgsLayoutExporter::ExportResult QgsLayoutExporter::exportToPdf( const QString &f ( void )contextRestorer; mLayout->renderContext().setDpi( settings.dpi ); + mLayout->renderContext().setFlags( settings.flags ); + // If we are not printing as raster, temporarily disable advanced effects // as QPrinter does not support composition modes and can result // in items missing from the output @@ -561,6 +563,8 @@ QgsLayoutExporter::ExportResult QgsLayoutExporter::exportToPdf( QgsAbstractLayou ( void )contextRestorer; iterator->layout()->renderContext().setDpi( settings.dpi ); + iterator->layout()->renderContext().setFlags( settings.flags ); + // If we are not printing as raster, temporarily disable advanced effects // as QPrinter does not support composition modes and can result // in items missing from the output @@ -671,6 +675,7 @@ QgsLayoutExporter::ExportResult QgsLayoutExporter::print( QPrinter &printer, con ( void )contextRestorer; mLayout->renderContext().setDpi( settings.dpi ); + mLayout->renderContext().setFlags( settings.flags ); // If we are not printing as raster, temporarily disable advanced effects // as QPrinter does not support composition modes and can result // in items missing from the output @@ -730,6 +735,8 @@ QgsLayoutExporter::ExportResult QgsLayoutExporter::print( QgsAbstractLayoutItera ( void )contextRestorer; iterator->layout()->renderContext().setDpi( settings.dpi ); + iterator->layout()->renderContext().setFlags( settings.flags ); + // If we are not printing as raster, temporarily disable advanced effects // as QPrinter does not support composition modes and can result // in items missing from the output @@ -784,6 +791,7 @@ QgsLayoutExporter::ExportResult QgsLayoutExporter::exportToSvg( const QString &f ( void )contextRestorer; mLayout->renderContext().setDpi( settings.dpi ); + mLayout->renderContext().setFlags( settings.flags ); mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagForceVectorOutput, settings.forceVectorOutput ); mLayout->renderContext().setTextRenderFormat( s.textRenderFormat ); diff --git a/tests/src/python/test_qgsserver_wms_getprint.py b/tests/src/python/test_qgsserver_wms_getprint.py index 0baede2a7188..58f1035e4448 100644 --- a/tests/src/python/test_qgsserver_wms_getprint.py +++ b/tests/src/python/test_qgsserver_wms_getprint.py @@ -28,12 +28,18 @@ from qgis.testing import unittest from qgis.PyQt.QtCore import QSize +from qgis.PyQt.QtGui import QImage, QPainter +from qgis.PyQt.QtSvg import QSvgRenderer, QSvgGenerator import osgeo.gdal # NOQA +import tempfile +import base64 +import subprocess from test_qgsserver import QgsServerTestBase -from qgis.core import QgsProject +from qgis.core import QgsProject, QgsRenderChecker from qgis.server import QgsServerRequest +from utilities import getExecutablePath, unitTestDataPath # Strip path and content length because path may vary RE_STRIP_UNCHECKABLE = b'MAP=[^"]+|Content-Length: \d+' @@ -42,6 +48,132 @@ class TestQgsServerWMSGetPrint(QgsServerTestBase): + def _pdf_to_png(self, pdf_file_path, rendered_file_path, page, dpi=96): + + # PDF-to-image utility + # look for Poppler w/ Cairo, then muPDF + # * Poppler w/ Cairo renders correctly + # * Poppler w/o Cairo does not always correctly render vectors in PDF to image + # * muPDF renders correctly, but slightly shifts colors + for util in [ + 'pdftocairo', + # 'mudraw', + ]: + PDFUTIL = getExecutablePath(util) + if PDFUTIL: + break + + # noinspection PyUnboundLocalVariable + if not PDFUTIL: + assert False, ('PDF-to-image utility not found on PATH: ' + 'install Poppler (with Cairo)') + + if PDFUTIL.strip().endswith('pdftocairo'): + filebase = os.path.join( + os.path.dirname(rendered_file_path), + os.path.splitext(os.path.basename(rendered_file_path))[0] + ) + call = [ + PDFUTIL, '-png', '-singlefile', '-r', str(dpi), + '-x', '0', '-y', '0', '-f', str(page), '-l', str(page), + pdf_file_path, filebase + ] + elif PDFUTIL.strip().endswith('mudraw'): + call = [ + PDFUTIL, '-c', 'rgba', + '-r', str(dpi), '-f', str(page), '-l', str(page), + # '-b', '8', + '-o', rendered_file_path, pdf_file_path + ] + else: + return False, '' + + print("exportToPdf call: {0}".format(' '.join(call))) + try: + subprocess.check_call(call) + except subprocess.CalledProcessError as e: + assert False, ("exportToPdf failed!\n" + "cmd: {0}\n" + "returncode: {1}\n" + "message: {2}".format(e.cmd, e.returncode, e.message)) + + def _pdf_diff(self, pdf, control_image, max_diff, max_size_diff=QSize(), dpi=96): + + temp_pdf = os.path.join(tempfile.gettempdir(), "%s_result.pdf" % control_image) + + with open(temp_pdf, "wb") as f: + f.write(pdf) + + temp_image = os.path.join(tempfile.gettempdir(), "%s_result.png" % control_image) + self._pdf_to_png(temp_pdf, temp_image, dpi=dpi, page=1) + + control = QgsRenderChecker() + control.setControlPathPrefix("qgis_server") + control.setControlName(control_image) + control.setRenderedImage(temp_image) + if max_size_diff.isValid(): + control.setSizeTolerance(max_size_diff.width(), max_size_diff.height()) + return control.compareImages(control_image, max_diff), control.report() + + def _pdf_diff_error(self, response, headers, image, max_diff=100, max_size_diff=QSize(), unittest_data_path='control_images', dpi=96): + + reference_path = unitTestDataPath(unittest_data_path) + '/qgis_server/' + image + '/' + image + '.pdf' + self.store_reference(reference_path, response) + + self.assertEqual( + headers.get("Content-Type"), 'application/pdf', + "Content type is wrong: %s instead of %s\n%s" % (headers.get("Content-Type"), 'application/pdf', response)) + + test, report = self._pdf_diff(response, image, max_diff, max_size_diff, dpi) + + with open(os.path.join(tempfile.gettempdir(), image + "_result.pdf"), "rb") as rendered_file: + if not os.environ.get('ENCODED_OUTPUT'): + message = "PDF is wrong\: rendered file %s/%s_result.%s" % (tempfile.gettempdir(), image, 'pdf') + else: + encoded_rendered_file = base64.b64encode(rendered_file.read()) + message = "PDF is wrong\n%s\File:\necho '%s' | base64 -d >%s/%s_result.%s" % ( + report, encoded_rendered_file.strip().decode('utf8'), tempfile.gettempdir(), image, 'pdf' + ) + + with open(os.path.join(tempfile.gettempdir(), image + "_result.png"), "rb") as rendered_file: + if not os.environ.get('ENCODED_OUTPUT'): + message = "Image is wrong\: rendered file %s/%s_result.%s" % (tempfile.gettempdir(), image, 'png') + else: + encoded_rendered_file = base64.b64encode(rendered_file.read()) + message = "Image is wrong\n%s\nImage:\necho '%s' | base64 -d >%s/%s_result.%s" % ( + report, encoded_rendered_file.strip().decode('utf8'), tempfile.gettempdir(), image, 'png' + ) + + # If the failure is in image sizes the diff file will not exists. + if os.path.exists(os.path.join(tempfile.gettempdir(), image + "_result_diff.png")): + with open(os.path.join(tempfile.gettempdir(), image + "_result_diff.png"), "rb") as diff_file: + if not os.environ.get('ENCODED_OUTPUT'): + message = "Image is wrong\: diff file %s/%s_result_diff.%s" % (tempfile.gettempdir(), image, 'png') + else: + encoded_diff_file = base64.b64encode(diff_file.read()) + message += "\nDiff:\necho '%s' | base64 -d > %s/%s_result_diff.%s" % ( + encoded_diff_file.strip().decode('utf8'), tempfile.gettempdir(), image, 'png' + ) + + self.assertTrue(test, message) + + def _svg_to_png(svg_file_path, rendered_file_path, width): + svgr = QSvgRenderer(svg_file_path) + + height = width / svgr.viewBoxF().width() * svgr.viewBoxF().height() + + image = QImage(width, height, QImage.Format_ARGB32) + image.fill(Qt.transparent) + + p = QPainter(image) + p.setRenderHint(QPainter.Antialiasing, False) + svgr.render(p) + p.end() + + res = image.save(rendered_file_path, 'png') + if not res: + os.unlink(rendered_file_path) + """QGIS Server WMS Tests for GetPrint request""" def test_wms_getprint_basic(self): @@ -107,6 +239,22 @@ def test_wms_getprint_basic(self): r, h = self._result(self._execute_request(qs)) self._img_diff_error(r, h, "WMS_GetPrint_Basic", outputJpg=True) + # Output PDF + qs = "?" + "&".join(["%s=%s" % i for i in list({ + "MAP": urllib.parse.quote(self.projectPath), + "SERVICE": "WMS", + "VERSION": "1.1.1", + "REQUEST": "GetPrint", + "TEMPLATE": "layoutA4", + "FORMAT": "pdf", + "map0:EXTENT": "-33626185.498,-13032965.185,33978427.737,16020257.031", + "LAYERS": "Country,Hello", + "CRS": "EPSG:3857" + }.items())]) + + r, h = self._result(self._execute_request(qs)) + self._pdf_diff_error(r, h, "WMS_GetPrint_Basic_Pdf", dpi=300) + def test_wms_getprint_style(self): # default style qs = "?" + "&".join(["%s=%s" % i for i in list({ @@ -312,6 +460,24 @@ def test_wms_getprint_selection(self): r, h = self._result(self._execute_request(qs)) self._img_diff_error(r, h, "WMS_GetPrint_Selection") + # Output PDF + qs = "?" + "&".join(["%s=%s" % i for i in list({ + "MAP": urllib.parse.quote(self.projectPath), + "SERVICE": "WMS", + "VERSION": "1.1.1", + "REQUEST": "GetPrint", + "TEMPLATE": "layoutA4", + "FORMAT": "pdf", + "LAYERS": "Country,Hello", + "map0:EXTENT": "-33626185.498,-13032965.185,33978427.737,16020257.031", + "map0:LAYERS": "Country,Hello", + "CRS": "EPSG:3857", + "SELECTION": "Country: 4" + }.items())]) + + r, h = self._result(self._execute_request(qs)) + self._pdf_diff_error(r, h, "WMS_GetPrint_Selection_Pdf", dpi=300) + def test_wms_getprint_opacity(self): qs = "?" + "&".join(["%s=%s" % i for i in list({ "MAP": urllib.parse.quote(self.projectPath), diff --git a/tests/testdata/control_images/qgis_server/WMS_GetPrint_Basic_Pdf/WMS_GetPrint_Basic_Pdf.png b/tests/testdata/control_images/qgis_server/WMS_GetPrint_Basic_Pdf/WMS_GetPrint_Basic_Pdf.png new file mode 100644 index 000000000000..10c23f0d8ea8 Binary files /dev/null and b/tests/testdata/control_images/qgis_server/WMS_GetPrint_Basic_Pdf/WMS_GetPrint_Basic_Pdf.png differ diff --git a/tests/testdata/control_images/qgis_server/WMS_GetPrint_Basic_Pdf/WMS_GetPrint_Basic_Pdf_mask.png b/tests/testdata/control_images/qgis_server/WMS_GetPrint_Basic_Pdf/WMS_GetPrint_Basic_Pdf_mask.png new file mode 100644 index 000000000000..37a8e472cde2 Binary files /dev/null and b/tests/testdata/control_images/qgis_server/WMS_GetPrint_Basic_Pdf/WMS_GetPrint_Basic_Pdf_mask.png differ diff --git a/tests/testdata/control_images/qgis_server/WMS_GetPrint_Selection_Pdf/WMS_GetPrint_Selection_Pdf.png b/tests/testdata/control_images/qgis_server/WMS_GetPrint_Selection_Pdf/WMS_GetPrint_Selection_Pdf.png new file mode 100644 index 000000000000..e13ebf39fff8 Binary files /dev/null and b/tests/testdata/control_images/qgis_server/WMS_GetPrint_Selection_Pdf/WMS_GetPrint_Selection_Pdf.png differ diff --git a/tests/testdata/control_images/qgis_server/WMS_GetPrint_Selection_Pdf/WMS_GetPrint_Selection_Pdf_mask.png b/tests/testdata/control_images/qgis_server/WMS_GetPrint_Selection_Pdf/WMS_GetPrint_Selection_Pdf_mask.png new file mode 100644 index 000000000000..dab52d6367ed Binary files /dev/null and b/tests/testdata/control_images/qgis_server/WMS_GetPrint_Selection_Pdf/WMS_GetPrint_Selection_Pdf_mask.png differ