QGIS API Documentation  3.0.2-Girona (307d082)
qgslayoutexporter.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslayoutexporter.cpp
3  -------------------
4  begin : October 2017
5  copyright : (C) 2017 by Nyall Dawson
6  email : nyall dot dawson at gmail dot com
7  ***************************************************************************/
8 /***************************************************************************
9  * *
10  * This program is free software; you can redistribute it and/or modify *
11  * it under the terms of the GNU General Public License as published by *
12  * the Free Software Foundation; either version 2 of the License, or *
13  * (at your option) any later version. *
14  * *
15  ***************************************************************************/
16 
17 #include "qgslayoutexporter.h"
18 #include "qgslayout.h"
19 #include "qgslayoutitemmap.h"
21 #include "qgsogrutils.h"
22 #include "qgspaintenginehack.h"
25 #include "qgsfeedback.h"
26 #include <QImageWriter>
27 #include <QSize>
28 #include <QSvgGenerator>
29 
30 #include "gdal.h"
31 #include "cpl_conv.h"
32 
34 class LayoutContextPreviewSettingRestorer
35 {
36  public:
37 
38  LayoutContextPreviewSettingRestorer( QgsLayout *layout )
39  : mLayout( layout )
40  , mPreviousSetting( layout->renderContext().mIsPreviewRender )
41  {
42  mLayout->renderContext().mIsPreviewRender = false;
43  }
44 
45  ~LayoutContextPreviewSettingRestorer()
46  {
47  mLayout->renderContext().mIsPreviewRender = mPreviousSetting;
48  }
49 
50  private:
51  QgsLayout *mLayout = nullptr;
52  bool mPreviousSetting = false;
53 };
54 
55 class LayoutGuideHider
56 {
57  public:
58 
59  LayoutGuideHider( QgsLayout *layout )
60  : mLayout( layout )
61  {
62  const QList< QgsLayoutGuide * > guides = mLayout->guides().guides();
63  for ( QgsLayoutGuide *guide : guides )
64  {
65  mPrevVisibility.insert( guide, guide->item()->isVisible() );
66  guide->item()->setVisible( false );
67  }
68  }
69 
70  ~LayoutGuideHider()
71  {
72  for ( auto it = mPrevVisibility.constBegin(); it != mPrevVisibility.constEnd(); ++it )
73  {
74  it.key()->item()->setVisible( it.value() );
75  }
76  }
77 
78  private:
79  QgsLayout *mLayout = nullptr;
80  QHash< QgsLayoutGuide *, bool > mPrevVisibility;
81 };
82 
83 class LayoutItemHider
84 {
85  public:
86  explicit LayoutItemHider( const QList<QGraphicsItem *> &items )
87  {
88  for ( QGraphicsItem *item : items )
89  {
90  mPrevVisibility[item] = item->isVisible();
91  item->hide();
92  }
93  }
94 
95  void hideAll()
96  {
97  for ( auto it = mPrevVisibility.constBegin(); it != mPrevVisibility.constEnd(); ++it )
98  {
99  it.key()->hide();
100  }
101  }
102 
103  ~LayoutItemHider()
104  {
105  for ( auto it = mPrevVisibility.constBegin(); it != mPrevVisibility.constEnd(); ++it )
106  {
107  it.key()->setVisible( it.value() );
108  }
109  }
110 
111  private:
112 
113  QHash<QGraphicsItem *, bool> mPrevVisibility;
114 };
115 
117 
119  : mLayout( layout )
120 {
121 
122 }
123 
125 {
126  return mLayout;
127 }
128 
129 void QgsLayoutExporter::renderPage( QPainter *painter, int page ) const
130 {
131  if ( !mLayout )
132  return;
133 
134  if ( mLayout->pageCollection()->pageCount() <= page || page < 0 )
135  {
136  return;
137  }
138 
139  QgsLayoutItemPage *pageItem = mLayout->pageCollection()->page( page );
140  if ( !pageItem )
141  {
142  return;
143  }
144 
145  LayoutContextPreviewSettingRestorer restorer( mLayout );
146  ( void )restorer;
147 
148  QRectF paperRect = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() );
149  renderRegion( painter, paperRect );
150 }
151 
152 QImage QgsLayoutExporter::renderPageToImage( int page, QSize imageSize, double dpi ) const
153 {
154  if ( !mLayout )
155  return QImage();
156 
157  if ( mLayout->pageCollection()->pageCount() <= page || page < 0 )
158  {
159  return QImage();
160  }
161 
162  QgsLayoutItemPage *pageItem = mLayout->pageCollection()->page( page );
163  if ( !pageItem )
164  {
165  return QImage();
166  }
167 
168  LayoutContextPreviewSettingRestorer restorer( mLayout );
169  ( void )restorer;
170 
171  QRectF paperRect = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() );
172  return renderRegionToImage( paperRect, imageSize, dpi );
173 }
174 
176 class LayoutItemCacheSettingRestorer
177 {
178  public:
179 
180  LayoutItemCacheSettingRestorer( QgsLayout *layout )
181  : mLayout( layout )
182  {
183  const QList< QGraphicsItem * > items = mLayout->items();
184  for ( QGraphicsItem *item : items )
185  {
186  mPrevCacheMode.insert( item, item->cacheMode() );
187  item->setCacheMode( QGraphicsItem::NoCache );
188  }
189  }
190 
191  ~LayoutItemCacheSettingRestorer()
192  {
193  for ( auto it = mPrevCacheMode.constBegin(); it != mPrevCacheMode.constEnd(); ++it )
194  {
195  it.key()->setCacheMode( it.value() );
196  }
197  }
198 
199  private:
200  QgsLayout *mLayout = nullptr;
201  QHash< QGraphicsItem *, QGraphicsItem::CacheMode > mPrevCacheMode;
202 };
203 
205 
206 void QgsLayoutExporter::renderRegion( QPainter *painter, const QRectF &region ) const
207 {
208  QPaintDevice *paintDevice = painter->device();
209  if ( !paintDevice || !mLayout )
210  {
211  return;
212  }
213 
214  LayoutItemCacheSettingRestorer cacheRestorer( mLayout );
215  ( void )cacheRestorer;
216  LayoutContextPreviewSettingRestorer restorer( mLayout );
217  ( void )restorer;
218  LayoutGuideHider guideHider( mLayout );
219  ( void ) guideHider;
220 
221  painter->setRenderHint( QPainter::Antialiasing, mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagAntialiasing );
222 
223  mLayout->render( painter, QRectF( 0, 0, paintDevice->width(), paintDevice->height() ), region );
224 }
225 
226 QImage QgsLayoutExporter::renderRegionToImage( const QRectF &region, QSize imageSize, double dpi ) const
227 {
228  if ( !mLayout )
229  return QImage();
230 
231  LayoutContextPreviewSettingRestorer restorer( mLayout );
232  ( void )restorer;
233 
234  double resolution = mLayout->renderContext().dpi();
235  double oneInchInLayoutUnits = mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 1, QgsUnitTypes::LayoutInches ) );
236  if ( imageSize.isValid() )
237  {
238  //output size in pixels specified, calculate resolution using average of
239  //derived x/y dpi
240  resolution = ( imageSize.width() / region.width()
241  + imageSize.height() / region.height() ) / 2.0 * oneInchInLayoutUnits;
242  }
243  else if ( dpi > 0 )
244  {
245  //dpi overridden by function parameters
246  resolution = dpi;
247  }
248 
249  int width = imageSize.isValid() ? imageSize.width()
250  : static_cast< int >( resolution * region.width() / oneInchInLayoutUnits );
251  int height = imageSize.isValid() ? imageSize.height()
252  : static_cast< int >( resolution * region.height() / oneInchInLayoutUnits );
253 
254  QImage image( QSize( width, height ), QImage::Format_ARGB32 );
255  if ( !image.isNull() )
256  {
257  image.setDotsPerMeterX( resolution / 25.4 * 1000 );
258  image.setDotsPerMeterY( resolution / 25.4 * 1000 );
259  image.fill( Qt::transparent );
260  QPainter imagePainter( &image );
261  renderRegion( &imagePainter, region );
262  if ( !imagePainter.isActive() )
263  return QImage();
264  }
265 
266  return image;
267 }
268 
270 class LayoutContextSettingsRestorer
271 {
272  public:
273 
274  LayoutContextSettingsRestorer( QgsLayout *layout )
275  : mLayout( layout )
276  , mPreviousDpi( layout->renderContext().dpi() )
277  , mPreviousFlags( layout->renderContext().flags() )
278  , mPreviousExportLayer( layout->renderContext().currentExportLayer() )
279  {
280  }
281 
282  ~LayoutContextSettingsRestorer()
283  {
284  mLayout->renderContext().setDpi( mPreviousDpi );
285  mLayout->renderContext().setFlags( mPreviousFlags );
286  mLayout->renderContext().setCurrentExportLayer( mPreviousExportLayer );
287  }
288 
289  private:
290  QgsLayout *mLayout = nullptr;
291  double mPreviousDpi = 0;
292  QgsLayoutRenderContext::Flags mPreviousFlags = 0;
293  int mPreviousExportLayer = 0;
294 };
296 
298 {
299  if ( !mLayout )
300  return PrintError;
301 
302  ImageExportSettings settings = s;
303  if ( settings.dpi <= 0 )
304  settings.dpi = mLayout->renderContext().dpi();
305 
306  mErrorFileName.clear();
307 
308  int worldFilePageNo = -1;
309  if ( QgsLayoutItemMap *referenceMap = mLayout->referenceMap() )
310  {
311  worldFilePageNo = referenceMap->page();
312  }
313 
314  QFileInfo fi( filePath );
315 
316  PageExportDetails pageDetails;
317  pageDetails.directory = fi.path();
318  pageDetails.baseName = fi.baseName();
319  pageDetails.extension = fi.completeSuffix();
320 
321  LayoutContextPreviewSettingRestorer restorer( mLayout );
322  ( void )restorer;
323  LayoutContextSettingsRestorer dpiRestorer( mLayout );
324  ( void )dpiRestorer;
325  mLayout->renderContext().setDpi( settings.dpi );
326  mLayout->renderContext().setFlags( settings.flags );
327 
328  QList< int > pages;
329  if ( settings.pages.empty() )
330  {
331  for ( int page = 0; page < mLayout->pageCollection()->pageCount(); ++page )
332  pages << page;
333  }
334  else
335  {
336  for ( int page : qgis::as_const( settings.pages ) )
337  {
338  if ( page >= 0 && page < mLayout->pageCollection()->pageCount() )
339  pages << page;
340  }
341  }
342 
343  for ( int page : qgis::as_const( pages ) )
344  {
345  if ( !mLayout->pageCollection()->shouldExportPage( page ) )
346  {
347  continue;
348  }
349 
350  bool skip = false;
351  QRectF bounds;
352  QImage image = createImage( settings, page, bounds, skip );
353 
354  if ( skip )
355  continue; // should skip this page, e.g. null size
356 
357  pageDetails.page = page;
358  QString outputFilePath = generateFileName( pageDetails );
359 
360  if ( image.isNull() )
361  {
362  mErrorFileName = outputFilePath;
363  return MemoryError;
364  }
365 
366  if ( !saveImage( image, outputFilePath, pageDetails.extension ) )
367  {
368  mErrorFileName = outputFilePath;
369  return FileError;
370  }
371 
372  if ( page == worldFilePageNo )
373  {
374  georeferenceOutput( outputFilePath, nullptr, bounds, settings.dpi );
375 
376  if ( settings.generateWorldFile )
377  {
378  // should generate world file for this page
379  double a, b, c, d, e, f;
380  if ( bounds.isValid() )
381  computeWorldFileParameters( bounds, a, b, c, d, e, f, settings.dpi );
382  else
383  computeWorldFileParameters( a, b, c, d, e, f, settings.dpi );
384 
385  QFileInfo fi( outputFilePath );
386  // build the world file name
387  QString outputSuffix = fi.suffix();
388  QString worldFileName = fi.absolutePath() + '/' + fi.baseName() + '.'
389  + outputSuffix.at( 0 ) + outputSuffix.at( fi.suffix().size() - 1 ) + 'w';
390 
391  writeWorldFile( worldFileName, a, b, c, d, e, f );
392  }
393  }
394 
395  }
396  return Success;
397 }
398 
399 QgsLayoutExporter::ExportResult QgsLayoutExporter::exportToImage( QgsAbstractLayoutIterator *iterator, const QString &baseFilePath, const QString &extension, const QgsLayoutExporter::ImageExportSettings &settings, QString &error, QgsFeedback *feedback )
400 {
401  error.clear();
402 
403  if ( !iterator->beginRender() )
404  return IteratorError;
405 
406  int total = iterator->count();
407  double step = total > 0 ? 100.0 / total : 100.0;
408  int i = 0;
409  while ( iterator->next() )
410  {
411  if ( feedback )
412  {
413  if ( total > 0 )
414  feedback->setProperty( "progress", QObject::tr( "Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
415  else
416  feedback->setProperty( "progress", QObject::tr( "Exporting section %1" ).arg( i + 1 ).arg( total ) );
417  feedback->setProgress( step * i );
418  }
419  if ( feedback && feedback->isCanceled() )
420  {
421  iterator->endRender();
422  return Canceled;
423  }
424 
425  QgsLayoutExporter exporter( iterator->layout() );
426  QString filePath = iterator->filePath( baseFilePath, extension );
427  ExportResult result = exporter.exportToImage( filePath, settings );
428  if ( result != Success )
429  {
430  if ( result == FileError )
431  error = QObject::tr( "Cannot write to %1. This file may be open in another application." ).arg( filePath );
432  iterator->endRender();
433  return result;
434  }
435  i++;
436  }
437 
438  if ( feedback )
439  {
440  feedback->setProgress( 100 );
441  }
442 
443  iterator->endRender();
444  return Success;
445 }
446 
448 {
449  if ( !mLayout )
450  return PrintError;
451 
452  PdfExportSettings settings = s;
453  if ( settings.dpi <= 0 )
454  settings.dpi = mLayout->renderContext().dpi();
455 
456  mErrorFileName.clear();
457 
458  LayoutContextPreviewSettingRestorer restorer( mLayout );
459  ( void )restorer;
460  LayoutContextSettingsRestorer contextRestorer( mLayout );
461  ( void )contextRestorer;
462  mLayout->renderContext().setDpi( settings.dpi );
463 
464  // If we are not printing as raster, temporarily disable advanced effects
465  // as QPrinter does not support composition modes and can result
466  // in items missing from the output
467  mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagUseAdvancedEffects, !settings.forceVectorOutput );
468 
469  mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagForceVectorOutput, settings.forceVectorOutput );
470 
471  QPrinter printer;
472  preparePrintAsPdf( mLayout, printer, filePath );
473  preparePrint( mLayout, printer, false );
474  QPainter p;
475  if ( !p.begin( &printer ) )
476  {
477  //error beginning print
478  return PrintError;
479  }
480 
481  ExportResult result = printPrivate( printer, p, false, settings.dpi, settings.rasterizeWholeImage );
482  p.end();
483 
484  if ( mLayout->pageCollection()->pageCount() == 1 )
485  {
486  georeferenceOutput( filePath, nullptr, QRectF(), settings.dpi );
487  }
488  return result;
489 }
490 
492 {
493  error.clear();
494 
495  if ( !iterator->beginRender() )
496  return IteratorError;
497 
498  PdfExportSettings settings = s;
499 
500  QPrinter printer;
501  QPainter p;
502 
503  int total = iterator->count();
504  double step = total > 0 ? 100.0 / total : 100.0;
505  int i = 0;
506  bool first = true;
507  while ( iterator->next() )
508  {
509  if ( feedback )
510  {
511  if ( total > 0 )
512  feedback->setProperty( "progress", QObject::tr( "Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
513  else
514  feedback->setProperty( "progress", QObject::tr( "Exporting section %1" ).arg( i + 1 ).arg( total ) );
515  feedback->setProgress( step * i );
516  }
517  if ( feedback && feedback->isCanceled() )
518  {
519  iterator->endRender();
520  return Canceled;
521  }
522 
523  if ( s.dpi <= 0 )
524  settings.dpi = iterator->layout()->renderContext().dpi();
525 
526  LayoutContextPreviewSettingRestorer restorer( iterator->layout() );
527  ( void )restorer;
528  LayoutContextSettingsRestorer contextRestorer( iterator->layout() );
529  ( void )contextRestorer;
530  iterator->layout()->renderContext().setDpi( settings.dpi );
531 
532  // If we are not printing as raster, temporarily disable advanced effects
533  // as QPrinter does not support composition modes and can result
534  // in items missing from the output
536 
538 
539  if ( first )
540  {
541  preparePrintAsPdf( iterator->layout(), printer, fileName );
542  preparePrint( iterator->layout(), printer, false );
543 
544  if ( !p.begin( &printer ) )
545  {
546  //error beginning print
547  return PrintError;
548  }
549  }
550 
551  QgsLayoutExporter exporter( iterator->layout() );
552 
553  ExportResult result = exporter.printPrivate( printer, p, !first, settings.dpi, settings.rasterizeWholeImage );
554  if ( result != Success )
555  {
556  if ( result == FileError )
557  error = QObject::tr( "Cannot write to %1. This file may be open in another application." ).arg( fileName );
558  iterator->endRender();
559  return result;
560  }
561  first = false;
562  i++;
563  }
564 
565  if ( feedback )
566  {
567  feedback->setProgress( 100 );
568  }
569 
570  iterator->endRender();
571  return Success;
572 }
573 
574 QgsLayoutExporter::ExportResult QgsLayoutExporter::exportToPdfs( QgsAbstractLayoutIterator *iterator, const QString &baseFilePath, const QgsLayoutExporter::PdfExportSettings &settings, QString &error, QgsFeedback *feedback )
575 {
576  error.clear();
577 
578  if ( !iterator->beginRender() )
579  return IteratorError;
580 
581  int total = iterator->count();
582  double step = total > 0 ? 100.0 / total : 100.0;
583  int i = 0;
584  while ( iterator->next() )
585  {
586  if ( feedback )
587  {
588  if ( total > 0 )
589  feedback->setProperty( "progress", QObject::tr( "Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
590  else
591  feedback->setProperty( "progress", QObject::tr( "Exporting section %1" ).arg( i + 1 ).arg( total ) );
592  feedback->setProgress( step * i );
593  }
594  if ( feedback && feedback->isCanceled() )
595  {
596  iterator->endRender();
597  return Canceled;
598  }
599 
600  QString filePath = iterator->filePath( baseFilePath, QStringLiteral( "pdf" ) );
601 
602  QgsLayoutExporter exporter( iterator->layout() );
603  ExportResult result = exporter.exportToPdf( filePath, settings );
604  if ( result != Success )
605  {
606  if ( result == FileError )
607  error = QObject::tr( "Cannot write to %1. This file may be open in another application." ).arg( filePath );
608  iterator->endRender();
609  return result;
610  }
611  i++;
612  }
613 
614  if ( feedback )
615  {
616  feedback->setProgress( 100 );
617  }
618 
619  iterator->endRender();
620  return Success;
621 }
622 
624 {
625  if ( !mLayout )
626  return PrintError;
627 
629  if ( settings.dpi <= 0 )
630  settings.dpi = mLayout->renderContext().dpi();
631 
632  mErrorFileName.clear();
633 
634  LayoutContextPreviewSettingRestorer restorer( mLayout );
635  ( void )restorer;
636  LayoutContextSettingsRestorer contextRestorer( mLayout );
637  ( void )contextRestorer;
638  mLayout->renderContext().setDpi( settings.dpi );
639 
640  // If we are not printing as raster, temporarily disable advanced effects
641  // as QPrinter does not support composition modes and can result
642  // in items missing from the output
643  mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagUseAdvancedEffects, !settings.rasterizeWholeImage );
644 
645  preparePrint( mLayout, printer, true );
646  QPainter p;
647  if ( !p.begin( &printer ) )
648  {
649  //error beginning print
650  return PrintError;
651  }
652 
653  ExportResult result = printPrivate( printer, p, false, settings.dpi, settings.rasterizeWholeImage );
654  p.end();
655 
656  return result;
657 }
658 
660 {
661  error.clear();
662 
663  if ( !iterator->beginRender() )
664  return IteratorError;
665 
666  PrintExportSettings settings = s;
667 
668  QPainter p;
669 
670  int total = iterator->count();
671  double step = total > 0 ? 100.0 / total : 100.0;
672  int i = 0;
673  bool first = true;
674  while ( iterator->next() )
675  {
676  if ( feedback )
677  {
678  if ( total > 0 )
679  feedback->setProperty( "progress", QObject::tr( "Printing %1 of %2" ).arg( i + 1 ).arg( total ) );
680  else
681  feedback->setProperty( "progress", QObject::tr( "Printing section %1" ).arg( i + 1 ).arg( total ) );
682  feedback->setProgress( step * i );
683  }
684  if ( feedback && feedback->isCanceled() )
685  {
686  iterator->endRender();
687  return Canceled;
688  }
689 
690  if ( s.dpi <= 0 )
691  settings.dpi = iterator->layout()->renderContext().dpi();
692 
693  LayoutContextPreviewSettingRestorer restorer( iterator->layout() );
694  ( void )restorer;
695  LayoutContextSettingsRestorer contextRestorer( iterator->layout() );
696  ( void )contextRestorer;
697  iterator->layout()->renderContext().setDpi( settings.dpi );
698 
699  // If we are not printing as raster, temporarily disable advanced effects
700  // as QPrinter does not support composition modes and can result
701  // in items missing from the output
703 
704  if ( first )
705  {
706  preparePrint( iterator->layout(), printer, true );
707 
708  if ( !p.begin( &printer ) )
709  {
710  //error beginning print
711  return PrintError;
712  }
713  }
714 
715  QgsLayoutExporter exporter( iterator->layout() );
716 
717  ExportResult result = exporter.printPrivate( printer, p, !first, settings.dpi, settings.rasterizeWholeImage );
718  if ( result != Success )
719  {
720  iterator->endRender();
721  return result;
722  }
723  first = false;
724  i++;
725  }
726 
727  if ( feedback )
728  {
729  feedback->setProgress( 100 );
730  }
731 
732  iterator->endRender();
733  return Success;
734 }
735 
737 {
738  if ( !mLayout )
739  return PrintError;
740 
741  SvgExportSettings settings = s;
742  if ( settings.dpi <= 0 )
743  settings.dpi = mLayout->renderContext().dpi();
744 
745  mErrorFileName.clear();
746 
747  LayoutContextPreviewSettingRestorer restorer( mLayout );
748  ( void )restorer;
749  LayoutContextSettingsRestorer contextRestorer( mLayout );
750  ( void )contextRestorer;
751  mLayout->renderContext().setDpi( settings.dpi );
752 
753  mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagForceVectorOutput, settings.forceVectorOutput );
754 
755  QFileInfo fi( filePath );
756  PageExportDetails pageDetails;
757  pageDetails.directory = fi.path();
758  pageDetails.baseName = fi.baseName();
759  pageDetails.extension = fi.completeSuffix();
760 
761  double inchesToLayoutUnits = mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 1, QgsUnitTypes::LayoutInches ) );
762 
763  for ( int i = 0; i < mLayout->pageCollection()->pageCount(); ++i )
764  {
765  if ( !mLayout->pageCollection()->shouldExportPage( i ) )
766  {
767  continue;
768  }
769 
770  pageDetails.page = i;
771  QString fileName = generateFileName( pageDetails );
772 
773  QgsLayoutItemPage *pageItem = mLayout->pageCollection()->page( i );
774  QRectF bounds;
775  if ( settings.cropToContents )
776  {
777  if ( mLayout->pageCollection()->pageCount() == 1 )
778  {
779  // single page, so include everything
780  bounds = mLayout->layoutBounds( true );
781  }
782  else
783  {
784  // multi page, so just clip to items on current page
785  bounds = mLayout->pageItemBounds( i, true );
786  }
787  bounds = bounds.adjusted( -settings.cropMargins.left(),
788  -settings.cropMargins.top(),
789  settings.cropMargins.right(),
790  settings.cropMargins.bottom() );
791  }
792  else
793  {
794  bounds = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() );
795  }
796 
797  //width in pixel
798  int width = ( int )( bounds.width() * settings.dpi / inchesToLayoutUnits );
799  //height in pixel
800  int height = ( int )( bounds.height() * settings.dpi / inchesToLayoutUnits );
801  if ( width == 0 || height == 0 )
802  {
803  //invalid size, skip this page
804  continue;
805  }
806 
807  if ( settings.exportAsLayers )
808  {
809  const QRectF paperRect = QRectF( pageItem->pos().x(),
810  pageItem->pos().y(),
811  pageItem->rect().width(),
812  pageItem->rect().height() );
813  QDomDocument svg;
814  QDomNode svgDocRoot;
815  const QList<QGraphicsItem *> items = mLayout->items( paperRect,
816  Qt::IntersectsItemBoundingRect,
817  Qt::AscendingOrder );
818 
819  LayoutItemHider itemHider( items );
820  ( void )itemHider;
821 
822  int layoutItemLayerIdx = 0;
823  auto it = items.constBegin();
824  for ( unsigned svgLayerId = 1; it != items.constEnd(); ++svgLayerId )
825  {
826  itemHider.hideAll();
827  QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( *it );
828  QString layerName = QObject::tr( "Layer %1" ).arg( svgLayerId );
829  if ( layoutItem && layoutItem->numberExportLayers() > 0 )
830  {
831  layoutItem->show();
832  mLayout->renderContext().setCurrentExportLayer( layoutItemLayerIdx );
833  ++layoutItemLayerIdx;
834  }
835  else
836  {
837  // show all items until the next item that renders on a separate layer
838  for ( ; it != items.constEnd(); ++it )
839  {
840  layoutItem = dynamic_cast<QgsLayoutItem *>( *it );
841  if ( layoutItem && layoutItem->numberExportLayers() > 0 )
842  {
843  break;
844  }
845  else
846  {
847  ( *it )->show();
848  }
849  }
850  }
851 
852  ExportResult result = renderToLayeredSvg( settings, width, height, i, bounds, fileName, svgLayerId, layerName, svg, svgDocRoot );
853  if ( result != Success )
854  return result;
855 
856  if ( layoutItem && layoutItem->numberExportLayers() > 0 && layoutItem->numberExportLayers() == layoutItemLayerIdx ) // restore and pass to next item
857  {
858  mLayout->renderContext().setCurrentExportLayer( -1 );
859  layoutItemLayerIdx = 0;
860  ++it;
861  }
862  }
863 
864  QFile out( fileName );
865  bool openOk = out.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate );
866  if ( !openOk )
867  {
868  mErrorFileName = fileName;
869  return FileError;
870  }
871 
872  out.write( svg.toByteArray() );
873  }
874  else
875  {
876  QSvgGenerator generator;
877  generator.setTitle( mLayout->project()->title() );
878  generator.setFileName( fileName );
879  generator.setSize( QSize( width, height ) );
880  generator.setViewBox( QRect( 0, 0, width, height ) );
881  generator.setResolution( settings.dpi );
882 
883  QPainter p;
884  bool createOk = p.begin( &generator );
885  if ( !createOk )
886  {
887  mErrorFileName = fileName;
888  return FileError;
889  }
890 
891  if ( settings.cropToContents )
892  renderRegion( &p, bounds );
893  else
894  renderPage( &p, i );
895 
896  p.end();
897  }
898  }
899 
900  return Success;
901 }
902 
903 QgsLayoutExporter::ExportResult QgsLayoutExporter::exportToSvg( QgsAbstractLayoutIterator *iterator, const QString &baseFilePath, const QgsLayoutExporter::SvgExportSettings &settings, QString &error, QgsFeedback *feedback )
904 {
905  error.clear();
906 
907  if ( !iterator->beginRender() )
908  return IteratorError;
909 
910  int total = iterator->count();
911  double step = total > 0 ? 100.0 / total : 100.0;
912  int i = 0;
913  while ( iterator->next() )
914  {
915  if ( feedback )
916  {
917  if ( total > 0 )
918  feedback->setProperty( "progress", QObject::tr( "Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
919  else
920  feedback->setProperty( "progress", QObject::tr( "Exporting section %1" ).arg( i + 1 ).arg( total ) );
921 
922  feedback->setProgress( step * i );
923  }
924  if ( feedback && feedback->isCanceled() )
925  {
926  iterator->endRender();
927  return Canceled;
928  }
929 
930  QString filePath = iterator->filePath( baseFilePath, QStringLiteral( "svg" ) );
931 
932  QgsLayoutExporter exporter( iterator->layout() );
933  ExportResult result = exporter.exportToSvg( filePath, settings );
934  if ( result != Success )
935  {
936  if ( result == FileError )
937  error = QObject::tr( "Cannot write to %1. This file may be open in another application." ).arg( filePath );
938  iterator->endRender();
939  return result;
940  }
941  i++;
942  }
943 
944  if ( feedback )
945  {
946  feedback->setProgress( 100 );
947  }
948 
949  iterator->endRender();
950  return Success;
951 
952 }
953 
954 void QgsLayoutExporter::preparePrintAsPdf( QgsLayout *layout, QPrinter &printer, const QString &filePath )
955 {
956  printer.setOutputFileName( filePath );
957  // setOutputFormat should come after setOutputFileName, which auto-sets format to QPrinter::PdfFormat.
958  // [LS] This should be QPrinter::NativeFormat for Mac, otherwise fonts are not embed-able
959  // and text is not searchable; however, there are several bugs with <= Qt 4.8.5, 5.1.1, 5.2.0:
960  // https://bugreports.qt-project.org/browse/QTBUG-10094 - PDF font embedding fails
961  // https://bugreports.qt-project.org/browse/QTBUG-33583 - PDF output converts text to outline
962  // Also an issue with PDF paper size using QPrinter::NativeFormat on Mac (always outputs portrait letter-size)
963  printer.setOutputFormat( QPrinter::PdfFormat );
964 
965  updatePrinterPageSize( layout, printer, 0 );
966 
967  // TODO: add option for this in layout
968  // May not work on Windows or non-X11 Linux. Works fine on Mac using QPrinter::NativeFormat
969  //printer.setFontEmbeddingEnabled( true );
970 
971  QgsPaintEngineHack::fixEngineFlags( printer.paintEngine() );
972 }
973 
974 void QgsLayoutExporter::preparePrint( QgsLayout *layout, QPrinter &printer, bool setFirstPageSize )
975 {
976  printer.setFullPage( true );
977  printer.setColorMode( QPrinter::Color );
978 
979  //set user-defined resolution
980  printer.setResolution( layout->renderContext().dpi() );
981 
982  if ( setFirstPageSize )
983  {
984  updatePrinterPageSize( layout, printer, 0 );
985  }
986 }
987 
989 {
990  preparePrint( mLayout, printer, true );
991  QPainter p;
992  if ( !p.begin( &printer ) )
993  {
994  //error beginning print
995  return PrintError;
996  }
997 
998  printPrivate( printer, p );
999  p.end();
1000  return Success;
1001 }
1002 
1003 QgsLayoutExporter::ExportResult QgsLayoutExporter::printPrivate( QPrinter &printer, QPainter &painter, bool startNewPage, double dpi, bool rasterize )
1004 {
1005  //layout starts page numbering at 0
1006  int fromPage = ( printer.fromPage() < 1 ) ? 0 : printer.fromPage() - 1;
1007  int toPage = ( printer.toPage() < 1 ) ? mLayout->pageCollection()->pageCount() - 1 : printer.toPage() - 1;
1008 
1009  bool pageExported = false;
1010  if ( rasterize )
1011  {
1012  for ( int i = fromPage; i <= toPage; ++i )
1013  {
1014  if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1015  {
1016  continue;
1017  }
1018 
1019  updatePrinterPageSize( mLayout, printer, i );
1020  if ( ( pageExported && i > fromPage ) || startNewPage )
1021  {
1022  printer.newPage();
1023  }
1024 
1025  QImage image = renderPageToImage( i, QSize(), dpi );
1026  if ( !image.isNull() )
1027  {
1028  QRectF targetArea( 0, 0, image.width(), image.height() );
1029  painter.drawImage( targetArea, image, targetArea );
1030  }
1031  else
1032  {
1033  return MemoryError;
1034  }
1035  pageExported = true;
1036  }
1037  }
1038  else
1039  {
1040  for ( int i = fromPage; i <= toPage; ++i )
1041  {
1042  if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1043  {
1044  continue;
1045  }
1046 
1047  updatePrinterPageSize( mLayout, printer, i );
1048 
1049  if ( ( pageExported && i > fromPage ) || startNewPage )
1050  {
1051  printer.newPage();
1052  }
1053  renderPage( &painter, i );
1054  pageExported = true;
1055  }
1056  }
1057  return Success;
1058 }
1059 
1060 void QgsLayoutExporter::updatePrinterPageSize( QgsLayout *layout, QPrinter &printer, int page )
1061 {
1062  //must set orientation to portrait before setting paper size, otherwise size will be flipped
1063  //for landscape sized outputs (#11352)
1064  printer.setOrientation( QPrinter::Portrait );
1065  QgsLayoutSize pageSize = layout->pageCollection()->page( page )->sizeWithUnits();
1067  printer.setPaperSize( pageSizeMM.toQSizeF(), QPrinter::Millimeter );
1068 }
1069 
1070 QgsLayoutExporter::ExportResult QgsLayoutExporter::renderToLayeredSvg( const SvgExportSettings &settings, double width, double height, int page, QRectF bounds, const QString &filename, int svgLayerId, const QString &layerName, QDomDocument &svg, QDomNode &svgDocRoot ) const
1071 {
1072  QBuffer svgBuffer;
1073  {
1074  QSvgGenerator generator;
1075  if ( const QgsMasterLayoutInterface *l = dynamic_cast< const QgsMasterLayoutInterface * >( mLayout.data() ) )
1076  generator.setTitle( l->name() );
1077  else if ( mLayout->project() )
1078  generator.setTitle( mLayout->project()->title() );
1079 
1080  generator.setOutputDevice( &svgBuffer );
1081  generator.setSize( QSize( width, height ) );
1082  generator.setViewBox( QRect( 0, 0, width, height ) );
1083  generator.setResolution( settings.dpi ); //because the rendering is done in mm, convert the dpi
1084 
1085  QPainter svgPainter( &generator );
1086  if ( settings.cropToContents )
1087  renderRegion( &svgPainter, bounds );
1088  else
1089  renderPage( &svgPainter, page );
1090  }
1091 
1092  // post-process svg output to create groups in a single svg file
1093  // we create inkscape layers since it's nice and clean and free
1094  // and fully svg compatible
1095  {
1096  svgBuffer.close();
1097  svgBuffer.open( QIODevice::ReadOnly );
1098  QDomDocument doc;
1099  QString errorMsg;
1100  int errorLine;
1101  if ( ! doc.setContent( &svgBuffer, false, &errorMsg, &errorLine ) )
1102  {
1103  mErrorFileName = filename;
1104  return SvgLayerError;
1105  }
1106  if ( 1 == svgLayerId )
1107  {
1108  svg = QDomDocument( doc.doctype() );
1109  svg.appendChild( svg.importNode( doc.firstChild(), false ) );
1110  svgDocRoot = svg.importNode( doc.elementsByTagName( QStringLiteral( "svg" ) ).at( 0 ), false );
1111  svgDocRoot.toElement().setAttribute( QStringLiteral( "xmlns:inkscape" ), QStringLiteral( "http://www.inkscape.org/namespaces/inkscape" ) );
1112  svg.appendChild( svgDocRoot );
1113  }
1114  QDomNode mainGroup = svg.importNode( doc.elementsByTagName( QStringLiteral( "g" ) ).at( 0 ), true );
1115  mainGroup.toElement().setAttribute( QStringLiteral( "id" ), layerName );
1116  mainGroup.toElement().setAttribute( QStringLiteral( "inkscape:label" ), layerName );
1117  mainGroup.toElement().setAttribute( QStringLiteral( "inkscape:groupmode" ), QStringLiteral( "layer" ) );
1118  QDomNode defs = svg.importNode( doc.elementsByTagName( QStringLiteral( "defs" ) ).at( 0 ), true );
1119  svgDocRoot.appendChild( defs );
1120  svgDocRoot.appendChild( mainGroup );
1121  }
1122  return Success;
1123 }
1124 
1125 std::unique_ptr<double[]> QgsLayoutExporter::computeGeoTransform( const QgsLayoutItemMap *map, const QRectF &region, double dpi ) const
1126 {
1127  if ( !map )
1128  map = mLayout->referenceMap();
1129 
1130  if ( !map )
1131  return nullptr;
1132 
1133  if ( dpi < 0 )
1134  dpi = mLayout->renderContext().dpi();
1135 
1136  // calculate region of composition to export (in mm)
1137  QRectF exportRegion = region;
1138  if ( !exportRegion.isValid() )
1139  {
1140  int pageNumber = map->page();
1141 
1142  QgsLayoutItemPage *page = mLayout->pageCollection()->page( pageNumber );
1143  double pageY = page->pos().y();
1144  QSizeF pageSize = page->rect().size();
1145  exportRegion = QRectF( 0, pageY, pageSize.width(), pageSize.height() );
1146  }
1147 
1148  // map rectangle (in mm)
1149  QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
1150 
1151  // destination width/height in mm
1152  double outputHeightMM = exportRegion.height();
1153  double outputWidthMM = exportRegion.width();
1154 
1155  // map properties
1156  QgsRectangle mapExtent = map->extent();
1157  double mapXCenter = mapExtent.center().x();
1158  double mapYCenter = mapExtent.center().y();
1159  double alpha = - map->mapRotation() / 180 * M_PI;
1160  double sinAlpha = std::sin( alpha );
1161  double cosAlpha = std::cos( alpha );
1162 
1163  // get the extent (in map units) for the exported region
1164  QPointF mapItemPos = map->pos();
1165  //adjust item position so it is relative to export region
1166  mapItemPos.rx() -= exportRegion.left();
1167  mapItemPos.ry() -= exportRegion.top();
1168 
1169  // calculate extent of entire page in map units
1170  double xRatio = mapExtent.width() / mapItemSceneRect.width();
1171  double yRatio = mapExtent.height() / mapItemSceneRect.height();
1172  double xmin = mapExtent.xMinimum() - mapItemPos.x() * xRatio;
1173  double ymax = mapExtent.yMaximum() + mapItemPos.y() * yRatio;
1174  QgsRectangle paperExtent( xmin, ymax - outputHeightMM * yRatio, xmin + outputWidthMM * xRatio, ymax );
1175 
1176  // calculate origin of page
1177  double X0 = paperExtent.xMinimum();
1178  double Y0 = paperExtent.yMaximum();
1179 
1180  if ( !qgsDoubleNear( alpha, 0.0 ) )
1181  {
1182  // translate origin to account for map rotation
1183  double X1 = X0 - mapXCenter;
1184  double Y1 = Y0 - mapYCenter;
1185  double X2 = X1 * cosAlpha + Y1 * sinAlpha;
1186  double Y2 = -X1 * sinAlpha + Y1 * cosAlpha;
1187  X0 = X2 + mapXCenter;
1188  Y0 = Y2 + mapYCenter;
1189  }
1190 
1191  // calculate scaling of pixels
1192  int pageWidthPixels = static_cast< int >( dpi * outputWidthMM / 25.4 );
1193  int pageHeightPixels = static_cast< int >( dpi * outputHeightMM / 25.4 );
1194  double pixelWidthScale = paperExtent.width() / pageWidthPixels;
1195  double pixelHeightScale = paperExtent.height() / pageHeightPixels;
1196 
1197  // transform matrix
1198  std::unique_ptr<double[]> t( new double[6] );
1199  t[0] = X0;
1200  t[1] = cosAlpha * pixelWidthScale;
1201  t[2] = -sinAlpha * pixelWidthScale;
1202  t[3] = Y0;
1203  t[4] = -sinAlpha * pixelHeightScale;
1204  t[5] = -cosAlpha * pixelHeightScale;
1205 
1206  return t;
1207 }
1208 
1209 void QgsLayoutExporter::writeWorldFile( const QString &worldFileName, double a, double b, double c, double d, double e, double f ) const
1210 {
1211  QFile worldFile( worldFileName );
1212  if ( !worldFile.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
1213  {
1214  return;
1215  }
1216  QTextStream fout( &worldFile );
1217 
1218  // QString::number does not use locale settings (for the decimal point)
1219  // which is what we want here
1220  fout << QString::number( a, 'f', 12 ) << "\r\n";
1221  fout << QString::number( d, 'f', 12 ) << "\r\n";
1222  fout << QString::number( b, 'f', 12 ) << "\r\n";
1223  fout << QString::number( e, 'f', 12 ) << "\r\n";
1224  fout << QString::number( c, 'f', 12 ) << "\r\n";
1225  fout << QString::number( f, 'f', 12 ) << "\r\n";
1226 }
1227 
1228 bool QgsLayoutExporter::georeferenceOutput( const QString &file, QgsLayoutItemMap *map, const QRectF &exportRegion, double dpi ) const
1229 {
1230  if ( !mLayout )
1231  return false;
1232 
1233  if ( !map )
1234  map = mLayout->referenceMap();
1235 
1236  if ( !map )
1237  return false; // no reference map
1238 
1239  if ( dpi < 0 )
1240  dpi = mLayout->renderContext().dpi();
1241 
1242  std::unique_ptr<double[]> t = computeGeoTransform( map, exportRegion, dpi );
1243  if ( !t )
1244  return false;
1245 
1246  // important - we need to manually specify the DPI in advance, as GDAL will otherwise
1247  // assume a DPI of 150
1248  CPLSetConfigOption( "GDAL_PDF_DPI", QString::number( dpi ).toLocal8Bit().constData() );
1249  gdal::dataset_unique_ptr outputDS( GDALOpen( file.toLocal8Bit().constData(), GA_Update ) );
1250  if ( outputDS )
1251  {
1252  GDALSetGeoTransform( outputDS.get(), t.get() );
1253 #if 0
1254  //TODO - metadata can be set here, e.g.:
1255  GDALSetMetadataItem( outputDS, "AUTHOR", "me", nullptr );
1256 #endif
1257  GDALSetProjection( outputDS.get(), map->crs().toWkt().toLocal8Bit().constData() );
1258  }
1259  CPLSetConfigOption( "GDAL_PDF_DPI", nullptr );
1260 
1261  return true;
1262 }
1263 
1264 void QgsLayoutExporter::computeWorldFileParameters( double &a, double &b, double &c, double &d, double &e, double &f, double dpi ) const
1265 {
1266  if ( !mLayout )
1267  return;
1268 
1269  QgsLayoutItemMap *map = mLayout->referenceMap();
1270  if ( !map )
1271  {
1272  return;
1273  }
1274 
1275  int pageNumber = map->page();
1276  QgsLayoutItemPage *page = mLayout->pageCollection()->page( pageNumber );
1277  double pageY = page->pos().y();
1278  QSizeF pageSize = page->rect().size();
1279  QRectF pageRect( 0, pageY, pageSize.width(), pageSize.height() );
1280  computeWorldFileParameters( pageRect, a, b, c, d, e, f, dpi );
1281 }
1282 
1283 void QgsLayoutExporter::computeWorldFileParameters( const QRectF &exportRegion, double &a, double &b, double &c, double &d, double &e, double &f, double dpi ) const
1284 {
1285  if ( !mLayout )
1286  return;
1287 
1288  // World file parameters : affine transformation parameters from pixel coordinates to map coordinates
1289  QgsLayoutItemMap *map = mLayout->referenceMap();
1290  if ( !map )
1291  {
1292  return;
1293  }
1294 
1295  double destinationHeight = exportRegion.height();
1296  double destinationWidth = exportRegion.width();
1297 
1298  QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
1299  QgsRectangle mapExtent = map->extent();
1300 
1301  double alpha = map->mapRotation() / 180 * M_PI;
1302 
1303  double xRatio = mapExtent.width() / mapItemSceneRect.width();
1304  double yRatio = mapExtent.height() / mapItemSceneRect.height();
1305 
1306  double xCenter = mapExtent.center().x();
1307  double yCenter = mapExtent.center().y();
1308 
1309  // get the extent (in map units) for the region
1310  QPointF mapItemPos = map->pos();
1311  //adjust item position so it is relative to export region
1312  mapItemPos.rx() -= exportRegion.left();
1313  mapItemPos.ry() -= exportRegion.top();
1314 
1315  double xmin = mapExtent.xMinimum() - mapItemPos.x() * xRatio;
1316  double ymax = mapExtent.yMaximum() + mapItemPos.y() * yRatio;
1317  QgsRectangle paperExtent( xmin, ymax - destinationHeight * yRatio, xmin + destinationWidth * xRatio, ymax );
1318 
1319  double X0 = paperExtent.xMinimum();
1320  double Y0 = paperExtent.yMinimum();
1321 
1322  if ( dpi < 0 )
1323  dpi = mLayout->renderContext().dpi();
1324 
1325  int widthPx = static_cast< int >( dpi * destinationWidth / 25.4 );
1326  int heightPx = static_cast< int >( dpi * destinationHeight / 25.4 );
1327 
1328  double Ww = paperExtent.width() / widthPx;
1329  double Hh = paperExtent.height() / heightPx;
1330 
1331  // scaling matrix
1332  double s[6];
1333  s[0] = Ww;
1334  s[1] = 0;
1335  s[2] = X0;
1336  s[3] = 0;
1337  s[4] = -Hh;
1338  s[5] = Y0 + paperExtent.height();
1339 
1340  // rotation matrix
1341  double r[6];
1342  r[0] = std::cos( alpha );
1343  r[1] = -std::sin( alpha );
1344  r[2] = xCenter * ( 1 - std::cos( alpha ) ) + yCenter * std::sin( alpha );
1345  r[3] = std::sin( alpha );
1346  r[4] = std::cos( alpha );
1347  r[5] = - xCenter * std::sin( alpha ) + yCenter * ( 1 - std::cos( alpha ) );
1348 
1349  // result = rotation x scaling = rotation(scaling(X))
1350  a = r[0] * s[0] + r[1] * s[3];
1351  b = r[0] * s[1] + r[1] * s[4];
1352  c = r[0] * s[2] + r[1] * s[5] + r[2];
1353  d = r[3] * s[0] + r[4] * s[3];
1354  e = r[3] * s[1] + r[4] * s[4];
1355  f = r[3] * s[2] + r[4] * s[5] + r[5];
1356 }
1357 
1358 QImage QgsLayoutExporter::createImage( const QgsLayoutExporter::ImageExportSettings &settings, int page, QRectF &bounds, bool &skipPage ) const
1359 {
1360  bounds = QRectF();
1361  skipPage = false;
1362 
1363  if ( settings.cropToContents )
1364  {
1365  if ( mLayout->pageCollection()->pageCount() == 1 )
1366  {
1367  // single page, so include everything
1368  bounds = mLayout->layoutBounds( true );
1369  }
1370  else
1371  {
1372  // multi page, so just clip to items on current page
1373  bounds = mLayout->pageItemBounds( page, true );
1374  }
1375  if ( bounds.width() <= 0 || bounds.height() <= 0 )
1376  {
1377  //invalid size, skip page
1378  skipPage = true;
1379  return QImage();
1380  }
1381 
1382  double pixelToLayoutUnits = mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 1, QgsUnitTypes::LayoutPixels ) );
1383  bounds = bounds.adjusted( -settings.cropMargins.left() * pixelToLayoutUnits,
1384  -settings.cropMargins.top() * pixelToLayoutUnits,
1385  settings.cropMargins.right() * pixelToLayoutUnits,
1386  settings.cropMargins.bottom() * pixelToLayoutUnits );
1387  return renderRegionToImage( bounds, QSize(), settings.dpi );
1388  }
1389  else
1390  {
1391  return renderPageToImage( page, settings.imageSize, settings.dpi );
1392  }
1393 }
1394 
1396 {
1397  if ( details.page == 0 )
1398  {
1399  return details.directory + '/' + details.baseName + '.' + details.extension;
1400  }
1401  else
1402  {
1403  return details.directory + '/' + details.baseName + '_' + QString::number( details.page + 1 ) + '.' + details.extension;
1404  }
1405 }
1406 
1407 bool QgsLayoutExporter::saveImage( const QImage &image, const QString &imageFilename, const QString &imageFormat )
1408 {
1409  QImageWriter w( imageFilename, imageFormat.toLocal8Bit().constData() );
1410  if ( imageFormat.compare( QLatin1String( "tiff" ), Qt::CaseInsensitive ) == 0 || imageFormat.compare( QLatin1String( "tif" ), Qt::CaseInsensitive ) == 0 )
1411  {
1412  w.setCompression( 1 ); //use LZW compression
1413  }
1414  return w.write( image );
1415 }
1416 
bool cropToContents
Set to true if image should be cropped so only parts of the layout containing items are exported...
double right() const
Returns the right margin.
Definition: qgsmargins.h:84
void setDpi(double dpi)
Sets the dpi for outputting the layout.
A rectangle specified with double values.
Definition: qgsrectangle.h:39
void renderRegion(QPainter *painter, const QRectF &region) const
Renders a region from the layout to a painter.
Contains settings relating to printing layouts.
Base class for graphical items within a QgsLayout.
QgsMargins cropMargins
Crop to content margins, in layout units.
Unable to allocate memory required to export.
virtual bool endRender()=0
Ends the render, performing any required cleanup tasks.
Contains the configuration for a single snap guide used by a layout.
Could not write to destination file, likely due to a lock held by another application.
ExportResult exportToImage(const QString &filePath, const QgsLayoutExporter::ImageExportSettings &settings)
Exports the layout to the a filePath, using the specified export settings.
double y
Definition: qgspointxy.h:48
QgsLayoutSize sizeWithUnits() const
Returns the item&#39;s current size, including units.
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition: qgsfeedback.h:63
virtual bool next()=0
Iterates to next feature, returning false if no more features exist to iterate over.
QgsMargins cropMargins
Crop to content margins, in pixels.
bool forceVectorOutput
Set to true to force vector object exports, even when the resultant appearance will differ from the l...
bool rasterizeWholeImage
Set to true to force whole layout to be rasterized while exporting.
QgsLayoutMeasurement convert(const QgsLayoutMeasurement &measurement, const QgsUnitTypes::LayoutUnit targetUnits) const
Converts a measurement from one unit to another.
QImage renderRegionToImage(const QRectF &region, QSize imageSize=QSize(), double dpi=0) const
Renders a region of the layout to an image.
ExportResult print(QPrinter &printer, const QgsLayoutExporter::PrintExportSettings &settings)
Prints the layout to a printer, using the specified export settings.
QgsLayoutRenderContext & renderContext()
Returns a reference to the layout&#39;s render context, which stores information relating to the current ...
Definition: qgslayout.cpp:355
int currentExportLayer() const
Returns the current item layer to draw while exporting.
bool georeferenceOutput(const QString &file, QgsLayoutItemMap *referenceMap=nullptr, const QRectF &exportRegion=QRectF(), double dpi=-1) const
Georeferences a file (image of PDF) exported from the layout.
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:251
QList< int > pages
List of specific pages to export, or an empty list to export all pages.
QgsLayoutExporter(QgsLayout *layout)
Constructor for QgsLayoutExporter, for the specified layout.
Base class for feedback objects to be used for cancelation of something running in a worker thread...
Definition: qgsfeedback.h:44
QSizeF toQSizeF() const
Converts the layout size to a QSizeF.
Contains details of a page being exported by the class.
QSize imageSize
Manual size in pixels for output image.
QgsRectangle extent() const
Returns the current map extent.
Layout graphical items for displaying a map.
QgsLayoutItemPage * page(int pageNumber)
Returns a specific page (by pageNumber) from the collection.
This class provides a method of storing measurements for use in QGIS layouts using a variety of diffe...
virtual QString generateFileName(const PageExportDetails &details) const
Generates the file name for a page during export.
bool exportAsLayers
Set to true to export as a layered SVG file.
An abstract base class for QgsLayout based classes which can be exported by QgsLayoutExporter.
double dpi
Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.
QgsLayoutPageCollection * pageCollection()
Returns a pointer to the layout&#39;s page collection, which stores and manages page items in the layout...
Definition: qgslayout.cpp:455
void computeWorldFileParameters(double &a, double &b, double &c, double &d, double &e, double &f, double dpi=-1) const
Compute world file parameters.
double bottom() const
Returns the bottom margin.
Definition: qgsmargins.h:90
QgsLayoutRenderContext::Flags flags() const
Returns the current combination of flags used for rendering the layout.
double dpi() const
Returns the dpi for outputting the layout.
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:142
bool rasterizeWholeImage
Set to true to force whole layout to be rasterized while exporting.
double dpi
Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.
bool forceVectorOutput
Set to true to force vector object exports, even when the resultant appearance will differ from the l...
virtual int numberExportLayers() const
Returns the number of layers that this item requires for exporting during layered exports (e...
virtual int count()=0
Returns the number of features to iterate over.
QString extension
File suffix/extension (without the leading &#39;.&#39;)
virtual QgsLayout * layout()=0
Returns the layout associated with the iterator.
double top() const
Returns the top margin.
Definition: qgsmargins.h:78
QString baseName
Base part of filename (i.e. file name without extension or &#39;.&#39;)
Could not create layered SVG file.
int page() const
Returns the page the item is currently on, with the first page returning 0.
ExportResult exportToSvg(const QString &filePath, const QgsLayoutExporter::SvgExportSettings &settings)
Exports the layout as an SVG to the a filePath, using the specified export settings.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:49
std::unique_ptr< void, GDALDatasetCloser > dataset_unique_ptr
Scoped GDAL dataset.
Definition: qgsogrutils.h:134
double x
Definition: qgspointxy.h:47
Use antialiasing when drawing items.
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:130
Handles rendering and exports of layouts to various formats.
Contains settings relating to exporting layouts to PDF.
bool generateWorldFile
Set to true to generate an external world file alongside exported images.
double mapRotation(QgsLayoutObject::PropertyValueType valueType=QgsLayoutObject::EvaluatedValue) const
Returns the rotation used for drawing the map within the layout item, in degrees clockwise.
Force output in vector format where possible, even if items require rasterization to keep their corre...
const QgsLayoutMeasurementConverter & measurementConverter() const
Returns the layout measurement converter to be used in the layout.
Contains settings relating to exporting layouts to raster images.
static ExportResult exportToPdfs(QgsAbstractLayoutIterator *iterator, const QString &baseFilePath, const QgsLayoutExporter::PdfExportSettings &settings, QString &error, QgsFeedback *feedback=nullptr)
Exports a layout iterator to multiple PDF files, with the specified export settings.
QImage renderPageToImage(int page, QSize imageSize=QSize(), double dpi=0) const
Renders a full page to an image.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
QgsCoordinateReferenceSystem crs() const
Returns coordinate reference system used for rendering the map.
Enable advanced effects such as blend modes.
double dpi
Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.
ExportResult exportToPdf(const QString &filePath, const QgsLayoutExporter::PdfExportSettings &settings)
Exports the layout as a PDF to the a filePath, using the specified export settings.
static void fixEngineFlags(QPaintEngine *engine)
QString toWkt() const
Returns a WKT representation of this CRS.
int page
Page number, where 0 = first page.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:120
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:125
void setFlag(const QgsLayoutRenderContext::Flag flag, const bool on=true)
Enables or disables a particular rendering flag for the layout.
Could not start printing to destination device.
QgsPointXY center() const
Returns the center point of the rectangle.
Definition: qgsrectangle.h:170
bool cropToContents
Set to true if image should be cropped so only parts of the layout containing items are exported...
This class provides a method of storing sizes, consisting of a width and height, for use in QGIS layo...
Definition: qgslayoutsize.h:40
Interface for master layout type objects, such as print layouts and reports.
double dpi
Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.
Contains settings relating to exporting layouts to SVG.
virtual bool beginRender()=0
Called when rendering begins, before iteration commences.
double left() const
Returns the left margin.
Definition: qgsmargins.h:72
Export was successful.
QgsLayout * layout() const
Returns the layout linked to this exporter.
virtual QString filePath(const QString &baseFilePath, const QString &extension)=0
Returns the file path for the current feature, based on a specified base file path and extension...
double height() const
Returns the height of the rectangle.
Definition: qgsrectangle.h:149
Item representing the paper in a layout.
QgsLayoutRenderContext::Flags flags
Layout context flags, which control how the export will be created.
ExportResult
Result codes for exporting layouts.
void renderPage(QPainter *painter, int page) const
Renders a full page to a destination painter.
Error iterating over layout.