QGIS API Documentation  3.25.0-Master (6b426f5f8a)
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 #if defined(HAS_KDE_QT5_PDF_TRANSFORM_FIX) || QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
1223  // paint engine hack not required, fixed upstream
1224 #else
1225  QgsPaintEngineHack::fixEngineFlags( printer.paintEngine() );
1226 #endif
1227 }
1228 
1229 void QgsLayoutExporter::preparePrint( QgsLayout *layout, QPrinter &printer, bool setFirstPageSize )
1230 {
1231  printer.setFullPage( true );
1232  printer.setColorMode( QPrinter::Color );
1233 
1234  //set user-defined resolution
1235  printer.setResolution( static_cast< int>( std::round( layout->renderContext().dpi() ) ) );
1236 
1237  if ( setFirstPageSize )
1238  {
1239  updatePrinterPageSize( layout, printer, firstPageToBeExported( layout ) );
1240  }
1241 }
1242 
1244 {
1245  if ( mLayout->pageCollection()->pageCount() == 0 )
1246  return PrintError;
1247 
1248  preparePrint( mLayout, printer, true );
1249  QPainter p;
1250  if ( !p.begin( &printer ) )
1251  {
1252  //error beginning print
1253  return PrintError;
1254  }
1255 
1256  printPrivate( printer, p );
1257  p.end();
1258  return Success;
1259 }
1260 
1261 QgsLayoutExporter::ExportResult QgsLayoutExporter::printPrivate( QPrinter &printer, QPainter &painter, bool startNewPage, double dpi, bool rasterize )
1262 {
1263  //layout starts page numbering at 0
1264  int fromPage = ( printer.fromPage() < 1 ) ? 0 : printer.fromPage() - 1;
1265  int toPage = ( printer.toPage() < 1 ) ? mLayout->pageCollection()->pageCount() - 1 : printer.toPage() - 1;
1266 
1267  bool pageExported = false;
1268  if ( rasterize )
1269  {
1270  for ( int i = fromPage; i <= toPage; ++i )
1271  {
1272  if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1273  {
1274  continue;
1275  }
1276 
1277  updatePrinterPageSize( mLayout, printer, i );
1278  if ( ( pageExported && i > fromPage ) || startNewPage )
1279  {
1280  printer.newPage();
1281  }
1282 
1283  QImage image = renderPageToImage( i, QSize(), dpi );
1284  if ( !image.isNull() )
1285  {
1286  QRectF targetArea( 0, 0, image.width(), image.height() );
1287  painter.drawImage( targetArea, image, targetArea );
1288  }
1289  else
1290  {
1291  return MemoryError;
1292  }
1293  pageExported = true;
1294  }
1295  }
1296  else
1297  {
1298  for ( int i = fromPage; i <= toPage; ++i )
1299  {
1300  if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1301  {
1302  continue;
1303  }
1304 
1305  updatePrinterPageSize( mLayout, printer, i );
1306 
1307  if ( ( pageExported && i > fromPage ) || startNewPage )
1308  {
1309  printer.newPage();
1310  }
1311  renderPage( &painter, i );
1312  pageExported = true;
1313  }
1314  }
1315  return Success;
1316 }
1317 
1318 void QgsLayoutExporter::updatePrinterPageSize( QgsLayout *layout, QPrinter &printer, int page )
1319 {
1320  QgsLayoutSize pageSize = layout->pageCollection()->page( page )->sizeWithUnits();
1322 
1323  QPageLayout pageLayout( QPageSize( pageSizeMM.toQSizeF(), QPageSize::Millimeter ),
1324  QPageLayout::Portrait,
1325  QMarginsF( 0, 0, 0, 0 ) );
1326  pageLayout.setMode( QPageLayout::FullPageMode );
1327  printer.setPageLayout( pageLayout );
1328  printer.setFullPage( true );
1329  printer.setPageMargins( QMarginsF( 0, 0, 0, 0 ) );
1330 }
1331 
1332 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
1333 {
1334  QBuffer svgBuffer;
1335  {
1336  QSvgGenerator generator;
1337  if ( includeMetadata )
1338  {
1339  if ( const QgsMasterLayoutInterface *l = dynamic_cast< const QgsMasterLayoutInterface * >( mLayout.data() ) )
1340  generator.setTitle( l->name() );
1341  else if ( mLayout->project() )
1342  generator.setTitle( mLayout->project()->title() );
1343  }
1344 
1345  generator.setOutputDevice( &svgBuffer );
1346  generator.setSize( QSize( static_cast< int >( std::round( width ) ),
1347  static_cast< int >( std::round( height ) ) ) );
1348  generator.setViewBox( QRect( 0, 0,
1349  static_cast< int >( std::round( width ) ),
1350  static_cast< int >( std::round( height ) ) ) );
1351  generator.setResolution( static_cast< int >( std::round( settings.dpi ) ) ); //because the rendering is done in mm, convert the dpi
1352 
1353  QPainter svgPainter( &generator );
1354  if ( settings.cropToContents )
1355  renderRegion( &svgPainter, bounds );
1356  else
1357  renderPage( &svgPainter, page );
1358  }
1359 
1360 // post-process svg output to create groups in a single svg file
1361 // we create inkscape layers since it's nice and clean and free
1362 // and fully svg compatible
1363  {
1364  svgBuffer.close();
1365  svgBuffer.open( QIODevice::ReadOnly );
1366  QDomDocument doc;
1367  QString errorMsg;
1368  int errorLine;
1369  if ( ! doc.setContent( &svgBuffer, false, &errorMsg, &errorLine ) )
1370  {
1371  mErrorFileName = filename;
1372  return SvgLayerError;
1373  }
1374  if ( 1 == svgLayerId )
1375  {
1376  svg = QDomDocument( doc.doctype() );
1377  svg.appendChild( svg.importNode( doc.firstChild(), false ) );
1378  svgDocRoot = svg.importNode( doc.elementsByTagName( QStringLiteral( "svg" ) ).at( 0 ), false );
1379  svgDocRoot.toElement().setAttribute( QStringLiteral( "xmlns:inkscape" ), QStringLiteral( "http://www.inkscape.org/namespaces/inkscape" ) );
1380  svg.appendChild( svgDocRoot );
1381  }
1382  QDomNode mainGroup = svg.importNode( doc.elementsByTagName( QStringLiteral( "g" ) ).at( 0 ), true );
1383  mainGroup.toElement().setAttribute( QStringLiteral( "id" ), layerName );
1384  mainGroup.toElement().setAttribute( QStringLiteral( "inkscape:label" ), layerName );
1385  mainGroup.toElement().setAttribute( QStringLiteral( "inkscape:groupmode" ), QStringLiteral( "layer" ) );
1386  QDomNode defs = svg.importNode( doc.elementsByTagName( QStringLiteral( "defs" ) ).at( 0 ), true );
1387  svgDocRoot.appendChild( defs );
1388  svgDocRoot.appendChild( mainGroup );
1389  }
1390  return Success;
1391 }
1392 
1393 void QgsLayoutExporter::appendMetadataToSvg( QDomDocument &svg ) const
1394 {
1395  const QgsProjectMetadata &metadata = mLayout->project()->metadata();
1396  QDomElement metadataElement = svg.createElement( QStringLiteral( "metadata" ) );
1397  QDomElement rdfElement = svg.createElement( QStringLiteral( "rdf:RDF" ) );
1398  rdfElement.setAttribute( QStringLiteral( "xmlns:rdf" ), QStringLiteral( "http://www.w3.org/1999/02/22-rdf-syntax-ns#" ) );
1399  rdfElement.setAttribute( QStringLiteral( "xmlns:rdfs" ), QStringLiteral( "http://www.w3.org/2000/01/rdf-schema#" ) );
1400  rdfElement.setAttribute( QStringLiteral( "xmlns:dc" ), QStringLiteral( "http://purl.org/dc/elements/1.1/" ) );
1401  QDomElement descriptionElement = svg.createElement( QStringLiteral( "rdf:Description" ) );
1402  QDomElement workElement = svg.createElement( QStringLiteral( "cc:Work" ) );
1403  workElement.setAttribute( QStringLiteral( "rdf:about" ), QString() );
1404 
1405  auto addTextNode = [&workElement, &descriptionElement, &svg]( const QString & tag, const QString & value )
1406  {
1407  // inkscape compatible
1408  QDomElement element = svg.createElement( tag );
1409  QDomText t = svg.createTextNode( value );
1410  element.appendChild( t );
1411  workElement.appendChild( element );
1412 
1413  // svg spec compatible
1414  descriptionElement.setAttribute( tag, value );
1415  };
1416 
1417  addTextNode( QStringLiteral( "dc:format" ), QStringLiteral( "image/svg+xml" ) );
1418  addTextNode( QStringLiteral( "dc:title" ), metadata.title() );
1419  addTextNode( QStringLiteral( "dc:date" ), metadata.creationDateTime().toString( Qt::ISODate ) );
1420  addTextNode( QStringLiteral( "dc:identifier" ), metadata.identifier() );
1421  addTextNode( QStringLiteral( "dc:description" ), metadata.abstract() );
1422 
1423  auto addAgentNode = [&workElement, &descriptionElement, &svg]( const QString & tag, const QString & value )
1424  {
1425  // inkscape compatible
1426  QDomElement inkscapeElement = svg.createElement( tag );
1427  QDomElement agentElement = svg.createElement( QStringLiteral( "cc:Agent" ) );
1428  QDomElement titleElement = svg.createElement( QStringLiteral( "dc:title" ) );
1429  QDomText t = svg.createTextNode( value );
1430  titleElement.appendChild( t );
1431  agentElement.appendChild( titleElement );
1432  inkscapeElement.appendChild( agentElement );
1433  workElement.appendChild( inkscapeElement );
1434 
1435  // svg spec compatible
1436  QDomElement bagElement = svg.createElement( QStringLiteral( "rdf:Bag" ) );
1437  QDomElement liElement = svg.createElement( QStringLiteral( "rdf:li" ) );
1438  t = svg.createTextNode( value );
1439  liElement.appendChild( t );
1440  bagElement.appendChild( liElement );
1441 
1442  QDomElement element = svg.createElement( tag );
1443  element.appendChild( bagElement );
1444  descriptionElement.appendChild( element );
1445  };
1446 
1447  addAgentNode( QStringLiteral( "dc:creator" ), metadata.author() );
1448  addAgentNode( QStringLiteral( "dc:publisher" ), QStringLiteral( "QGIS %1" ).arg( Qgis::version() ) );
1449 
1450  // keywords
1451  {
1452  QDomElement element = svg.createElement( QStringLiteral( "dc:subject" ) );
1453  QDomElement bagElement = svg.createElement( QStringLiteral( "rdf:Bag" ) );
1454  QgsAbstractMetadataBase::KeywordMap keywords = metadata.keywords();
1455  for ( auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
1456  {
1457  const QStringList words = it.value();
1458  for ( const QString &keyword : words )
1459  {
1460  QDomElement liElement = svg.createElement( QStringLiteral( "rdf:li" ) );
1461  QDomText t = svg.createTextNode( keyword );
1462  liElement.appendChild( t );
1463  bagElement.appendChild( liElement );
1464  }
1465  }
1466  element.appendChild( bagElement );
1467  workElement.appendChild( element );
1468  descriptionElement.appendChild( element );
1469  }
1470 
1471  rdfElement.appendChild( descriptionElement );
1472  rdfElement.appendChild( workElement );
1473  metadataElement.appendChild( rdfElement );
1474  svg.documentElement().appendChild( metadataElement );
1475  svg.documentElement().setAttribute( QStringLiteral( "xmlns:cc" ), QStringLiteral( "http://creativecommons.org/ns#" ) );
1476 }
1477 
1478 std::unique_ptr<double[]> QgsLayoutExporter::computeGeoTransform( const QgsLayoutItemMap *map, const QRectF &region, double dpi ) const
1479 {
1480  if ( !map )
1481  map = mLayout->referenceMap();
1482 
1483  if ( !map )
1484  return nullptr;
1485 
1486  if ( dpi < 0 )
1487  dpi = mLayout->renderContext().dpi();
1488 
1489  // calculate region of composition to export (in mm)
1490  QRectF exportRegion = region;
1491  if ( !exportRegion.isValid() )
1492  {
1493  int pageNumber = map->page();
1494 
1495  QgsLayoutItemPage *page = mLayout->pageCollection()->page( pageNumber );
1496  double pageY = page->pos().y();
1497  QSizeF pageSize = page->rect().size();
1498  exportRegion = QRectF( 0, pageY, pageSize.width(), pageSize.height() );
1499  }
1500 
1501  // map rectangle (in mm)
1502  QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
1503 
1504  // destination width/height in mm
1505  double outputHeightMM = exportRegion.height();
1506  double outputWidthMM = exportRegion.width();
1507 
1508  // map properties
1509  QgsRectangle mapExtent = map->extent();
1510  double mapXCenter = mapExtent.center().x();
1511  double mapYCenter = mapExtent.center().y();
1512  double alpha = - map->mapRotation() / 180 * M_PI;
1513  double sinAlpha = std::sin( alpha );
1514  double cosAlpha = std::cos( alpha );
1515 
1516  // get the extent (in map units) for the exported region
1517  QPointF mapItemPos = map->pos();
1518  //adjust item position so it is relative to export region
1519  mapItemPos.rx() -= exportRegion.left();
1520  mapItemPos.ry() -= exportRegion.top();
1521 
1522  // calculate extent of entire page in map units
1523  double xRatio = mapExtent.width() / mapItemSceneRect.width();
1524  double yRatio = mapExtent.height() / mapItemSceneRect.height();
1525  double xmin = mapExtent.xMinimum() - mapItemPos.x() * xRatio;
1526  double ymax = mapExtent.yMaximum() + mapItemPos.y() * yRatio;
1527  QgsRectangle paperExtent( xmin, ymax - outputHeightMM * yRatio, xmin + outputWidthMM * xRatio, ymax );
1528 
1529  // calculate origin of page
1530  double X0 = paperExtent.xMinimum();
1531  double Y0 = paperExtent.yMaximum();
1532 
1533  if ( !qgsDoubleNear( alpha, 0.0 ) )
1534  {
1535  // translate origin to account for map rotation
1536  double X1 = X0 - mapXCenter;
1537  double Y1 = Y0 - mapYCenter;
1538  double X2 = X1 * cosAlpha + Y1 * sinAlpha;
1539  double Y2 = -X1 * sinAlpha + Y1 * cosAlpha;
1540  X0 = X2 + mapXCenter;
1541  Y0 = Y2 + mapYCenter;
1542  }
1543 
1544  // calculate scaling of pixels
1545  int pageWidthPixels = static_cast< int >( dpi * outputWidthMM / 25.4 );
1546  int pageHeightPixels = static_cast< int >( dpi * outputHeightMM / 25.4 );
1547  double pixelWidthScale = paperExtent.width() / pageWidthPixels;
1548  double pixelHeightScale = paperExtent.height() / pageHeightPixels;
1549 
1550  // transform matrix
1551  std::unique_ptr<double[]> t( new double[6] );
1552  t[0] = X0;
1553  t[1] = cosAlpha * pixelWidthScale;
1554  t[2] = -sinAlpha * pixelWidthScale;
1555  t[3] = Y0;
1556  t[4] = -sinAlpha * pixelHeightScale;
1557  t[5] = -cosAlpha * pixelHeightScale;
1558 
1559  return t;
1560 }
1561 
1562 void QgsLayoutExporter::writeWorldFile( const QString &worldFileName, double a, double b, double c, double d, double e, double f ) const
1563 {
1564  QFile worldFile( worldFileName );
1565  if ( !worldFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
1566  {
1567  return;
1568  }
1569  QTextStream fout( &worldFile );
1570 
1571  // QString::number does not use locale settings (for the decimal point)
1572  // which is what we want here
1573  fout << QString::number( a, 'f', 12 ) << "\r\n";
1574  fout << QString::number( d, 'f', 12 ) << "\r\n";
1575  fout << QString::number( b, 'f', 12 ) << "\r\n";
1576  fout << QString::number( e, 'f', 12 ) << "\r\n";
1577  fout << QString::number( c, 'f', 12 ) << "\r\n";
1578  fout << QString::number( f, 'f', 12 ) << "\r\n";
1579 }
1580 
1581 bool QgsLayoutExporter::georeferenceOutput( const QString &file, QgsLayoutItemMap *map, const QRectF &exportRegion, double dpi ) const
1582 {
1583  return georeferenceOutputPrivate( file, map, exportRegion, dpi, false );
1584 }
1585 
1586 bool QgsLayoutExporter::georeferenceOutputPrivate( const QString &file, QgsLayoutItemMap *map, const QRectF &exportRegion, double dpi, bool includeGeoreference, bool includeMetadata ) const
1587 {
1588  if ( !mLayout )
1589  return false;
1590 
1591  if ( !map && includeGeoreference )
1592  map = mLayout->referenceMap();
1593 
1594  std::unique_ptr<double[]> t;
1595 
1596  if ( map && includeGeoreference )
1597  {
1598  if ( dpi < 0 )
1599  dpi = mLayout->renderContext().dpi();
1600 
1601  t = computeGeoTransform( map, exportRegion, dpi );
1602  }
1603 
1604  // important - we need to manually specify the DPI in advance, as GDAL will otherwise
1605  // assume a DPI of 150
1606  CPLSetConfigOption( "GDAL_PDF_DPI", QString::number( dpi ).toLocal8Bit().constData() );
1607  gdal::dataset_unique_ptr outputDS( GDALOpen( file.toLocal8Bit().constData(), GA_Update ) );
1608  if ( outputDS )
1609  {
1610  if ( t )
1611  GDALSetGeoTransform( outputDS.get(), t.get() );
1612 
1613  if ( includeMetadata )
1614  {
1615  QString creationDateString;
1616  const QDateTime creationDateTime = mLayout->project()->metadata().creationDateTime();
1617  if ( creationDateTime.isValid() )
1618  {
1619  creationDateString = QStringLiteral( "D:%1" ).arg( mLayout->project()->metadata().creationDateTime().toString( QStringLiteral( "yyyyMMddHHmmss" ) ) );
1620  if ( creationDateTime.timeZone().isValid() )
1621  {
1622  int offsetFromUtc = creationDateTime.timeZone().offsetFromUtc( creationDateTime );
1623  creationDateString += ( offsetFromUtc >= 0 ) ? '+' : '-';
1624  offsetFromUtc = std::abs( offsetFromUtc );
1625  int offsetHours = offsetFromUtc / 3600;
1626  int offsetMins = ( offsetFromUtc % 3600 ) / 60;
1627  creationDateString += QStringLiteral( "%1'%2'" ).arg( offsetHours ).arg( offsetMins );
1628  }
1629  }
1630  GDALSetMetadataItem( outputDS.get(), "CREATION_DATE", creationDateString.toUtf8().constData(), nullptr );
1631 
1632  GDALSetMetadataItem( outputDS.get(), "AUTHOR", mLayout->project()->metadata().author().toUtf8().constData(), nullptr );
1633  const QString creator = QStringLiteral( "QGIS %1" ).arg( Qgis::version() );
1634  GDALSetMetadataItem( outputDS.get(), "CREATOR", creator.toUtf8().constData(), nullptr );
1635  GDALSetMetadataItem( outputDS.get(), "PRODUCER", creator.toUtf8().constData(), nullptr );
1636  GDALSetMetadataItem( outputDS.get(), "SUBJECT", mLayout->project()->metadata().abstract().toUtf8().constData(), nullptr );
1637  GDALSetMetadataItem( outputDS.get(), "TITLE", mLayout->project()->metadata().title().toUtf8().constData(), nullptr );
1638 
1639  const QgsAbstractMetadataBase::KeywordMap keywords = mLayout->project()->metadata().keywords();
1640  QStringList allKeywords;
1641  for ( auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
1642  {
1643  allKeywords.append( QStringLiteral( "%1: %2" ).arg( it.key(), it.value().join( ',' ) ) );
1644  }
1645  const QString keywordString = allKeywords.join( ';' );
1646  GDALSetMetadataItem( outputDS.get(), "KEYWORDS", keywordString.toUtf8().constData(), nullptr );
1647  }
1648 
1649  if ( t )
1650  GDALSetProjection( outputDS.get(), map->crs().toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED_GDAL ).toLocal8Bit().constData() );
1651  }
1652  CPLSetConfigOption( "GDAL_PDF_DPI", nullptr );
1653 
1654  return true;
1655 }
1656 
1657 QString nameForLayerWithItems( const QList< QGraphicsItem * > &items, unsigned int layerId )
1658 {
1659  if ( items.count() == 1 )
1660  {
1661  if ( QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( items.at( 0 ) ) )
1662  {
1663  QString name = layoutItem->displayName();
1664  // cleanup default item ID format
1665  if ( name.startsWith( '<' ) && name.endsWith( '>' ) )
1666  name = name.mid( 1, name.length() - 2 );
1667  return name;
1668  }
1669  }
1670  else if ( items.count() > 1 )
1671  {
1672  QStringList currentLayerItemTypes;
1673  for ( QGraphicsItem *item : items )
1674  {
1675  if ( QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item ) )
1676  {
1677  const QString itemType = QgsApplication::layoutItemRegistry()->itemMetadata( layoutItem->type() )->visibleName();
1678  const QString itemTypePlural = QgsApplication::layoutItemRegistry()->itemMetadata( layoutItem->type() )->visiblePluralName();
1679  if ( !currentLayerItemTypes.contains( itemType ) && !currentLayerItemTypes.contains( itemTypePlural ) )
1680  currentLayerItemTypes << itemType;
1681  else if ( currentLayerItemTypes.contains( itemType ) )
1682  {
1683  currentLayerItemTypes.replace( currentLayerItemTypes.indexOf( itemType ), itemTypePlural );
1684  }
1685  }
1686  else
1687  {
1688  if ( !currentLayerItemTypes.contains( QObject::tr( "Other" ) ) )
1689  currentLayerItemTypes.append( QObject::tr( "Other" ) );
1690  }
1691  }
1692  return currentLayerItemTypes.join( QLatin1String( ", " ) );
1693  }
1694  return QObject::tr( "Layer %1" ).arg( layerId );
1695 }
1696 
1697 QgsLayoutExporter::ExportResult QgsLayoutExporter::handleLayeredExport( const QList<QGraphicsItem *> &items,
1698  const std::function<QgsLayoutExporter::ExportResult( unsigned int, const QgsLayoutItem::ExportLayerDetail & )> &exportFunc )
1699 {
1700  LayoutItemHider itemHider( items );
1701  ( void )itemHider;
1702 
1703  int prevType = -1;
1705  unsigned int layerId = 1;
1706  QgsLayoutItem::ExportLayerDetail layerDetails;
1707  itemHider.hideAll();
1708  const QList< QGraphicsItem * > itemsToIterate = itemHider.itemsToIterate();
1709  QList< QGraphicsItem * > currentLayerItems;
1710  for ( QGraphicsItem *item : itemsToIterate )
1711  {
1712  QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item );
1713 
1714  bool canPlaceInExistingLayer = false;
1715  if ( layoutItem )
1716  {
1717  switch ( layoutItem->exportLayerBehavior() )
1718  {
1720  {
1721  switch ( prevItemBehavior )
1722  {
1724  canPlaceInExistingLayer = true;
1725  break;
1726 
1728  canPlaceInExistingLayer = prevType == -1 || prevType == layoutItem->type();
1729  break;
1730 
1733  canPlaceInExistingLayer = false;
1734  break;
1735  }
1736  break;
1737  }
1738 
1740  {
1741  switch ( prevItemBehavior )
1742  {
1745  canPlaceInExistingLayer = prevType == -1 || prevType == layoutItem->type();
1746  break;
1747 
1750  canPlaceInExistingLayer = false;
1751  break;
1752  }
1753  break;
1754  }
1755 
1757  {
1758  canPlaceInExistingLayer = false;
1759  break;
1760  }
1761 
1763  canPlaceInExistingLayer = false;
1764  break;
1765  }
1766  prevItemBehavior = layoutItem->exportLayerBehavior();
1767  prevType = layoutItem->type();
1768  }
1769  else
1770  {
1771  prevItemBehavior = QgsLayoutItem::MustPlaceInOwnLayer;
1772  }
1773 
1774  if ( canPlaceInExistingLayer )
1775  {
1776  currentLayerItems << item;
1777  item->show();
1778  }
1779  else
1780  {
1781  if ( !currentLayerItems.isEmpty() )
1782  {
1783  layerDetails.name = nameForLayerWithItems( currentLayerItems, layerId );
1784 
1785  ExportResult result = exportFunc( layerId, layerDetails );
1786  if ( result != Success )
1787  return result;
1788  layerId++;
1789  currentLayerItems.clear();
1790  }
1791 
1792  itemHider.hideAll();
1793  item->show();
1794 
1795  if ( layoutItem && layoutItem->exportLayerBehavior() == QgsLayoutItem::ItemContainsSubLayers )
1796  {
1797  int layoutItemLayerIdx = 0;
1799  mLayout->renderContext().setCurrentExportLayer( layoutItemLayerIdx );
1801  layoutItem->startLayeredExport();
1802  while ( layoutItem->nextExportPart() )
1803  {
1805  mLayout->renderContext().setCurrentExportLayer( layoutItemLayerIdx );
1807 
1808  layerDetails = layoutItem->exportLayerDetails();
1809  ExportResult result = exportFunc( layerId, layerDetails );
1810  if ( result != Success )
1811  return result;
1812  layerId++;
1813 
1814  layoutItemLayerIdx++;
1815  }
1816  layerDetails.mapLayerId.clear();
1818  mLayout->renderContext().setCurrentExportLayer( -1 );
1820  layoutItem->stopLayeredExport();
1821  currentLayerItems.clear();
1822  }
1823  else
1824  {
1825  currentLayerItems << item;
1826  }
1827  }
1828  }
1829  if ( !currentLayerItems.isEmpty() )
1830  {
1831  layerDetails.name = nameForLayerWithItems( currentLayerItems, layerId );
1832  ExportResult result = exportFunc( layerId, layerDetails );
1833  if ( result != Success )
1834  return result;
1835  }
1836  return Success;
1837 }
1838 
1839 QgsVectorSimplifyMethod QgsLayoutExporter::createExportSimplifyMethod()
1840 {
1841  QgsVectorSimplifyMethod simplifyMethod;
1843  simplifyMethod.setForceLocalOptimization( true );
1844  // we use SnappedToGridGlobal, because it avoids gaps and slivers between previously adjacent polygons
1846  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.
1847  return simplifyMethod;
1848 }
1849 
1850 void QgsLayoutExporter::computeWorldFileParameters( double &a, double &b, double &c, double &d, double &e, double &f, double dpi ) const
1851 {
1852  if ( !mLayout )
1853  return;
1854 
1855  QgsLayoutItemMap *map = mLayout->referenceMap();
1856  if ( !map )
1857  {
1858  return;
1859  }
1860 
1861  int pageNumber = map->page();
1862  QgsLayoutItemPage *page = mLayout->pageCollection()->page( pageNumber );
1863  double pageY = page->pos().y();
1864  QSizeF pageSize = page->rect().size();
1865  QRectF pageRect( 0, pageY, pageSize.width(), pageSize.height() );
1866  computeWorldFileParameters( pageRect, a, b, c, d, e, f, dpi );
1867 }
1868 
1869 void QgsLayoutExporter::computeWorldFileParameters( const QRectF &exportRegion, double &a, double &b, double &c, double &d, double &e, double &f, double dpi ) const
1870 {
1871  if ( !mLayout )
1872  return;
1873 
1874  // World file parameters : affine transformation parameters from pixel coordinates to map coordinates
1875  QgsLayoutItemMap *map = mLayout->referenceMap();
1876  if ( !map )
1877  {
1878  return;
1879  }
1880 
1881  double destinationHeight = exportRegion.height();
1882  double destinationWidth = exportRegion.width();
1883 
1884  QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
1885  QgsRectangle mapExtent = map->extent();
1886 
1887  double alpha = map->mapRotation() / 180 * M_PI;
1888 
1889  double xRatio = mapExtent.width() / mapItemSceneRect.width();
1890  double yRatio = mapExtent.height() / mapItemSceneRect.height();
1891 
1892  double xCenter = mapExtent.center().x();
1893  double yCenter = mapExtent.center().y();
1894 
1895  // get the extent (in map units) for the region
1896  QPointF mapItemPos = map->pos();
1897  //adjust item position so it is relative to export region
1898  mapItemPos.rx() -= exportRegion.left();
1899  mapItemPos.ry() -= exportRegion.top();
1900 
1901  double xmin = mapExtent.xMinimum() - mapItemPos.x() * xRatio;
1902  double ymax = mapExtent.yMaximum() + mapItemPos.y() * yRatio;
1903  QgsRectangle paperExtent( xmin, ymax - destinationHeight * yRatio, xmin + destinationWidth * xRatio, ymax );
1904 
1905  double X0 = paperExtent.xMinimum();
1906  double Y0 = paperExtent.yMinimum();
1907 
1908  if ( dpi < 0 )
1909  dpi = mLayout->renderContext().dpi();
1910 
1911  int widthPx = static_cast< int >( dpi * destinationWidth / 25.4 );
1912  int heightPx = static_cast< int >( dpi * destinationHeight / 25.4 );
1913 
1914  double Ww = paperExtent.width() / widthPx;
1915  double Hh = paperExtent.height() / heightPx;
1916 
1917  // scaling matrix
1918  double s[6];
1919  s[0] = Ww;
1920  s[1] = 0;
1921  s[2] = X0;
1922  s[3] = 0;
1923  s[4] = -Hh;
1924  s[5] = Y0 + paperExtent.height();
1925 
1926  // rotation matrix
1927  double r[6];
1928  r[0] = std::cos( alpha );
1929  r[1] = -std::sin( alpha );
1930  r[2] = xCenter * ( 1 - std::cos( alpha ) ) + yCenter * std::sin( alpha );
1931  r[3] = std::sin( alpha );
1932  r[4] = std::cos( alpha );
1933  r[5] = - xCenter * std::sin( alpha ) + yCenter * ( 1 - std::cos( alpha ) );
1934 
1935  // result = rotation x scaling = rotation(scaling(X))
1936  a = r[0] * s[0] + r[1] * s[3];
1937  b = r[0] * s[1] + r[1] * s[4];
1938  c = r[0] * s[2] + r[1] * s[5] + r[2];
1939  d = r[3] * s[0] + r[4] * s[3];
1940  e = r[3] * s[1] + r[4] * s[4];
1941  f = r[3] * s[2] + r[4] * s[5] + r[5];
1942 }
1943 
1945 {
1946  if ( !layout )
1947  return false;
1948 
1949  QList< QgsLayoutItem *> items;
1950  layout->layoutItems( items );
1951 
1952  for ( QgsLayoutItem *currentItem : std::as_const( items ) )
1953  {
1954  // ignore invisible items, they won't affect the output in any way...
1955  if ( currentItem->isVisible() && currentItem->requiresRasterization() )
1956  return true;
1957  }
1958  return false;
1959 }
1960 
1962 {
1963  if ( !layout )
1964  return false;
1965 
1966  QList< QgsLayoutItem *> items;
1967  layout->layoutItems( items );
1968 
1969  for ( QgsLayoutItem *currentItem : std::as_const( items ) )
1970  {
1971  // ignore invisible items, they won't affect the output in any way...
1972  if ( currentItem->isVisible() && currentItem->containsAdvancedEffects() )
1973  return true;
1974  }
1975  return false;
1976 }
1977 
1978 QImage QgsLayoutExporter::createImage( const QgsLayoutExporter::ImageExportSettings &settings, int page, QRectF &bounds, bool &skipPage ) const
1979 {
1980  bounds = QRectF();
1981  skipPage = false;
1982 
1983  if ( settings.cropToContents )
1984  {
1985  if ( mLayout->pageCollection()->pageCount() == 1 )
1986  {
1987  // single page, so include everything
1988  bounds = mLayout->layoutBounds( true );
1989  }
1990  else
1991  {
1992  // multi page, so just clip to items on current page
1993  bounds = mLayout->pageItemBounds( page, true );
1994  }
1995  if ( bounds.width() <= 0 || bounds.height() <= 0 )
1996  {
1997  //invalid size, skip page
1998  skipPage = true;
1999  return QImage();
2000  }
2001 
2002  double pixelToLayoutUnits = mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 1, QgsUnitTypes::LayoutPixels ) );
2003  bounds = bounds.adjusted( -settings.cropMargins.left() * pixelToLayoutUnits,
2004  -settings.cropMargins.top() * pixelToLayoutUnits,
2005  settings.cropMargins.right() * pixelToLayoutUnits,
2006  settings.cropMargins.bottom() * pixelToLayoutUnits );
2007  return renderRegionToImage( bounds, QSize(), settings.dpi );
2008  }
2009  else
2010  {
2011  return renderPageToImage( page, settings.imageSize, settings.dpi );
2012  }
2013 }
2014 
2015 int QgsLayoutExporter::firstPageToBeExported( QgsLayout *layout )
2016 {
2017  const int pageCount = layout->pageCollection()->pageCount();
2018  for ( int i = 0; i < pageCount; ++i )
2019  {
2020  if ( !layout->pageCollection()->shouldExportPage( i ) )
2021  {
2022  continue;
2023  }
2024 
2025  return i;
2026  }
2027  return 0; // shouldn't really matter -- we aren't exporting ANY pages!
2028 }
2029 
2031 {
2032  if ( details.page == 0 )
2033  {
2034  return details.directory + '/' + details.baseName + '.' + details.extension;
2035  }
2036  else
2037  {
2038  return details.directory + '/' + details.baseName + '_' + QString::number( details.page + 1 ) + '.' + details.extension;
2039  }
2040 }
2041 
2042 void QgsLayoutExporter::captureLabelingResults()
2043 {
2044  qDeleteAll( mLabelingResults );
2045  mLabelingResults.clear();
2046 
2047  QList< QgsLayoutItemMap * > maps;
2048  mLayout->layoutItems( maps );
2049 
2050  for ( QgsLayoutItemMap *map : std::as_const( maps ) )
2051  {
2052  mLabelingResults[ map->uuid() ] = map->mExportLabelingResults.release();
2053  }
2054 }
2055 
2056 bool QgsLayoutExporter::saveImage( const QImage &image, const QString &imageFilename, const QString &imageFormat, QgsProject *projectForMetadata )
2057 {
2058  QImageWriter w( imageFilename, imageFormat.toLocal8Bit().constData() );
2059  if ( imageFormat.compare( QLatin1String( "tiff" ), Qt::CaseInsensitive ) == 0 || imageFormat.compare( QLatin1String( "tif" ), Qt::CaseInsensitive ) == 0 )
2060  {
2061  w.setCompression( 1 ); //use LZW compression
2062  }
2063  if ( projectForMetadata )
2064  {
2065  w.setText( QStringLiteral( "Author" ), projectForMetadata->metadata().author() );
2066  const QString creator = QStringLiteral( "QGIS %1" ).arg( Qgis::version() );
2067  w.setText( QStringLiteral( "Creator" ), creator );
2068  w.setText( QStringLiteral( "Producer" ), creator );
2069  w.setText( QStringLiteral( "Subject" ), projectForMetadata->metadata().abstract() );
2070  w.setText( QStringLiteral( "Created" ), projectForMetadata->metadata().creationDateTime().toString( Qt::ISODate ) );
2071  w.setText( QStringLiteral( "Title" ), projectForMetadata->metadata().title() );
2072 
2073  const QgsAbstractMetadataBase::KeywordMap keywords = projectForMetadata->metadata().keywords();
2074  QStringList allKeywords;
2075  for ( auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
2076  {
2077  allKeywords.append( QStringLiteral( "%1: %2" ).arg( it.key(), it.value().join( ',' ) ) );
2078  }
2079  const QString keywordString = allKeywords.join( ';' );
2080  w.setText( QStringLiteral( "Keywords" ), keywordString );
2081  }
2082  return w.write( image );
2083 }
2084 
2085 #endif // ! QT_NO_PRINTER
static QString version()
Version string.
Definition: qgis.cpp:277
TextRenderFormat
Options for rendering text.
Definition: qgis.h:1167
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:103
QgsProjectMetadata metadata
Definition: qgsproject.h:116
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:139
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:2533
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:2532
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:1978
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.