Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix regression server print selection pdf release 3.4 #9692

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/core/layout/qgslayoutexporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,8 @@ QgsLayoutExporter::ExportResult QgsLayoutExporter::exportToPdf( const QString &f
( void )contextRestorer;
mLayout->renderContext().setDpi( settings.dpi );

mLayout->renderContext().setFlags( settings.flags );
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% sure, but this change may only have been ok in 3.6/master because of corresponding changes in the layout designer dialog. Can you look through the git blame on master for when I introduced this and please confirm that there's no related changes made in the designer to accompany this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nyalldawson you're right an other commit is associated in the PR [needs-docs][layouts] Add checkbox to disable raster tiling for PDF/SVG exports #9016.

Do you think the other commit has to be backported ?
Does the Fix inconsistent use of layout render context flags 2752f83 introduced some regressions ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue fixed by your PR is https://issues.qgis.org/issues/19500 Layout export - raster divided into tiles, edges evident in pdf/svg

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is not linked to layout designer dialog. I have introduced flag in layout to print selection in server context.
I have done the code for release 3.4.0 with the PR #8320 [Server] Reactivate the capability to print selection with Server 3.4

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @nyalldawson,

The mLayout->renderContext().setFlags( settings.flags ); has been introduced before the Refactor layout context 5bc543a
https://github.com/qgis/QGIS/blame/master/src/core/layout/qgslayoutexporter.cpp#L356 in QGIS 3.0.0 in method exportToImage because this commit only rename the context access in layout.

The mLayout->context().setFlags( settings.flags ); has been introduced before the **Work on PDF export ** 91179f1 in QGIS 3.0.0 in which you introduce exportToPdf and the setFlags missed.

In this commit you wrote

  // 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
  mLayout->context().setFlag( QgsLayoutContext::FlagUseAdvancedEffects, settings.rasteriseWholeImage );

But flags are not set before.

The mLayout->context().setFlags( settings.flags ); in exportToImage has been introduced by Expose antialiasing option in image export dialog 2f0969e#diff-7393cc3d739cdcd8a6531909637b63b5


// 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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 );

Expand Down
168 changes: 167 additions & 1 deletion tests/src/python/test_qgsserver_wms_getprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -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+'
Expand All @@ -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):
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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),
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.