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