1# -*- coding: utf-8 -*-
2"""QGIS Unit tests for QgsLayoutExporter
3
4.. note:: This program is free software; you can redistribute it and/or modify
5it under the terms of the GNU General Public License as published by
6the Free Software Foundation; either version 2 of the License, or
7(at your option) any later version.
8"""
9__author__ = 'Nyall Dawson'
10__date__ = '11/12/2017'
11__copyright__ = 'Copyright 2017, The QGIS Project'
12
13import qgis  # NOQA
14from qgis.PyQt import sip
15import tempfile
16import shutil
17import os
18import subprocess
19from xml.dom import minidom
20from osgeo import gdal
21
22from qgis.core import (QgsMultiRenderChecker,
23                       QgsLayoutExporter,
24                       QgsLayout,
25                       QgsProject,
26                       QgsMargins,
27                       QgsLayoutItemShape,
28                       QgsLayoutGuide,
29                       QgsRectangle,
30                       QgsLayoutItemPage,
31                       QgsLayoutItemMap,
32                       QgsLayoutItemScaleBar,
33                       QgsLayoutPoint,
34                       QgsLayoutMeasurement,
35                       QgsUnitTypes,
36                       QgsSimpleFillSymbolLayer,
37                       QgsFillSymbol,
38                       QgsVectorLayer,
39                       QgsCoordinateReferenceSystem,
40                       QgsPrintLayout,
41                       QgsSingleSymbolRenderer,
42                       QgsRenderContext,
43                       QgsReport)
44from qgis.PyQt.QtCore import QSize, QSizeF, QDir, QRectF, Qt, QDateTime, QDate, QTime, QTimeZone
45from qgis.PyQt.QtGui import QImage, QPainter
46from qgis.PyQt.QtPrintSupport import QPrinter
47from qgis.PyQt.QtSvg import QSvgRenderer, QSvgGenerator
48
49from qgis.testing import start_app, unittest
50
51from utilities import getExecutablePath, unitTestDataPath
52
53TEST_DATA_DIR = unitTestDataPath()
54
55# PDF-to-image utility
56# look for Poppler w/ Cairo, then muPDF
57# * Poppler w/ Cairo renders correctly
58# * Poppler w/o Cairo does not always correctly render vectors in PDF to image
59# * muPDF renders correctly, but slightly shifts colors
60for util in [
61    'pdftocairo',
62    # 'mudraw',
63]:
64    PDFUTIL = getExecutablePath(util)
65    if PDFUTIL:
66        break
67
68# noinspection PyUnboundLocalVariable
69if not PDFUTIL:
70    raise Exception('PDF-to-image utility not found on PATH: '
71                    'install Poppler (with Cairo)')
72
73
74def pdfToPng(pdf_file_path, rendered_file_path, page, dpi=96):
75    if PDFUTIL.strip().endswith('pdftocairo'):
76        filebase = os.path.join(
77            os.path.dirname(rendered_file_path),
78            os.path.splitext(os.path.basename(rendered_file_path))[0]
79        )
80        call = [
81            PDFUTIL, '-png', '-singlefile', '-r', str(dpi),
82            '-x', '0', '-y', '0', '-f', str(page), '-l', str(page),
83            pdf_file_path, filebase
84        ]
85    elif PDFUTIL.strip().endswith('mudraw'):
86        call = [
87            PDFUTIL, '-c', 'rgba',
88            '-r', str(dpi), '-f', str(page), '-l', str(page),
89            # '-b', '8',
90            '-o', rendered_file_path, pdf_file_path
91        ]
92    else:
93        return False, ''
94
95    print("exportToPdf call: {0}".format(' '.join(call)))
96    try:
97        subprocess.check_call(call)
98    except subprocess.CalledProcessError as e:
99        assert False, ("exportToPdf failed!\n"
100                       "cmd: {0}\n"
101                       "returncode: {1}\n"
102                       "message: {2}".format(e.cmd, e.returncode, e.message))
103
104
105def svgToPng(svg_file_path, rendered_file_path, width):
106    svgr = QSvgRenderer(svg_file_path)
107
108    height = width / svgr.viewBoxF().width() * svgr.viewBoxF().height()
109
110    image = QImage(width, height, QImage.Format_ARGB32)
111    image.fill(Qt.transparent)
112
113    p = QPainter(image)
114    p.setRenderHint(QPainter.Antialiasing, False)
115    svgr.render(p)
116    p.end()
117
118    res = image.save(rendered_file_path, 'png')
119    if not res:
120        os.unlink(rendered_file_path)
121
122
123start_app()
124
125
126class TestQgsLayoutExporter(unittest.TestCase):
127
128    @classmethod
129    def setUpClass(cls):
130        """Run before all tests"""
131        cls.basetestpath = tempfile.mkdtemp()
132        cls.dots_per_meter = 96 / 25.4 * 1000
133
134    def setUp(self):
135        self.report = "<h1>Python QgsLayoutExporter Tests</h1>\n"
136
137    def tearDown(self):
138        report_file_path = "%s/qgistest.html" % QDir.tempPath()
139        with open(report_file_path, 'a') as report_file:
140            report_file.write(self.report)
141
142    def checkImage(self, name, reference_image, rendered_image, size_tolerance=0):
143        checker = QgsMultiRenderChecker()
144        checker.setControlPathPrefix("layout_exporter")
145        checker.setControlName("expected_layoutexporter_" + reference_image)
146        checker.setRenderedImage(rendered_image)
147        checker.setColorTolerance(2)
148        checker.setSizeTolerance(size_tolerance, size_tolerance)
149        result = checker.runTest(name, 20)
150        self.report += checker.report()
151        print((self.report))
152        return result
153
154    def testRenderPage(self):
155        l = QgsLayout(QgsProject.instance())
156        l.initializeDefaults()
157
158        # add some items
159        item1 = QgsLayoutItemShape(l)
160        item1.attemptSetSceneRect(QRectF(10, 20, 100, 150))
161        fill = QgsSimpleFillSymbolLayer()
162        fill_symbol = QgsFillSymbol()
163        fill_symbol.changeSymbolLayer(0, fill)
164        fill.setColor(Qt.green)
165        fill.setStrokeStyle(Qt.NoPen)
166        item1.setSymbol(fill_symbol)
167        l.addItem(item1)
168
169        # get width/height, create image and render the composition to it
170        size = QSize(1122, 794)
171        output_image = QImage(size, QImage.Format_RGB32)
172
173        output_image.setDotsPerMeterX(self.dots_per_meter)
174        output_image.setDotsPerMeterY(self.dots_per_meter)
175        QgsMultiRenderChecker.drawBackground(output_image)
176        painter = QPainter(output_image)
177        exporter = QgsLayoutExporter(l)
178
179        # valid page
180        exporter.renderPage(painter, 0)
181        painter.end()
182
183        rendered_file_path = os.path.join(self.basetestpath, 'test_renderpage.png')
184        output_image.save(rendered_file_path, "PNG")
185        self.assertTrue(self.checkImage('renderpage', 'renderpage', rendered_file_path))
186
187    def testRenderPageToImage(self):
188        l = QgsLayout(QgsProject.instance())
189        l.initializeDefaults()
190
191        # add some items
192        item1 = QgsLayoutItemShape(l)
193        item1.attemptSetSceneRect(QRectF(10, 20, 100, 150))
194        fill = QgsSimpleFillSymbolLayer()
195        fill_symbol = QgsFillSymbol()
196        fill_symbol.changeSymbolLayer(0, fill)
197        fill.setColor(Qt.green)
198        fill.setStrokeStyle(Qt.NoPen)
199        item1.setSymbol(fill_symbol)
200        l.addItem(item1)
201
202        exporter = QgsLayoutExporter(l)
203        size = QSize(1122, 794)
204
205        # bad page numbers
206        image = exporter.renderPageToImage(-1, size)
207        self.assertTrue(image.isNull())
208        image = exporter.renderPageToImage(1, size)
209        self.assertTrue(image.isNull())
210
211        # good page
212        image = exporter.renderPageToImage(0, size)
213        self.assertFalse(image.isNull())
214
215        rendered_file_path = os.path.join(self.basetestpath, 'test_rendertoimagepage.png')
216        image.save(rendered_file_path, "PNG")
217        self.assertTrue(self.checkImage('rendertoimagepage', 'rendertoimagepage', rendered_file_path))
218
219    def testRenderRegion(self):
220        l = QgsLayout(QgsProject.instance())
221        l.initializeDefaults()
222
223        # add a guide, to ensure it is not included in export
224        g1 = QgsLayoutGuide(Qt.Horizontal, QgsLayoutMeasurement(15, QgsUnitTypes.LayoutMillimeters), l.pageCollection().page(0))
225        l.guides().addGuide(g1)
226
227        # add some items
228        item1 = QgsLayoutItemShape(l)
229        item1.attemptSetSceneRect(QRectF(10, 20, 100, 150))
230        fill = QgsSimpleFillSymbolLayer()
231        fill_symbol = QgsFillSymbol()
232        fill_symbol.changeSymbolLayer(0, fill)
233        fill.setColor(Qt.green)
234        fill.setStrokeStyle(Qt.NoPen)
235        item1.setSymbol(fill_symbol)
236        l.addItem(item1)
237
238        # get width/height, create image and render the composition to it
239        size = QSize(560, 509)
240        output_image = QImage(size, QImage.Format_RGB32)
241
242        output_image.setDotsPerMeterX(self.dots_per_meter)
243        output_image.setDotsPerMeterY(self.dots_per_meter)
244        QgsMultiRenderChecker.drawBackground(output_image)
245        painter = QPainter(output_image)
246        exporter = QgsLayoutExporter(l)
247
248        exporter.renderRegion(painter, QRectF(5, 10, 110, 100))
249        painter.end()
250
251        rendered_file_path = os.path.join(self.basetestpath, 'test_renderregion.png')
252        output_image.save(rendered_file_path, "PNG")
253        self.assertTrue(self.checkImage('renderregion', 'renderregion', rendered_file_path))
254
255    def testRenderRegionToImage(self):
256        l = QgsLayout(QgsProject.instance())
257        l.initializeDefaults()
258
259        # add some items
260        item1 = QgsLayoutItemShape(l)
261        item1.attemptSetSceneRect(QRectF(10, 20, 100, 150))
262        fill = QgsSimpleFillSymbolLayer()
263        fill_symbol = QgsFillSymbol()
264        fill_symbol.changeSymbolLayer(0, fill)
265        fill.setColor(Qt.green)
266        fill.setStrokeStyle(Qt.NoPen)
267        item1.setSymbol(fill_symbol)
268        l.addItem(item1)
269
270        exporter = QgsLayoutExporter(l)
271        size = QSize(560, 509)
272
273        image = exporter.renderRegionToImage(QRectF(5, 10, 110, 100), size)
274        self.assertFalse(image.isNull())
275
276        rendered_file_path = os.path.join(self.basetestpath, 'test_rendertoimageregionsize.png')
277        image.save(rendered_file_path, "PNG")
278        self.assertTrue(self.checkImage('rendertoimageregionsize', 'rendertoimageregionsize', rendered_file_path))
279
280        # using layout dpi
281        l.renderContext().setDpi(40)
282        image = exporter.renderRegionToImage(QRectF(5, 10, 110, 100))
283        self.assertFalse(image.isNull())
284
285        rendered_file_path = os.path.join(self.basetestpath, 'test_rendertoimageregiondpi.png')
286        image.save(rendered_file_path, "PNG")
287        self.assertTrue(self.checkImage('rendertoimageregiondpi', 'rendertoimageregiondpi', rendered_file_path))
288
289        # overriding dpi
290        image = exporter.renderRegionToImage(QRectF(5, 10, 110, 100), QSize(), 80)
291        self.assertFalse(image.isNull())
292
293        rendered_file_path = os.path.join(self.basetestpath, 'test_rendertoimageregionoverridedpi.png')
294        image.save(rendered_file_path, "PNG")
295        self.assertTrue(self.checkImage('rendertoimageregionoverridedpi', 'rendertoimageregionoverridedpi', rendered_file_path))
296
297    def testExportToImage(self):
298        md = QgsProject.instance().metadata()
299        md.setTitle('proj title')
300        md.setAuthor('proj author')
301        md.setCreationDateTime(QDateTime(QDate(2011, 5, 3), QTime(9, 4, 5), QTimeZone(36000)))
302        md.setIdentifier('proj identifier')
303        md.setAbstract('proj abstract')
304        md.setKeywords({'kw': ['kw1', 'kw2'], 'KWx': ['kw3', 'kw4']})
305        QgsProject.instance().setMetadata(md)
306        l = QgsLayout(QgsProject.instance())
307        l.initializeDefaults()
308
309        # add a second page
310        page2 = QgsLayoutItemPage(l)
311        page2.setPageSize('A5')
312        l.pageCollection().addPage(page2)
313
314        # add some items
315        item1 = QgsLayoutItemShape(l)
316        item1.attemptSetSceneRect(QRectF(10, 20, 100, 150))
317        fill = QgsSimpleFillSymbolLayer()
318        fill_symbol = QgsFillSymbol()
319        fill_symbol.changeSymbolLayer(0, fill)
320        fill.setColor(Qt.green)
321        fill.setStrokeStyle(Qt.NoPen)
322        item1.setSymbol(fill_symbol)
323        l.addItem(item1)
324
325        item2 = QgsLayoutItemShape(l)
326        item2.attemptSetSceneRect(QRectF(10, 20, 100, 150))
327        item2.attemptMove(QgsLayoutPoint(10, 20), page=1)
328        fill = QgsSimpleFillSymbolLayer()
329        fill_symbol = QgsFillSymbol()
330        fill_symbol.changeSymbolLayer(0, fill)
331        fill.setColor(Qt.cyan)
332        fill.setStrokeStyle(Qt.NoPen)
333        item2.setSymbol(fill_symbol)
334        l.addItem(item2)
335
336        exporter = QgsLayoutExporter(l)
337        # setup settings
338        settings = QgsLayoutExporter.ImageExportSettings()
339        settings.dpi = 80
340
341        rendered_file_path = os.path.join(self.basetestpath, 'test_exporttoimagedpi.png')
342        self.assertEqual(exporter.exportToImage(rendered_file_path, settings), QgsLayoutExporter.Success)
343
344        self.assertTrue(self.checkImage('exporttoimagedpi_page1', 'exporttoimagedpi_page1', rendered_file_path))
345        page2_path = os.path.join(self.basetestpath, 'test_exporttoimagedpi_2.png')
346        self.assertTrue(self.checkImage('exporttoimagedpi_page2', 'exporttoimagedpi_page2', page2_path))
347
348        for f in (rendered_file_path, page2_path):
349            d = gdal.Open(f)
350            metadata = d.GetMetadata()
351            self.assertEqual(metadata['Author'], 'proj author')
352            self.assertEqual(metadata['Created'], '2011-05-03T09:04:05+10:00')
353            self.assertEqual(metadata['Keywords'], 'KWx: kw3,kw4;kw: kw1,kw2')
354            self.assertEqual(metadata['Subject'], 'proj abstract')
355            self.assertEqual(metadata['Title'], 'proj title')
356
357        # crop to contents
358        settings.cropToContents = True
359        settings.cropMargins = QgsMargins(10, 20, 30, 40)
360
361        rendered_file_path = os.path.join(self.basetestpath, 'test_exporttoimagecropped.png')
362        self.assertEqual(exporter.exportToImage(rendered_file_path, settings), QgsLayoutExporter.Success)
363
364        self.assertTrue(self.checkImage('exporttoimagecropped_page1', 'exporttoimagecropped_page1', rendered_file_path))
365        page2_path = os.path.join(self.basetestpath, 'test_exporttoimagecropped_2.png')
366        self.assertTrue(self.checkImage('exporttoimagecropped_page2', 'exporttoimagecropped_page2', page2_path))
367
368        # specific pages
369        settings.cropToContents = False
370        settings.pages = [1]
371
372        rendered_file_path = os.path.join(self.basetestpath, 'test_exporttoimagepages.png')
373        self.assertEqual(exporter.exportToImage(rendered_file_path, settings), QgsLayoutExporter.Success)
374
375        self.assertFalse(os.path.exists(rendered_file_path))
376        page2_path = os.path.join(self.basetestpath, 'test_exporttoimagepages_2.png')
377        self.assertTrue(self.checkImage('exporttoimagedpi_page2', 'exporttoimagedpi_page2', page2_path))
378
379        # image size
380        settings.imageSize = QSize(600, 851)
381        rendered_file_path = os.path.join(self.basetestpath, 'test_exporttoimagesize.png')
382        self.assertEqual(exporter.exportToImage(rendered_file_path, settings), QgsLayoutExporter.Success)
383        self.assertFalse(os.path.exists(rendered_file_path))
384        page2_path = os.path.join(self.basetestpath, 'test_exporttoimagesize_2.png')
385        self.assertTrue(self.checkImage('exporttoimagesize_page2', 'exporttoimagesize_page2', page2_path))
386
387        # image size with incorrect aspect ratio
388        # this can happen as a result of data defined page sizes
389        settings.imageSize = QSize(851, 600)
390        rendered_file_path = os.path.join(self.basetestpath, 'test_exporttoimagesizebadaspect.png')
391        self.assertEqual(exporter.exportToImage(rendered_file_path, settings), QgsLayoutExporter.Success)
392
393        page2_path = os.path.join(self.basetestpath, 'test_exporttoimagesizebadaspect_2.png')
394        im = QImage(page2_path)
395        self.assertTrue(self.checkImage('exporttoimagesize_badaspect', 'exporttoimagedpi_page2', page2_path), '{}x{}'.format(im.width(), im.height()))
396
397    def testExportToPdf(self):
398        md = QgsProject.instance().metadata()
399        md.setTitle('proj title')
400        md.setAuthor('proj author')
401        md.setCreationDateTime(QDateTime(QDate(2011, 5, 3), QTime(9, 4, 5), QTimeZone(36000)))
402        md.setIdentifier('proj identifier')
403        md.setAbstract('proj abstract')
404        md.setKeywords({'kw': ['kw1', 'kw2'], 'KWx': ['kw3', 'kw4']})
405        QgsProject.instance().setMetadata(md)
406
407        l = QgsLayout(QgsProject.instance())
408        l.initializeDefaults()
409
410        # add a second page
411        page2 = QgsLayoutItemPage(l)
412        page2.setPageSize('A5')
413        l.pageCollection().addPage(page2)
414
415        # add some items
416        item1 = QgsLayoutItemShape(l)
417        item1.attemptSetSceneRect(QRectF(10, 20, 100, 150))
418        fill = QgsSimpleFillSymbolLayer()
419        fill_symbol = QgsFillSymbol()
420        fill_symbol.changeSymbolLayer(0, fill)
421        fill.setColor(Qt.green)
422        fill.setStrokeStyle(Qt.NoPen)
423        item1.setSymbol(fill_symbol)
424        l.addItem(item1)
425
426        item2 = QgsLayoutItemShape(l)
427        item2.attemptSetSceneRect(QRectF(10, 20, 100, 150))
428        item2.attemptMove(QgsLayoutPoint(10, 20), page=1)
429        fill = QgsSimpleFillSymbolLayer()
430        fill_symbol = QgsFillSymbol()
431        fill_symbol.changeSymbolLayer(0, fill)
432        fill.setColor(Qt.cyan)
433        fill.setStrokeStyle(Qt.NoPen)
434        item2.setSymbol(fill_symbol)
435        l.addItem(item2)
436
437        exporter = QgsLayoutExporter(l)
438        # setup settings
439        settings = QgsLayoutExporter.PdfExportSettings()
440        settings.dpi = 80
441        settings.rasterizeWholeImage = False
442        settings.forceVectorOutput = False
443        settings.exportMetadata = True
444
445        pdf_file_path = os.path.join(self.basetestpath, 'test_exporttopdfdpi.pdf')
446        self.assertEqual(exporter.exportToPdf(pdf_file_path, settings), QgsLayoutExporter.Success)
447        self.assertTrue(os.path.exists(pdf_file_path))
448
449        rendered_page_1 = os.path.join(self.basetestpath, 'test_exporttopdfdpi.png')
450        dpi = 80
451        pdfToPng(pdf_file_path, rendered_page_1, dpi=dpi, page=1)
452        rendered_page_2 = os.path.join(self.basetestpath, 'test_exporttopdfdpi2.png')
453        pdfToPng(pdf_file_path, rendered_page_2, dpi=dpi, page=2)
454
455        self.assertTrue(self.checkImage('exporttopdfdpi_page1', 'exporttopdfdpi_page1', rendered_page_1, size_tolerance=1))
456        self.assertTrue(self.checkImage('exporttopdfdpi_page2', 'exporttopdfdpi_page2', rendered_page_2, size_tolerance=1))
457
458        d = gdal.Open(pdf_file_path)
459        metadata = d.GetMetadata()
460        self.assertEqual(metadata['AUTHOR'], 'proj author')
461        self.assertEqual(metadata['CREATION_DATE'], "D:20110503090405+10'0'")
462        self.assertEqual(metadata['KEYWORDS'], 'KWx: kw3,kw4;kw: kw1,kw2')
463        self.assertEqual(metadata['SUBJECT'], 'proj abstract')
464        self.assertEqual(metadata['TITLE'], 'proj title')
465
466    def testExportToPdfGeoreference(self):
467        md = QgsProject.instance().metadata()
468        md.setTitle('proj title')
469        md.setAuthor('proj author')
470        md.setCreationDateTime(QDateTime(QDate(2011, 5, 3), QTime(9, 4, 5), QTimeZone(36000)))
471        md.setIdentifier('proj identifier')
472        md.setAbstract('proj abstract')
473        md.setKeywords({'kw': ['kw1', 'kw2'], 'KWx': ['kw3', 'kw4']})
474        QgsProject.instance().setMetadata(md)
475
476        l = QgsLayout(QgsProject.instance())
477        l.initializeDefaults()
478
479        # add some items
480        map = QgsLayoutItemMap(l)
481        map.attemptSetSceneRect(QRectF(30, 60, 200, 100))
482        extent = QgsRectangle(333218, 1167809, 348781, 1180875)
483        map.setCrs(QgsCoordinateReferenceSystem('EPSG:3148'))
484        map.setExtent(extent)
485        l.addLayoutItem(map)
486
487        exporter = QgsLayoutExporter(l)
488        # setup settings
489        settings = QgsLayoutExporter.PdfExportSettings()
490        settings.dpi = 96
491        settings.rasterizeWholeImage = False
492        settings.forceVectorOutput = False
493        settings.appendGeoreference = True
494        settings.exportMetadata = False
495
496        pdf_file_path = os.path.join(self.basetestpath, 'test_exporttopdf_georeference.pdf')
497        self.assertEqual(exporter.exportToPdf(pdf_file_path, settings), QgsLayoutExporter.Success)
498        self.assertTrue(os.path.exists(pdf_file_path))
499
500        d = gdal.Open(pdf_file_path)
501
502        # check if georeferencing was successful
503        geoTransform = d.GetGeoTransform()
504        self.assertAlmostEqual(geoTransform[0], 330883.5499999996, 4)
505        self.assertAlmostEqual(geoTransform[1], 13.184029109934016, 4)
506        self.assertAlmostEqual(geoTransform[2], 0.0, 4)
507        self.assertAlmostEqual(geoTransform[3], 1185550.768915511, 4)
508        self.assertAlmostEqual(geoTransform[4], 0.0, 4)
509        self.assertAlmostEqual(geoTransform[5], -13.183886222186642, 4)
510
511        # check that the metadata has _not_ been added to the exported PDF
512        metadata = d.GetMetadata()
513        self.assertFalse('AUTHOR' in metadata)
514
515        exporter = QgsLayoutExporter(l)
516        # setup settings
517        settings = QgsLayoutExporter.PdfExportSettings()
518        settings.dpi = 96
519        settings.rasterizeWholeImage = False
520        settings.forceVectorOutput = False
521        settings.appendGeoreference = False
522        settings.exportMetadata = False
523
524        pdf_file_path = os.path.join(self.basetestpath, 'test_exporttopdf_nogeoreference.pdf')
525        self.assertEqual(exporter.exportToPdf(pdf_file_path, settings), QgsLayoutExporter.Success)
526        self.assertTrue(os.path.exists(pdf_file_path))
527
528        d = gdal.Open(pdf_file_path)
529        # check that georeference information has _not_ been added to the exported PDF
530        self.assertEqual(d.GetGeoTransform(), (0.0, 1.0, 0.0, 0.0, 0.0, 1.0))
531
532    def testExportToPdfSkipFirstPage(self):
533        l = QgsLayout(QgsProject.instance())
534        l.initializeDefaults()
535
536        # page 1 is excluded from export
537        page1 = l.pageCollection().page(0)
538        page1.setExcludeFromExports(True)
539
540        # add a second page
541        page2 = QgsLayoutItemPage(l)
542        page2.setPageSize('A5')
543        l.pageCollection().addPage(page2)
544
545        item2 = QgsLayoutItemShape(l)
546        item2.attemptSetSceneRect(QRectF(10, 20, 100, 150))
547        item2.attemptMove(QgsLayoutPoint(10, 20), page=1)
548        fill = QgsSimpleFillSymbolLayer()
549        fill_symbol = QgsFillSymbol()
550        fill_symbol.changeSymbolLayer(0, fill)
551        fill.setColor(Qt.cyan)
552        fill.setStrokeStyle(Qt.NoPen)
553        item2.setSymbol(fill_symbol)
554        l.addItem(item2)
555
556        exporter = QgsLayoutExporter(l)
557        # setup settings
558        settings = QgsLayoutExporter.PdfExportSettings()
559        settings.dpi = 80
560        settings.rasterizeWholeImage = False
561        settings.forceVectorOutput = False
562        settings.exportMetadata = True
563
564        pdf_file_path = os.path.join(self.basetestpath, 'test_exporttopdfdpi_skip_first.pdf')
565        self.assertEqual(exporter.exportToPdf(pdf_file_path, settings), QgsLayoutExporter.Success)
566        self.assertTrue(os.path.exists(pdf_file_path))
567
568        rendered_page_1 = os.path.join(self.basetestpath, 'test_exporttopdfdpi_skip_first.png')
569        dpi = 80
570        pdfToPng(pdf_file_path, rendered_page_1, dpi=dpi, page=1)
571
572        self.assertTrue(self.checkImage('test_exporttopdfdpi_skip_first', 'exporttopdfdpi_page2', rendered_page_1, size_tolerance=1))
573
574    def testExportToSvg(self):
575        md = QgsProject.instance().metadata()
576        md.setTitle('proj title')
577        md.setAuthor('proj author')
578        md.setCreationDateTime(QDateTime(QDate(2011, 5, 3), QTime(9, 4, 5), QTimeZone(36000)))
579        md.setIdentifier('proj identifier')
580        md.setAbstract('proj abstract')
581        md.setKeywords({'kw': ['kw1', 'kw2']})
582        QgsProject.instance().setMetadata(md)
583        l = QgsLayout(QgsProject.instance())
584        l.initializeDefaults()
585
586        # add a second page
587        page2 = QgsLayoutItemPage(l)
588        page2.setPageSize('A5')
589        l.pageCollection().addPage(page2)
590
591        # add some items
592        item1 = QgsLayoutItemShape(l)
593        item1.attemptSetSceneRect(QRectF(10, 20, 100, 150))
594        fill = QgsSimpleFillSymbolLayer()
595        fill_symbol = QgsFillSymbol()
596        fill_symbol.changeSymbolLayer(0, fill)
597        fill.setColor(Qt.green)
598        fill.setStrokeStyle(Qt.NoPen)
599        item1.setSymbol(fill_symbol)
600        l.addItem(item1)
601
602        item2 = QgsLayoutItemShape(l)
603        item2.attemptSetSceneRect(QRectF(10, 20, 100, 150))
604        item2.attemptMove(QgsLayoutPoint(10, 20), page=1)
605        fill = QgsSimpleFillSymbolLayer()
606        fill_symbol = QgsFillSymbol()
607        fill_symbol.changeSymbolLayer(0, fill)
608        fill.setColor(Qt.cyan)
609        fill.setStrokeStyle(Qt.NoPen)
610        item2.setSymbol(fill_symbol)
611        l.addItem(item2)
612
613        exporter = QgsLayoutExporter(l)
614        # setup settings
615        settings = QgsLayoutExporter.SvgExportSettings()
616        settings.dpi = 80
617        settings.forceVectorOutput = False
618        settings.exportMetadata = True
619
620        svg_file_path = os.path.join(self.basetestpath, 'test_exporttosvgdpi.svg')
621        svg_file_path_2 = os.path.join(self.basetestpath, 'test_exporttosvgdpi_2.svg')
622        self.assertEqual(exporter.exportToSvg(svg_file_path, settings), QgsLayoutExporter.Success)
623        self.assertTrue(os.path.exists(svg_file_path))
624        self.assertTrue(os.path.exists(svg_file_path_2))
625
626        # metadata
627        def checkMetadata(f, expected):
628            # ideally we'd check the path too - but that's very complex given that
629            # the output from Qt svg generator isn't valid XML, and no Python standard library
630            # xml parser handles invalid xml...
631            self.assertEqual('proj title' in open(f).read(), expected)
632            self.assertEqual('proj author' in open(f).read(), expected)
633            self.assertEqual('proj identifier' in open(f).read(), expected)
634            self.assertEqual('2011-05-03' in open(f).read(), expected)
635            self.assertEqual('proj abstract' in open(f).read(), expected)
636            self.assertEqual('kw1' in open(f).read(), expected)
637            self.assertEqual('kw2' in open(f).read(), expected)
638            self.assertEqual('xmlns:cc="http://creativecommons.org/ns#"' in open(f).read(), expected)
639
640        for f in [svg_file_path, svg_file_path_2]:
641            checkMetadata(f, True)
642
643        rendered_page_1 = os.path.join(self.basetestpath, 'test_exporttosvgdpi.png')
644        svgToPng(svg_file_path, rendered_page_1, width=936)
645        rendered_page_2 = os.path.join(self.basetestpath, 'test_exporttosvgdpi2.png')
646        svgToPng(svg_file_path_2, rendered_page_2, width=467)
647
648        self.assertTrue(self.checkImage('exporttosvgdpi_page1', 'exporttopdfdpi_page1', rendered_page_1, size_tolerance=1))
649        self.assertTrue(self.checkImage('exporttosvgdpi_page2', 'exporttopdfdpi_page2', rendered_page_2, size_tolerance=1))
650
651        # no metadata
652        settings.exportMetadata = False
653        self.assertEqual(exporter.exportToSvg(svg_file_path, settings), QgsLayoutExporter.Success)
654        for f in [svg_file_path, svg_file_path_2]:
655            checkMetadata(f, False)
656
657        # layered
658        settings.exportAsLayers = True
659        settings.exportMetadata = True
660
661        svg_file_path = os.path.join(self.basetestpath, 'test_exporttosvglayered.svg')
662        svg_file_path_2 = os.path.join(self.basetestpath, 'test_exporttosvglayered_2.svg')
663        self.assertEqual(exporter.exportToSvg(svg_file_path, settings), QgsLayoutExporter.Success)
664        self.assertTrue(os.path.exists(svg_file_path))
665        self.assertTrue(os.path.exists(svg_file_path_2))
666
667        rendered_page_1 = os.path.join(self.basetestpath, 'test_exporttosvglayered.png')
668        svgToPng(svg_file_path, rendered_page_1, width=936)
669        rendered_page_2 = os.path.join(self.basetestpath, 'test_exporttosvglayered2.png')
670        svgToPng(svg_file_path_2, rendered_page_2, width=467)
671
672        self.assertTrue(self.checkImage('exporttosvglayered_page1', 'exporttopdfdpi_page1', rendered_page_1, size_tolerance=1))
673        self.assertTrue(self.checkImage('exporttosvglayered_page2', 'exporttopdfdpi_page2', rendered_page_2, size_tolerance=1))
674
675        for f in [svg_file_path, svg_file_path_2]:
676            checkMetadata(f, True)
677
678        # layered no metadata
679        settings.exportAsLayers = True
680        settings.exportMetadata = False
681        self.assertEqual(exporter.exportToSvg(svg_file_path, settings), QgsLayoutExporter.Success)
682        for f in [svg_file_path, svg_file_path_2]:
683            checkMetadata(f, False)
684
685    def testExportToSvgTextRenderFormat(self):
686        l = QgsLayout(QgsProject.instance())
687        l.initializeDefaults()
688
689        # add a map and scalebar
690        mapitem = QgsLayoutItemMap(l)
691        mapitem.attemptSetSceneRect(QRectF(110, 120, 200, 250))
692        mapitem.zoomToExtent(QgsRectangle(1, 1, 10, 10))
693        mapitem.setScale(666)  # unlikely to appear in the SVG by accident... unless... oh no! RUN!
694        l.addItem(mapitem)
695
696        item1 = QgsLayoutItemScaleBar(l)
697        item1.attemptSetSceneRect(QRectF(10, 20, 100, 150))
698        item1.setLinkedMap(mapitem)
699        item1.setStyle('Numeric')
700        l.addItem(item1)
701
702        exporter = QgsLayoutExporter(l)
703        # setup settings
704        settings = QgsLayoutExporter.SvgExportSettings()
705        settings.dpi = 80
706        settings.forceVectorOutput = False
707        settings.exportMetadata = True
708        settings.textRenderFormat = QgsRenderContext.TextFormatAlwaysText
709
710        svg_file_path = os.path.join(self.basetestpath, 'test_exporttosvgtextformattext.svg')
711        self.assertEqual(exporter.exportToSvg(svg_file_path, settings), QgsLayoutExporter.Success)
712        self.assertTrue(os.path.exists(svg_file_path))
713
714        # expect svg to contain a text object with the scale
715        with open(svg_file_path, 'r') as f:
716            lines = ''.join(f.readlines())
717        self.assertIn('<text', lines)
718        self.assertIn('>1:666<', lines)
719
720        # force use of outlines
721        os.unlink(svg_file_path)
722        settings.textRenderFormat = QgsRenderContext.TextFormatAlwaysOutlines
723        self.assertEqual(exporter.exportToSvg(svg_file_path, settings), QgsLayoutExporter.Success)
724        self.assertTrue(os.path.exists(svg_file_path))
725
726        # expect svg NOT to contain a text object with the scale
727        with open(svg_file_path, 'r') as f:
728            lines = ''.join(f.readlines())
729        self.assertNotIn('<text', lines)
730        self.assertNotIn('>1:666<', lines)
731
732    def testPrint(self):
733        l = QgsLayout(QgsProject.instance())
734        l.initializeDefaults()
735
736        # add a second page
737        page2 = QgsLayoutItemPage(l)
738        page2.setPageSize('A5')
739        l.pageCollection().addPage(page2)
740
741        # add some items
742        item1 = QgsLayoutItemShape(l)
743        item1.attemptSetSceneRect(QRectF(10, 20, 100, 150))
744        fill = QgsSimpleFillSymbolLayer()
745        fill_symbol = QgsFillSymbol()
746        fill_symbol.changeSymbolLayer(0, fill)
747        fill.setColor(Qt.green)
748        fill.setStrokeStyle(Qt.NoPen)
749        item1.setSymbol(fill_symbol)
750        l.addItem(item1)
751
752        item2 = QgsLayoutItemShape(l)
753        item2.attemptSetSceneRect(QRectF(10, 20, 100, 150))
754        item2.attemptMove(QgsLayoutPoint(10, 20), page=1)
755        fill = QgsSimpleFillSymbolLayer()
756        fill_symbol = QgsFillSymbol()
757        fill_symbol.changeSymbolLayer(0, fill)
758        fill.setColor(Qt.cyan)
759        fill.setStrokeStyle(Qt.NoPen)
760        item2.setSymbol(fill_symbol)
761        l.addItem(item2)
762
763        exporter = QgsLayoutExporter(l)
764        # setup settings
765        settings = QgsLayoutExporter.PrintExportSettings()
766        settings.dpi = 80
767        settings.rasterizeWholeImage = False
768
769        pdf_file_path = os.path.join(self.basetestpath, 'test_printdpi.pdf')
770        # make a qprinter directed to pdf
771        printer = QPrinter()
772        printer.setOutputFileName(pdf_file_path)
773        printer.setOutputFormat(QPrinter.PdfFormat)
774
775        self.assertEqual(exporter.print(printer, settings), QgsLayoutExporter.Success)
776        self.assertTrue(os.path.exists(pdf_file_path))
777
778        rendered_page_1 = os.path.join(self.basetestpath, 'test_exporttopdfdpi.png')
779        dpi = 80
780        pdfToPng(pdf_file_path, rendered_page_1, dpi=dpi, page=1)
781        rendered_page_2 = os.path.join(self.basetestpath, 'test_exporttopdfdpi2.png')
782        pdfToPng(pdf_file_path, rendered_page_2, dpi=dpi, page=2)
783
784        self.assertTrue(self.checkImage('printdpi_page1', 'exporttopdfdpi_page1', rendered_page_1, size_tolerance=1))
785        self.assertTrue(self.checkImage('printdpi_page2', 'exporttopdfdpi_page2', rendered_page_2, size_tolerance=1))
786
787    def testExportWorldFile(self):
788        l = QgsLayout(QgsProject.instance())
789        l.initializeDefaults()
790
791        # add some items
792        map = QgsLayoutItemMap(l)
793        map.attemptSetSceneRect(QRectF(30, 60, 200, 100))
794        extent = QgsRectangle(2000, 2800, 2500, 2900)
795        map.setExtent(extent)
796        l.addLayoutItem(map)
797
798        exporter = QgsLayoutExporter(l)
799        # setup settings
800        settings = QgsLayoutExporter.ImageExportSettings()
801        settings.dpi = 80
802        settings.generateWorldFile = False
803
804        rendered_file_path = os.path.join(self.basetestpath, 'test_exportwithworldfile.png')
805        world_file_path = os.path.join(self.basetestpath, 'test_exportwithworldfile.pgw')
806        self.assertEqual(exporter.exportToImage(rendered_file_path, settings), QgsLayoutExporter.Success)
807        self.assertTrue(os.path.exists(rendered_file_path))
808        self.assertFalse(os.path.exists(world_file_path))
809
810        # with world file
811        settings.generateWorldFile = True
812        rendered_file_path = os.path.join(self.basetestpath, 'test_exportwithworldfile.png')
813        self.assertEqual(exporter.exportToImage(rendered_file_path, settings), QgsLayoutExporter.Success)
814        self.assertTrue(os.path.exists(rendered_file_path))
815        self.assertTrue(os.path.exists(world_file_path))
816
817        lines = tuple(open(world_file_path, 'r'))
818        values = [float(f) for f in lines]
819        self.assertAlmostEqual(values[0], 0.794117647059, 2)
820        self.assertAlmostEqual(values[1], 0.0, 2)
821        self.assertAlmostEqual(values[2], 0.0, 2)
822        self.assertAlmostEqual(values[3], -0.794251134644, 2)
823        self.assertAlmostEqual(values[4], 1925.000000000000, 2)
824        self.assertAlmostEqual(values[5], 3050.000000000000, 2)
825
826    def testExcludePagesImage(self):
827        l = QgsLayout(QgsProject.instance())
828        l.initializeDefaults()
829        # add a second page
830        page2 = QgsLayoutItemPage(l)
831        page2.setPageSize('A5')
832        l.pageCollection().addPage(page2)
833
834        exporter = QgsLayoutExporter(l)
835        # setup settings
836        settings = QgsLayoutExporter.ImageExportSettings()
837        settings.dpi = 80
838        settings.generateWorldFile = False
839
840        rendered_file_path = os.path.join(self.basetestpath, 'test_exclude_export.png')
841        details = QgsLayoutExporter.PageExportDetails()
842        details.directory = self.basetestpath
843        details.baseName = 'test_exclude_export'
844        details.extension = 'png'
845        details.page = 0
846
847        self.assertEqual(exporter.exportToImage(rendered_file_path, settings), QgsLayoutExporter.Success)
848        self.assertTrue(os.path.exists(exporter.generateFileName(details)))
849        details.page = 1
850        self.assertTrue(os.path.exists(exporter.generateFileName(details)))
851
852        # exclude a page
853        l.pageCollection().page(0).setExcludeFromExports(True)
854        rendered_file_path = os.path.join(self.basetestpath, 'test_exclude_export_excluded.png')
855        details.baseName = 'test_exclude_export_excluded'
856        details.page = 0
857        self.assertEqual(exporter.exportToImage(rendered_file_path, settings), QgsLayoutExporter.Success)
858        self.assertFalse(os.path.exists(exporter.generateFileName(details)))
859        details.page = 1
860        self.assertTrue(os.path.exists(exporter.generateFileName(details)))
861
862        # exclude second page
863        l.pageCollection().page(1).setExcludeFromExports(True)
864        rendered_file_path = os.path.join(self.basetestpath, 'test_exclude_export_excluded_all.png')
865        details.baseName = 'test_exclude_export_excluded_all'
866        details.page = 0
867        self.assertEqual(exporter.exportToImage(rendered_file_path, settings), QgsLayoutExporter.Success)
868        self.assertFalse(os.path.exists(exporter.generateFileName(details)))
869        details.page = 1
870        self.assertFalse(os.path.exists(exporter.generateFileName(details)))
871
872    def testPageFileName(self):
873        l = QgsLayout(QgsProject.instance())
874        exporter = QgsLayoutExporter(l)
875        details = QgsLayoutExporter.PageExportDetails()
876        details.directory = '/tmp/output'
877        details.baseName = 'my_maps'
878        details.extension = 'png'
879        details.page = 0
880        self.assertEqual(exporter.generateFileName(details), '/tmp/output/my_maps.png')
881        details.page = 1
882        self.assertEqual(exporter.generateFileName(details), '/tmp/output/my_maps_2.png')
883        details.page = 2
884        self.assertEqual(exporter.generateFileName(details), '/tmp/output/my_maps_3.png')
885
886    def prepareIteratorLayout(self):
887        layer_path = os.path.join(TEST_DATA_DIR, 'france_parts.shp')
888        layer = QgsVectorLayer(layer_path, 'test', "ogr")
889
890        project = QgsProject()
891        project.addMapLayers([layer])
892        # select epsg:2154
893        crs = QgsCoordinateReferenceSystem('epsg:2154')
894        project.setCrs(crs)
895
896        layout = QgsPrintLayout(project)
897        layout.initializeDefaults()
898
899        # fix the renderer, fill with green
900        props = {"color": "0,127,0", "outline_width": "4", "outline_color": '255,255,255'}
901        fillSymbol = QgsFillSymbol.createSimple(props)
902        renderer = QgsSingleSymbolRenderer(fillSymbol)
903        layer.setRenderer(renderer)
904
905        # the atlas map
906        atlas_map = QgsLayoutItemMap(layout)
907        atlas_map.attemptSetSceneRect(QRectF(20, 20, 130, 130))
908        atlas_map.setFrameEnabled(True)
909        atlas_map.setLayers([layer])
910        layout.addLayoutItem(atlas_map)
911
912        # the atlas
913        atlas = layout.atlas()
914        atlas.setCoverageLayer(layer)
915        atlas.setEnabled(True)
916
917        atlas_map.setExtent(
918            QgsRectangle(332719.06221504929, 6765214.5887386119, 560957.85090677091, 6993453.3774303338))
919
920        atlas_map.setAtlasDriven(True)
921        atlas_map.setAtlasScalingMode(QgsLayoutItemMap.Auto)
922        atlas_map.setAtlasMargin(0.10)
923
924        return project, layout
925
926    def testIteratorToImages(self):
927        project, layout = self.prepareIteratorLayout()
928        atlas = layout.atlas()
929        atlas.setFilenameExpression("'test_exportiteratortoimage_' || \"NAME_1\"")
930
931        # setup settings
932        settings = QgsLayoutExporter.ImageExportSettings()
933        settings.dpi = 80
934
935        result, error = QgsLayoutExporter.exportToImage(atlas, self.basetestpath + '/', 'png', settings)
936        self.assertEqual(result, QgsLayoutExporter.Success, error)
937
938        page1_path = os.path.join(self.basetestpath, 'test_exportiteratortoimage_Basse-Normandie.png')
939        self.assertTrue(self.checkImage('iteratortoimage1', 'iteratortoimage1', page1_path))
940        page2_path = os.path.join(self.basetestpath, 'test_exportiteratortoimage_Bretagne.png')
941        self.assertTrue(self.checkImage('iteratortoimage2', 'iteratortoimage2', page2_path))
942        page3_path = os.path.join(self.basetestpath, 'test_exportiteratortoimage_Centre.png')
943        self.assertTrue(os.path.exists(page3_path))
944        page4_path = os.path.join(self.basetestpath, 'test_exportiteratortoimage_Pays de la Loire.png')
945        self.assertTrue(os.path.exists(page4_path))
946
947    def testIteratorToSvgs(self):
948        project, layout = self.prepareIteratorLayout()
949        atlas = layout.atlas()
950        atlas.setFilenameExpression("'test_exportiteratortosvg_' || \"NAME_1\"")
951
952        # setup settings
953        settings = QgsLayoutExporter.SvgExportSettings()
954        settings.dpi = 80
955        settings.forceVectorOutput = False
956
957        result, error = QgsLayoutExporter.exportToSvg(atlas, self.basetestpath + '/', settings)
958        self.assertEqual(result, QgsLayoutExporter.Success, error)
959
960        page1_path = os.path.join(self.basetestpath, 'test_exportiteratortosvg_Basse-Normandie.svg')
961        rendered_page_1 = os.path.join(self.basetestpath, 'test_exportiteratortosvg_Basse-Normandie.png')
962        svgToPng(page1_path, rendered_page_1, width=935)
963        self.assertTrue(self.checkImage('iteratortosvg1', 'iteratortoimage1', rendered_page_1, size_tolerance=2))
964        page2_path = os.path.join(self.basetestpath, 'test_exportiteratortosvg_Bretagne.svg')
965        rendered_page_2 = os.path.join(self.basetestpath, 'test_exportiteratortosvg_Bretagne.png')
966        svgToPng(page2_path, rendered_page_2, width=935)
967        self.assertTrue(self.checkImage('iteratortosvg2', 'iteratortoimage2', rendered_page_2, size_tolerance=2))
968        page3_path = os.path.join(self.basetestpath, 'test_exportiteratortosvg_Centre.svg')
969        self.assertTrue(os.path.exists(page3_path))
970        page4_path = os.path.join(self.basetestpath, 'test_exportiteratortosvg_Pays de la Loire.svg')
971        self.assertTrue(os.path.exists(page4_path))
972
973    def testIteratorToPdfs(self):
974        project, layout = self.prepareIteratorLayout()
975        atlas = layout.atlas()
976        atlas.setFilenameExpression("'test_exportiteratortopdf_' || \"NAME_1\"")
977
978        # setup settings
979        settings = QgsLayoutExporter.PdfExportSettings()
980        settings.dpi = 80
981        settings.rasterizeWholeImage = False
982        settings.forceVectorOutput = False
983
984        result, error = QgsLayoutExporter.exportToPdfs(atlas, self.basetestpath + '/', settings)
985        self.assertEqual(result, QgsLayoutExporter.Success, error)
986
987        page1_path = os.path.join(self.basetestpath, 'test_exportiteratortopdf_Basse-Normandie.pdf')
988        rendered_page_1 = os.path.join(self.basetestpath, 'test_exportiteratortopdf_Basse-Normandie.png')
989        pdfToPng(page1_path, rendered_page_1, dpi=80, page=1)
990        self.assertTrue(self.checkImage('iteratortopdf1', 'iteratortoimage1', rendered_page_1, size_tolerance=2))
991        page2_path = os.path.join(self.basetestpath, 'test_exportiteratortopdf_Bretagne.pdf')
992        rendered_page_2 = os.path.join(self.basetestpath, 'test_exportiteratortopdf_Bretagne.png')
993        pdfToPng(page2_path, rendered_page_2, dpi=80, page=1)
994        self.assertTrue(self.checkImage('iteratortopdf2', 'iteratortoimage2', rendered_page_2, size_tolerance=2))
995        page3_path = os.path.join(self.basetestpath, 'test_exportiteratortopdf_Centre.pdf')
996        self.assertTrue(os.path.exists(page3_path))
997        page4_path = os.path.join(self.basetestpath, 'test_exportiteratortopdf_Pays de la Loire.pdf')
998        self.assertTrue(os.path.exists(page4_path))
999
1000    def testIteratorToPdf(self):
1001        project, layout = self.prepareIteratorLayout()
1002        atlas = layout.atlas()
1003
1004        # setup settings
1005        settings = QgsLayoutExporter.PdfExportSettings()
1006        settings.dpi = 80
1007        settings.rasterizeWholeImage = False
1008        settings.forceVectorOutput = False
1009
1010        pdf_path = os.path.join(self.basetestpath, 'test_exportiteratortopdf_single.pdf')
1011        result, error = QgsLayoutExporter.exportToPdf(atlas, pdf_path, settings)
1012        self.assertEqual(result, QgsLayoutExporter.Success, error)
1013
1014        rendered_page_1 = os.path.join(self.basetestpath, 'test_exportiteratortopdf_single1.png')
1015        pdfToPng(pdf_path, rendered_page_1, dpi=80, page=1)
1016        self.assertTrue(self.checkImage('iteratortopdfsingle1', 'iteratortoimage1', rendered_page_1, size_tolerance=2))
1017
1018        rendered_page_2 = os.path.join(self.basetestpath, 'test_exportiteratortopdf_single2.png')
1019        pdfToPng(pdf_path, rendered_page_2, dpi=80, page=2)
1020        self.assertTrue(self.checkImage('iteratortopdfsingle2', 'iteratortoimage2', rendered_page_2, size_tolerance=2))
1021
1022        rendered_page_3 = os.path.join(self.basetestpath, 'test_exportiteratortopdf_single3.png')
1023        pdfToPng(pdf_path, rendered_page_3, dpi=80, page=3)
1024        self.assertTrue(os.path.exists(rendered_page_3))
1025        rendered_page_4 = os.path.join(self.basetestpath, 'test_exportiteratortopdf_single4.png')
1026        pdfToPng(pdf_path, rendered_page_4, dpi=80, page=4)
1027        self.assertTrue(os.path.exists(rendered_page_4))
1028
1029    def testPrintIterator(self):
1030        project, layout = self.prepareIteratorLayout()
1031        atlas = layout.atlas()
1032
1033        # setup settings
1034        settings = QgsLayoutExporter.PrintExportSettings()
1035        settings.dpi = 80
1036        settings.rasterizeWholeImage = False
1037
1038        pdf_path = os.path.join(self.basetestpath, 'test_printiterator.pdf')
1039        # make a qprinter directed to pdf
1040        printer = QPrinter()
1041        printer.setOutputFileName(pdf_path)
1042        printer.setOutputFormat(QPrinter.PdfFormat)
1043
1044        result, error = QgsLayoutExporter.print(atlas, printer, settings)
1045        self.assertEqual(result, QgsLayoutExporter.Success, error)
1046
1047        rendered_page_1 = os.path.join(self.basetestpath, 'test_printiterator1.png')
1048        pdfToPng(pdf_path, rendered_page_1, dpi=80, page=1)
1049        self.assertTrue(self.checkImage('printeriterator1', 'iteratortoimage1', rendered_page_1, size_tolerance=2))
1050
1051        rendered_page_2 = os.path.join(self.basetestpath, 'test_printiterator2.png')
1052        pdfToPng(pdf_path, rendered_page_2, dpi=80, page=2)
1053        self.assertTrue(self.checkImage('printiterator2', 'iteratortoimage2', rendered_page_2, size_tolerance=2))
1054
1055        rendered_page_3 = os.path.join(self.basetestpath, 'test_printiterator3.png')
1056        pdfToPng(pdf_path, rendered_page_3, dpi=80, page=3)
1057        self.assertTrue(os.path.exists(rendered_page_3))
1058        rendered_page_4 = os.path.join(self.basetestpath, 'test_printiterator4.png')
1059        pdfToPng(pdf_path, rendered_page_4, dpi=80, page=4)
1060        self.assertTrue(os.path.exists(rendered_page_4))
1061
1062    def testExportReport(self):
1063        p = QgsProject()
1064        r = QgsReport(p)
1065
1066        # add a header
1067        r.setHeaderEnabled(True)
1068        report_header = QgsLayout(p)
1069        report_header.initializeDefaults()
1070        item1 = QgsLayoutItemShape(report_header)
1071        item1.attemptSetSceneRect(QRectF(10, 20, 100, 150))
1072        fill = QgsSimpleFillSymbolLayer()
1073        fill_symbol = QgsFillSymbol()
1074        fill_symbol.changeSymbolLayer(0, fill)
1075        fill.setColor(Qt.green)
1076        fill.setStrokeStyle(Qt.NoPen)
1077        item1.setSymbol(fill_symbol)
1078        report_header.addItem(item1)
1079
1080        r.setHeader(report_header)
1081
1082        # add a footer
1083        r.setFooterEnabled(True)
1084        report_footer = QgsLayout(p)
1085        report_footer.initializeDefaults()
1086        item2 = QgsLayoutItemShape(report_footer)
1087        item2.attemptSetSceneRect(QRectF(10, 20, 100, 150))
1088        item2.attemptMove(QgsLayoutPoint(10, 20))
1089        fill = QgsSimpleFillSymbolLayer()
1090        fill_symbol = QgsFillSymbol()
1091        fill_symbol.changeSymbolLayer(0, fill)
1092        fill.setColor(Qt.cyan)
1093        fill.setStrokeStyle(Qt.NoPen)
1094        item2.setSymbol(fill_symbol)
1095        report_footer.addItem(item2)
1096
1097        r.setFooter(report_footer)
1098
1099        # setup settings
1100        settings = QgsLayoutExporter.ImageExportSettings()
1101        settings.dpi = 80
1102
1103        report_path = os.path.join(self.basetestpath, 'test_report')
1104        result, error = QgsLayoutExporter.exportToImage(r, report_path, 'png', settings)
1105        self.assertEqual(result, QgsLayoutExporter.Success, error)
1106
1107        page1_path = os.path.join(self.basetestpath, 'test_report_0001.png')
1108        self.assertTrue(self.checkImage('report_page1', 'report_page1', page1_path))
1109        page2_path = os.path.join(self.basetestpath, 'test_report_0002.png')
1110        self.assertTrue(self.checkImage('report_page2', 'report_page2', page2_path))
1111
1112
1113if __name__ == '__main__':
1114    unittest.main()
1115