1717import tempfile
1818import shutil
1919import os
20+ import subprocess
2021
2122from qgis .core import (QgsMultiRenderChecker ,
2223 QgsLayoutExporter ,
3536
3637from qgis .testing import start_app , unittest
3738
39+ from utilities import getExecutablePath
40+
41+ # PDF-to-image utility
42+ # look for Poppler w/ Cairo, then muPDF
43+ # * Poppler w/ Cairo renders correctly
44+ # * Poppler w/o Cairo does not always correctly render vectors in PDF to image
45+ # * muPDF renders correctly, but sightly shifts colors
46+ for util in [
47+ 'pdftocairo' ,
48+ # 'mudraw',
49+ ]:
50+ PDFUTIL = getExecutablePath (util )
51+ if PDFUTIL :
52+ break
53+
54+ # noinspection PyUnboundLocalVariable
55+ if not PDFUTIL :
56+ raise Exception ('PDF-to-image utility not found on PATH: '
57+ 'install Poppler (with Cairo)' )
58+
59+
60+ def pdfToPng (pdf_file_path , rendered_file_path , page , dpi = 96 ):
61+ if PDFUTIL .strip ().endswith ('pdftocairo' ):
62+ filebase = os .path .join (
63+ os .path .dirname (rendered_file_path ),
64+ os .path .splitext (os .path .basename (rendered_file_path ))[0 ]
65+ )
66+ call = [
67+ PDFUTIL , '-png' , '-singlefile' , '-r' , str (dpi ),
68+ '-x' , '0' , '-y' , '0' , '-f' , str (page ), '-l' , str (page ),
69+ pdf_file_path , filebase
70+ ]
71+ elif PDFUTIL .strip ().endswith ('mudraw' ):
72+ call = [
73+ PDFUTIL , '-c' , 'rgba' ,
74+ '-r' , str (dpi ), '-f' , str (page ), '-l' , str (page ),
75+ # '-b', '8',
76+ '-o' , rendered_file_path , pdf_file_path
77+ ]
78+ else :
79+ return False , ''
80+
81+ print ("exportToPdf call: {0}" .format (' ' .join (call )))
82+ try :
83+ subprocess .check_call (call )
84+ except subprocess .CalledProcessError as e :
85+ assert False , ("exportToPdf failed!\n "
86+ "cmd: {0}\n "
87+ "returncode: {1}\n "
88+ "message: {2}" .format (e .cmd , e .returncode , e .message ))
89+
90+
3891start_app ()
3992
4093
@@ -54,12 +107,13 @@ def tearDown(self):
54107 with open (report_file_path , 'a' ) as report_file :
55108 report_file .write (self .report )
56109
57- def checkImage (self , name , reference_image , rendered_image ):
110+ def checkImage (self , name , reference_image , rendered_image , size_tolerance = 0 ):
58111 checker = QgsMultiRenderChecker ()
59112 checker .setControlPathPrefix ("layout_exporter" )
60113 checker .setControlName ("expected_layoutexporter_" + reference_image )
61114 checker .setRenderedImage (rendered_image )
62115 checker .setColorTolerance (2 )
116+ checker .setSizeTolerance (size_tolerance , size_tolerance )
63117 result = checker .runTest (name , 20 )
64118 self .report += checker .report ()
65119 print ((self .report ))
@@ -278,6 +332,57 @@ def testExportToImage(self):
278332 page2_path = os .path .join (self .basetestpath , 'test_exporttoimagesize_2.png' )
279333 self .assertTrue (self .checkImage ('exporttoimagesize_page2' , 'exporttoimagesize_page2' , page2_path ))
280334
335+ def testExportToPdf (self ):
336+ l = QgsLayout (QgsProject .instance ())
337+ l .initializeDefaults ()
338+
339+ # add a second page
340+ page2 = QgsLayoutItemPage (l )
341+ page2 .setPageSize ('A5' )
342+ l .pageCollection ().addPage (page2 )
343+
344+ # add some items
345+ item1 = QgsLayoutItemShape (l )
346+ item1 .attemptSetSceneRect (QRectF (10 , 20 , 100 , 150 ))
347+ fill = QgsSimpleFillSymbolLayer ()
348+ fill_symbol = QgsFillSymbol ()
349+ fill_symbol .changeSymbolLayer (0 , fill )
350+ fill .setColor (Qt .green )
351+ fill .setStrokeStyle (Qt .NoPen )
352+ item1 .setSymbol (fill_symbol )
353+ l .addItem (item1 )
354+
355+ item2 = QgsLayoutItemShape (l )
356+ item2 .attemptSetSceneRect (QRectF (10 , 20 , 100 , 150 ))
357+ item2 .attemptMove (QgsLayoutPoint (10 , 20 ), page = 1 )
358+ fill = QgsSimpleFillSymbolLayer ()
359+ fill_symbol = QgsFillSymbol ()
360+ fill_symbol .changeSymbolLayer (0 , fill )
361+ fill .setColor (Qt .cyan )
362+ fill .setStrokeStyle (Qt .NoPen )
363+ item2 .setSymbol (fill_symbol )
364+ l .addItem (item2 )
365+
366+ exporter = QgsLayoutExporter (l )
367+ # setup settings
368+ settings = QgsLayoutExporter .PdfExportSettings ()
369+ settings .dpi = 80
370+ settings .rasterizeWholeImage = False
371+ settings .forceVectorOutput = False
372+
373+ pdf_file_path = os .path .join (self .basetestpath , 'test_exporttopdfdpi.pdf' )
374+ self .assertEqual (exporter .exportToPdf (pdf_file_path , settings ), QgsLayoutExporter .Success )
375+ self .assertTrue (os .path .exists (pdf_file_path ))
376+
377+ rendered_page_1 = os .path .join (self .basetestpath , 'test_exporttopdfdpi.png' )
378+ dpi = 80
379+ pdfToPng (pdf_file_path , rendered_page_1 , dpi = dpi , page = 1 )
380+ rendered_page_2 = os .path .join (self .basetestpath , 'test_exporttopdfdpi2.png' )
381+ pdfToPng (pdf_file_path , rendered_page_2 , dpi = dpi , page = 2 )
382+
383+ self .assertTrue (self .checkImage ('exporttopdfdpi_page1' , 'exporttopdfdpi_page1' , rendered_page_1 , size_tolerance = 1 ))
384+ self .assertTrue (self .checkImage ('exporttopdfdpi_page2' , 'exporttopdfdpi_page2' , rendered_page_2 , size_tolerance = 1 ))
385+
281386 def testExportWorldFile (self ):
282387 l = QgsLayout (QgsProject .instance ())
283388 l .initializeDefaults ()
0 commit comments