QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
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 #ifndef QT_NO_PRINTER
19 
20 #include "qgslayout.h"
21 #include "qgslayoutitemmap.h"
23 #include "qgsogrutils.h"
24 #include "qgspaintenginehack.h"
27 #include "qgsfeedback.h"
29 #include "qgslinestring.h"
30 #include "qgsmessagelog.h"
31 #include "qgslabelingresults.h"
32 #include <QImageWriter>
33 #include <QSize>
34 #include <QSvgGenerator>
35 #include <QBuffer>
36 #include <QTimeZone>
37 #include <QTextStream>
38 
39 #include "gdal.h"
40 #include "cpl_conv.h"
41 
43 class LayoutContextPreviewSettingRestorer
44 {
45  public:
46 
47  LayoutContextPreviewSettingRestorer( QgsLayout *layout )
48  : mLayout( layout )
49  , mPreviousSetting( layout->renderContext().mIsPreviewRender )
50  {
51  mLayout->renderContext().mIsPreviewRender = false;
52  }
53 
54  ~LayoutContextPreviewSettingRestorer()
55  {
56  mLayout->renderContext().mIsPreviewRender = mPreviousSetting;
57  }
58 
59  LayoutContextPreviewSettingRestorer( const LayoutContextPreviewSettingRestorer &other ) = delete;
60  LayoutContextPreviewSettingRestorer &operator=( const LayoutContextPreviewSettingRestorer &other ) = delete;
61 
62  private:
63  QgsLayout *mLayout = nullptr;
64  bool mPreviousSetting = false;
65 };
66 
67 class LayoutGuideHider
68 {
69  public:
70 
71  LayoutGuideHider( QgsLayout *layout )
72  : mLayout( layout )
73  {
74  const QList< QgsLayoutGuide * > guides = mLayout->guides().guides();
75  for ( QgsLayoutGuide *guide : guides )
76  {
77  mPrevVisibility.insert( guide, guide->item()->isVisible() );
78  guide->item()->setVisible( false );
79  }
80  }
81 
82  ~LayoutGuideHider()
83  {
84  for ( auto it = mPrevVisibility.constBegin(); it != mPrevVisibility.constEnd(); ++it )
85  {
86  it.key()->item()->setVisible( it.value() );
87  }
88  }
89 
90  LayoutGuideHider( const LayoutGuideHider &other ) = delete;
91  LayoutGuideHider &operator=( const LayoutGuideHider &other ) = delete;
92 
93  private:
94  QgsLayout *mLayout = nullptr;
95  QHash< QgsLayoutGuide *, bool > mPrevVisibility;
96 };
97 
98 class LayoutItemHider
99 {
100  public:
101  explicit LayoutItemHider( const QList<QGraphicsItem *> &items )
102  {
103  mItemsToIterate.reserve( items.count() );
104  for ( QGraphicsItem *item : items )
105  {
106  const bool isVisible = item->isVisible();
107  mPrevVisibility[item] = isVisible;
108  if ( isVisible )
109  mItemsToIterate.append( item );
110  if ( QgsLayoutItem *layoutItem = dynamic_cast< QgsLayoutItem * >( item ) )
111  layoutItem->setProperty( "wasVisible", isVisible );
112 
113  item->hide();
114  }
115  }
116 
117  void hideAll()
118  {
119  for ( auto it = mPrevVisibility.constBegin(); it != mPrevVisibility.constEnd(); ++it )
120  {
121  it.key()->hide();
122  }
123  }
124 
125  ~LayoutItemHider()
126  {
127  for ( auto it = mPrevVisibility.constBegin(); it != mPrevVisibility.constEnd(); ++it )
128  {
129  it.key()->setVisible( it.value() );
130  if ( QgsLayoutItem *layoutItem = dynamic_cast< QgsLayoutItem * >( it.key() ) )
131  layoutItem->setProperty( "wasVisible", QVariant() );
132  }
133  }
134 
135  QList< QGraphicsItem * > itemsToIterate() const { return mItemsToIterate; }
136 
137  LayoutItemHider( const LayoutItemHider &other ) = delete;
138  LayoutItemHider &operator=( const LayoutItemHider &other ) = delete;
139 
140  private:
141 
142  QList<QGraphicsItem * > mItemsToIterate;
143  QHash<QGraphicsItem *, bool> mPrevVisibility;
144 };
145 
147 
149  : mLayout( layout )
150 {
151 
152 }
153 
155 {
156  qDeleteAll( mLabelingResults );
157 }
158 
160 {
161  return mLayout;
162 }
163 
164 void QgsLayoutExporter::renderPage( QPainter *painter, int page ) const
165 {
166  if ( !mLayout )
167  return;
168 
169  if ( mLayout->pageCollection()->pageCount() <= page || page < 0 )
170  {
171  return;
172  }
173 
174  QgsLayoutItemPage *pageItem = mLayout->pageCollection()->page( page );
175  if ( !pageItem )
176  {
177  return;
178  }
179 
180  LayoutContextPreviewSettingRestorer restorer( mLayout );
181  ( void )restorer;
182 
183  QRectF paperRect = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() );
184  renderRegion( painter, paperRect );
185 }
186 
187 QImage QgsLayoutExporter::renderPageToImage( int page, QSize imageSize, double dpi ) const
188 {
189  if ( !mLayout )
190  return QImage();
191 
192  if ( mLayout->pageCollection()->pageCount() <= page || page < 0 )
193  {
194  return QImage();
195  }
196 
197  QgsLayoutItemPage *pageItem = mLayout->pageCollection()->page( page );
198  if ( !pageItem )
199  {
200  return QImage();
201  }
202 
203  LayoutContextPreviewSettingRestorer restorer( mLayout );
204  ( void )restorer;
205 
206  QRectF paperRect = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() );
207 
208  const double imageAspectRatio = static_cast< double >( imageSize.width() ) / imageSize.height();
209  const double paperAspectRatio = paperRect.width() / paperRect.height();
210  if ( imageSize.isValid() && ( !qgsDoubleNear( imageAspectRatio, paperAspectRatio, 0.008 ) ) )
211  {
212  // specified image size is wrong aspect ratio for paper rect - so ignore it and just use dpi
213  // this can happen e.g. as a result of data defined page sizes
214  // see https://github.com/qgis/QGIS/issues/26422
215  QgsMessageLog::logMessage( QObject::tr( "Ignoring custom image size because aspect ratio %1 does not match paper ratio %2" ).arg( QString::number( imageAspectRatio, 'g', 3 ), QString::number( paperAspectRatio, 'g', 3 ) ), QStringLiteral( "Layout" ), Qgis::MessageLevel::Warning );
216  imageSize = QSize();
217  }
218 
219  return renderRegionToImage( paperRect, imageSize, dpi );
220 }
221 
223 class LayoutItemCacheSettingRestorer
224 {
225  public:
226 
227  LayoutItemCacheSettingRestorer( QgsLayout *layout )
228  : mLayout( layout )
229  {
230  const QList< QGraphicsItem * > items = mLayout->items();
231  for ( QGraphicsItem *item : items )
232  {
233  mPrevCacheMode.insert( item, item->cacheMode() );
234  item->setCacheMode( QGraphicsItem::NoCache );
235  }
236  }
237 
238  ~LayoutItemCacheSettingRestorer()
239  {
240  for ( auto it = mPrevCacheMode.constBegin(); it != mPrevCacheMode.constEnd(); ++it )
241  {
242  it.key()->setCacheMode( it.value() );
243  }
244  }
245 
246  LayoutItemCacheSettingRestorer( const LayoutItemCacheSettingRestorer &other ) = delete;
247  LayoutItemCacheSettingRestorer &operator=( const LayoutItemCacheSettingRestorer &other ) = delete;
248 
249  private:
250  QgsLayout *mLayout = nullptr;
251  QHash< QGraphicsItem *, QGraphicsItem::CacheMode > mPrevCacheMode;
252 };
253 
255 
256 void QgsLayoutExporter::renderRegion( QPainter *painter, const QRectF &region ) const
257 {
258  QPaintDevice *paintDevice = painter->device();
259  if ( !paintDevice || !mLayout )
260  {
261  return;
262  }
263 
264  LayoutItemCacheSettingRestorer cacheRestorer( mLayout );
265  ( void )cacheRestorer;
266  LayoutContextPreviewSettingRestorer restorer( mLayout );
267  ( void )restorer;
268  LayoutGuideHider guideHider( mLayout );
269  ( void ) guideHider;
270 
271  painter->setRenderHint( QPainter::Antialiasing, mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagAntialiasing );
272 
273  mLayout->render( painter, QRectF( 0, 0, paintDevice->width(), paintDevice->height() ), region );
274 }
275 
276 QImage QgsLayoutExporter::renderRegionToImage( const QRectF &region, QSize imageSize, double dpi ) const
277 {
278  if ( !mLayout )
279  return QImage();
280 
281  LayoutContextPreviewSettingRestorer restorer( mLayout );
282  ( void )restorer;
283 
284  double resolution = mLayout->renderContext().dpi();
285  double oneInchInLayoutUnits = mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 1, QgsUnitTypes::LayoutInches ) );
286  if ( imageSize.isValid() )
287  {
288  //output size in pixels specified, calculate resolution using average of
289  //derived x/y dpi
290  resolution = ( imageSize.width() / region.width()
291  + imageSize.height() / region.height() ) / 2.0 * oneInchInLayoutUnits;
292  }
293  else if ( dpi > 0 )
294  {
295  //dpi overridden by function parameters
296  resolution = dpi;
297  }
298 
299  int width = imageSize.isValid() ? imageSize.width()
300  : static_cast< int >( resolution * region.width() / oneInchInLayoutUnits );
301  int height = imageSize.isValid() ? imageSize.height()
302  : static_cast< int >( resolution * region.height() / oneInchInLayoutUnits );
303 
304  QImage image( QSize( width, height ), QImage::Format_ARGB32 );
305  if ( !image.isNull() )
306  {
307  // see https://doc.qt.io/qt-5/qpainter.html#limitations
308  if ( width > 32768 || height > 32768 )
309  QgsMessageLog::logMessage( QObject::tr( "Error: output width or height is larger than 32768 pixel, result will be clipped" ) );
310  image.setDotsPerMeterX( static_cast< int >( std::round( resolution / 25.4 * 1000 ) ) );
311  image.setDotsPerMeterY( static_cast< int>( std::round( resolution / 25.4 * 1000 ) ) );
312  image.fill( Qt::transparent );
313  QPainter imagePainter( &image );
314  renderRegion( &imagePainter, region );
315  if ( !imagePainter.isActive() )
316  return QImage();
317  }
318 
319  return image;
320 }
321 
323 class LayoutContextSettingsRestorer
324 {
325  public:
326 
328  LayoutContextSettingsRestorer( QgsLayout *layout )
329  : mLayout( layout )
330  , mPreviousDpi( layout->renderContext().dpi() )
331  , mPreviousFlags( layout->renderContext().flags() )
332  , mPreviousTextFormat( layout->renderContext().textRenderFormat() )
333  , mPreviousExportLayer( layout->renderContext().currentExportLayer() )
334  , mPreviousSimplifyMethod( layout->renderContext().simplifyMethod() )
335  , mExportThemes( layout->renderContext().exportThemes() )
336  , mPredefinedScales( layout->renderContext().predefinedScales() )
337  {
338  }
340 
341  ~LayoutContextSettingsRestorer()
342  {
343  mLayout->renderContext().setDpi( mPreviousDpi );
344  mLayout->renderContext().setFlags( mPreviousFlags );
345  mLayout->renderContext().setTextRenderFormat( mPreviousTextFormat );
347  mLayout->renderContext().setCurrentExportLayer( mPreviousExportLayer );
349  mLayout->renderContext().setSimplifyMethod( mPreviousSimplifyMethod );
350  mLayout->renderContext().setExportThemes( mExportThemes );
351  mLayout->renderContext().setPredefinedScales( mPredefinedScales );
352  }
353 
354  LayoutContextSettingsRestorer( const LayoutContextSettingsRestorer &other ) = delete;
355  LayoutContextSettingsRestorer &operator=( const LayoutContextSettingsRestorer &other ) = delete;
356 
357  private:
358  QgsLayout *mLayout = nullptr;
359  double mPreviousDpi = 0;
360  QgsLayoutRenderContext::Flags mPreviousFlags = QgsLayoutRenderContext::Flags();
361  Qgis::TextRenderFormat mPreviousTextFormat = Qgis::TextRenderFormat::AlwaysOutlines;
362  int mPreviousExportLayer = 0;
363  QgsVectorSimplifyMethod mPreviousSimplifyMethod;
364  QStringList mExportThemes;
365  QVector< double > mPredefinedScales;
366 
367 };
369 
371 {
372  if ( !mLayout )
373  return PrintError;
374 
375  ImageExportSettings settings = s;
376  if ( settings.dpi <= 0 )
377  settings.dpi = mLayout->renderContext().dpi();
378 
379  mErrorFileName.clear();
380 
381  int worldFilePageNo = -1;
382  if ( QgsLayoutItemMap *referenceMap = mLayout->referenceMap() )
383  {
384  worldFilePageNo = referenceMap->page();
385  }
386 
387  QFileInfo fi( filePath );
388 
389  PageExportDetails pageDetails;
390  pageDetails.directory = fi.path();
391  pageDetails.baseName = fi.completeBaseName();
392  pageDetails.extension = fi.suffix();
393 
394  LayoutContextPreviewSettingRestorer restorer( mLayout );
395  ( void )restorer;
396  LayoutContextSettingsRestorer dpiRestorer( mLayout );
397  ( void )dpiRestorer;
398  mLayout->renderContext().setDpi( settings.dpi );
399  mLayout->renderContext().setFlags( settings.flags );
400  mLayout->renderContext().setPredefinedScales( settings.predefinedMapScales );
401 
402  QList< int > pages;
403  if ( settings.pages.empty() )
404  {
405  for ( int page = 0; page < mLayout->pageCollection()->pageCount(); ++page )
406  pages << page;
407  }
408  else
409  {
410  for ( int page : std::as_const( settings.pages ) )
411  {
412  if ( page >= 0 && page < mLayout->pageCollection()->pageCount() )
413  pages << page;
414  }
415  }
416 
417  for ( int page : std::as_const( pages ) )
418  {
419  if ( !mLayout->pageCollection()->shouldExportPage( page ) )
420  {
421  continue;
422  }
423 
424  bool skip = false;
425  QRectF bounds;
426  QImage image = createImage( settings, page, bounds, skip );
427 
428  if ( skip )
429  continue; // should skip this page, e.g. null size
430 
431  pageDetails.page = page;
432  QString outputFilePath = generateFileName( pageDetails );
433 
434  if ( image.isNull() )
435  {
436  mErrorFileName = outputFilePath;
437  return MemoryError;
438  }
439 
440  if ( !saveImage( image, outputFilePath, pageDetails.extension, settings.exportMetadata ? mLayout->project() : nullptr ) )
441  {
442  mErrorFileName = outputFilePath;
443  return FileError;
444  }
445 
446  const bool shouldGeoreference = ( page == worldFilePageNo );
447  if ( shouldGeoreference )
448  {
449  georeferenceOutputPrivate( outputFilePath, nullptr, bounds, settings.dpi, shouldGeoreference );
450 
451  if ( settings.generateWorldFile )
452  {
453  // should generate world file for this page
454  double a, b, c, d, e, f;
455  if ( bounds.isValid() )
456  computeWorldFileParameters( bounds, a, b, c, d, e, f, settings.dpi );
457  else
458  computeWorldFileParameters( a, b, c, d, e, f, settings.dpi );
459 
460  QFileInfo fi( outputFilePath );
461  // build the world file name
462  QString outputSuffix = fi.suffix();
463  QString worldFileName = fi.absolutePath() + '/' + fi.completeBaseName() + '.'
464  + outputSuffix.at( 0 ) + outputSuffix.at( fi.suffix().size() - 1 ) + 'w';
465 
466  writeWorldFile( worldFileName, a, b, c, d, e, f );
467  }
468  }
469 
470  }
471  captureLabelingResults();
472  return Success;
473 }
474 
475 QgsLayoutExporter::ExportResult QgsLayoutExporter::exportToImage( QgsAbstractLayoutIterator *iterator, const QString &baseFilePath, const QString &extension, const QgsLayoutExporter::ImageExportSettings &settings, QString &error, QgsFeedback *feedback )
476 {
477  error.clear();
478 
479  if ( !iterator->beginRender() )
480  return IteratorError;
481 
482  int total = iterator->count();
483  double step = total > 0 ? 100.0 / total : 100.0;
484  int i = 0;
485  while ( iterator->next() )
486  {
487  if ( feedback )
488  {
489  if ( total > 0 )
490  feedback->setProperty( "progress", QObject::tr( "Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
491  else
492  feedback->setProperty( "progress", QObject::tr( "Exporting section %1" ).arg( i + 1 ).arg( total ) );
493  feedback->setProgress( step * i );
494  }
495  if ( feedback && feedback->isCanceled() )
496  {
497  iterator->endRender();
498  return Canceled;
499  }
500 
501  QgsLayoutExporter exporter( iterator->layout() );
502  QString filePath = iterator->filePath( baseFilePath, extension );
503  ExportResult result = exporter.exportToImage( filePath, settings );
504  if ( result != Success )
505  {
506  if ( result == FileError )
507  error = QObject::tr( "Cannot write to %1. This file may be open in another application or may be an invalid path." ).arg( QDir::toNativeSeparators( filePath ) );
508  iterator->endRender();
509  return result;
510  }
511  i++;
512  }
513 
514  if ( feedback )
515  {
516  feedback->setProgress( 100 );
517  }
518 
519  iterator->endRender();
520  return Success;
521 }
522 
524 {
525  if ( !mLayout || mLayout->pageCollection()->pageCount() == 0 )
526  return PrintError;
527 
528  PdfExportSettings settings = s;
529  if ( settings.dpi <= 0 )
530  settings.dpi = mLayout->renderContext().dpi();
531 
532  mErrorFileName.clear();
533 
534  LayoutContextPreviewSettingRestorer restorer( mLayout );
535  ( void )restorer;
536  LayoutContextSettingsRestorer contextRestorer( mLayout );
537  ( void )contextRestorer;
538  mLayout->renderContext().setDpi( settings.dpi );
539  mLayout->renderContext().setPredefinedScales( settings.predefinedMapScales );
540 
541  if ( settings.simplifyGeometries )
542  {
543  mLayout->renderContext().setSimplifyMethod( createExportSimplifyMethod() );
544  }
545 
546  std::unique_ptr< QgsLayoutGeoPdfExporter > geoPdfExporter;
547  if ( settings.writeGeoPdf || settings.exportLayersAsSeperateFiles ) //#spellok
548  geoPdfExporter = std::make_unique< QgsLayoutGeoPdfExporter >( mLayout );
549 
550  mLayout->renderContext().setFlags( settings.flags );
551 
552  // If we are not printing as raster, temporarily disable advanced effects
553  // as QPrinter does not support composition modes and can result
554  // in items missing from the output
555  mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagUseAdvancedEffects, !settings.forceVectorOutput );
556  mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagForceVectorOutput, settings.forceVectorOutput );
557  mLayout->renderContext().setTextRenderFormat( settings.textRenderFormat );
558  mLayout->renderContext().setExportThemes( settings.exportThemes );
559 
560  ExportResult result = Success;
561  if ( settings.writeGeoPdf || settings.exportLayersAsSeperateFiles ) //#spellok
562  {
563  mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagRenderLabelsByMapLayer, true );
564 
565  // here we need to export layers to individual PDFs
566  PdfExportSettings subSettings = settings;
567  subSettings.writeGeoPdf = false;
568  subSettings.exportLayersAsSeperateFiles = false; //#spellok
569 
570  const QList<QGraphicsItem *> items = mLayout->items( Qt::AscendingOrder );
571 
572  QList< QgsLayoutGeoPdfExporter::ComponentLayerDetail > pdfComponents;
573 
574  const QDir baseDir = settings.exportLayersAsSeperateFiles ? QFileInfo( filePath ).dir() : QDir(); //#spellok
575  const QString baseFileName = settings.exportLayersAsSeperateFiles ? QFileInfo( filePath ).completeBaseName() : QString(); //#spellok
576 
577  auto exportFunc = [this, &subSettings, &pdfComponents, &geoPdfExporter, &settings, &baseDir, &baseFileName]( unsigned int layerId, const QgsLayoutItem::ExportLayerDetail & layerDetail )->QgsLayoutExporter::ExportResult
578  {
579  ExportResult layerExportResult = Success;
580  QPrinter printer;
582  component.name = layerDetail.name;
583  component.mapLayerId = layerDetail.mapLayerId;
584  component.opacity = layerDetail.opacity;
585  component.compositionMode = layerDetail.compositionMode;
586  component.group = layerDetail.mapTheme;
587  component.sourcePdfPath = settings.writeGeoPdf ? geoPdfExporter->generateTemporaryFilepath( QStringLiteral( "layer_%1.pdf" ).arg( layerId ) ) : baseDir.filePath( QStringLiteral( "%1_%2.pdf" ).arg( baseFileName ).arg( layerId, 4, 10, QChar( '0' ) ) );
588  pdfComponents << component;
589  preparePrintAsPdf( mLayout, printer, component.sourcePdfPath );
590  preparePrint( mLayout, printer, false );
591  QPainter p;
592  if ( !p.begin( &printer ) )
593  {
594  //error beginning print
595  return FileError;
596  }
597 
598  layerExportResult = printPrivate( printer, p, false, subSettings.dpi, subSettings.rasterizeWholeImage );
599  p.end();
600  return layerExportResult;
601  };
602  result = handleLayeredExport( items, exportFunc );
603  if ( result != Success )
604  return result;
605 
606  if ( settings.writeGeoPdf )
607  {
609  details.dpi = settings.dpi;
610  // TODO - multipages
611  QgsLayoutSize pageSize = mLayout->pageCollection()->page( 0 )->sizeWithUnits();
612  QgsLayoutSize pageSizeMM = mLayout->renderContext().measurementConverter().convert( pageSize, QgsUnitTypes::LayoutMillimeters );
613  details.pageSizeMm = pageSizeMM.toQSizeF();
614 
615  if ( settings.exportMetadata )
616  {
617  // copy layout metadata to GeoPDF export settings
618  details.author = mLayout->project()->metadata().author();
619  details.producer = QStringLiteral( "QGIS %1" ).arg( Qgis::version() );
620  details.creator = QStringLiteral( "QGIS %1" ).arg( Qgis::version() );
621  details.creationDateTime = mLayout->project()->metadata().creationDateTime();
622  details.subject = mLayout->project()->metadata().abstract();
623  details.title = mLayout->project()->metadata().title();
624  details.keywords = mLayout->project()->metadata().keywords();
625  }
626 
627  const QList< QgsMapLayer * > layers = mLayout->project()->mapLayers().values();
628  for ( const QgsMapLayer *layer : layers )
629  {
630  details.layerIdToPdfLayerTreeNameMap.insert( layer->id(), layer->name() );
631  }
632 
633  if ( settings.appendGeoreference )
634  {
635  // setup georeferencing
636  QList< QgsLayoutItemMap * > maps;
637  mLayout->layoutItems( maps );
638  for ( QgsLayoutItemMap *map : std::as_const( maps ) )
639  {
641  georef.crs = map->crs();
642 
643  const QPointF topLeft = map->mapToScene( QPointF( 0, 0 ) );
644  const QPointF topRight = map->mapToScene( QPointF( map->rect().width(), 0 ) );
645  const QPointF bottomLeft = map->mapToScene( QPointF( 0, map->rect().height() ) );
646  const QPointF bottomRight = map->mapToScene( QPointF( map->rect().width(), map->rect().height() ) );
647  const QgsLayoutPoint topLeftMm = mLayout->convertFromLayoutUnits( topLeft, QgsUnitTypes::LayoutMillimeters );
648  const QgsLayoutPoint topRightMm = mLayout->convertFromLayoutUnits( topRight, QgsUnitTypes::LayoutMillimeters );
649  const QgsLayoutPoint bottomLeftMm = mLayout->convertFromLayoutUnits( bottomLeft, QgsUnitTypes::LayoutMillimeters );
650  const QgsLayoutPoint bottomRightMm = mLayout->convertFromLayoutUnits( bottomRight, QgsUnitTypes::LayoutMillimeters );
651 
652  georef.pageBoundsPolygon.setExteriorRing( new QgsLineString( QVector< QgsPointXY >() << QgsPointXY( topLeftMm.x(), topLeftMm.y() )
653  << QgsPointXY( topRightMm.x(), topRightMm.y() )
654  << QgsPointXY( bottomRightMm.x(), bottomRightMm.y() )
655  << QgsPointXY( bottomLeftMm.x(), bottomLeftMm.y() )
656  << QgsPointXY( topLeftMm.x(), topLeftMm.y() ) ) );
657 
658  georef.controlPoints.reserve( 4 );
659  const QTransform t = map->layoutToMapCoordsTransform();
660  const QgsPointXY topLeftMap = t.map( topLeft );
661  const QgsPointXY topRightMap = t.map( topRight );
662  const QgsPointXY bottomLeftMap = t.map( bottomLeft );
663  const QgsPointXY bottomRightMap = t.map( bottomRight );
664 
665  georef.controlPoints << QgsAbstractGeoPdfExporter::ControlPoint( QgsPointXY( topLeftMm.x(), topLeftMm.y() ), topLeftMap );
666  georef.controlPoints << QgsAbstractGeoPdfExporter::ControlPoint( QgsPointXY( topRightMm.x(), topRightMm.y() ), topRightMap );
667  georef.controlPoints << QgsAbstractGeoPdfExporter::ControlPoint( QgsPointXY( bottomLeftMm.x(), bottomLeftMm.y() ), bottomLeftMap );
668  georef.controlPoints << QgsAbstractGeoPdfExporter::ControlPoint( QgsPointXY( bottomRightMm.x(), bottomRightMm.y() ), bottomRightMap );
669  details.georeferencedSections << georef;
670  }
671  }
672 
673  details.customLayerTreeGroups = geoPdfExporter->customLayerTreeGroups();
674  details.initialLayerVisibility = geoPdfExporter->initialLayerVisibility();
675  details.layerOrder = geoPdfExporter->layerOrder();
676  details.includeFeatures = settings.includeGeoPdfFeatures;
677  details.useOgcBestPracticeFormatGeoreferencing = settings.useOgcBestPracticeFormatGeoreferencing;
678  details.useIso32000ExtensionFormatGeoreferencing = settings.useIso32000ExtensionFormatGeoreferencing;
679 
680  if ( !geoPdfExporter->finalize( pdfComponents, filePath, details ) )
681  result = PrintError;
682  }
683  else
684  {
685  result = Success;
686  }
687  }
688  else
689  {
690  QPrinter printer;
691  preparePrintAsPdf( mLayout, printer, filePath );
692  preparePrint( mLayout, printer, false );
693  QPainter p;
694  if ( !p.begin( &printer ) )
695  {
696  //error beginning print
697  return FileError;
698  }
699 
700  result = printPrivate( printer, p, false, settings.dpi, settings.rasterizeWholeImage );
701  p.end();
702 
703  bool shouldAppendGeoreference = settings.appendGeoreference && mLayout && mLayout->referenceMap() && mLayout->referenceMap()->page() == 0;
704  if ( settings.appendGeoreference || settings.exportMetadata )
705  {
706  georeferenceOutputPrivate( filePath, nullptr, QRectF(), settings.dpi, shouldAppendGeoreference, settings.exportMetadata );
707  }
708  }
709  captureLabelingResults();
710  return result;
711 }
712 
714 {
715  error.clear();
716 
717  if ( !iterator->beginRender() )
718  return IteratorError;
719 
720  PdfExportSettings settings = s;
721 
722  QPrinter printer;
723  QPainter p;
724 
725  int total = iterator->count();
726  double step = total > 0 ? 100.0 / total : 100.0;
727  int i = 0;
728  bool first = true;
729  while ( iterator->next() )
730  {
731  if ( feedback )
732  {
733  if ( total > 0 )
734  feedback->setProperty( "progress", QObject::tr( "Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
735  else
736  feedback->setProperty( "progress", QObject::tr( "Exporting section %1" ).arg( i + 1 ) );
737  feedback->setProgress( step * i );
738  }
739  if ( feedback && feedback->isCanceled() )
740  {
741  iterator->endRender();
742  return Canceled;
743  }
744 
745  if ( s.dpi <= 0 )
746  settings.dpi = iterator->layout()->renderContext().dpi();
747 
748  LayoutContextPreviewSettingRestorer restorer( iterator->layout() );
749  ( void )restorer;
750  LayoutContextSettingsRestorer contextRestorer( iterator->layout() );
751  ( void )contextRestorer;
752  iterator->layout()->renderContext().setDpi( settings.dpi );
753 
754  iterator->layout()->renderContext().setFlags( settings.flags );
755  iterator->layout()->renderContext().setPredefinedScales( settings.predefinedMapScales );
756 
757  if ( settings.simplifyGeometries )
758  {
759  iterator->layout()->renderContext().setSimplifyMethod( createExportSimplifyMethod() );
760  }
761 
762  // If we are not printing as raster, temporarily disable advanced effects
763  // as QPrinter does not support composition modes and can result
764  // in items missing from the output
766 
768 
769  iterator->layout()->renderContext().setTextRenderFormat( settings.textRenderFormat );
770 
771  if ( first )
772  {
773  preparePrintAsPdf( iterator->layout(), printer, fileName );
774  preparePrint( iterator->layout(), printer, false );
775 
776  if ( !p.begin( &printer ) )
777  {
778  //error beginning print
779  return PrintError;
780  }
781  }
782 
783  QgsLayoutExporter exporter( iterator->layout() );
784 
785  ExportResult result = exporter.printPrivate( printer, p, !first, settings.dpi, settings.rasterizeWholeImage );
786  if ( result != Success )
787  {
788  if ( result == FileError )
789  error = QObject::tr( "Cannot write to %1. This file may be open in another application or may be an invalid path." ).arg( QDir::toNativeSeparators( fileName ) );
790  iterator->endRender();
791  return result;
792  }
793  first = false;
794  i++;
795  }
796 
797  if ( feedback )
798  {
799  feedback->setProgress( 100 );
800  }
801 
802  iterator->endRender();
803  return Success;
804 }
805 
806 QgsLayoutExporter::ExportResult QgsLayoutExporter::exportToPdfs( QgsAbstractLayoutIterator *iterator, const QString &baseFilePath, const QgsLayoutExporter::PdfExportSettings &settings, QString &error, QgsFeedback *feedback )
807 {
808  error.clear();
809 
810  if ( !iterator->beginRender() )
811  return IteratorError;
812 
813  int total = iterator->count();
814  double step = total > 0 ? 100.0 / total : 100.0;
815  int i = 0;
816  while ( iterator->next() )
817  {
818  if ( feedback )
819  {
820  if ( total > 0 )
821  feedback->setProperty( "progress", QObject::tr( "Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
822  else
823  feedback->setProperty( "progress", QObject::tr( "Exporting section %1" ).arg( i + 1 ).arg( total ) );
824  feedback->setProgress( step * i );
825  }
826  if ( feedback && feedback->isCanceled() )
827  {
828  iterator->endRender();
829  return Canceled;
830  }
831 
832  QString filePath = iterator->filePath( baseFilePath, QStringLiteral( "pdf" ) );
833 
834  QgsLayoutExporter exporter( iterator->layout() );
835  ExportResult result = exporter.exportToPdf( filePath, settings );
836  if ( result != Success )
837  {
838  if ( result == FileError )
839  error = QObject::tr( "Cannot write to %1. This file may be open in another application or may be an invalid path." ).arg( QDir::toNativeSeparators( filePath ) );
840  iterator->endRender();
841  return result;
842  }
843  i++;
844  }
845 
846  if ( feedback )
847  {
848  feedback->setProgress( 100 );
849  }
850 
851  iterator->endRender();
852  return Success;
853 }
854 
856 {
857  if ( !mLayout )
858  return PrintError;
859 
861  if ( settings.dpi <= 0 )
862  settings.dpi = mLayout->renderContext().dpi();
863 
864  mErrorFileName.clear();
865 
866  LayoutContextPreviewSettingRestorer restorer( mLayout );
867  ( void )restorer;
868  LayoutContextSettingsRestorer contextRestorer( mLayout );
869  ( void )contextRestorer;
870  mLayout->renderContext().setDpi( settings.dpi );
871 
872  mLayout->renderContext().setFlags( settings.flags );
873  mLayout->renderContext().setPredefinedScales( settings.predefinedMapScales );
874  // If we are not printing as raster, temporarily disable advanced effects
875  // as QPrinter does not support composition modes and can result
876  // in items missing from the output
877  mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagUseAdvancedEffects, !settings.rasterizeWholeImage );
878 
879  preparePrint( mLayout, printer, true );
880  QPainter p;
881  if ( !p.begin( &printer ) )
882  {
883  //error beginning print
884  return PrintError;
885  }
886 
887  ExportResult result = printPrivate( printer, p, false, settings.dpi, settings.rasterizeWholeImage );
888  p.end();
889 
890  captureLabelingResults();
891  return result;
892 }
893 
895 {
896  error.clear();
897 
898  if ( !iterator->beginRender() )
899  return IteratorError;
900 
901  PrintExportSettings settings = s;
902 
903  QPainter p;
904 
905  int total = iterator->count();
906  double step = total > 0 ? 100.0 / total : 100.0;
907  int i = 0;
908  bool first = true;
909  while ( iterator->next() )
910  {
911  if ( feedback )
912  {
913  if ( total > 0 )
914  feedback->setProperty( "progress", QObject::tr( "Printing %1 of %2" ).arg( i + 1 ).arg( total ) );
915  else
916  feedback->setProperty( "progress", QObject::tr( "Printing section %1" ).arg( i + 1 ).arg( total ) );
917  feedback->setProgress( step * i );
918  }
919  if ( feedback && feedback->isCanceled() )
920  {
921  iterator->endRender();
922  return Canceled;
923  }
924 
925  if ( s.dpi <= 0 )
926  settings.dpi = iterator->layout()->renderContext().dpi();
927 
928  LayoutContextPreviewSettingRestorer restorer( iterator->layout() );
929  ( void )restorer;
930  LayoutContextSettingsRestorer contextRestorer( iterator->layout() );
931  ( void )contextRestorer;
932  iterator->layout()->renderContext().setDpi( settings.dpi );
933 
934  iterator->layout()->renderContext().setFlags( settings.flags );
935  iterator->layout()->renderContext().setPredefinedScales( settings.predefinedMapScales );
936 
937  // If we are not printing as raster, temporarily disable advanced effects
938  // as QPrinter does not support composition modes and can result
939  // in items missing from the output
941 
942  if ( first )
943  {
944  preparePrint( iterator->layout(), printer, true );
945 
946  if ( !p.begin( &printer ) )
947  {
948  //error beginning print
949  return PrintError;
950  }
951  }
952 
953  QgsLayoutExporter exporter( iterator->layout() );
954 
955  ExportResult result = exporter.printPrivate( printer, p, !first, settings.dpi, settings.rasterizeWholeImage );
956  if ( result != Success )
957  {
958  iterator->endRender();
959  return result;
960  }
961  first = false;
962  i++;
963  }
964 
965  if ( feedback )
966  {
967  feedback->setProgress( 100 );
968  }
969 
970  iterator->endRender();
971  return Success;
972 }
973 
975 {
976  if ( !mLayout )
977  return PrintError;
978 
979  SvgExportSettings settings = s;
980  if ( settings.dpi <= 0 )
981  settings.dpi = mLayout->renderContext().dpi();
982 
983  mErrorFileName.clear();
984 
985  LayoutContextPreviewSettingRestorer restorer( mLayout );
986  ( void )restorer;
987  LayoutContextSettingsRestorer contextRestorer( mLayout );
988  ( void )contextRestorer;
989  mLayout->renderContext().setDpi( settings.dpi );
990 
991  mLayout->renderContext().setFlags( settings.flags );
992  mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagForceVectorOutput, settings.forceVectorOutput );
993  mLayout->renderContext().setTextRenderFormat( s.textRenderFormat );
994  mLayout->renderContext().setPredefinedScales( settings.predefinedMapScales );
995 
996  if ( settings.simplifyGeometries )
997  {
998  mLayout->renderContext().setSimplifyMethod( createExportSimplifyMethod() );
999  }
1000 
1001  QFileInfo fi( filePath );
1002  PageExportDetails pageDetails;
1003  pageDetails.directory = fi.path();
1004  pageDetails.baseName = fi.baseName();
1005  pageDetails.extension = fi.completeSuffix();
1006 
1007  double inchesToLayoutUnits = mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 1, QgsUnitTypes::LayoutInches ) );
1008 
1009  for ( int i = 0; i < mLayout->pageCollection()->pageCount(); ++i )
1010  {
1011  if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1012  {
1013  continue;
1014  }
1015 
1016  pageDetails.page = i;
1017  QString fileName = generateFileName( pageDetails );
1018 
1019  QgsLayoutItemPage *pageItem = mLayout->pageCollection()->page( i );
1020  QRectF bounds;
1021  if ( settings.cropToContents )
1022  {
1023  if ( mLayout->pageCollection()->pageCount() == 1 )
1024  {
1025  // single page, so include everything
1026  bounds = mLayout->layoutBounds( true );
1027  }
1028  else
1029  {
1030  // multi page, so just clip to items on current page
1031  bounds = mLayout->pageItemBounds( i, true );
1032  }
1033  bounds = bounds.adjusted( -settings.cropMargins.left(),
1034  -settings.cropMargins.top(),
1035  settings.cropMargins.right(),
1036  settings.cropMargins.bottom() );
1037  }
1038  else
1039  {
1040  bounds = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() );
1041  }
1042 
1043  //width in pixel
1044  int width = static_cast< int >( bounds.width() * settings.dpi / inchesToLayoutUnits );
1045  //height in pixel
1046  int height = static_cast< int >( bounds.height() * settings.dpi / inchesToLayoutUnits );
1047  if ( width == 0 || height == 0 )
1048  {
1049  //invalid size, skip this page
1050  continue;
1051  }
1052 
1053  if ( settings.exportAsLayers )
1054  {
1055  mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagRenderLabelsByMapLayer, settings.exportLabelsToSeparateLayers );
1056  const QRectF paperRect = QRectF( pageItem->pos().x(),
1057  pageItem->pos().y(),
1058  pageItem->rect().width(),
1059  pageItem->rect().height() );
1060  QDomDocument svg;
1061  QDomNode svgDocRoot;
1062  const QList<QGraphicsItem *> items = mLayout->items( paperRect,
1063  Qt::IntersectsItemBoundingRect,
1064  Qt::AscendingOrder );
1065 
1066  auto exportFunc = [this, &settings, width, height, i, bounds, fileName, &svg, &svgDocRoot]( unsigned int layerId, const QgsLayoutItem::ExportLayerDetail & layerDetail )->QgsLayoutExporter::ExportResult
1067  {
1068  return renderToLayeredSvg( settings, width, height, i, bounds, fileName, layerId, layerDetail.name, svg, svgDocRoot, settings.exportMetadata );
1069  };
1070  ExportResult res = handleLayeredExport( items, exportFunc );
1071  if ( res != Success )
1072  return res;
1073 
1074  if ( settings.exportMetadata )
1075  appendMetadataToSvg( svg );
1076 
1077  QFile out( fileName );
1078  bool openOk = out.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate );
1079  if ( !openOk )
1080  {
1081  mErrorFileName = fileName;
1082  return FileError;
1083  }
1084 
1085  out.write( svg.toByteArray() );
1086  }
1087  else
1088  {
1089  QBuffer svgBuffer;
1090  {
1091  QSvgGenerator generator;
1092  if ( settings.exportMetadata )
1093  {
1094  generator.setTitle( mLayout->project()->metadata().title() );
1095  generator.setDescription( mLayout->project()->metadata().abstract() );
1096  }
1097  generator.setOutputDevice( &svgBuffer );
1098  generator.setSize( QSize( width, height ) );
1099  generator.setViewBox( QRect( 0, 0, width, height ) );
1100  generator.setResolution( static_cast< int >( std::round( settings.dpi ) ) );
1101 
1102  QPainter p;
1103  bool createOk = p.begin( &generator );
1104  if ( !createOk )
1105  {
1106  mErrorFileName = fileName;
1107  return FileError;
1108  }
1109 
1110  if ( settings.cropToContents )
1111  renderRegion( &p, bounds );
1112  else
1113  renderPage( &p, i );
1114 
1115  p.end();
1116  }
1117  {
1118  svgBuffer.close();
1119  svgBuffer.open( QIODevice::ReadOnly );
1120  QDomDocument svg;
1121  QString errorMsg;
1122  int errorLine;
1123  if ( ! svg.setContent( &svgBuffer, false, &errorMsg, &errorLine ) )
1124  {
1125  mErrorFileName = fileName;
1126  return SvgLayerError;
1127  }
1128 
1129  if ( settings.exportMetadata )
1130  appendMetadataToSvg( svg );
1131 
1132  QFile out( fileName );
1133  bool openOk = out.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate );
1134  if ( !openOk )
1135  {
1136  mErrorFileName = fileName;
1137  return FileError;
1138  }
1139 
1140  out.write( svg.toByteArray() );
1141  }
1142  }
1143  }
1144  captureLabelingResults();
1145  return Success;
1146 }
1147 
1148 QgsLayoutExporter::ExportResult QgsLayoutExporter::exportToSvg( QgsAbstractLayoutIterator *iterator, const QString &baseFilePath, const QgsLayoutExporter::SvgExportSettings &settings, QString &error, QgsFeedback *feedback )
1149 {
1150  error.clear();
1151 
1152  if ( !iterator->beginRender() )
1153  return IteratorError;
1154 
1155  int total = iterator->count();
1156  double step = total > 0 ? 100.0 / total : 100.0;
1157  int i = 0;
1158  while ( iterator->next() )
1159  {
1160  if ( feedback )
1161  {
1162  if ( total > 0 )
1163  feedback->setProperty( "progress", QObject::tr( "Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
1164  else
1165  feedback->setProperty( "progress", QObject::tr( "Exporting section %1" ).arg( i + 1 ).arg( total ) );
1166 
1167  feedback->setProgress( step * i );
1168  }
1169  if ( feedback && feedback->isCanceled() )
1170  {
1171  iterator->endRender();
1172  return Canceled;
1173  }
1174 
1175  QString filePath = iterator->filePath( baseFilePath, QStringLiteral( "svg" ) );
1176 
1177  QgsLayoutExporter exporter( iterator->layout() );
1178  ExportResult result = exporter.exportToSvg( filePath, settings );
1179  if ( result != Success )
1180  {
1181  if ( result == FileError )
1182  error = QObject::tr( "Cannot write to %1. This file may be open in another application or may be an invalid path." ).arg( QDir::toNativeSeparators( filePath ) );
1183  iterator->endRender();
1184  return result;
1185  }
1186  i++;
1187  }
1188 
1189  if ( feedback )
1190  {
1191  feedback->setProgress( 100 );
1192  }
1193 
1194  iterator->endRender();
1195  return Success;
1196 
1197 }
1198 
1199 QMap<QString, QgsLabelingResults *> QgsLayoutExporter::labelingResults()
1200 {
1201  return mLabelingResults;
1202 }
1203 
1204 QMap<QString, QgsLabelingResults *> QgsLayoutExporter::takeLabelingResults()
1205 {
1206  QMap<QString, QgsLabelingResults *> res;
1207  std::swap( mLabelingResults, res );
1208  return res;
1209 }
1210 
1211 void QgsLayoutExporter::preparePrintAsPdf( QgsLayout *layout, QPrinter &printer, const QString &filePath )
1212 {
1213  printer.setOutputFileName( filePath );
1214  printer.setOutputFormat( QPrinter::PdfFormat );
1215 
1216  updatePrinterPageSize( layout, printer, firstPageToBeExported( layout ) );
1217 
1218  // TODO: add option for this in layout
1219  // May not work on Windows or non-X11 Linux. Works fine on Mac using QPrinter::NativeFormat
1220  //printer.setFontEmbeddingEnabled( true );
1221 
1222  QgsPaintEngineHack::fixEngineFlags( printer.paintEngine() );
1223 }
1224 
1225 void QgsLayoutExporter::preparePrint( QgsLayout *layout, QPrinter &printer, bool setFirstPageSize )
1226 {
1227  printer.setFullPage( true );
1228  printer.setColorMode( QPrinter::Color );
1229 
1230  //set user-defined resolution
1231  printer.setResolution( static_cast< int>( std::round( layout->renderContext().dpi() ) ) );
1232 
1233  if ( setFirstPageSize )
1234  {
1235  updatePrinterPageSize( layout, printer, firstPageToBeExported( layout ) );
1236  }
1237 }
1238 
1240 {
1241  if ( mLayout->pageCollection()->pageCount() == 0 )
1242  return PrintError;
1243 
1244  preparePrint( mLayout, printer, true );
1245  QPainter p;
1246  if ( !p.begin( &printer ) )
1247  {
1248  //error beginning print
1249  return PrintError;
1250  }
1251 
1252  printPrivate( printer, p );
1253  p.end();
1254  return Success;
1255 }
1256 
1257 QgsLayoutExporter::ExportResult QgsLayoutExporter::printPrivate( QPrinter &printer, QPainter &painter, bool startNewPage, double dpi, bool rasterize )
1258 {
1259  //layout starts page numbering at 0
1260  int fromPage = ( printer.fromPage() < 1 ) ? 0 : printer.fromPage() - 1;
1261  int toPage = ( printer.toPage() < 1 ) ? mLayout->pageCollection()->pageCount() - 1 : printer.toPage() - 1;
1262 
1263  bool pageExported = false;
1264  if ( rasterize )
1265  {
1266  for ( int i = fromPage; i <= toPage; ++i )
1267  {
1268  if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1269  {
1270  continue;
1271  }
1272 
1273  updatePrinterPageSize( mLayout, printer, i );
1274  if ( ( pageExported && i > fromPage ) || startNewPage )
1275  {
1276  printer.newPage();
1277  }
1278 
1279  QImage image = renderPageToImage( i, QSize(), dpi );
1280  if ( !image.isNull() )
1281  {
1282  QRectF targetArea( 0, 0, image.width(), image.height() );
1283  painter.drawImage( targetArea, image, targetArea );
1284  }
1285  else
1286  {
1287  return MemoryError;
1288  }
1289  pageExported = true;
1290  }
1291  }
1292  else
1293  {
1294  for ( int i = fromPage; i <= toPage; ++i )
1295  {
1296  if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1297  {
1298  continue;
1299  }
1300 
1301  updatePrinterPageSize( mLayout, printer, i );
1302 
1303  if ( ( pageExported && i > fromPage ) || startNewPage )
1304  {
1305  printer.newPage();
1306  }
1307  renderPage( &painter, i );
1308  pageExported = true;
1309  }
1310  }
1311  return Success;
1312 }
1313 
1314 void QgsLayoutExporter::updatePrinterPageSize( QgsLayout *layout, QPrinter &printer, int page )
1315 {
1316  QgsLayoutSize pageSize = layout->pageCollection()->page( page )->sizeWithUnits();
1318 
1319  QPageLayout pageLayout( QPageSize( pageSizeMM.toQSizeF(), QPageSize::Millimeter ),
1320  QPageLayout::Portrait,
1321  QMarginsF( 0, 0, 0, 0 ) );
1322  pageLayout.setMode( QPageLayout::FullPageMode );
1323  printer.setPageLayout( pageLayout );
1324  printer.setFullPage( true );
1325  printer.setPageMargins( QMarginsF( 0, 0, 0, 0 ) );
1326 }
1327 
1328 QgsLayoutExporter::ExportResult QgsLayoutExporter::renderToLayeredSvg( const SvgExportSettings &settings, double width, double height, int page, const QRectF &bounds, const QString &filename, unsigned int svgLayerId, const QString &layerName, QDomDocument &svg, QDomNode &svgDocRoot, bool includeMetadata ) const
1329 {
1330  QBuffer svgBuffer;
1331  {
1332  QSvgGenerator generator;
1333  if ( includeMetadata )
1334  {
1335  if ( const QgsMasterLayoutInterface *l = dynamic_cast< const QgsMasterLayoutInterface * >( mLayout.data() ) )
1336  generator.setTitle( l->name() );
1337  else if ( mLayout->project() )
1338  generator.setTitle( mLayout->project()->title() );
1339  }
1340 
1341  generator.setOutputDevice( &svgBuffer );
1342  generator.setSize( QSize( static_cast< int >( std::round( width ) ),
1343  static_cast< int >( std::round( height ) ) ) );
1344  generator.setViewBox( QRect( 0, 0,
1345  static_cast< int >( std::round( width ) ),
1346  static_cast< int >( std::round( height ) ) ) );
1347  generator.setResolution( static_cast< int >( std::round( settings.dpi ) ) ); //because the rendering is done in mm, convert the dpi
1348 
1349  QPainter svgPainter( &generator );
1350  if ( settings.cropToContents )
1351  renderRegion( &svgPainter, bounds );
1352  else
1353  renderPage( &svgPainter, page );
1354  }
1355 
1356 // post-process svg output to create groups in a single svg file
1357 // we create inkscape layers since it's nice and clean and free
1358 // and fully svg compatible
1359  {
1360  svgBuffer.close();
1361  svgBuffer.open( QIODevice::ReadOnly );
1362  QDomDocument doc;
1363  QString errorMsg;
1364  int errorLine;
1365  if ( ! doc.setContent( &svgBuffer, false, &errorMsg, &errorLine ) )
1366  {
1367  mErrorFileName = filename;
1368  return SvgLayerError;
1369  }
1370  if ( 1 == svgLayerId )
1371  {
1372  svg = QDomDocument( doc.doctype() );
1373  svg.appendChild( svg.importNode( doc.firstChild(), false ) );
1374  svgDocRoot = svg.importNode( doc.elementsByTagName( QStringLiteral( "svg" ) ).at( 0 ), false );
1375  svgDocRoot.toElement().setAttribute( QStringLiteral( "xmlns:inkscape" ), QStringLiteral( "http://www.inkscape.org/namespaces/inkscape" ) );
1376  svg.appendChild( svgDocRoot );
1377  }
1378  QDomNode mainGroup = svg.importNode( doc.elementsByTagName( QStringLiteral( "g" ) ).at( 0 ), true );
1379  mainGroup.toElement().setAttribute( QStringLiteral( "id" ), layerName );
1380  mainGroup.toElement().setAttribute( QStringLiteral( "inkscape:label" ), layerName );
1381  mainGroup.toElement().setAttribute( QStringLiteral( "inkscape:groupmode" ), QStringLiteral( "layer" ) );
1382  QDomNode defs = svg.importNode( doc.elementsByTagName( QStringLiteral( "defs" ) ).at( 0 ), true );
1383  svgDocRoot.appendChild( defs );
1384  svgDocRoot.appendChild( mainGroup );
1385  }
1386  return Success;
1387 }
1388 
1389 void QgsLayoutExporter::appendMetadataToSvg( QDomDocument &svg ) const
1390 {
1391  const QgsProjectMetadata &metadata = mLayout->project()->metadata();
1392  QDomElement metadataElement = svg.createElement( QStringLiteral( "metadata" ) );
1393  QDomElement rdfElement = svg.createElement( QStringLiteral( "rdf:RDF" ) );
1394  rdfElement.setAttribute( QStringLiteral( "xmlns:rdf" ), QStringLiteral( "http://www.w3.org/1999/02/22-rdf-syntax-ns#" ) );
1395  rdfElement.setAttribute( QStringLiteral( "xmlns:rdfs" ), QStringLiteral( "http://www.w3.org/2000/01/rdf-schema#" ) );
1396  rdfElement.setAttribute( QStringLiteral( "xmlns:dc" ), QStringLiteral( "http://purl.org/dc/elements/1.1/" ) );
1397  QDomElement descriptionElement = svg.createElement( QStringLiteral( "rdf:Description" ) );
1398  QDomElement workElement = svg.createElement( QStringLiteral( "cc:Work" ) );
1399  workElement.setAttribute( QStringLiteral( "rdf:about" ), QString() );
1400 
1401  auto addTextNode = [&workElement, &descriptionElement, &svg]( const QString & tag, const QString & value )
1402  {
1403  // inkscape compatible
1404  QDomElement element = svg.createElement( tag );
1405  QDomText t = svg.createTextNode( value );
1406  element.appendChild( t );
1407  workElement.appendChild( element );
1408 
1409  // svg spec compatible
1410  descriptionElement.setAttribute( tag, value );
1411  };
1412 
1413  addTextNode( QStringLiteral( "dc:format" ), QStringLiteral( "image/svg+xml" ) );
1414  addTextNode( QStringLiteral( "dc:title" ), metadata.title() );
1415  addTextNode( QStringLiteral( "dc:date" ), metadata.creationDateTime().toString( Qt::ISODate ) );
1416  addTextNode( QStringLiteral( "dc:identifier" ), metadata.identifier() );
1417  addTextNode( QStringLiteral( "dc:description" ), metadata.abstract() );
1418 
1419  auto addAgentNode = [&workElement, &descriptionElement, &svg]( const QString & tag, const QString & value )
1420  {
1421  // inkscape compatible
1422  QDomElement inkscapeElement = svg.createElement( tag );
1423  QDomElement agentElement = svg.createElement( QStringLiteral( "cc:Agent" ) );
1424  QDomElement titleElement = svg.createElement( QStringLiteral( "dc:title" ) );
1425  QDomText t = svg.createTextNode( value );
1426  titleElement.appendChild( t );
1427  agentElement.appendChild( titleElement );
1428  inkscapeElement.appendChild( agentElement );
1429  workElement.appendChild( inkscapeElement );
1430 
1431  // svg spec compatible
1432  QDomElement bagElement = svg.createElement( QStringLiteral( "rdf:Bag" ) );
1433  QDomElement liElement = svg.createElement( QStringLiteral( "rdf:li" ) );
1434  t = svg.createTextNode( value );
1435  liElement.appendChild( t );
1436  bagElement.appendChild( liElement );
1437 
1438  QDomElement element = svg.createElement( tag );
1439  element.appendChild( bagElement );
1440  descriptionElement.appendChild( element );
1441  };
1442 
1443  addAgentNode( QStringLiteral( "dc:creator" ), metadata.author() );
1444  addAgentNode( QStringLiteral( "dc:publisher" ), QStringLiteral( "QGIS %1" ).arg( Qgis::version() ) );
1445 
1446  // keywords
1447  {
1448  QDomElement element = svg.createElement( QStringLiteral( "dc:subject" ) );
1449  QDomElement bagElement = svg.createElement( QStringLiteral( "rdf:Bag" ) );
1450  QgsAbstractMetadataBase::KeywordMap keywords = metadata.keywords();
1451  for ( auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
1452  {
1453  const QStringList words = it.value();
1454  for ( const QString &keyword : words )
1455  {
1456  QDomElement liElement = svg.createElement( QStringLiteral( "rdf:li" ) );
1457  QDomText t = svg.createTextNode( keyword );
1458  liElement.appendChild( t );
1459  bagElement.appendChild( liElement );
1460  }
1461  }
1462  element.appendChild( bagElement );
1463  workElement.appendChild( element );
1464  descriptionElement.appendChild( element );
1465  }
1466 
1467  rdfElement.appendChild( descriptionElement );
1468  rdfElement.appendChild( workElement );
1469  metadataElement.appendChild( rdfElement );
1470  svg.documentElement().appendChild( metadataElement );
1471  svg.documentElement().setAttribute( QStringLiteral( "xmlns:cc" ), QStringLiteral( "http://creativecommons.org/ns#" ) );
1472 }
1473 
1474 std::unique_ptr<double[]> QgsLayoutExporter::computeGeoTransform( const QgsLayoutItemMap *map, const QRectF &region, double dpi ) const
1475 {
1476  if ( !map )
1477  map = mLayout->referenceMap();
1478 
1479  if ( !map )
1480  return nullptr;
1481 
1482  if ( dpi < 0 )
1483  dpi = mLayout->renderContext().dpi();
1484 
1485  // calculate region of composition to export (in mm)
1486  QRectF exportRegion = region;
1487  if ( !exportRegion.isValid() )
1488  {
1489  int pageNumber = map->page();
1490 
1491  QgsLayoutItemPage *page = mLayout->pageCollection()->page( pageNumber );
1492  double pageY = page->pos().y();
1493  QSizeF pageSize = page->rect().size();
1494  exportRegion = QRectF( 0, pageY, pageSize.width(), pageSize.height() );
1495  }
1496 
1497  // map rectangle (in mm)
1498  QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
1499 
1500  // destination width/height in mm
1501  double outputHeightMM = exportRegion.height();
1502  double outputWidthMM = exportRegion.width();
1503 
1504  // map properties
1505  QgsRectangle mapExtent = map->extent();
1506  double mapXCenter = mapExtent.center().x();
1507  double mapYCenter = mapExtent.center().y();
1508  double alpha = - map->mapRotation() / 180 * M_PI;
1509  double sinAlpha = std::sin( alpha );
1510  double cosAlpha = std::cos( alpha );
1511 
1512  // get the extent (in map units) for the exported region
1513  QPointF mapItemPos = map->pos();
1514  //adjust item position so it is relative to export region
1515  mapItemPos.rx() -= exportRegion.left();
1516  mapItemPos.ry() -= exportRegion.top();
1517 
1518  // calculate extent of entire page in map units
1519  double xRatio = mapExtent.width() / mapItemSceneRect.width();
1520  double yRatio = mapExtent.height() / mapItemSceneRect.height();
1521  double xmin = mapExtent.xMinimum() - mapItemPos.x() * xRatio;
1522  double ymax = mapExtent.yMaximum() + mapItemPos.y() * yRatio;
1523  QgsRectangle paperExtent( xmin, ymax - outputHeightMM * yRatio, xmin + outputWidthMM * xRatio, ymax );
1524 
1525  // calculate origin of page
1526  double X0 = paperExtent.xMinimum();
1527  double Y0 = paperExtent.yMaximum();
1528 
1529  if ( !qgsDoubleNear( alpha, 0.0 ) )
1530  {
1531  // translate origin to account for map rotation
1532  double X1 = X0 - mapXCenter;
1533  double Y1 = Y0 - mapYCenter;
1534  double X2 = X1 * cosAlpha + Y1 * sinAlpha;
1535  double Y2 = -X1 * sinAlpha + Y1 * cosAlpha;
1536  X0 = X2 + mapXCenter;
1537  Y0 = Y2 + mapYCenter;
1538  }
1539 
1540  // calculate scaling of pixels
1541  int pageWidthPixels = static_cast< int >( dpi * outputWidthMM / 25.4 );
1542  int pageHeightPixels = static_cast< int >( dpi * outputHeightMM / 25.4 );
1543  double pixelWidthScale = paperExtent.width() / pageWidthPixels;
1544  double pixelHeightScale = paperExtent.height() / pageHeightPixels;
1545 
1546  // transform matrix
1547  std::unique_ptr<double[]> t( new double[6] );
1548  t[0] = X0;
1549  t[1] = cosAlpha * pixelWidthScale;
1550  t[2] = -sinAlpha * pixelWidthScale;
1551  t[3] = Y0;
1552  t[4] = -sinAlpha * pixelHeightScale;
1553  t[5] = -cosAlpha * pixelHeightScale;
1554 
1555  return t;
1556 }
1557 
1558 void QgsLayoutExporter::writeWorldFile( const QString &worldFileName, double a, double b, double c, double d, double e, double f ) const
1559 {
1560  QFile worldFile( worldFileName );
1561  if ( !worldFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
1562  {
1563  return;
1564  }
1565  QTextStream fout( &worldFile );
1566 
1567  // QString::number does not use locale settings (for the decimal point)
1568  // which is what we want here
1569  fout << QString::number( a, 'f', 12 ) << "\r\n";
1570  fout << QString::number( d, 'f', 12 ) << "\r\n";
1571  fout << QString::number( b, 'f', 12 ) << "\r\n";
1572  fout << QString::number( e, 'f', 12 ) << "\r\n";
1573  fout << QString::number( c, 'f', 12 ) << "\r\n";
1574  fout << QString::number( f, 'f', 12 ) << "\r\n";
1575 }
1576 
1577 bool QgsLayoutExporter::georeferenceOutput( const QString &file, QgsLayoutItemMap *map, const QRectF &exportRegion, double dpi ) const
1578 {
1579  return georeferenceOutputPrivate( file, map, exportRegion, dpi, false );
1580 }
1581 
1582 bool QgsLayoutExporter::georeferenceOutputPrivate( const QString &file, QgsLayoutItemMap *map, const QRectF &exportRegion, double dpi, bool includeGeoreference, bool includeMetadata ) const
1583 {
1584  if ( !mLayout )
1585  return false;
1586 
1587  if ( !map && includeGeoreference )
1588  map = mLayout->referenceMap();
1589 
1590  std::unique_ptr<double[]> t;
1591 
1592  if ( map && includeGeoreference )
1593  {
1594  if ( dpi < 0 )
1595  dpi = mLayout->renderContext().dpi();
1596 
1597  t = computeGeoTransform( map, exportRegion, dpi );
1598  }
1599 
1600  // important - we need to manually specify the DPI in advance, as GDAL will otherwise
1601  // assume a DPI of 150
1602  CPLSetConfigOption( "GDAL_PDF_DPI", QString::number( dpi ).toLocal8Bit().constData() );
1603  gdal::dataset_unique_ptr outputDS( GDALOpen( file.toLocal8Bit().constData(), GA_Update ) );
1604  if ( outputDS )
1605  {
1606  if ( t )
1607  GDALSetGeoTransform( outputDS.get(), t.get() );
1608 
1609  if ( includeMetadata )
1610  {
1611  QString creationDateString;
1612  const QDateTime creationDateTime = mLayout->project()->metadata().creationDateTime();
1613  if ( creationDateTime.isValid() )
1614  {
1615  creationDateString = QStringLiteral( "D:%1" ).arg( mLayout->project()->metadata().creationDateTime().toString( QStringLiteral( "yyyyMMddHHmmss" ) ) );
1616  if ( creationDateTime.timeZone().isValid() )
1617  {
1618  int offsetFromUtc = creationDateTime.timeZone().offsetFromUtc( creationDateTime );
1619  creationDateString += ( offsetFromUtc >= 0 ) ? '+' : '-';
1620  offsetFromUtc = std::abs( offsetFromUtc );
1621  int offsetHours = offsetFromUtc / 3600;
1622  int offsetMins = ( offsetFromUtc % 3600 ) / 60;
1623  creationDateString += QStringLiteral( "%1'%2'" ).arg( offsetHours ).arg( offsetMins );
1624  }
1625  }
1626  GDALSetMetadataItem( outputDS.get(), "CREATION_DATE", creationDateString.toUtf8().constData(), nullptr );
1627 
1628  GDALSetMetadataItem( outputDS.get(), "AUTHOR", mLayout->project()->metadata().author().toUtf8().constData(), nullptr );
1629  const QString creator = QStringLiteral( "QGIS %1" ).arg( Qgis::version() );
1630  GDALSetMetadataItem( outputDS.get(), "CREATOR", creator.toUtf8().constData(), nullptr );
1631  GDALSetMetadataItem( outputDS.get(), "PRODUCER", creator.toUtf8().constData(), nullptr );
1632  GDALSetMetadataItem( outputDS.get(), "SUBJECT", mLayout->project()->metadata().abstract().toUtf8().constData(), nullptr );
1633  GDALSetMetadataItem( outputDS.get(), "TITLE", mLayout->project()->metadata().title().toUtf8().constData(), nullptr );
1634 
1635  const QgsAbstractMetadataBase::KeywordMap keywords = mLayout->project()->metadata().keywords();
1636  QStringList allKeywords;
1637  for ( auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
1638  {
1639  allKeywords.append( QStringLiteral( "%1: %2" ).arg( it.key(), it.value().join( ',' ) ) );
1640  }
1641  const QString keywordString = allKeywords.join( ';' );
1642  GDALSetMetadataItem( outputDS.get(), "KEYWORDS", keywordString.toUtf8().constData(), nullptr );
1643  }
1644 
1645  if ( t )
1646  GDALSetProjection( outputDS.get(), map->crs().toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED_GDAL ).toLocal8Bit().constData() );
1647  }
1648  CPLSetConfigOption( "GDAL_PDF_DPI", nullptr );
1649 
1650  return true;
1651 }
1652 
1653 QString nameForLayerWithItems( const QList< QGraphicsItem * > &items, unsigned int layerId )
1654 {
1655  if ( items.count() == 1 )
1656  {
1657  if ( QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( items.at( 0 ) ) )
1658  {
1659  QString name = layoutItem->displayName();
1660  // cleanup default item ID format
1661  if ( name.startsWith( '<' ) && name.endsWith( '>' ) )
1662  name = name.mid( 1, name.length() - 2 );
1663  return name;
1664  }
1665  }
1666  else if ( items.count() > 1 )
1667  {
1668  QStringList currentLayerItemTypes;
1669  for ( QGraphicsItem *item : items )
1670  {
1671  if ( QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item ) )
1672  {
1673  const QString itemType = QgsApplication::layoutItemRegistry()->itemMetadata( layoutItem->type() )->visibleName();
1674  const QString itemTypePlural = QgsApplication::layoutItemRegistry()->itemMetadata( layoutItem->type() )->visiblePluralName();
1675  if ( !currentLayerItemTypes.contains( itemType ) && !currentLayerItemTypes.contains( itemTypePlural ) )
1676  currentLayerItemTypes << itemType;
1677  else if ( currentLayerItemTypes.contains( itemType ) )
1678  {
1679  currentLayerItemTypes.replace( currentLayerItemTypes.indexOf( itemType ), itemTypePlural );
1680  }
1681  }
1682  else
1683  {
1684  if ( !currentLayerItemTypes.contains( QObject::tr( "Other" ) ) )
1685  currentLayerItemTypes.append( QObject::tr( "Other" ) );
1686  }
1687  }
1688  return currentLayerItemTypes.join( QLatin1String( ", " ) );
1689  }
1690  return QObject::tr( "Layer %1" ).arg( layerId );
1691 }
1692 
1693 QgsLayoutExporter::ExportResult QgsLayoutExporter::handleLayeredExport( const QList<QGraphicsItem *> &items,
1694  const std::function<QgsLayoutExporter::ExportResult( unsigned int, const QgsLayoutItem::ExportLayerDetail & )> &exportFunc )
1695 {
1696  LayoutItemHider itemHider( items );
1697  ( void )itemHider;
1698 
1699  int prevType = -1;
1701  unsigned int layerId = 1;
1702  QgsLayoutItem::ExportLayerDetail layerDetails;
1703  itemHider.hideAll();
1704  const QList< QGraphicsItem * > itemsToIterate = itemHider.itemsToIterate();
1705  QList< QGraphicsItem * > currentLayerItems;
1706  for ( QGraphicsItem *item : itemsToIterate )
1707  {
1708  QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item );
1709 
1710  bool canPlaceInExistingLayer = false;
1711  if ( layoutItem )
1712  {
1713  switch ( layoutItem->exportLayerBehavior() )
1714  {
1716  {
1717  switch ( prevItemBehavior )
1718  {
1720  canPlaceInExistingLayer = true;
1721  break;
1722 
1724  canPlaceInExistingLayer = prevType == -1 || prevType == layoutItem->type();
1725  break;
1726 
1729  canPlaceInExistingLayer = false;
1730  break;
1731  }
1732  break;
1733  }
1734 
1736  {
1737  switch ( prevItemBehavior )
1738  {
1741  canPlaceInExistingLayer = prevType == -1 || prevType == layoutItem->type();
1742  break;
1743 
1746  canPlaceInExistingLayer = false;
1747  break;
1748  }
1749  break;
1750  }
1751 
1753  {
1754  canPlaceInExistingLayer = false;
1755  break;
1756  }
1757 
1759  canPlaceInExistingLayer = false;
1760  break;
1761  }
1762  prevItemBehavior = layoutItem->exportLayerBehavior();
1763  prevType = layoutItem->type();
1764  }
1765  else
1766  {
1767  prevItemBehavior = QgsLayoutItem::MustPlaceInOwnLayer;
1768  }
1769 
1770  if ( canPlaceInExistingLayer )
1771  {
1772  currentLayerItems << item;
1773  item->show();
1774  }
1775  else
1776  {
1777  if ( !currentLayerItems.isEmpty() )
1778  {
1779  layerDetails.name = nameForLayerWithItems( currentLayerItems, layerId );
1780 
1781  ExportResult result = exportFunc( layerId, layerDetails );
1782  if ( result != Success )
1783  return result;
1784  layerId++;
1785  currentLayerItems.clear();
1786  }
1787 
1788  itemHider.hideAll();
1789  item->show();
1790 
1791  if ( layoutItem && layoutItem->exportLayerBehavior() == QgsLayoutItem::ItemContainsSubLayers )
1792  {
1793  int layoutItemLayerIdx = 0;
1795  mLayout->renderContext().setCurrentExportLayer( layoutItemLayerIdx );
1797  layoutItem->startLayeredExport();
1798  while ( layoutItem->nextExportPart() )
1799  {
1801  mLayout->renderContext().setCurrentExportLayer( layoutItemLayerIdx );
1803 
1804  layerDetails = layoutItem->exportLayerDetails();
1805  ExportResult result = exportFunc( layerId, layerDetails );
1806  if ( result != Success )
1807  return result;
1808  layerId++;
1809 
1810  layoutItemLayerIdx++;
1811  }
1812  layerDetails.mapLayerId.clear();
1814  mLayout->renderContext().setCurrentExportLayer( -1 );
1816  layoutItem->stopLayeredExport();
1817  currentLayerItems.clear();
1818  }
1819  else
1820  {
1821  currentLayerItems << item;
1822  }
1823  }
1824  }
1825  if ( !currentLayerItems.isEmpty() )
1826  {
1827  layerDetails.name = nameForLayerWithItems( currentLayerItems, layerId );
1828  ExportResult result = exportFunc( layerId, layerDetails );
1829  if ( result != Success )
1830  return result;
1831  }
1832  return Success;
1833 }
1834 
1835 QgsVectorSimplifyMethod QgsLayoutExporter::createExportSimplifyMethod()
1836 {
1837  QgsVectorSimplifyMethod simplifyMethod;
1839  simplifyMethod.setForceLocalOptimization( true );
1840  // we use SnappedToGridGlobal, because it avoids gaps and slivers between previously adjacent polygons
1842  simplifyMethod.setThreshold( 0.1f ); // (pixels). We are quite conservative here. This could possibly be bumped all the way up to 1. But let's play it safe.
1843  return simplifyMethod;
1844 }
1845 
1846 void QgsLayoutExporter::computeWorldFileParameters( double &a, double &b, double &c, double &d, double &e, double &f, double dpi ) const
1847 {
1848  if ( !mLayout )
1849  return;
1850 
1851  QgsLayoutItemMap *map = mLayout->referenceMap();
1852  if ( !map )
1853  {
1854  return;
1855  }
1856 
1857  int pageNumber = map->page();
1858  QgsLayoutItemPage *page = mLayout->pageCollection()->page( pageNumber );
1859  double pageY = page->pos().y();
1860  QSizeF pageSize = page->rect().size();
1861  QRectF pageRect( 0, pageY, pageSize.width(), pageSize.height() );
1862  computeWorldFileParameters( pageRect, a, b, c, d, e, f, dpi );
1863 }
1864 
1865 void QgsLayoutExporter::computeWorldFileParameters( const QRectF &exportRegion, double &a, double &b, double &c, double &d, double &e, double &f, double dpi ) const
1866 {
1867  if ( !mLayout )
1868  return;
1869 
1870  // World file parameters : affine transformation parameters from pixel coordinates to map coordinates
1871  QgsLayoutItemMap *map = mLayout->referenceMap();
1872  if ( !map )
1873  {
1874  return;
1875  }
1876 
1877  double destinationHeight = exportRegion.height();
1878  double destinationWidth = exportRegion.width();
1879 
1880  QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
1881  QgsRectangle mapExtent = map->extent();
1882 
1883  double alpha = map->mapRotation() / 180 * M_PI;
1884 
1885  double xRatio = mapExtent.width() / mapItemSceneRect.width();
1886  double yRatio = mapExtent.height() / mapItemSceneRect.height();
1887 
1888  double xCenter = mapExtent.center().x();
1889  double yCenter = mapExtent.center().y();
1890 
1891  // get the extent (in map units) for the region
1892  QPointF mapItemPos = map->pos();
1893  //adjust item position so it is relative to export region
1894  mapItemPos.rx() -= exportRegion.left();
1895  mapItemPos.ry() -= exportRegion.top();
1896 
1897  double xmin = mapExtent.xMinimum() - mapItemPos.x() * xRatio;
1898  double ymax = mapExtent.yMaximum() + mapItemPos.y() * yRatio;
1899  QgsRectangle paperExtent( xmin, ymax - destinationHeight * yRatio, xmin + destinationWidth * xRatio, ymax );
1900 
1901  double X0 = paperExtent.xMinimum();
1902  double Y0 = paperExtent.yMinimum();
1903 
1904  if ( dpi < 0 )
1905  dpi = mLayout->renderContext().dpi();
1906 
1907  int widthPx = static_cast< int >( dpi * destinationWidth / 25.4 );
1908  int heightPx = static_cast< int >( dpi * destinationHeight / 25.4 );
1909 
1910  double Ww = paperExtent.width() / widthPx;
1911  double Hh = paperExtent.height() / heightPx;
1912 
1913  // scaling matrix
1914  double s[6];
1915  s[0] = Ww;
1916  s[1] = 0;
1917  s[2] = X0;
1918  s[3] = 0;
1919  s[4] = -Hh;
1920  s[5] = Y0 + paperExtent.height();
1921 
1922  // rotation matrix
1923  double r[6];
1924  r[0] = std::cos( alpha );
1925  r[1] = -std::sin( alpha );
1926  r[2] = xCenter * ( 1 - std::cos( alpha ) ) + yCenter * std::sin( alpha );
1927  r[3] = std::sin( alpha );
1928  r[4] = std::cos( alpha );
1929  r[5] = - xCenter * std::sin( alpha ) + yCenter * ( 1 - std::cos( alpha ) );
1930 
1931  // result = rotation x scaling = rotation(scaling(X))
1932  a = r[0] * s[0] + r[1] * s[3];
1933  b = r[0] * s[1] + r[1] * s[4];
1934  c = r[0] * s[2] + r[1] * s[5] + r[2];
1935  d = r[3] * s[0] + r[4] * s[3];
1936  e = r[3] * s[1] + r[4] * s[4];
1937  f = r[3] * s[2] + r[4] * s[5] + r[5];
1938 }
1939 
1941 {
1942  if ( !layout )
1943  return false;
1944 
1945  QList< QgsLayoutItem *> items;
1946  layout->layoutItems( items );
1947 
1948  for ( QgsLayoutItem *currentItem : std::as_const( items ) )
1949  {
1950  // ignore invisible items, they won't affect the output in any way...
1951  if ( currentItem->isVisible() && currentItem->requiresRasterization() )
1952  return true;
1953  }
1954  return false;
1955 }
1956 
1958 {
1959  if ( !layout )
1960  return false;
1961 
1962  QList< QgsLayoutItem *> items;
1963  layout->layoutItems( items );
1964 
1965  for ( QgsLayoutItem *currentItem : std::as_const( items ) )
1966  {
1967  // ignore invisible items, they won't affect the output in any way...
1968  if ( currentItem->isVisible() && currentItem->containsAdvancedEffects() )
1969  return true;
1970  }
1971  return false;
1972 }
1973 
1974 QImage QgsLayoutExporter::createImage( const QgsLayoutExporter::ImageExportSettings &settings, int page, QRectF &bounds, bool &skipPage ) const
1975 {
1976  bounds = QRectF();
1977  skipPage = false;
1978 
1979  if ( settings.cropToContents )
1980  {
1981  if ( mLayout->pageCollection()->pageCount() == 1 )
1982  {
1983  // single page, so include everything
1984  bounds = mLayout->layoutBounds( true );
1985  }
1986  else
1987  {
1988  // multi page, so just clip to items on current page
1989  bounds = mLayout->pageItemBounds( page, true );
1990  }
1991  if ( bounds.width() <= 0 || bounds.height() <= 0 )
1992  {
1993  //invalid size, skip page
1994  skipPage = true;
1995  return QImage();
1996  }
1997 
1998  double pixelToLayoutUnits = mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 1, QgsUnitTypes::LayoutPixels ) );
1999  bounds = bounds.adjusted( -settings.cropMargins.left() * pixelToLayoutUnits,
2000  -settings.cropMargins.top() * pixelToLayoutUnits,
2001  settings.cropMargins.right() * pixelToLayoutUnits,
2002  settings.cropMargins.bottom() * pixelToLayoutUnits );
2003  return renderRegionToImage( bounds, QSize(), settings.dpi );
2004  }
2005  else
2006  {
2007  return renderPageToImage( page, settings.imageSize, settings.dpi );
2008  }
2009 }
2010 
2011 int QgsLayoutExporter::firstPageToBeExported( QgsLayout *layout )
2012 {
2013  const int pageCount = layout->pageCollection()->pageCount();
2014  for ( int i = 0; i < pageCount; ++i )
2015  {
2016  if ( !layout->pageCollection()->shouldExportPage( i ) )
2017  {
2018  continue;
2019  }
2020 
2021  return i;
2022  }
2023  return 0; // shouldn't really matter -- we aren't exporting ANY pages!
2024 }
2025 
2027 {
2028  if ( details.page == 0 )
2029  {
2030  return details.directory + '/' + details.baseName + '.' + details.extension;
2031  }
2032  else
2033  {
2034  return details.directory + '/' + details.baseName + '_' + QString::number( details.page + 1 ) + '.' + details.extension;
2035  }
2036 }
2037 
2038 void QgsLayoutExporter::captureLabelingResults()
2039 {
2040  qDeleteAll( mLabelingResults );
2041  mLabelingResults.clear();
2042 
2043  QList< QgsLayoutItemMap * > maps;
2044  mLayout->layoutItems( maps );
2045 
2046  for ( QgsLayoutItemMap *map : std::as_const( maps ) )
2047  {
2048  mLabelingResults[ map->uuid() ] = map->mExportLabelingResults.release();
2049  }
2050 }
2051 
2052 bool QgsLayoutExporter::saveImage( const QImage &image, const QString &imageFilename, const QString &imageFormat, QgsProject *projectForMetadata )
2053 {
2054  QImageWriter w( imageFilename, imageFormat.toLocal8Bit().constData() );
2055  if ( imageFormat.compare( QLatin1String( "tiff" ), Qt::CaseInsensitive ) == 0 || imageFormat.compare( QLatin1String( "tif" ), Qt::CaseInsensitive ) == 0 )
2056  {
2057  w.setCompression( 1 ); //use LZW compression
2058  }
2059  if ( projectForMetadata )
2060  {
2061  w.setText( QStringLiteral( "Author" ), projectForMetadata->metadata().author() );
2062  const QString creator = QStringLiteral( "QGIS %1" ).arg( Qgis::version() );
2063  w.setText( QStringLiteral( "Creator" ), creator );
2064  w.setText( QStringLiteral( "Producer" ), creator );
2065  w.setText( QStringLiteral( "Subject" ), projectForMetadata->metadata().abstract() );
2066  w.setText( QStringLiteral( "Created" ), projectForMetadata->metadata().creationDateTime().toString( Qt::ISODate ) );
2067  w.setText( QStringLiteral( "Title" ), projectForMetadata->metadata().title() );
2068 
2069  const QgsAbstractMetadataBase::KeywordMap keywords = projectForMetadata->metadata().keywords();
2070  QStringList allKeywords;
2071  for ( auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
2072  {
2073  allKeywords.append( QStringLiteral( "%1: %2" ).arg( it.key(), it.value().join( ',' ) ) );
2074  }
2075  const QString keywordString = allKeywords.join( ';' );
2076  w.setText( QStringLiteral( "Keywords" ), keywordString );
2077  }
2078  return w.write( image );
2079 }
2080 
2081 #endif // ! QT_NO_PRINTER
static QString version()
Version string.
Definition: qgis.cpp:285
TextRenderFormat
Options for rendering text.
Definition: qgis.h:965
An abstract base class for QgsLayout based classes which can be exported by QgsLayoutExporter.
virtual bool endRender()=0
Ends the render, performing any required cleanup tasks.
virtual bool next()=0
Iterates to next feature, returning false if no more features exist to iterate over.
virtual QgsLayout * layout()=0
Returns the layout associated with the iterator.
virtual bool beginRender()=0
Called when rendering begins, before iteration commences.
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.
virtual int count() const =0
Returns the number of features to iterate over.
QMap< QString, QStringList > KeywordMap
Map of vocabulary string to keyword list.
QString abstract() const
Returns a free-form description of the resource.
QString title() const
Returns the human readable name of the resource, typically displayed in search results.
QgsAbstractMetadataBase::KeywordMap keywords() const
Returns the keywords map, which is a set of descriptive keywords associated with the resource.
QString identifier() const
A reference, URI, URL or some other mechanism to identify the resource.
static QgsLayoutItemRegistry * layoutItemRegistry()
Returns the application's layout item registry, used for layout item types.
@ WKT_PREFERRED_GDAL
Preferred format for conversion of CRS to WKT for use with the GDAL library.
QString toWkt(WktVariant variant=WKT1_GDAL, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition: qgsfeedback.h:45
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition: qgsfeedback.h:63
Handles rendering and exports of layouts to various formats.
ExportResult exportToSvg(const QString &filePath, const QgsLayoutExporter::SvgExportSettings &settings)
Exports the layout as an SVG to the filePath, using the specified export settings.
ExportResult exportToImage(const QString &filePath, const QgsLayoutExporter::ImageExportSettings &settings)
Exports the layout to the filePath, using the specified export settings.
ExportResult exportToPdf(const QString &filePath, const QgsLayoutExporter::PdfExportSettings &settings)
Exports the layout as a PDF to the filePath, using the specified export settings.
QImage renderRegionToImage(const QRectF &region, QSize imageSize=QSize(), double dpi=-1) const
Renders a region of the layout to an image.
QMap< QString, QgsLabelingResults * > takeLabelingResults()
Takes the labeling results for all map items included in the export.
static bool requiresRasterization(const QgsLayout *layout)
Returns true if the specified layout contains visible items which have settings that require rasteriz...
QgsLayout * layout() const
Returns the layout linked to this exporter.
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.
virtual QString generateFileName(const PageExportDetails &details) const
Generates the file name for a page during export.
ExportResult
Result codes for exporting layouts.
@ Canceled
Export was canceled.
@ MemoryError
Unable to allocate memory required to export.
@ PrintError
Could not start printing to destination device.
@ IteratorError
Error iterating over layout.
@ FileError
Could not write to destination file, likely due to a lock held by another application.
@ Success
Export was successful.
@ SvgLayerError
Could not create layered SVG file.
QImage renderPageToImage(int page, QSize imageSize=QSize(), double dpi=-1) const
Renders a full page to an image.
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.
void computeWorldFileParameters(double &a, double &b, double &c, double &d, double &e, double &f, double dpi=-1) const
Compute world file parameters.
void renderPage(QPainter *painter, int page) const
Renders a full page to a destination painter.
ExportResult print(QPrinter &printer, const QgsLayoutExporter::PrintExportSettings &settings)
Prints the layout to a printer, using the specified export settings.
QMap< QString, QgsLabelingResults * > labelingResults()
Returns the labeling results for all map items included in the export.
static bool containsAdvancedEffects(const QgsLayout *layout)
Returns true if the specified layout contains visible items which have settings such as opacity which...
void renderRegion(QPainter *painter, const QRectF &region) const
Renders a region from the layout to a painter.
QgsLayoutExporter(QgsLayout *layout)
Constructor for QgsLayoutExporter, for the specified layout.
Contains the configuration for a single snap guide used by a layout.
QString visibleName() const
Returns a translated, user visible name for the layout item class.
QString visiblePluralName() const
Returns a translated, user visible name for plurals of the layout item class (e.g.
Layout graphical items for displaying a map.
double mapRotation(QgsLayoutObject::PropertyValueType valueType=QgsLayoutObject::EvaluatedValue) const
Returns the rotation used for drawing the map within the layout item, in degrees clockwise.
QgsRectangle extent() const
Returns the current map extent.
QgsCoordinateReferenceSystem crs() const
Returns coordinate reference system used for rendering the map.
Item representing the paper in a layout.
QgsLayoutItemAbstractMetadata * itemMetadata(int type) const
Returns the metadata for the specified item type.
Base class for graphical items within a QgsLayout.
QgsLayoutSize sizeWithUnits() const
Returns the item's current size, including units.
virtual QgsLayoutItem::ExportLayerDetail exportLayerDetails() const
Returns the details for the specified current export layer.
virtual bool nextExportPart()
Moves to the next export part for a multi-layered export item, during a multi-layered export.
virtual void startLayeredExport()
Starts a multi-layer export operation.
int page() const
Returns the page the item is currently on, with the first page returning 0.
int type() const override
Returns a unique graphics item type identifier.
virtual void stopLayeredExport()
Stops a multi-layer export operation.
virtual QString uuid() const
Returns the item identification string.
ExportLayerBehavior
Behavior of item when exporting to layered outputs.
@ ItemContainsSubLayers
Item contains multiple sublayers which must be individually exported.
@ MustPlaceInOwnLayer
Item must be placed in its own individual layer.
@ CanGroupWithItemsOfSameType
Item can only be placed on layers with other items of the same type, but multiple items of this type ...
@ CanGroupWithAnyOtherItem
Item can be placed on a layer with any other item (default behavior)
virtual ExportLayerBehavior exportLayerBehavior() const
Returns the behavior of this item during exporting to layered exports (e.g.
QgsLayoutMeasurement convert(QgsLayoutMeasurement measurement, QgsUnitTypes::LayoutUnit targetUnits) const
Converts a measurement from one unit to another.
This class provides a method of storing measurements for use in QGIS layouts using a variety of diffe...
int pageCount() const
Returns the number of pages in the collection.
bool shouldExportPage(int page) const
Returns whether the specified page number should be included in exports of the layouts.
QgsLayoutItemPage * page(int pageNumber)
Returns a specific page (by pageNumber) from the collection.
This class provides a method of storing points, consisting of an x and y coordinate,...
double x() const
Returns x coordinate of point.
double y() const
Returns y coordinate of point.
void setDpi(double dpi)
Sets the dpi for outputting the layout.
void setSimplifyMethod(const QgsVectorSimplifyMethod &method)
Sets the simplification setting to use when rendering vector layers.
void setTextRenderFormat(Qgis::TextRenderFormat format)
Sets the text render format, which dictates how text is rendered (e.g.
void setFlag(QgsLayoutRenderContext::Flag flag, bool on=true)
Enables or disables a particular rendering flag for the layout.
double dpi() const
Returns the dpi for outputting the layout.
@ FlagRenderLabelsByMapLayer
When rendering map items to multi-layered exports, render labels belonging to different layers into s...
@ FlagUseAdvancedEffects
Enable advanced effects such as blend modes.
@ FlagAntialiasing
Use antialiasing when drawing items.
@ FlagForceVectorOutput
Force output in vector format where possible, even if items require rasterization to keep their corre...
void setPredefinedScales(const QVector< qreal > &scales)
Sets the list of predefined scales to use with the layout.
const QgsLayoutMeasurementConverter & measurementConverter() const
Returns the layout measurement converter to be used in the layout.
void setFlags(QgsLayoutRenderContext::Flags flags)
Sets the combination of flags that will be used for rendering the layout.
This class provides a method of storing sizes, consisting of a width and height, for use in QGIS layo...
Definition: qgslayoutsize.h:41
QSizeF toQSizeF() const
Converts the layout size to a QSizeF.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:51
QgsLayoutRenderContext & renderContext()
Returns a reference to the layout's render context, which stores information relating to the current ...
Definition: qgslayout.cpp:359
QgsLayoutPageCollection * pageCollection()
Returns a pointer to the layout's page collection, which stores and manages page items in the layout.
Definition: qgslayout.cpp:459
void layoutItems(QList< T * > &itemList) const
Returns a list of layout items of a specific type.
Definition: qgslayout.h:122
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:44
Base class for all map layer types.
Definition: qgsmaplayer.h:73
double top() const
Returns the top margin.
Definition: qgsmargins.h:78
double right() const
Returns the right margin.
Definition: qgsmargins.h:84
double bottom() const
Returns the bottom margin.
Definition: qgsmargins.h:90
double left() const
Returns the left margin.
Definition: qgsmargins.h:72
Interface for master layout type objects, such as print layouts and reports.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
static void fixEngineFlags(QPaintEngine *engine)
A class to represent a 2D point.
Definition: qgspointxy.h:59
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
void setExteriorRing(QgsCurve *ring) override
Sets the exterior ring of the polygon.
Definition: qgspolygon.cpp:219
A structured metadata store for a map layer.
QString author() const
Returns the project author string.
QDateTime creationDateTime() const
Returns the project's creation date/timestamp.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:101
QgsProjectMetadata metadata
Definition: qgsproject.h:114
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:230
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
QgsPointXY center() const SIP_HOLDGIL
Returns the center point of the rectangle.
Definition: qgsrectangle.h:251
@ LayoutInches
Inches.
Definition: qgsunittypes.h:186
@ LayoutMillimeters
Millimeters.
Definition: qgsunittypes.h:183
@ LayoutPixels
Pixels.
Definition: qgsunittypes.h:190
This class contains information how to simplify geometries fetched from a vector layer.
void setSimplifyHints(SimplifyHints simplifyHints)
Sets the simplification hints of the vector layer managed.
void setThreshold(float threshold)
Sets the simplification threshold of the vector layer managed.
void setSimplifyAlgorithm(SimplifyAlgorithm simplifyAlgorithm)
Sets the local simplification algorithm of the vector layer managed.
void setForceLocalOptimization(bool localOptimization)
Sets where the simplification executes, after fetch the geometries from provider, or when supported,...
@ GeometrySimplification
The geometries can be simplified using the current map2pixel context state.
@ SnappedToGridGlobal
Snap to a global grid based on the tolerance. Good for consistent results for incoming vertices,...
std::unique_ptr< std::remove_pointer< GDALDatasetH >::type, GDALDatasetCloser > dataset_unique_ptr
Scoped GDAL dataset.
Definition: qgsogrutils.h:138
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:1742
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:1741
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:1246
QString nameForLayerWithItems(const QList< QGraphicsItem * > &items, unsigned int layerId)
Contains details of a particular input component to be used during PDF composition.
QString sourcePdfPath
File path to the (already created) PDF to use as the source for this component layer.
QString mapLayerId
Associated map layer ID, or an empty string if this component layer is not associated with a map laye...
QPainter::CompositionMode compositionMode
Component composition mode.
QString group
Optional group name, for arranging layers in top-level groups.
QString name
User-friendly name for the generated PDF layer.
Contains details of a control point used during georeferencing GeoPDF outputs.
QgsAbstractMetadataBase::KeywordMap keywords
Metadata keyword map.
bool useIso32000ExtensionFormatGeoreferencing
true if ISO3200 extension format georeferencing should be used.
QMap< QString, QString > layerIdToPdfLayerTreeNameMap
Optional map of map layer ID to custom layer tree name to show in the created PDF file.
bool useOgcBestPracticeFormatGeoreferencing
true if OGC "best practice" format georeferencing should be used.
QDateTime creationDateTime
Metadata creation datetime.
QList< QgsAbstractGeoPdfExporter::GeoReferencedSection > georeferencedSections
List of georeferenced sections.
QMap< QString, bool > initialLayerVisibility
Optional map of map layer ID to initial visibility state.
QMap< QString, QString > customLayerTreeGroups
Optional map of map layer ID to custom logical layer tree group in created PDF file.
bool includeFeatures
true if feature vector information (such as attributes) should be exported.
QStringList layerOrder
Optional list of layer IDs, in the order desired to appear in the generated GeoPDF file.
QgsCoordinateReferenceSystem crs
Coordinate reference system for georeferenced section.
QgsPolygon pageBoundsPolygon
Bounds of the georeferenced section on the page, in millimeters, as a free-form polygon.
QList< QgsAbstractGeoPdfExporter::ControlPoint > controlPoints
List of control points corresponding to this georeferenced section.
Contains settings relating to exporting layouts to raster images.
QgsMargins cropMargins
Crop to content margins, in pixels.
QList< int > pages
List of specific pages to export, or an empty list to export all pages.
bool generateWorldFile
Set to true to generate an external world file alongside exported images.
QSize imageSize
Manual size in pixels for output image.
bool exportMetadata
Indicates whether image export should include metadata generated from the layout's project's metadata...
QgsLayoutRenderContext::Flags flags
Layout context flags, which control how the export will be created.
bool cropToContents
Set to true if image should be cropped so only parts of the layout containing items are exported.
double dpi
Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.
QVector< qreal > predefinedMapScales
A list of predefined scales to use with the layout.
Contains details of a page being exported by the class.
QString baseName
Base part of filename (i.e. file name without extension or '.')
QString extension
File suffix/extension (without the leading '.')
int page
Page number, where 0 = first page.
Contains settings relating to exporting layouts to PDF.
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.
QStringList exportThemes
Optional list of map themes to export as GeoPDF layer groups.
bool exportMetadata
Indicates whether PDF export should include metadata generated from the layout's project's metadata.
bool appendGeoreference
Indicates whether PDF export should append georeference data.
QgsLayoutRenderContext::Flags flags
Layout context flags, which control how the export will be created.
bool writeGeoPdf
true if GeoPDF files should be created, instead of normal PDF files.
double dpi
Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.
QVector< qreal > predefinedMapScales
A list of predefined scales to use with the layout.
bool exportLayersAsSeperateFiles
true if individual layers from the layout should be rendered to separate PDF files.
bool simplifyGeometries
Indicates whether vector geometries should be simplified to avoid redundant extraneous detail,...
Qgis::TextRenderFormat textRenderFormat
Text rendering format, which controls how text should be rendered in the export (e....
Contains settings relating to printing layouts.
QVector< qreal > predefinedMapScales
A list of predefined scales to use with the layout.
double dpi
Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.
QgsLayoutRenderContext::Flags flags
Layout context flags, which control how the export will be created.
bool rasterizeWholeImage
Set to true to force whole layout to be rasterized while exporting.
Contains settings relating to exporting layouts to SVG.
bool forceVectorOutput
Set to true to force vector object exports, even when the resultant appearance will differ from the l...
Qgis::TextRenderFormat textRenderFormat
Text rendering format, which controls how text should be rendered in the export (e....
bool exportAsLayers
Set to true to export as a layered SVG file.
bool simplifyGeometries
Indicates whether vector geometries should be simplified to avoid redundant extraneous detail,...
bool exportMetadata
Indicates whether SVG export should include RDF metadata generated from the layout's project's metada...
double dpi
Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.
QgsLayoutRenderContext::Flags flags
Layout context flags, which control how the export will be created.
QVector< qreal > predefinedMapScales
A list of predefined scales to use with the layout.
bool exportLabelsToSeparateLayers
Set to true to export labels to separate layers (grouped by map layer) in layered SVG exports.
bool cropToContents
Set to true if image should be cropped so only parts of the layout containing items are exported.
QgsMargins cropMargins
Crop to content margins, in layout units.
Contains details of a particular export layer relating to a layout item.
QString name
User-friendly name for the export layer.