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