QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
Loading...
Searching...
No Matches
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
20#include "qgsfeedback.h"
21#include "qgslabelingresults.h"
22#include "qgslayout.h"
25#include "qgslayoutitemmap.h"
27#include "qgslinestring.h"
28#include "qgsmessagelog.h"
29#include "qgsogrutils.h"
32#include "qgssettingstree.h"
33
34#include <QBuffer>
35#include <QImageWriter>
36#include <QSize>
37#include <QString>
38#include <QSvgGenerator>
39#include <QTextStream>
40#include <QTimeZone>
41
42using namespace Qt::StringLiterals;
43
44#if QT_VERSION >= QT_VERSION_CHECK( 6, 8, 0 )
45#include <QColorSpace>
46#include <QPdfOutputIntent>
47#endif
48#include <QXmlStreamWriter>
49
50#include "gdal.h"
51#include "cpl_conv.h"
52
54class LayoutContextPreviewSettingRestorer
55{
56 public:
57 LayoutContextPreviewSettingRestorer( QgsLayout *layout )
58 : mLayout( layout )
59 , mPreviousSetting( layout->renderContext().mIsPreviewRender )
60 {
61 mLayout->renderContext().mIsPreviewRender = false;
62 }
63
64 ~LayoutContextPreviewSettingRestorer() { mLayout->renderContext().mIsPreviewRender = mPreviousSetting; }
65
66 LayoutContextPreviewSettingRestorer( const LayoutContextPreviewSettingRestorer &other ) = delete;
67 LayoutContextPreviewSettingRestorer &operator=( const LayoutContextPreviewSettingRestorer &other ) = delete;
68
69 private:
70 QgsLayout *mLayout = nullptr;
71 bool mPreviousSetting = false;
72};
73
74class LayoutGuideHider
75{
76 public:
77 LayoutGuideHider( QgsLayout *layout )
78 : mLayout( layout )
79 {
80 const QList< QgsLayoutGuide * > guides = mLayout->guides().guides();
81 for ( QgsLayoutGuide *guide : guides )
82 {
83 mPrevVisibility.insert( guide, guide->item()->isVisible() );
84 guide->item()->setVisible( false );
85 }
86 }
87
88 ~LayoutGuideHider()
89 {
90 for ( auto it = mPrevVisibility.constBegin(); it != mPrevVisibility.constEnd(); ++it )
91 {
92 it.key()->item()->setVisible( it.value() );
93 }
94 }
95
96 LayoutGuideHider( const LayoutGuideHider &other ) = delete;
97 LayoutGuideHider &operator=( const LayoutGuideHider &other ) = delete;
98
99 private:
100 QgsLayout *mLayout = nullptr;
101 QHash< QgsLayoutGuide *, bool > mPrevVisibility;
102};
103
104class LayoutItemHider
105{
106 public:
107 explicit LayoutItemHider( const QList<QGraphicsItem *> &items )
108 {
109 mItemsToIterate.reserve( items.count() );
110 for ( QGraphicsItem *item : items )
111 {
112 const bool isVisible = item->isVisible();
113 mPrevVisibility[item] = isVisible;
114 if ( isVisible )
115 mItemsToIterate.append( item );
116 if ( QgsLayoutItem *layoutItem = dynamic_cast< QgsLayoutItem * >( item ) )
117 layoutItem->setProperty( "wasVisible", isVisible );
118
119 item->hide();
120 }
121 }
122
123 void hideAll()
124 {
125 for ( auto it = mPrevVisibility.constBegin(); it != mPrevVisibility.constEnd(); ++it )
126 {
127 it.key()->hide();
128 }
129 }
130
131 ~LayoutItemHider()
132 {
133 for ( auto it = mPrevVisibility.constBegin(); it != mPrevVisibility.constEnd(); ++it )
134 {
135 it.key()->setVisible( it.value() );
136 if ( QgsLayoutItem *layoutItem = dynamic_cast< QgsLayoutItem * >( it.key() ) )
137 layoutItem->setProperty( "wasVisible", QVariant() );
138 }
139 }
140
141 QList< QGraphicsItem * > itemsToIterate() const { return mItemsToIterate; }
142
143 LayoutItemHider( const LayoutItemHider &other ) = delete;
144 LayoutItemHider &operator=( const LayoutItemHider &other ) = delete;
145
146 private:
147 QList<QGraphicsItem * > mItemsToIterate;
148 QHash<QGraphicsItem *, bool> mPrevVisibility;
149};
150
152
154 = new QgsSettingsEntryBool( u"open-after-exporting-image"_s, QgsSettingsTree::sTreeLayout, false, QObject::tr( "Whether to open the exported image file with the default viewer after exporting a print layout" ) );
156 = new QgsSettingsEntryBool( u"open-after-exporting-pdf"_s, QgsSettingsTree::sTreeLayout, false, QObject::tr( "Whether to open the exported PDF file with the default viewer after exporting a print layout" ) );
158 = new QgsSettingsEntryBool( u"open-after-exporting-svg"_s, QgsSettingsTree::sTreeLayout, false, QObject::tr( "Whether to open the exported SVG file with the default viewer after exporting a print layout" ) );
160 = new QgsSettingsEntryInteger( u"image-quality"_s, QgsSettingsTree::sTreeLayout, 90, QObject::tr( "Image quality for lossy formats (e.g. JPEG)" ) );
161
165
167{
168 qDeleteAll( mLabelingResults );
169}
170
172{
173 return mLayout;
174}
175
176void QgsLayoutExporter::renderPage( QPainter *painter, int page ) const
177{
178 if ( !mLayout )
179 return;
180
181 if ( mLayout->pageCollection()->pageCount() <= page || page < 0 )
182 {
183 return;
184 }
185
186 QgsLayoutItemPage *pageItem = mLayout->pageCollection()->page( page );
187 if ( !pageItem )
188 {
189 return;
190 }
191
192 LayoutContextPreviewSettingRestorer restorer( mLayout );
193 ( void ) restorer;
194
195 QRectF paperRect = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() );
196 renderRegion( painter, paperRect );
197}
198
199QImage QgsLayoutExporter::renderPageToImage( int page, QSize imageSize, double dpi ) const
200{
201 if ( !mLayout )
202 return QImage();
203
204 if ( mLayout->pageCollection()->pageCount() <= page || page < 0 )
205 {
206 return QImage();
207 }
208
209 QgsLayoutItemPage *pageItem = mLayout->pageCollection()->page( page );
210 if ( !pageItem )
211 {
212 return QImage();
213 }
214
215 LayoutContextPreviewSettingRestorer restorer( mLayout );
216 ( void ) restorer;
217
218 QRectF paperRect = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() );
219
220 const double imageAspectRatio = static_cast< double >( imageSize.width() ) / imageSize.height();
221 const double paperAspectRatio = paperRect.width() / paperRect.height();
222 if ( imageSize.isValid() && ( !qgsDoubleNear( imageAspectRatio, paperAspectRatio, 0.008 ) ) )
223 {
224 // specified image size is wrong aspect ratio for paper rect - so ignore it and just use dpi
225 // this can happen e.g. as a result of data defined page sizes
226 // see https://github.com/qgis/QGIS/issues/26422
228 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 ) ), u"Layout"_s, Qgis::MessageLevel::Warning );
229 imageSize = QSize();
230 }
231
232 return renderRegionToImage( paperRect, imageSize, dpi );
233}
234
236class LayoutItemCacheSettingRestorer
237{
238 public:
239 LayoutItemCacheSettingRestorer( QgsLayout *layout )
240 : mLayout( layout )
241 {
242 const QList< QGraphicsItem * > items = mLayout->items();
243 for ( QGraphicsItem *item : items )
244 {
245 mPrevCacheMode.insert( item, item->cacheMode() );
246 item->setCacheMode( QGraphicsItem::NoCache );
247 }
248 }
249
250 ~LayoutItemCacheSettingRestorer()
251 {
252 for ( auto it = mPrevCacheMode.constBegin(); it != mPrevCacheMode.constEnd(); ++it )
253 {
254 it.key()->setCacheMode( it.value() );
255 }
256 }
257
258 LayoutItemCacheSettingRestorer( const LayoutItemCacheSettingRestorer &other ) = delete;
259 LayoutItemCacheSettingRestorer &operator=( const LayoutItemCacheSettingRestorer &other ) = delete;
260
261 private:
262 QgsLayout *mLayout = nullptr;
263 QHash< QGraphicsItem *, QGraphicsItem::CacheMode > mPrevCacheMode;
264};
265
267
268void QgsLayoutExporter::renderRegion( QPainter *painter, const QRectF &region ) const
269{
270 QPaintDevice *paintDevice = painter->device();
271 if ( !paintDevice || !mLayout )
272 {
273 return;
274 }
275
276 LayoutItemCacheSettingRestorer cacheRestorer( mLayout );
277 ( void ) cacheRestorer;
278 LayoutContextPreviewSettingRestorer restorer( mLayout );
279 ( void ) restorer;
280 LayoutGuideHider guideHider( mLayout );
281 ( void ) guideHider;
282
283 painter->setRenderHint( QPainter::Antialiasing, mLayout->renderContext().flags() & Qgis::LayoutRenderFlag::Antialiasing );
284
285 mLayout->render( painter, QRectF( 0, 0, paintDevice->width(), paintDevice->height() ), region );
286}
287
288QImage QgsLayoutExporter::renderRegionToImage( const QRectF &region, QSize imageSize, double dpi ) const
289{
290 if ( !mLayout )
291 return QImage();
292
293 LayoutContextPreviewSettingRestorer restorer( mLayout );
294 ( void ) restorer;
295
296 double resolution = mLayout->renderContext().dpi();
297 double oneInchInLayoutUnits = mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 1, Qgis::LayoutUnit::Inches ) );
298 if ( imageSize.isValid() )
299 {
300 //output size in pixels specified, calculate resolution using average of
301 //derived x/y dpi
302 resolution = ( imageSize.width() / region.width() + imageSize.height() / region.height() ) / 2.0 * oneInchInLayoutUnits;
303 }
304 else if ( dpi > 0 )
305 {
306 //dpi overridden by function parameters
307 resolution = dpi;
308 }
309
310 int width = imageSize.isValid() ? imageSize.width() : static_cast< int >( resolution * region.width() / oneInchInLayoutUnits );
311 int height = imageSize.isValid() ? imageSize.height() : static_cast< int >( resolution * region.height() / oneInchInLayoutUnits );
312
313 QImage image( QSize( width, height ), QImage::Format_ARGB32 );
314 if ( !image.isNull() )
315 {
316 // see https://doc.qt.io/qt-5/qpainter.html#limitations
317 if ( width > 32768 || height > 32768 )
318 QgsMessageLog::logMessage( QObject::tr( "Error: output width or height is larger than 32768 pixel, result will be clipped" ) );
319 image.setDotsPerMeterX( static_cast< int >( std::round( resolution / 25.4 * 1000 ) ) );
320 image.setDotsPerMeterY( static_cast< int>( std::round( resolution / 25.4 * 1000 ) ) );
321 image.fill( Qt::transparent );
322 QPainter imagePainter( &image );
323 renderRegion( &imagePainter, region );
324 if ( !imagePainter.isActive() )
325 return QImage();
326 }
327
328 return image;
329}
330
332class LayoutContextSettingsRestorer
333{
334 public:
336 LayoutContextSettingsRestorer( QgsLayout *layout )
337 : mLayout( layout )
338 , mPreviousDpi( layout->renderContext().dpi() )
339 , mPreviousFlags( layout->renderContext().flags() )
340 , mPreviousRasterPolicy( layout->renderContext().rasterizedRenderingPolicy() )
341 , mPreviousTextFormat( layout->renderContext().textRenderFormat() )
342 , mPreviousExportLayer( layout->renderContext().currentExportLayer() )
343 , mPreviousSimplifyMethod( layout->renderContext().simplifyMethod() )
344 , mPreviousMaskSettings( layout->renderContext().maskSettings() )
345 , mExportThemes( layout->renderContext().exportThemes() )
346 , mPredefinedScales( layout->renderContext().predefinedScales() )
347 {}
349
350 ~LayoutContextSettingsRestorer()
351 {
352 mLayout->renderContext().setDpi( mPreviousDpi );
353 mLayout->renderContext().setFlags( mPreviousFlags );
354 mLayout->renderContext().setRasterizedRenderingPolicy( mPreviousRasterPolicy );
355 mLayout->renderContext().setTextRenderFormat( mPreviousTextFormat );
357 mLayout->renderContext().setCurrentExportLayer( mPreviousExportLayer );
359 mLayout->renderContext().setSimplifyMethod( mPreviousSimplifyMethod );
360 mLayout->renderContext().setMaskSettings( mPreviousMaskSettings );
361 mLayout->renderContext().setExportThemes( mExportThemes );
362 mLayout->renderContext().setPredefinedScales( mPredefinedScales );
363 }
364
365 LayoutContextSettingsRestorer( const LayoutContextSettingsRestorer &other ) = delete;
366 LayoutContextSettingsRestorer &operator=( const LayoutContextSettingsRestorer &other ) = delete;
367
368 private:
369 QgsLayout *mLayout = nullptr;
370 double mPreviousDpi = 0;
371 Qgis::LayoutRenderFlags mPreviousFlags;
374 int mPreviousExportLayer = 0;
375 QgsVectorSimplifyMethod mPreviousSimplifyMethod;
376 QgsMaskRenderSettings mPreviousMaskSettings;
377 QStringList mExportThemes;
378 QVector< double > mPredefinedScales;
379};
381
383{
384 if ( !mLayout )
385 return PrintError;
386
387 ImageExportSettings settings = s;
388 if ( settings.dpi <= 0 )
389 settings.dpi = mLayout->renderContext().dpi();
390
391 mErrorFileName.clear();
392
393 int worldFilePageNo = -1;
394 if ( QgsLayoutItemMap *referenceMap = mLayout->referenceMap() )
395 {
396 worldFilePageNo = referenceMap->page();
397 }
398
399 QFileInfo fi( filePath );
400 QDir dir;
401 if ( !dir.exists( fi.absolutePath() ) )
402 {
403 dir.mkpath( fi.absolutePath() );
404 }
405
406 PageExportDetails pageDetails;
407 pageDetails.directory = fi.path();
408 pageDetails.baseName = fi.completeBaseName();
409 pageDetails.extension = fi.suffix();
410
411 LayoutContextPreviewSettingRestorer restorer( mLayout );
412 ( void ) restorer;
413 LayoutContextSettingsRestorer dpiRestorer( mLayout );
414 ( void ) dpiRestorer;
415 mLayout->renderContext().setDpi( settings.dpi );
416 mLayout->renderContext().setFlags( settings.flags );
417 mLayout->renderContext().setRasterizedRenderingPolicy( Qgis::RasterizedRenderingPolicy::PreferVector );
418 mLayout->renderContext().setPredefinedScales( settings.predefinedMapScales );
419
420 QList< int > pages;
421 if ( settings.pages.empty() )
422 {
423 for ( int page = 0; page < mLayout->pageCollection()->pageCount(); ++page )
424 pages << page;
425 }
426 else
427 {
428 for ( int page : std::as_const( settings.pages ) )
429 {
430 if ( page >= 0 && page < mLayout->pageCollection()->pageCount() )
431 pages << page;
432 }
433 }
434
435 for ( int page : std::as_const( pages ) )
436 {
437 if ( !mLayout->pageCollection()->shouldExportPage( page ) )
438 {
439 continue;
440 }
441
442 bool skip = false;
443 QRectF bounds;
444 QImage image = createImage( settings, page, bounds, skip );
445
446 if ( skip )
447 continue; // should skip this page, e.g. null size
448
449 pageDetails.page = page;
450 QString outputFilePath = generateFileName( pageDetails );
451
452 if ( image.isNull() )
453 {
454 mErrorFileName = outputFilePath;
455 return MemoryError;
456 }
457
458 if ( !saveImage( image, outputFilePath, pageDetails.extension, settings.exportMetadata ? mLayout->project() : nullptr, settings.quality ) )
459 {
460 mErrorFileName = outputFilePath;
461 return FileError;
462 }
463
464 const bool shouldGeoreference = ( page == worldFilePageNo );
465 if ( shouldGeoreference )
466 {
467 georeferenceOutputPrivate( outputFilePath, nullptr, bounds, settings.dpi, shouldGeoreference );
468
469 if ( settings.generateWorldFile )
470 {
471 // should generate world file for this page
472 double a, b, c, d, e, f;
473 if ( bounds.isValid() )
474 computeWorldFileParameters( bounds, a, b, c, d, e, f, settings.dpi );
475 else
476 computeWorldFileParameters( a, b, c, d, e, f, settings.dpi );
477
478 QFileInfo fi( outputFilePath );
479 // build the world file name
480 QString outputSuffix = fi.suffix();
481 QString worldFileName = fi.absolutePath() + '/' + fi.completeBaseName() + '.' + outputSuffix.at( 0 ) + outputSuffix.at( fi.suffix().size() - 1 ) + 'w';
482
483 writeWorldFile( worldFileName, a, b, c, d, e, f );
484 }
485 }
486 }
487 captureLabelingResults();
488 return Success;
489}
490
492 QgsAbstractLayoutIterator *iterator, const QString &baseFilePath, const QString &extension, const QgsLayoutExporter::ImageExportSettings &settings, QString &error, QgsFeedback *feedback
493)
494{
495 error.clear();
496
497 if ( !iterator->beginRender() )
498 return IteratorError;
499
500 int total = iterator->count();
501 double step = total > 0 ? 100.0 / total : 100.0;
502 int i = 0;
503 while ( iterator->next() )
504 {
505 if ( feedback )
506 {
507 if ( total > 0 )
508 feedback->setProperty( "progress", QObject::tr( "Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
509 else
510 feedback->setProperty( "progress", QObject::tr( "Exporting section %1" ).arg( i + 1 ) );
511 feedback->setProgress( step * i );
512 }
513 if ( feedback && feedback->isCanceled() )
514 {
515 iterator->endRender();
516 return Canceled;
517 }
518
519 QgsLayoutExporter exporter( iterator->layout() );
520 QString filePath = iterator->filePath( baseFilePath, extension );
521 ExportResult result = exporter.exportToImage( filePath, settings );
522 if ( result != Success )
523 {
524 if ( result == FileError )
525 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 ) );
526 else
527 error = exporter.errorMessage();
528 iterator->endRender();
529 return result;
530 }
531 i++;
532 }
533
534 if ( feedback )
535 {
536 feedback->setProgress( 100 );
537 }
538
539 iterator->endRender();
540 return Success;
541}
542
544{
545 if ( !mLayout || mLayout->pageCollection()->pageCount() == 0 )
546 return PrintError;
547
548 PdfExportSettings settings = s;
549 if ( settings.dpi <= 0 )
550 settings.dpi = mLayout->renderContext().dpi();
551
552 mErrorFileName.clear();
553
554 LayoutContextPreviewSettingRestorer restorer( mLayout );
555 ( void ) restorer;
556 LayoutContextSettingsRestorer contextRestorer( mLayout );
557 ( void ) contextRestorer;
558 mLayout->renderContext().setDpi( settings.dpi );
559 mLayout->renderContext().setPredefinedScales( settings.predefinedMapScales );
560 mLayout->renderContext().setMaskSettings( createExportMaskSettings() );
561
562 if ( settings.simplifyGeometries )
563 {
564 mLayout->renderContext().setSimplifyMethod( createExportSimplifyMethod() );
565 }
566
567 std::unique_ptr< QgsLayoutGeospatialPdfExporter > geospatialPdfExporter;
568 if ( settings.writeGeoPdf || settings.exportLayersAsSeperateFiles ) //#spellok
569 geospatialPdfExporter = std::make_unique< QgsLayoutGeospatialPdfExporter >( mLayout );
570
571 mLayout->renderContext().setFlags( settings.flags );
572
573 // If we are not printing as raster, temporarily disable advanced effects
574 // as QPrinter does not support composition modes and can result
575 // in items missing from the output
576
577 if ( settings.forceVectorOutput )
578 {
579 mLayout->renderContext().setRasterizedRenderingPolicy( Qgis::RasterizedRenderingPolicy::ForceVector );
580 }
581 else
582 {
583 mLayout->renderContext().setRasterizedRenderingPolicy( Qgis::RasterizedRenderingPolicy::PreferVector );
584 }
585
586 // Force synchronous legend graphics requests. Necessary for WMS GetPrint,
587 // as otherwise processing the request ends before remote graphics are downloaded.
588 mLayout->renderContext().setFlag( Qgis::LayoutRenderFlag::SynchronousLegendGraphics, true );
589
590 mLayout->renderContext().setTextRenderFormat( settings.textRenderFormat );
591 mLayout->renderContext().setExportThemes( settings.exportThemes );
592
593 ExportResult result = Success;
594 if ( settings.writeGeoPdf || settings.exportLayersAsSeperateFiles ) //#spellok
595 {
596 mLayout->renderContext().setFlag( Qgis::LayoutRenderFlag::RenderLabelsByMapLayer, true );
597
598 // here we need to export layers to individual PDFs
599 PdfExportSettings subSettings = settings;
600 subSettings.writeGeoPdf = false;
601 subSettings.exportLayersAsSeperateFiles = false; //#spellok
602
603 const QList<QGraphicsItem *> items = mLayout->items( Qt::AscendingOrder );
604
605 QList< QgsLayoutGeospatialPdfExporter::ComponentLayerDetail > pdfComponents;
606
607 const QDir baseDir = settings.exportLayersAsSeperateFiles ? QFileInfo( filePath ).dir() : QDir(); //#spellok
608 const QString baseFileName = settings.exportLayersAsSeperateFiles ? QFileInfo( filePath ).completeBaseName() : QString(); //#spellok
609
610 QSet<QString> mutuallyExclusiveGroups;
611
612 auto exportFunc =
613 [this, &subSettings, &pdfComponents, &geospatialPdfExporter, &settings, &baseDir, &baseFileName, &mutuallyExclusiveGroups]( unsigned int layerId, const QgsLayoutItem::ExportLayerDetail &layerDetail )
615 ExportResult layerExportResult = Success;
617 component.name = layerDetail.name;
618 component.mapLayerId = layerDetail.mapLayerId;
619 component.opacity = layerDetail.opacity;
620 component.compositionMode = layerDetail.compositionMode;
621 component.group = layerDetail.groupName;
622 if ( !layerDetail.mapTheme.isEmpty() )
623 {
624 component.group = layerDetail.mapTheme;
625 mutuallyExclusiveGroups.insert( layerDetail.mapTheme );
626 }
627
628 component.sourcePdfPath = settings.writeGeoPdf ? geospatialPdfExporter->generateTemporaryFilepath( u"layer_%1.pdf"_s.arg( layerId ) )
629 : baseDir.filePath( u"%1_%2.pdf"_s.arg( baseFileName ).arg( layerId, 4, 10, QChar( '0' ) ) );
630 pdfComponents << component;
631 QPdfWriter printer = QPdfWriter( component.sourcePdfPath );
632 preparePrintAsPdf( mLayout, &printer, component.sourcePdfPath );
633 preparePrint( mLayout, &printer, false );
634 QPainter p;
635 if ( !p.begin( &printer ) )
636 {
637 //error beginning print
638 return FileError;
639 }
640 p.setRenderHint( QPainter::LosslessImageRendering, mLayout->renderContext().flags() & Qgis::LayoutRenderFlag::LosslessImageRendering );
641 layerExportResult = printPrivate( &printer, p, false, subSettings.dpi, subSettings.rasterizeWholeImage );
642 p.end();
643 return layerExportResult;
644 };
645 auto getExportGroupNameFunc = []( QgsLayoutItem *item ) -> QString { return item->customProperty( u"pdfExportGroup"_s ).toString(); };
646 result = handleLayeredExport( items, exportFunc, getExportGroupNameFunc );
647 if ( result != Success )
648 return result;
649
650 if ( settings.writeGeoPdf )
651 {
653 details.dpi = settings.dpi;
654 // TODO - multipages
655 QgsLayoutSize pageSize = mLayout->pageCollection()->page( 0 )->sizeWithUnits();
656 QgsLayoutSize pageSizeMM = mLayout->renderContext().measurementConverter().convert( pageSize, Qgis::LayoutUnit::Millimeters );
657 details.pageSizeMm = pageSizeMM.toQSizeF();
658 details.mutuallyExclusiveGroups = mutuallyExclusiveGroups;
659
660 if ( settings.exportMetadata )
661 {
662 // copy layout metadata to geospatial PDF export settings
663 details.author = mLayout->project()->metadata().author();
664 details.producer = getCreator();
665 details.creator = getCreator();
666 details.creationDateTime = mLayout->project()->metadata().creationDateTime();
667 details.subject = mLayout->project()->metadata().abstract();
668 details.title = mLayout->project()->metadata().title();
669 details.keywords = mLayout->project()->metadata().keywords();
670 }
671
672 const QList< QgsMapLayer * > layers = mLayout->project()->mapLayers().values();
673 for ( const QgsMapLayer *layer : layers )
674 {
675 details.layerIdToPdfLayerTreeNameMap.insert( layer->id(), layer->name() );
676 }
677
678 if ( settings.appendGeoreference )
679 {
680 // setup georeferencing
681 QList< QgsLayoutItemMap * > maps;
682 mLayout->layoutItems( maps );
683 for ( QgsLayoutItemMap *map : std::as_const( maps ) )
684 {
686 georef.crs = map->crs();
687
688 const QPointF topLeft = map->mapToScene( QPointF( 0, 0 ) );
689 const QPointF topRight = map->mapToScene( QPointF( map->rect().width(), 0 ) );
690 const QPointF bottomLeft = map->mapToScene( QPointF( 0, map->rect().height() ) );
691 const QPointF bottomRight = map->mapToScene( QPointF( map->rect().width(), map->rect().height() ) );
692 const QgsLayoutPoint topLeftMm = mLayout->convertFromLayoutUnits( topLeft, Qgis::LayoutUnit::Millimeters );
693 const QgsLayoutPoint topRightMm = mLayout->convertFromLayoutUnits( topRight, Qgis::LayoutUnit::Millimeters );
694 const QgsLayoutPoint bottomLeftMm = mLayout->convertFromLayoutUnits( bottomLeft, Qgis::LayoutUnit::Millimeters );
695 const QgsLayoutPoint bottomRightMm = mLayout->convertFromLayoutUnits( bottomRight, Qgis::LayoutUnit::Millimeters );
696
698 QVector< QgsPointXY >()
699 << QgsPointXY( topLeftMm.x(), topLeftMm.y() )
700 << QgsPointXY( topRightMm.x(), topRightMm.y() )
701 << QgsPointXY( bottomRightMm.x(), bottomRightMm.y() )
702 << QgsPointXY( bottomLeftMm.x(), bottomLeftMm.y() )
703 << QgsPointXY( topLeftMm.x(), topLeftMm.y() )
704 ) );
705
706 georef.controlPoints.reserve( 4 );
707 const QTransform t = map->layoutToMapCoordsTransform();
708 const QgsPointXY topLeftMap = t.map( topLeft );
709 const QgsPointXY topRightMap = t.map( topRight );
710 const QgsPointXY bottomLeftMap = t.map( bottomLeft );
711 const QgsPointXY bottomRightMap = t.map( bottomRight );
712
713 georef.controlPoints << QgsAbstractGeospatialPdfExporter::ControlPoint( QgsPointXY( topLeftMm.x(), topLeftMm.y() ), topLeftMap );
714 georef.controlPoints << QgsAbstractGeospatialPdfExporter::ControlPoint( QgsPointXY( topRightMm.x(), topRightMm.y() ), topRightMap );
715 georef.controlPoints << QgsAbstractGeospatialPdfExporter::ControlPoint( QgsPointXY( bottomLeftMm.x(), bottomLeftMm.y() ), bottomLeftMap );
716 georef.controlPoints << QgsAbstractGeospatialPdfExporter::ControlPoint( QgsPointXY( bottomRightMm.x(), bottomRightMm.y() ), bottomRightMap );
717 details.georeferencedSections << georef;
718 }
719 }
720
721 details.customLayerTreeGroups = geospatialPdfExporter->customLayerTreeGroups();
722 details.initialLayerVisibility = geospatialPdfExporter->initialLayerVisibility();
723 details.layerOrder = geospatialPdfExporter->layerOrder();
724 details.layerTreeGroupOrder = geospatialPdfExporter->layerTreeGroupOrder();
725 details.includeFeatures = settings.includeGeoPdfFeatures;
726 details.useIso32000ExtensionFormatGeoreferencing = settings.useIso32000ExtensionFormatGeoreferencing;
727
728 if ( !geospatialPdfExporter->finalize( pdfComponents, filePath, details ) )
729 {
730 result = PrintError;
731 mErrorMessage = geospatialPdfExporter->errorMessage();
732 }
733 }
734 else
735 {
736 result = Success;
737 }
738 }
739 else
740 {
741 QPdfWriter printer = QPdfWriter( filePath );
742 preparePrintAsPdf( mLayout, &printer, filePath );
743 preparePrint( mLayout, &printer, false );
744 QPainter p;
745 if ( !p.begin( &printer ) )
746 {
747 //error beginning print
748 return FileError;
749 }
750 p.setRenderHint( QPainter::LosslessImageRendering, mLayout->renderContext().flags() & Qgis::LayoutRenderFlag::LosslessImageRendering );
751 result = printPrivate( &printer, p, false, settings.dpi, settings.rasterizeWholeImage );
752 p.end();
753
754 bool shouldAppendGeoreference = settings.appendGeoreference && mLayout && mLayout->referenceMap() && mLayout->referenceMap()->page() == 0;
755 if ( settings.appendGeoreference || settings.exportMetadata )
756 {
757 georeferenceOutputPrivate( filePath, nullptr, QRectF(), settings.dpi, shouldAppendGeoreference, settings.exportMetadata );
758 }
759 }
760 captureLabelingResults();
761 return result;
762}
763
765 QgsAbstractLayoutIterator *iterator, const QString &fileName, const QgsLayoutExporter::PdfExportSettings &s, QString &error, QgsFeedback *feedback
766)
767{
768 error.clear();
769
770 if ( !iterator->beginRender() )
771 return IteratorError;
772
773 PdfExportSettings settings = s;
774
775 QPdfWriter printer = QPdfWriter( fileName );
776 QPainter p;
777
778 int total = iterator->count();
779 double step = total > 0 ? 100.0 / total : 100.0;
780 int i = 0;
781 bool first = true;
782 while ( iterator->next() )
783 {
784 if ( feedback )
785 {
786 if ( total > 0 )
787 feedback->setProperty( "progress", QObject::tr( "Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
788 else
789 feedback->setProperty( "progress", QObject::tr( "Exporting section %1" ).arg( i + 1 ) );
790 feedback->setProgress( step * i );
791 }
792 if ( feedback && feedback->isCanceled() )
793 {
794 iterator->endRender();
795 return Canceled;
796 }
797
798 if ( s.dpi <= 0 )
799 settings.dpi = iterator->layout()->renderContext().dpi();
800
801 LayoutContextPreviewSettingRestorer restorer( iterator->layout() );
802 ( void ) restorer;
803 LayoutContextSettingsRestorer contextRestorer( iterator->layout() );
804 ( void ) contextRestorer;
805 iterator->layout()->renderContext().setDpi( settings.dpi );
806
807 iterator->layout()->renderContext().setFlags( settings.flags );
809 iterator->layout()->renderContext().setMaskSettings( createExportMaskSettings() );
810
811 if ( settings.simplifyGeometries )
812 {
813 iterator->layout()->renderContext().setSimplifyMethod( createExportSimplifyMethod() );
814 }
815
816 // If we are not printing as raster, temporarily disable advanced effects
817 // as QPrinter does not support composition modes and can result
818 // in items missing from the output
819 if ( settings.forceVectorOutput )
820 {
822 }
823 else
824 {
826 }
827 iterator->layout()->renderContext().setTextRenderFormat( settings.textRenderFormat );
828
829 if ( first )
830 {
831 preparePrintAsPdf( iterator->layout(), &printer, fileName );
832 preparePrint( iterator->layout(), &printer, false );
833
834 if ( !p.begin( &printer ) )
835 {
836 //error beginning print
837 return PrintError;
838 }
839 p.setRenderHint( QPainter::LosslessImageRendering, iterator->layout()->renderContext().flags() & Qgis::LayoutRenderFlag::LosslessImageRendering );
840 }
841
842 QgsLayoutExporter exporter( iterator->layout() );
843
844 ExportResult result = exporter.printPrivate( &printer, p, !first, settings.dpi, settings.rasterizeWholeImage );
845 if ( result != Success )
846 {
847 if ( result == FileError )
848 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 ) );
849 else
850 error = exporter.errorMessage();
851
852 iterator->endRender();
853 return result;
854 }
855 first = false;
856 i++;
857 }
858
859 if ( feedback )
860 {
861 feedback->setProgress( 100 );
862 }
863
864 iterator->endRender();
865 return Success;
866}
867
869 QgsAbstractLayoutIterator *iterator, const QString &baseFilePath, const QgsLayoutExporter::PdfExportSettings &settings, QString &error, QgsFeedback *feedback
870)
871{
872 error.clear();
873
874 if ( !iterator->beginRender() )
875 return IteratorError;
876
877 int total = iterator->count();
878 double step = total > 0 ? 100.0 / total : 100.0;
879 int i = 0;
880 while ( iterator->next() )
881 {
882 if ( feedback )
883 {
884 if ( total > 0 )
885 feedback->setProperty( "progress", QObject::tr( "Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
886 else
887 feedback->setProperty( "progress", QObject::tr( "Exporting section %1" ).arg( i + 1 ) );
888 feedback->setProgress( step * i );
889 }
890 if ( feedback && feedback->isCanceled() )
891 {
892 iterator->endRender();
893 return Canceled;
894 }
895
896 QString filePath = iterator->filePath( baseFilePath, u"pdf"_s );
897
898 QgsLayoutExporter exporter( iterator->layout() );
899 ExportResult result = exporter.exportToPdf( filePath, settings );
900 if ( result != Success )
901 {
902 if ( result == FileError )
903 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 ) );
904 else
905 error = exporter.errorMessage();
906 iterator->endRender();
907 return result;
908 }
909 i++;
910 }
911
912 if ( feedback )
913 {
914 feedback->setProgress( 100 );
915 }
916
917 iterator->endRender();
918 return Success;
919}
920
921#if defined( HAVE_QTPRINTER )
922QgsLayoutExporter::ExportResult QgsLayoutExporter::print( QPrinter &printer, const QgsLayoutExporter::PrintExportSettings &s )
923{
924 if ( !mLayout )
925 return PrintError;
926
928 if ( settings.dpi <= 0 )
929 settings.dpi = mLayout->renderContext().dpi();
930
931 mErrorFileName.clear();
932
933 LayoutContextPreviewSettingRestorer restorer( mLayout );
934 ( void ) restorer;
935 LayoutContextSettingsRestorer contextRestorer( mLayout );
936 ( void ) contextRestorer;
937 mLayout->renderContext().setDpi( settings.dpi );
938
939 mLayout->renderContext().setFlags( settings.flags );
940 mLayout->renderContext().setPredefinedScales( settings.predefinedMapScales );
941 // If we are not printing as raster, temporarily disable advanced effects
942 // as QPrinter does not support composition modes and can result
943 // in items missing from the output
944 if ( !settings.rasterizeWholeImage )
945 mLayout->renderContext().setRasterizedRenderingPolicy( Qgis::RasterizedRenderingPolicy::ForceVector );
946 preparePrint( mLayout, &printer, true );
947 QPainter p;
948 if ( !p.begin( &printer ) )
949 {
950 //error beginning print
951 return PrintError;
952 }
953 p.setRenderHint( QPainter::LosslessImageRendering, mLayout->renderContext().flags() & Qgis::LayoutRenderFlag::LosslessImageRendering );
954 ExportResult result = printPrivate( &printer, p, false, settings.dpi, settings.rasterizeWholeImage );
955 p.end();
956
957 captureLabelingResults();
958 return result;
959}
960
961QgsLayoutExporter::ExportResult QgsLayoutExporter::print( QgsAbstractLayoutIterator *iterator, QPrinter &printer, const QgsLayoutExporter::PrintExportSettings &s, QString &error, QgsFeedback *feedback )
962{
963 error.clear();
964
965 if ( !iterator->beginRender() )
966 return IteratorError;
967
968 PrintExportSettings settings = s;
969
970 QPainter p;
971
972 int total = iterator->count();
973 double step = total > 0 ? 100.0 / total : 100.0;
974 int i = 0;
975 bool first = true;
976 while ( iterator->next() )
977 {
978 if ( feedback )
979 {
980 if ( total > 0 )
981 feedback->setProperty( "progress", QObject::tr( "Printing %1 of %2" ).arg( i + 1 ).arg( total ) );
982 else
983 feedback->setProperty( "progress", QObject::tr( "Printing section %1" ).arg( i + 1 ) );
984 feedback->setProgress( step * i );
985 }
986 if ( feedback && feedback->isCanceled() )
987 {
988 iterator->endRender();
989 return Canceled;
990 }
991
992 if ( s.dpi <= 0 )
993 settings.dpi = iterator->layout()->renderContext().dpi();
994
995 LayoutContextPreviewSettingRestorer restorer( iterator->layout() );
996 ( void ) restorer;
997 LayoutContextSettingsRestorer contextRestorer( iterator->layout() );
998 ( void ) contextRestorer;
999 iterator->layout()->renderContext().setDpi( settings.dpi );
1000
1001 iterator->layout()->renderContext().setFlags( settings.flags );
1002 iterator->layout()->renderContext().setPredefinedScales( settings.predefinedMapScales );
1003
1004 // If we are not printing as raster, temporarily disable advanced effects
1005 // as QPrinter does not support composition modes and can result
1006 // in items missing from the output
1007 if ( !settings.rasterizeWholeImage )
1009
1010 if ( first )
1011 {
1012 preparePrint( iterator->layout(), &printer, true );
1013
1014 if ( !p.begin( &printer ) )
1015 {
1016 //error beginning print
1017 return PrintError;
1018 }
1019 p.setRenderHint( QPainter::LosslessImageRendering, iterator->layout()->renderContext().flags() & Qgis::LayoutRenderFlag::LosslessImageRendering );
1020 }
1021
1022 QgsLayoutExporter exporter( iterator->layout() );
1023
1024 ExportResult result = exporter.printPrivate( &printer, p, !first, settings.dpi, settings.rasterizeWholeImage );
1025 if ( result != Success )
1026 {
1027 iterator->endRender();
1028 error = exporter.errorMessage();
1029 return result;
1030 }
1031 first = false;
1032 i++;
1033 }
1034
1035 if ( feedback )
1036 {
1037 feedback->setProgress( 100 );
1038 }
1039
1040 iterator->endRender();
1041 return Success;
1042}
1043#endif // HAVE_QTPRINTER
1044
1046{
1047 if ( !mLayout )
1048 return PrintError;
1049
1050 SvgExportSettings settings = s;
1051 if ( settings.dpi <= 0 )
1052 settings.dpi = mLayout->renderContext().dpi();
1053
1054 mErrorFileName.clear();
1055
1056 LayoutContextPreviewSettingRestorer restorer( mLayout );
1057 ( void ) restorer;
1058 LayoutContextSettingsRestorer contextRestorer( mLayout );
1059 ( void ) contextRestorer;
1060 mLayout->renderContext().setDpi( settings.dpi );
1061
1062 mLayout->renderContext().setFlags( settings.flags );
1063 if ( settings.forceVectorOutput )
1064 {
1065 mLayout->renderContext().setRasterizedRenderingPolicy( Qgis::RasterizedRenderingPolicy::ForceVector );
1066 }
1067 else
1068 {
1069 mLayout->renderContext().setRasterizedRenderingPolicy( Qgis::RasterizedRenderingPolicy::PreferVector );
1070 }
1071
1072 mLayout->renderContext().setTextRenderFormat( s.textRenderFormat );
1073 mLayout->renderContext().setPredefinedScales( settings.predefinedMapScales );
1074 mLayout->renderContext().setMaskSettings( createExportMaskSettings() );
1075
1076 if ( settings.simplifyGeometries )
1077 {
1078 mLayout->renderContext().setSimplifyMethod( createExportSimplifyMethod() );
1079 }
1080
1081 QFileInfo fi( filePath );
1082 PageExportDetails pageDetails;
1083 pageDetails.directory = fi.path();
1084 pageDetails.baseName = fi.baseName();
1085 pageDetails.extension = fi.completeSuffix();
1086
1087 double inchesToLayoutUnits = mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 1, Qgis::LayoutUnit::Inches ) );
1088
1089 for ( int i = 0; i < mLayout->pageCollection()->pageCount(); ++i )
1090 {
1091 if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1092 {
1093 continue;
1094 }
1095
1096 pageDetails.page = i;
1097 QString fileName = generateFileName( pageDetails );
1098
1099 QgsLayoutItemPage *pageItem = mLayout->pageCollection()->page( i );
1100 QRectF bounds;
1101 if ( settings.cropToContents )
1102 {
1103 if ( mLayout->pageCollection()->pageCount() == 1 )
1104 {
1105 // single page, so include everything
1106 bounds = mLayout->layoutBounds( true );
1107 }
1108 else
1109 {
1110 // multi page, so just clip to items on current page
1111 bounds = mLayout->pageItemBounds( i, true );
1112 }
1113 bounds = bounds.adjusted( -settings.cropMargins.left(), -settings.cropMargins.top(), settings.cropMargins.right(), settings.cropMargins.bottom() );
1114 }
1115 else
1116 {
1117 bounds = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() );
1118 }
1119
1120 //width in pixel
1121 int width = static_cast< int >( bounds.width() * settings.dpi / inchesToLayoutUnits );
1122 //height in pixel
1123 int height = static_cast< int >( bounds.height() * settings.dpi / inchesToLayoutUnits );
1124 if ( width == 0 || height == 0 )
1125 {
1126 //invalid size, skip this page
1127 continue;
1128 }
1129
1130 if ( settings.exportAsLayers )
1131 {
1132 mLayout->renderContext().setFlag( Qgis::LayoutRenderFlag::RenderLabelsByMapLayer, settings.exportLabelsToSeparateLayers );
1133 const QRectF paperRect = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() );
1134 QDomDocument svg;
1135 QDomNode svgDocRoot;
1136 const QList<QGraphicsItem *> items = mLayout->items( paperRect, Qt::IntersectsItemBoundingRect, Qt::AscendingOrder );
1137
1138 auto exportFunc =
1139 [this, &settings, width, height, i, bounds, fileName, &svg, &svgDocRoot]( unsigned int layerId, const QgsLayoutItem::ExportLayerDetail &layerDetail ) -> QgsLayoutExporter::ExportResult {
1140 return renderToLayeredSvg( settings, width, height, i, bounds, fileName, layerId, layerDetail.name, svg, svgDocRoot, settings.exportMetadata );
1141 };
1142 auto getExportGroupNameFunc = []( QgsLayoutItem * ) -> QString { return QString(); };
1143 ExportResult res = handleLayeredExport( items, exportFunc, getExportGroupNameFunc );
1144 if ( res != Success )
1145 return res;
1146
1147 if ( settings.exportMetadata )
1148 appendMetadataToSvg( svg );
1149
1150 QFile out( fileName );
1151 bool openOk = out.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate );
1152 if ( !openOk )
1153 {
1154 mErrorFileName = fileName;
1155 return FileError;
1156 }
1157
1158 out.write( svg.toByteArray() );
1159 }
1160 else
1161 {
1162 QBuffer svgBuffer;
1163 {
1164 QSvgGenerator generator;
1165 if ( settings.exportMetadata )
1166 {
1167 generator.setTitle( mLayout->project()->metadata().title() );
1168 generator.setDescription( mLayout->project()->metadata().abstract() );
1169 }
1170 generator.setOutputDevice( &svgBuffer );
1171 generator.setSize( QSize( width, height ) );
1172 generator.setViewBox( QRect( 0, 0, width, height ) );
1173 generator.setResolution( static_cast< int >( std::round( settings.dpi ) ) );
1174
1175 QPainter p;
1176 bool createOk = p.begin( &generator );
1177 if ( !createOk )
1178 {
1179 mErrorFileName = fileName;
1180 return FileError;
1181 }
1182
1183 if ( settings.cropToContents )
1184 renderRegion( &p, bounds );
1185 else
1186 renderPage( &p, i );
1187
1188 p.end();
1189 }
1190 {
1191 svgBuffer.close();
1192 svgBuffer.open( QIODevice::ReadOnly );
1193 QDomDocument svg;
1194 QString errorMsg;
1195 int errorLine;
1196 if ( !svg.setContent( &svgBuffer, false, &errorMsg, &errorLine ) )
1197 {
1198 mErrorFileName = fileName;
1199 return SvgLayerError;
1200 }
1201
1202 if ( settings.exportMetadata )
1203 appendMetadataToSvg( svg );
1204
1205 QFile out( fileName );
1206 bool openOk = out.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate );
1207 if ( !openOk )
1208 {
1209 mErrorFileName = fileName;
1210 return FileError;
1211 }
1212
1213 out.write( svg.toByteArray() );
1214 }
1215 }
1216 }
1217 captureLabelingResults();
1218 return Success;
1219}
1220
1222 QgsAbstractLayoutIterator *iterator, const QString &baseFilePath, const QgsLayoutExporter::SvgExportSettings &settings, QString &error, QgsFeedback *feedback
1223)
1224{
1225 error.clear();
1226
1227 if ( !iterator->beginRender() )
1228 return IteratorError;
1229
1230 int total = iterator->count();
1231 double step = total > 0 ? 100.0 / total : 100.0;
1232 int i = 0;
1233 while ( iterator->next() )
1234 {
1235 if ( feedback )
1236 {
1237 if ( total > 0 )
1238 feedback->setProperty( "progress", QObject::tr( "Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
1239 else
1240 feedback->setProperty( "progress", QObject::tr( "Exporting section %1" ).arg( i + 1 ) );
1241
1242 feedback->setProgress( step * i );
1243 }
1244 if ( feedback && feedback->isCanceled() )
1245 {
1246 iterator->endRender();
1247 return Canceled;
1248 }
1249
1250 QString filePath = iterator->filePath( baseFilePath, u"svg"_s );
1251
1252 QgsLayoutExporter exporter( iterator->layout() );
1253 ExportResult result = exporter.exportToSvg( filePath, settings );
1254 if ( result != Success )
1255 {
1256 if ( result == FileError )
1257 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 ) );
1258 else
1259 error = exporter.errorMessage();
1260 iterator->endRender();
1261 return result;
1262 }
1263 i++;
1264 }
1265
1266 if ( feedback )
1267 {
1268 feedback->setProgress( 100 );
1269 }
1270
1271 iterator->endRender();
1272 return Success;
1273}
1274
1275QMap<QString, QgsLabelingResults *> QgsLayoutExporter::labelingResults()
1276{
1277 return mLabelingResults;
1278}
1279
1280QMap<QString, QgsLabelingResults *> QgsLayoutExporter::takeLabelingResults()
1281{
1282 QMap<QString, QgsLabelingResults *> res;
1283 std::swap( mLabelingResults, res );
1284 return res;
1285}
1286
1287void QgsLayoutExporter::preparePrintAsPdf( QgsLayout *layout, QPdfWriter *device, const QString &filePath )
1288{
1289 QFileInfo fi( filePath );
1290 QDir dir;
1291 if ( !dir.exists( fi.absolutePath() ) )
1292 {
1293 dir.mkpath( fi.absolutePath() );
1294 }
1295
1296 updatePrinterPageSize( layout, device, firstPageToBeExported( layout ) );
1297
1298 // force a non empty title to avoid invalid (according to specification) PDF/X-4
1299 const QString title = !layout->project() || layout->project()->metadata().title().isEmpty() ? fi.baseName() : layout->project()->metadata().title();
1300
1301 device->setTitle( title );
1302
1303 QPagedPaintDevice::PdfVersion pdfVersion = QPagedPaintDevice::PdfVersion_1_4;
1304
1305#if QT_VERSION >= QT_VERSION_CHECK( 6, 8, 0 )
1306
1307 if ( const QgsProjectStyleSettings *styleSettings = ( layout->project() ? layout->project()->styleSettings() : nullptr ) )
1308 {
1309 // We don't want to let AUTO color model because we could end up writing RGB colors with a CMYK
1310 // output intent color model and vice versa, so we force color conversion
1311 switch ( styleSettings->colorModel() )
1312 {
1314 device->setColorModel( QPdfWriter::ColorModel::CMYK );
1315 break;
1316
1318 device->setColorModel( QPdfWriter::ColorModel::RGB );
1319 break;
1320 }
1321
1322 const QColorSpace colorSpace = styleSettings->colorSpace();
1323 if ( colorSpace.isValid() )
1324 {
1325 QPdfOutputIntent outputIntent;
1326 outputIntent.setOutputProfile( colorSpace );
1327 outputIntent.setOutputCondition( colorSpace.description() );
1328
1329 // There is no way to actually get the color space registry identifier or even
1330 // the registry it comes from.
1331 outputIntent.setOutputConditionIdentifier( u"Unknown identifier"_s );
1332 outputIntent.setRegistryName( u"Unknown registry"_s );
1333 device->setOutputIntent( outputIntent );
1334
1335 // PDF/X-4 standard allows PDF to be printing ready and is only possible if a color space has been set
1336 pdfVersion = QPagedPaintDevice::PdfVersion_X4;
1337 }
1338 }
1339
1340#endif
1341
1342 device->setPdfVersion( pdfVersion );
1343 setXmpMetadata( device, layout );
1344
1345 // TODO: add option for this in layout
1346 // May not work on Windows or non-X11 Linux. Works fine on Mac using QPrinter::NativeFormat
1347 //printer.setFontEmbeddingEnabled( true );
1348}
1349
1350void QgsLayoutExporter::preparePrint( QgsLayout *layout, QPagedPaintDevice *device, bool setFirstPageSize )
1351{
1352 if ( QPdfWriter *pdf = dynamic_cast<QPdfWriter *>( device ) )
1353 {
1354 pdf->setResolution( static_cast< int>( std::round( layout->renderContext().dpi() ) ) );
1355 }
1356#if defined( HAVE_QTPRINTER )
1357 else if ( QPrinter *printer = dynamic_cast<QPrinter *>( device ) )
1358 {
1359 printer->setFullPage( true );
1360 printer->setColorMode( QPrinter::Color );
1361 //set user-defined resolution
1362 printer->setResolution( static_cast< int>( std::round( layout->renderContext().dpi() ) ) );
1363 }
1364#endif
1365
1366 if ( setFirstPageSize )
1367 {
1368 updatePrinterPageSize( layout, device, firstPageToBeExported( layout ) );
1369 }
1370}
1371
1372QgsLayoutExporter::ExportResult QgsLayoutExporter::print( QPagedPaintDevice *device )
1373{
1374 if ( mLayout->pageCollection()->pageCount() == 0 )
1375 return PrintError;
1376
1377 preparePrint( mLayout, device, true );
1378 QPainter p;
1379 if ( !p.begin( device ) )
1380 {
1381 //error beginning print
1382 return PrintError;
1383 }
1384
1385 printPrivate( device, p );
1386 p.end();
1387 return Success;
1388}
1389
1390QgsLayoutExporter::ExportResult QgsLayoutExporter::printPrivate( QPagedPaintDevice *device, QPainter &painter, bool startNewPage, double dpi, bool rasterize )
1391{
1392 // layout starts page numbering at 0
1393 int fromPage = 0;
1394 int toPage = mLayout->pageCollection()->pageCount() - 1;
1395
1396#if defined( HAVE_QTPRINTER )
1397 if ( QPrinter *printer = dynamic_cast<QPrinter *>( device ) )
1398 {
1399 if ( printer->fromPage() >= 1 )
1400 fromPage = printer->fromPage() - 1;
1401 if ( printer->toPage() >= 1 )
1402 toPage = printer->toPage() - 1;
1403 }
1404#endif
1405
1406 bool pageExported = false;
1407 if ( rasterize )
1408 {
1409 for ( int i = fromPage; i <= toPage; ++i )
1410 {
1411 if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1412 {
1413 continue;
1414 }
1415
1416 updatePrinterPageSize( mLayout, device, i );
1417 if ( ( pageExported && i > fromPage ) || startNewPage )
1418 {
1419 device->newPage();
1420 }
1421
1422 QImage image = renderPageToImage( i, QSize(), dpi );
1423 if ( !image.isNull() )
1424 {
1425 QRectF targetArea( 0, 0, image.width(), image.height() );
1426 painter.drawImage( targetArea, image, targetArea );
1427 }
1428 else
1429 {
1430 return MemoryError;
1431 }
1432 pageExported = true;
1433 }
1434 }
1435 else
1436 {
1437 for ( int i = fromPage; i <= toPage; ++i )
1438 {
1439 if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1440 {
1441 continue;
1442 }
1443
1444 updatePrinterPageSize( mLayout, device, i );
1445
1446 if ( ( pageExported && i > fromPage ) || startNewPage )
1447 {
1448 device->newPage();
1449 }
1450 renderPage( &painter, i );
1451 pageExported = true;
1452 }
1453 }
1454 return Success;
1455}
1456
1457void QgsLayoutExporter::updatePrinterPageSize( QgsLayout *layout, QPagedPaintDevice *device, int page )
1458{
1459 QgsLayoutSize pageSize = layout->pageCollection()->page( page )->sizeWithUnits();
1460 QgsLayoutSize pageSizeMM = layout->renderContext().measurementConverter().convert( pageSize, Qgis::LayoutUnit::Millimeters );
1461
1462 QPageLayout pageLayout( QPageSize( pageSizeMM.toQSizeF(), QPageSize::Millimeter ), QPageLayout::Portrait, QMarginsF( 0, 0, 0, 0 ) );
1463 pageLayout.setMode( QPageLayout::FullPageMode );
1464 device->setPageLayout( pageLayout );
1465 device->setPageMargins( QMarginsF( 0, 0, 0, 0 ) );
1466
1467#if defined( HAVE_QTPRINTER )
1468 if ( QPrinter *printer = dynamic_cast<QPrinter *>( device ) )
1469 {
1470 printer->setFullPage( true );
1471 }
1472#endif
1473}
1474
1475QgsLayoutExporter::ExportResult QgsLayoutExporter::renderToLayeredSvg(
1476 const SvgExportSettings &settings,
1477 double width,
1478 double height,
1479 int page,
1480 const QRectF &bounds,
1481 const QString &filename,
1482 unsigned int svgLayerId,
1483 const QString &layerName,
1484 QDomDocument &svg,
1485 QDomNode &svgDocRoot,
1486 bool includeMetadata
1487) const
1488{
1489 QBuffer svgBuffer;
1490 {
1491 QSvgGenerator generator;
1492 if ( includeMetadata )
1493 {
1494 if ( const QgsMasterLayoutInterface *l = dynamic_cast< const QgsMasterLayoutInterface * >( mLayout.data() ) )
1495 generator.setTitle( l->name() );
1496 else if ( mLayout->project() )
1497 generator.setTitle( mLayout->project()->title() );
1498 }
1499
1500 generator.setOutputDevice( &svgBuffer );
1501 generator.setSize( QSize( static_cast< int >( std::round( width ) ), static_cast< int >( std::round( height ) ) ) );
1502 generator.setViewBox( QRect( 0, 0, static_cast< int >( std::round( width ) ), static_cast< int >( std::round( height ) ) ) );
1503 generator.setResolution( static_cast< int >( std::round( settings.dpi ) ) ); //because the rendering is done in mm, convert the dpi
1504
1505 QPainter svgPainter( &generator );
1506 if ( settings.cropToContents )
1507 renderRegion( &svgPainter, bounds );
1508 else
1509 renderPage( &svgPainter, page );
1510 }
1511
1512 // post-process svg output to create groups in a single svg file
1513 // we create inkscape layers since it's nice and clean and free
1514 // and fully svg compatible
1515 {
1516 svgBuffer.close();
1517 svgBuffer.open( QIODevice::ReadOnly );
1518 QDomDocument doc;
1519 QString errorMsg;
1520 int errorLine;
1521 if ( !doc.setContent( &svgBuffer, false, &errorMsg, &errorLine ) )
1522 {
1523 mErrorFileName = filename;
1524 return SvgLayerError;
1525 }
1526 if ( 1 == svgLayerId )
1527 {
1528 svg = QDomDocument( doc.doctype() );
1529 svg.appendChild( svg.importNode( doc.firstChild(), false ) );
1530 svgDocRoot = svg.importNode( doc.elementsByTagName( u"svg"_s ).at( 0 ), false );
1531 svgDocRoot.toElement().setAttribute( u"xmlns:inkscape"_s, u"http://www.inkscape.org/namespaces/inkscape"_s );
1532 svg.appendChild( svgDocRoot );
1533 }
1534 QDomNode mainGroup = svg.importNode( doc.elementsByTagName( u"g"_s ).at( 0 ), true );
1535 mainGroup.toElement().setAttribute( u"id"_s, layerName );
1536 mainGroup.toElement().setAttribute( u"inkscape:label"_s, layerName );
1537 mainGroup.toElement().setAttribute( u"inkscape:groupmode"_s, u"layer"_s );
1538 QDomNode defs = svg.importNode( doc.elementsByTagName( u"defs"_s ).at( 0 ), true );
1539 svgDocRoot.appendChild( defs );
1540 svgDocRoot.appendChild( mainGroup );
1541 }
1542 return Success;
1543}
1544
1545void QgsLayoutExporter::appendMetadataToSvg( QDomDocument &svg ) const
1546{
1547 const QgsProjectMetadata &metadata = mLayout->project()->metadata();
1548 QDomElement metadataElement = svg.createElement( u"metadata"_s );
1549 QDomElement rdfElement = svg.createElement( u"rdf:RDF"_s );
1550 rdfElement.setAttribute( u"xmlns:rdf"_s, u"http://www.w3.org/1999/02/22-rdf-syntax-ns#"_s );
1551 rdfElement.setAttribute( u"xmlns:rdfs"_s, u"http://www.w3.org/2000/01/rdf-schema#"_s );
1552 rdfElement.setAttribute( u"xmlns:dc"_s, u"http://purl.org/dc/elements/1.1/"_s );
1553 QDomElement descriptionElement = svg.createElement( u"rdf:Description"_s );
1554 QDomElement workElement = svg.createElement( u"cc:Work"_s );
1555 workElement.setAttribute( u"rdf:about"_s, QString() );
1556
1557 auto addTextNode = [&workElement, &descriptionElement, &svg]( const QString &tag, const QString &value ) {
1558 // inkscape compatible
1559 QDomElement element = svg.createElement( tag );
1560 QDomText t = svg.createTextNode( value );
1561 element.appendChild( t );
1562 workElement.appendChild( element );
1563
1564 // svg spec compatible
1565 descriptionElement.setAttribute( tag, value );
1566 };
1567
1568 addTextNode( u"dc:format"_s, u"image/svg+xml"_s );
1569 addTextNode( u"dc:title"_s, metadata.title() );
1570 addTextNode( u"dc:date"_s, metadata.creationDateTime().toString( Qt::ISODate ) );
1571 addTextNode( u"dc:identifier"_s, metadata.identifier() );
1572 addTextNode( u"dc:description"_s, metadata.abstract() );
1573
1574 auto addAgentNode = [&workElement, &descriptionElement, &svg]( const QString &tag, const QString &value ) {
1575 // inkscape compatible
1576 QDomElement inkscapeElement = svg.createElement( tag );
1577 QDomElement agentElement = svg.createElement( u"cc:Agent"_s );
1578 QDomElement titleElement = svg.createElement( u"dc:title"_s );
1579 QDomText t = svg.createTextNode( value );
1580 titleElement.appendChild( t );
1581 agentElement.appendChild( titleElement );
1582 inkscapeElement.appendChild( agentElement );
1583 workElement.appendChild( inkscapeElement );
1584
1585 // svg spec compatible
1586 QDomElement bagElement = svg.createElement( u"rdf:Bag"_s );
1587 QDomElement liElement = svg.createElement( u"rdf:li"_s );
1588 t = svg.createTextNode( value );
1589 liElement.appendChild( t );
1590 bagElement.appendChild( liElement );
1591
1592 QDomElement element = svg.createElement( tag );
1593 element.appendChild( bagElement );
1594 descriptionElement.appendChild( element );
1595 };
1596
1597 addAgentNode( u"dc:creator"_s, metadata.author() );
1598 addAgentNode( u"dc:publisher"_s, getCreator() );
1599
1600 // keywords
1601 {
1602 QDomElement element = svg.createElement( u"dc:subject"_s );
1603 QDomElement bagElement = svg.createElement( u"rdf:Bag"_s );
1604 QgsAbstractMetadataBase::KeywordMap keywords = metadata.keywords();
1605 for ( auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
1606 {
1607 const QStringList words = it.value();
1608 for ( const QString &keyword : words )
1609 {
1610 QDomElement liElement = svg.createElement( u"rdf:li"_s );
1611 QDomText t = svg.createTextNode( keyword );
1612 liElement.appendChild( t );
1613 bagElement.appendChild( liElement );
1614 }
1615 }
1616 element.appendChild( bagElement );
1617 workElement.appendChild( element );
1618 descriptionElement.appendChild( element );
1619 }
1620
1621 rdfElement.appendChild( descriptionElement );
1622 rdfElement.appendChild( workElement );
1623 metadataElement.appendChild( rdfElement );
1624 svg.documentElement().appendChild( metadataElement );
1625 svg.documentElement().setAttribute( u"xmlns:cc"_s, u"http://creativecommons.org/ns#"_s );
1626}
1627
1628std::unique_ptr<double[]> QgsLayoutExporter::computeGeoTransform( const QgsLayoutItemMap *map, const QRectF &region, double dpi ) const
1629{
1630 if ( !map )
1631 map = mLayout->referenceMap();
1632
1633 if ( !map )
1634 return nullptr;
1635
1636 if ( dpi < 0 )
1637 dpi = mLayout->renderContext().dpi();
1638
1639 // calculate region of composition to export (in mm)
1640 QRectF exportRegion = region;
1641 if ( !exportRegion.isValid() )
1642 {
1643 int pageNumber = map->page();
1644
1645 QgsLayoutItemPage *page = mLayout->pageCollection()->page( pageNumber );
1646 double pageY = page->pos().y();
1647 QSizeF pageSize = page->rect().size();
1648 exportRegion = QRectF( 0, pageY, pageSize.width(), pageSize.height() );
1649 }
1650
1651 // map rectangle (in mm)
1652 QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
1653
1654 // destination width/height in mm
1655 double outputHeightMM = exportRegion.height();
1656 double outputWidthMM = exportRegion.width();
1657
1658 // map properties
1659 QgsRectangle mapExtent = map->extent();
1660 double mapXCenter = mapExtent.center().x();
1661 double mapYCenter = mapExtent.center().y();
1662 double alpha = -map->mapRotation() / 180 * M_PI;
1663 double sinAlpha = std::sin( alpha );
1664 double cosAlpha = std::cos( alpha );
1665
1666 // get the extent (in map units) for the exported region
1667 QPointF mapItemPos = map->pos();
1668 //adjust item position so it is relative to export region
1669 mapItemPos.rx() -= exportRegion.left();
1670 mapItemPos.ry() -= exportRegion.top();
1671
1672 // calculate extent of entire page in map units
1673 double xRatio = mapExtent.width() / mapItemSceneRect.width();
1674 double yRatio = mapExtent.height() / mapItemSceneRect.height();
1675 double xmin = mapExtent.xMinimum() - mapItemPos.x() * xRatio;
1676 double ymax = mapExtent.yMaximum() + mapItemPos.y() * yRatio;
1677 QgsRectangle paperExtent( xmin, ymax - outputHeightMM * yRatio, xmin + outputWidthMM * xRatio, ymax );
1678
1679 // calculate origin of page
1680 double X0 = paperExtent.xMinimum();
1681 double Y0 = paperExtent.yMaximum();
1682
1683 if ( !qgsDoubleNear( alpha, 0.0 ) )
1684 {
1685 // translate origin to account for map rotation
1686 double X1 = X0 - mapXCenter;
1687 double Y1 = Y0 - mapYCenter;
1688 double X2 = X1 * cosAlpha + Y1 * sinAlpha;
1689 double Y2 = -X1 * sinAlpha + Y1 * cosAlpha;
1690 X0 = X2 + mapXCenter;
1691 Y0 = Y2 + mapYCenter;
1692 }
1693
1694 // calculate scaling of pixels
1695 int pageWidthPixels = static_cast< int >( dpi * outputWidthMM / 25.4 );
1696 int pageHeightPixels = static_cast< int >( dpi * outputHeightMM / 25.4 );
1697 double pixelWidthScale = paperExtent.width() / pageWidthPixels;
1698 double pixelHeightScale = paperExtent.height() / pageHeightPixels;
1699
1700 // transform matrix
1701 std::unique_ptr<double[]> t( new double[6] );
1702 t[0] = X0;
1703 t[1] = cosAlpha * pixelWidthScale;
1704 t[2] = -sinAlpha * pixelWidthScale;
1705 t[3] = Y0;
1706 t[4] = -sinAlpha * pixelHeightScale;
1707 t[5] = -cosAlpha * pixelHeightScale;
1708
1709 return t;
1710}
1711
1712void QgsLayoutExporter::writeWorldFile( const QString &worldFileName, double a, double b, double c, double d, double e, double f ) const
1713{
1714 QFile worldFile( worldFileName );
1715 if ( !worldFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
1716 {
1717 return;
1718 }
1719 QTextStream fout( &worldFile );
1720
1721 // QString::number does not use locale settings (for the decimal point)
1722 // which is what we want here
1723 fout << QString::number( a, 'f', 12 ) << "\r\n";
1724 fout << QString::number( d, 'f', 12 ) << "\r\n";
1725 fout << QString::number( b, 'f', 12 ) << "\r\n";
1726 fout << QString::number( e, 'f', 12 ) << "\r\n";
1727 fout << QString::number( c, 'f', 12 ) << "\r\n";
1728 fout << QString::number( f, 'f', 12 ) << "\r\n";
1729}
1730
1731bool QgsLayoutExporter::georeferenceOutput( const QString &file, QgsLayoutItemMap *map, const QRectF &exportRegion, double dpi ) const
1732{
1733 return georeferenceOutputPrivate( file, map, exportRegion, dpi, false );
1734}
1735
1736bool QgsLayoutExporter::georeferenceOutputPrivate( const QString &file, QgsLayoutItemMap *map, const QRectF &exportRegion, double dpi, bool includeGeoreference, bool includeMetadata ) const
1737{
1738 if ( !mLayout )
1739 return false;
1740
1741 if ( !map && includeGeoreference )
1742 map = mLayout->referenceMap();
1743
1744 std::unique_ptr<double[]> t;
1745
1746 if ( map && includeGeoreference )
1747 {
1748 if ( dpi < 0 )
1749 dpi = mLayout->renderContext().dpi();
1750
1751 t = computeGeoTransform( map, exportRegion, dpi );
1752 }
1753
1754 // important - we need to manually specify the DPI in advance, as GDAL will otherwise
1755 // assume a DPI of 150
1756 CPLSetConfigOption( "GDAL_PDF_DPI", QString::number( dpi ).toUtf8().constData() );
1757 gdal::dataset_unique_ptr outputDS( GDALOpen( file.toUtf8().constData(), GA_Update ) );
1758 if ( outputDS )
1759 {
1760 if ( t )
1761 GDALSetGeoTransform( outputDS.get(), t.get() );
1762
1763 if ( includeMetadata )
1764 {
1765 QString creationDateString;
1766 const QDateTime creationDateTime = mLayout->project()->metadata().creationDateTime();
1767#if QT_FEATURE_timezone > 0
1768 if ( creationDateTime.isValid() )
1769 {
1770 creationDateString = u"D:%1"_s.arg( mLayout->project()->metadata().creationDateTime().toString( u"yyyyMMddHHmmss"_s ) );
1771 if ( creationDateTime.timeZone().isValid() )
1772 {
1773 int offsetFromUtc = creationDateTime.timeZone().offsetFromUtc( creationDateTime );
1774 creationDateString += ( offsetFromUtc >= 0 ) ? '+' : '-';
1775 offsetFromUtc = std::abs( offsetFromUtc );
1776 int offsetHours = offsetFromUtc / 3600;
1777 int offsetMins = ( offsetFromUtc % 3600 ) / 60;
1778 creationDateString += u"%1'%2'"_s.arg( offsetHours ).arg( offsetMins );
1779 }
1780 }
1781#else
1782 QgsDebugError( u"Qt is built without timezone support, skipping timezone for pdf export"_s );
1783#endif
1784 GDALSetMetadataItem( outputDS.get(), "CREATION_DATE", creationDateString.toUtf8().constData(), nullptr );
1785
1786 GDALSetMetadataItem( outputDS.get(), "AUTHOR", mLayout->project()->metadata().author().toUtf8().constData(), nullptr );
1787 const QString creator = getCreator();
1788 GDALSetMetadataItem( outputDS.get(), "CREATOR", creator.toUtf8().constData(), nullptr );
1789 GDALSetMetadataItem( outputDS.get(), "PRODUCER", creator.toUtf8().constData(), nullptr );
1790 GDALSetMetadataItem( outputDS.get(), "SUBJECT", mLayout->project()->metadata().abstract().toUtf8().constData(), nullptr );
1791 GDALSetMetadataItem( outputDS.get(), "TITLE", mLayout->project()->metadata().title().toUtf8().constData(), nullptr );
1792
1793 const QgsAbstractMetadataBase::KeywordMap keywords = mLayout->project()->metadata().keywords();
1794 QStringList allKeywords;
1795 for ( auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
1796 {
1797 allKeywords.append( u"%1: %2"_s.arg( it.key(), it.value().join( ',' ) ) );
1798 }
1799 const QString keywordString = allKeywords.join( ';' );
1800 GDALSetMetadataItem( outputDS.get(), "KEYWORDS", keywordString.toUtf8().constData(), nullptr );
1801 }
1802
1803 if ( t )
1804 GDALSetProjection( outputDS.get(), map->crs().toWkt( Qgis::CrsWktVariant::PreferredGdal ).toLocal8Bit().constData() );
1805 }
1806 CPLSetConfigOption( "GDAL_PDF_DPI", nullptr );
1807
1808 return true;
1809}
1810
1811QString nameForLayerWithItems( const QList< QGraphicsItem * > &items, unsigned int layerId )
1812{
1813 if ( items.count() == 1 )
1814 {
1815 if ( QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( items.at( 0 ) ) )
1816 {
1817 QString name = layoutItem->displayName();
1818 // cleanup default item ID format
1819 if ( name.startsWith( '<' ) && name.endsWith( '>' ) )
1820 name = name.mid( 1, name.length() - 2 );
1821 return name;
1822 }
1823 }
1824 else if ( items.count() > 1 )
1825 {
1826 QStringList currentLayerItemTypes;
1827 for ( QGraphicsItem *item : items )
1828 {
1829 if ( QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item ) )
1830 {
1831 const QString itemType = QgsApplication::layoutItemRegistry()->itemMetadata( layoutItem->type() )->visibleName();
1832 const QString itemTypePlural = QgsApplication::layoutItemRegistry()->itemMetadata( layoutItem->type() )->visiblePluralName();
1833 if ( !currentLayerItemTypes.contains( itemType ) && !currentLayerItemTypes.contains( itemTypePlural ) )
1834 currentLayerItemTypes << itemType;
1835 else if ( currentLayerItemTypes.contains( itemType ) )
1836 {
1837 currentLayerItemTypes.replace( currentLayerItemTypes.indexOf( itemType ), itemTypePlural );
1838 }
1839 }
1840 else
1841 {
1842 if ( !currentLayerItemTypes.contains( QObject::tr( "Other" ) ) )
1843 currentLayerItemTypes.append( QObject::tr( "Other" ) );
1844 }
1845 }
1846 return currentLayerItemTypes.join( ", "_L1 );
1847 }
1848 return QObject::tr( "Layer %1" ).arg( layerId );
1849}
1850
1851QgsLayoutExporter::ExportResult QgsLayoutExporter::handleLayeredExport(
1852 const QList<QGraphicsItem *> &items,
1853 const std::function<QgsLayoutExporter::ExportResult( unsigned int, const QgsLayoutItem::ExportLayerDetail & )> &exportFunc,
1854 const std::function<QString( QgsLayoutItem *item )> &getItemExportGroupFunc
1855)
1856{
1857 LayoutItemHider itemHider( items );
1858 ( void ) itemHider;
1859
1860 int prevType = -1;
1862 QString previousItemGroup;
1863 unsigned int layerId = 1;
1864 QgsLayoutItem::ExportLayerDetail layerDetails;
1865 itemHider.hideAll();
1866 const QList< QGraphicsItem * > itemsToIterate = itemHider.itemsToIterate();
1867 QList< QGraphicsItem * > currentLayerItems;
1868 for ( QGraphicsItem *item : itemsToIterate )
1869 {
1870 QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item );
1871
1872 bool canPlaceInExistingLayer = false;
1873 QString thisItemExportGroupName;
1874 if ( layoutItem )
1875 {
1876 QgsLayoutItem::ExportLayerBehavior itemExportBehavior = layoutItem->exportLayerBehavior();
1877 thisItemExportGroupName = getItemExportGroupFunc( layoutItem );
1878 if ( !thisItemExportGroupName.isEmpty() )
1879 {
1880 if ( thisItemExportGroupName != previousItemGroup && !currentLayerItems.empty() )
1881 itemExportBehavior = QgsLayoutItem::MustPlaceInOwnLayer;
1882 else
1883 layerDetails.groupName = thisItemExportGroupName;
1884 }
1885
1886 switch ( itemExportBehavior )
1887 {
1889 {
1890 switch ( prevItemBehavior )
1891 {
1893 canPlaceInExistingLayer = true;
1894 break;
1895
1897 canPlaceInExistingLayer = prevType == -1 || prevType == layoutItem->type();
1898 break;
1899
1902 canPlaceInExistingLayer = false;
1903 break;
1904 }
1905 break;
1906 }
1907
1909 {
1910 switch ( prevItemBehavior )
1911 {
1914 canPlaceInExistingLayer = prevType == -1 || prevType == layoutItem->type();
1915 break;
1916
1919 canPlaceInExistingLayer = false;
1920 break;
1921 }
1922 break;
1923 }
1924
1926 {
1927 canPlaceInExistingLayer = false;
1928 break;
1929 }
1930
1932 canPlaceInExistingLayer = false;
1933 break;
1934 }
1935 prevItemBehavior = itemExportBehavior;
1936 prevType = layoutItem->type();
1937 previousItemGroup = thisItemExportGroupName;
1938 }
1939 else
1940 {
1941 prevItemBehavior = QgsLayoutItem::MustPlaceInOwnLayer;
1942 previousItemGroup.clear();
1943 }
1944
1945 if ( canPlaceInExistingLayer )
1946 {
1947 currentLayerItems << item;
1948 item->show();
1949 }
1950 else
1951 {
1952 if ( !currentLayerItems.isEmpty() )
1953 {
1954 layerDetails.name = nameForLayerWithItems( currentLayerItems, layerId );
1955
1956 ExportResult result = exportFunc( layerId, layerDetails );
1957 if ( result != Success )
1958 return result;
1959 layerId++;
1960 currentLayerItems.clear();
1961 }
1962
1963 itemHider.hideAll();
1964 item->show();
1965
1966 if ( layoutItem && layoutItem->exportLayerBehavior() == QgsLayoutItem::ItemContainsSubLayers )
1967 {
1968 int layoutItemLayerIdx = 0;
1970 mLayout->renderContext().setCurrentExportLayer( layoutItemLayerIdx );
1972 layoutItem->startLayeredExport();
1973 while ( layoutItem->nextExportPart() )
1974 {
1976 mLayout->renderContext().setCurrentExportLayer( layoutItemLayerIdx );
1978
1979 layerDetails = layoutItem->exportLayerDetails();
1980 ExportResult result = exportFunc( layerId, layerDetails );
1981 if ( result != Success )
1982 return result;
1983 layerId++;
1984
1985 layoutItemLayerIdx++;
1986 }
1987 layerDetails.mapLayerId.clear();
1988 layerDetails.mapTheme.clear();
1990 mLayout->renderContext().setCurrentExportLayer( -1 );
1992 layoutItem->stopLayeredExport();
1993 currentLayerItems.clear();
1994 }
1995 else
1996 {
1997 currentLayerItems << item;
1998 }
1999 layerDetails.groupName = thisItemExportGroupName;
2000 }
2001 }
2002 if ( !currentLayerItems.isEmpty() )
2003 {
2004 layerDetails.name = nameForLayerWithItems( currentLayerItems, layerId );
2005 ExportResult result = exportFunc( layerId, layerDetails );
2006 if ( result != Success )
2007 return result;
2008 }
2009 return Success;
2010}
2011
2012QgsVectorSimplifyMethod QgsLayoutExporter::createExportSimplifyMethod()
2013{
2014 QgsVectorSimplifyMethod simplifyMethod;
2016 simplifyMethod.setForceLocalOptimization( true );
2017 // we use SnappedToGridGlobal, because it avoids gaps and slivers between previously adjacent polygons
2019 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.
2020 return simplifyMethod;
2021}
2022
2023QgsMaskRenderSettings QgsLayoutExporter::createExportMaskSettings()
2024{
2025 QgsMaskRenderSettings settings;
2026 // this is quite a conservative setting -- I think we could make this more aggressive and get smaller file sizes
2027 // without too much loss of quality...
2028 settings.setSimplificationTolerance( 0.5 );
2029 return settings;
2030}
2031
2032void QgsLayoutExporter::computeWorldFileParameters( double &a, double &b, double &c, double &d, double &e, double &f, double dpi ) const
2033{
2034 if ( !mLayout )
2035 return;
2036
2037 QgsLayoutItemMap *map = mLayout->referenceMap();
2038 if ( !map )
2039 {
2040 return;
2041 }
2042
2043 int pageNumber = map->page();
2044 QgsLayoutItemPage *page = mLayout->pageCollection()->page( pageNumber );
2045 double pageY = page->pos().y();
2046 QSizeF pageSize = page->rect().size();
2047 QRectF pageRect( 0, pageY, pageSize.width(), pageSize.height() );
2048 computeWorldFileParameters( pageRect, a, b, c, d, e, f, dpi );
2049}
2050
2051void QgsLayoutExporter::computeWorldFileParameters( const QRectF &exportRegion, double &a, double &b, double &c, double &d, double &e, double &f, double dpi ) const
2052{
2053 if ( !mLayout )
2054 return;
2055
2056 // World file parameters : affine transformation parameters from pixel coordinates to map coordinates
2057 QgsLayoutItemMap *map = mLayout->referenceMap();
2058 if ( !map )
2059 {
2060 return;
2061 }
2062
2063 double destinationHeight = exportRegion.height();
2064 double destinationWidth = exportRegion.width();
2065
2066 QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
2067 QgsRectangle mapExtent = map->extent();
2068
2069 double alpha = map->mapRotation() / 180 * M_PI;
2070
2071 double xRatio = mapExtent.width() / mapItemSceneRect.width();
2072 double yRatio = mapExtent.height() / mapItemSceneRect.height();
2073
2074 double xCenter = mapExtent.center().x();
2075 double yCenter = mapExtent.center().y();
2076
2077 // get the extent (in map units) for the region
2078 QPointF mapItemPos = map->pos();
2079 //adjust item position so it is relative to export region
2080 mapItemPos.rx() -= exportRegion.left();
2081 mapItemPos.ry() -= exportRegion.top();
2082
2083 double xmin = mapExtent.xMinimum() - mapItemPos.x() * xRatio;
2084 double ymax = mapExtent.yMaximum() + mapItemPos.y() * yRatio;
2085 QgsRectangle paperExtent( xmin, ymax - destinationHeight * yRatio, xmin + destinationWidth * xRatio, ymax );
2086
2087 double X0 = paperExtent.xMinimum();
2088 double Y0 = paperExtent.yMinimum();
2089
2090 if ( dpi < 0 )
2091 dpi = mLayout->renderContext().dpi();
2092
2093 int widthPx = static_cast< int >( dpi * destinationWidth / 25.4 );
2094 int heightPx = static_cast< int >( dpi * destinationHeight / 25.4 );
2095
2096 double Ww = paperExtent.width() / widthPx;
2097 double Hh = paperExtent.height() / heightPx;
2098
2099 // scaling matrix
2100 double s[6];
2101 s[0] = Ww;
2102 s[1] = 0;
2103 s[2] = X0;
2104 s[3] = 0;
2105 s[4] = -Hh;
2106 s[5] = Y0 + paperExtent.height();
2107
2108 // rotation matrix
2109 double r[6];
2110 r[0] = std::cos( alpha );
2111 r[1] = -std::sin( alpha );
2112 r[2] = xCenter * ( 1 - std::cos( alpha ) ) + yCenter * std::sin( alpha );
2113 r[3] = std::sin( alpha );
2114 r[4] = std::cos( alpha );
2115 r[5] = -xCenter * std::sin( alpha ) + yCenter * ( 1 - std::cos( alpha ) );
2116
2117 // result = rotation x scaling = rotation(scaling(X))
2118 a = r[0] * s[0] + r[1] * s[3];
2119 b = r[0] * s[1] + r[1] * s[4];
2120 c = r[0] * s[2] + r[1] * s[5] + r[2];
2121 d = r[3] * s[0] + r[4] * s[3];
2122 e = r[3] * s[1] + r[4] * s[4];
2123 f = r[3] * s[2] + r[4] * s[5] + r[5];
2124}
2125
2127{
2128 if ( !layout )
2129 return false;
2130
2131 QList< QgsLayoutItem *> items;
2132 layout->layoutItems( items );
2133
2134 for ( QgsLayoutItem *currentItem : std::as_const( items ) )
2135 {
2136 // ignore invisible items, they won't affect the output in any way...
2137 if ( currentItem->isVisible() && currentItem->requiresRasterization() )
2138 return true;
2139 }
2140 return false;
2141}
2142
2144{
2145 if ( !layout )
2146 return false;
2147
2148 QList< QgsLayoutItem *> items;
2149 layout->layoutItems( items );
2150
2151 for ( QgsLayoutItem *currentItem : std::as_const( items ) )
2152 {
2153 // ignore invisible items, they won't affect the output in any way...
2154 if ( currentItem->isVisible() && currentItem->containsAdvancedEffects() )
2155 return true;
2156 }
2157 return false;
2158}
2159
2160QImage QgsLayoutExporter::createImage( const QgsLayoutExporter::ImageExportSettings &settings, int page, QRectF &bounds, bool &skipPage ) const
2161{
2162 bounds = QRectF();
2163 skipPage = false;
2164
2165 if ( settings.cropToContents )
2166 {
2167 if ( mLayout->pageCollection()->pageCount() == 1 )
2168 {
2169 // single page, so include everything
2170 bounds = mLayout->layoutBounds( true );
2171 }
2172 else
2173 {
2174 // multi page, so just clip to items on current page
2175 bounds = mLayout->pageItemBounds( page, true );
2176 }
2177 if ( bounds.width() <= 0 || bounds.height() <= 0 )
2178 {
2179 //invalid size, skip page
2180 skipPage = true;
2181 return QImage();
2182 }
2183
2184 double pixelToLayoutUnits = mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 1, Qgis::LayoutUnit::Pixels ) );
2185 bounds
2186 = bounds.adjusted( -settings.cropMargins.left() * pixelToLayoutUnits, -settings.cropMargins.top() * pixelToLayoutUnits, settings.cropMargins.right() * pixelToLayoutUnits, settings.cropMargins.bottom() * pixelToLayoutUnits );
2187 return renderRegionToImage( bounds, QSize(), settings.dpi );
2188 }
2189 else
2190 {
2191 return renderPageToImage( page, settings.imageSize, settings.dpi );
2192 }
2193}
2194
2195int QgsLayoutExporter::firstPageToBeExported( QgsLayout *layout )
2196{
2197 const int pageCount = layout->pageCollection()->pageCount();
2198 for ( int i = 0; i < pageCount; ++i )
2199 {
2200 if ( !layout->pageCollection()->shouldExportPage( i ) )
2201 {
2202 continue;
2203 }
2204
2205 return i;
2206 }
2207 return 0; // shouldn't really matter -- we aren't exporting ANY pages!
2208}
2209
2211{
2212 if ( details.page == 0 )
2213 {
2214 return details.directory + '/' + details.baseName + '.' + details.extension;
2215 }
2216 else
2217 {
2218 return details.directory + '/' + details.baseName + '_' + QString::number( details.page + 1 ) + '.' + details.extension;
2219 }
2220}
2221
2222void QgsLayoutExporter::captureLabelingResults()
2223{
2224 qDeleteAll( mLabelingResults );
2225 mLabelingResults.clear();
2226
2227 QList< QgsLayoutItemMap * > maps;
2228 mLayout->layoutItems( maps );
2229
2230 for ( QgsLayoutItemMap *map : std::as_const( maps ) )
2231 {
2232 mLabelingResults[map->uuid()] = map->mExportLabelingResults.release();
2233 }
2234}
2235
2236bool QgsLayoutExporter::saveImage( const QImage &image, const QString &imageFilename, const QString &imageFormat, QgsProject *projectForMetadata, int quality )
2237{
2238 QImageWriter w( imageFilename, imageFormat.toLocal8Bit().constData() );
2239 if ( imageFormat.compare( "tiff"_L1, Qt::CaseInsensitive ) == 0 || imageFormat.compare( "tif"_L1, Qt::CaseInsensitive ) == 0 )
2240 {
2241 w.setCompression( 1 ); //use LZW compression
2242 }
2243
2244 // Set the quality for i.e. JPEG images. -1 means default quality.
2245 w.setQuality( quality );
2246
2247 if ( projectForMetadata )
2248 {
2249 w.setText( u"Author"_s, projectForMetadata->metadata().author() );
2250 const QString creator = getCreator();
2251 w.setText( u"Creator"_s, creator );
2252 w.setText( u"Producer"_s, creator );
2253 w.setText( u"Subject"_s, projectForMetadata->metadata().abstract() );
2254 w.setText( u"Created"_s, projectForMetadata->metadata().creationDateTime().toString( Qt::ISODate ) );
2255 w.setText( u"Title"_s, projectForMetadata->metadata().title() );
2256
2257 const QgsAbstractMetadataBase::KeywordMap keywords = projectForMetadata->metadata().keywords();
2258 QStringList allKeywords;
2259 for ( auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
2260 {
2261 allKeywords.append( u"%1: %2"_s.arg( it.key(), it.value().join( ',' ) ) );
2262 }
2263 const QString keywordString = allKeywords.join( ';' );
2264 w.setText( u"Keywords"_s, keywordString );
2265 }
2266 return w.write( image );
2267}
2268
2269QString QgsLayoutExporter::getCreator()
2270{
2271 return u"QGIS %1"_s.arg( Qgis::version() );
2272}
2273
2274void QgsLayoutExporter::setXmpMetadata( QPdfWriter *pdfWriter, QgsLayout *layout )
2275{
2276#if QT_VERSION >= QT_VERSION_CHECK( 6, 8, 0 )
2277 QUuid documentId = pdfWriter->documentId();
2278#else
2279 QUuid documentId = QUuid::createUuid();
2280#endif
2281
2282 // XMP metadata date format differs from PDF dictionary one
2283 const QDateTime creationDateTime = layout->project() ? layout->project()->metadata().creationDateTime() : QDateTime();
2284 const QString metaDataDate = creationDateTime.isValid() ? creationDateTime.toOffsetFromUtc( creationDateTime.offsetFromUtc() ).toString( Qt::ISODate ) : QString();
2285 const QString title = pdfWriter->title();
2286 const QString creator = getCreator();
2287 const QString producer = creator;
2288 const QString author = layout->project() ? layout->project()->metadata().author() : QString();
2289
2290 // heavily inspired from qpdf.cpp QPdfEnginePrivate::writeXmpDocumentMetaData
2291
2292 const QLatin1String xmlNS( "http://www.w3.org/XML/1998/namespace" );
2293 const QLatin1String adobeNS( "adobe:ns:meta/" );
2294 const QLatin1String rdfNS( "http://www.w3.org/1999/02/22-rdf-syntax-ns#" );
2295 const QLatin1String dcNS( "http://purl.org/dc/elements/1.1/" );
2296 const QLatin1String xmpNS( "http://ns.adobe.com/xap/1.0/" );
2297 const QLatin1String xmpMMNS( "http://ns.adobe.com/xap/1.0/mm/" );
2298 const QLatin1String pdfNS( "http://ns.adobe.com/pdf/1.3/" );
2299 const QLatin1String pdfaidNS( "http://www.aiim.org/pdfa/ns/id/" );
2300
2301 QByteArray xmpMetadata;
2302 QBuffer output( &xmpMetadata );
2303 output.open( QIODevice::WriteOnly );
2304 output.write( "<?xpacket begin='' ?>" );
2305
2306 QXmlStreamWriter w( &output );
2307 w.setAutoFormatting( true );
2308 w.writeNamespace( adobeNS, "x" ); //#spellok
2309 w.writeNamespace( rdfNS, "rdf" ); //#spellok
2310 w.writeNamespace( dcNS, "dc" ); //#spellok
2311 w.writeNamespace( xmpNS, "xmp" ); //#spellok
2312 w.writeNamespace( xmpMMNS, "xmpMM" ); //#spellok
2313 w.writeNamespace( pdfNS, "pdf" ); //#spellok
2314 w.writeNamespace( pdfaidNS, "pdfaid" ); //#spellok
2315
2316 w.writeStartElement( adobeNS, "xmpmeta" );
2317 w.writeStartElement( rdfNS, "RDF" );
2318
2319 // DC
2320 w.writeStartElement( rdfNS, "Description" );
2321 w.writeAttribute( rdfNS, "about", "" );
2322 w.writeStartElement( dcNS, "title" );
2323 w.writeStartElement( rdfNS, "Alt" );
2324 w.writeStartElement( rdfNS, "li" );
2325 w.writeAttribute( xmlNS, "lang", "x-default" );
2326 w.writeCharacters( title );
2327 w.writeEndElement();
2328 w.writeEndElement();
2329 w.writeEndElement();
2330
2331 w.writeStartElement( dcNS, "creator" );
2332 w.writeStartElement( rdfNS, "Seq" );
2333 w.writeStartElement( rdfNS, "li" );
2334 w.writeCharacters( author );
2335 w.writeEndElement();
2336 w.writeEndElement();
2337 w.writeEndElement();
2338
2339 w.writeEndElement();
2340
2341 // PDF
2342 w.writeStartElement( rdfNS, "Description" );
2343 w.writeAttribute( rdfNS, "about", "" );
2344 w.writeAttribute( pdfNS, "Producer", producer );
2345 w.writeAttribute( pdfNS, "Trapped", "False" );
2346 w.writeEndElement();
2347
2348 // XMP
2349 w.writeStartElement( rdfNS, "Description" );
2350 w.writeAttribute( rdfNS, "about", "" );
2351 w.writeAttribute( xmpNS, "CreatorTool", creator );
2352 w.writeAttribute( xmpNS, "CreateDate", metaDataDate );
2353 w.writeAttribute( xmpNS, "ModifyDate", metaDataDate );
2354 w.writeAttribute( xmpNS, "MetadataDate", metaDataDate );
2355 w.writeEndElement();
2356
2357 // XMPMM
2358 w.writeStartElement( rdfNS, "Description" );
2359 w.writeAttribute( rdfNS, "about", "" );
2360 w.writeAttribute( xmpMMNS, "DocumentID", "uuid:" + documentId.toString( QUuid::WithoutBraces ) );
2361 w.writeAttribute( xmpMMNS, "VersionID", "1" );
2362 w.writeAttribute( xmpMMNS, "RenditionClass", "default" );
2363 w.writeEndElement();
2364
2365#if QT_VERSION >= QT_VERSION_CHECK( 6, 8, 0 )
2366
2367 // Version-specific
2368 switch ( pdfWriter->pdfVersion() )
2369 {
2370 case QPagedPaintDevice::PdfVersion_1_4:
2371 case QPagedPaintDevice::PdfVersion_A1b: // A1b and 1.6 are not used by QGIS
2372 case QPagedPaintDevice::PdfVersion_1_6:
2373 break;
2374 case QPagedPaintDevice::PdfVersion_X4:
2375 const QLatin1String pdfxidNS( "http://www.npes.org/pdfx/ns/id/" );
2376 w.writeNamespace( pdfxidNS, "pdfxid" ); //#spellok
2377 w.writeStartElement( rdfNS, "Description" );
2378 w.writeAttribute( rdfNS, "about", "" );
2379 w.writeAttribute( pdfxidNS, "GTS_PDFXVersion", "PDF/X-4" );
2380 w.writeEndElement();
2381 break;
2382 }
2383
2384#endif
2385
2386 w.writeEndElement(); // </RDF>
2387 w.writeEndElement(); // </xmpmeta>
2388
2389 w.writeEndDocument();
2390 output.write( "<?xpacket end='w'?>" );
2391
2392 pdfWriter->setDocumentXmpMetadata( xmpMetadata );
2393}
RasterizedRenderingPolicy
Policies controlling when rasterisation of content during renders is permitted.
Definition qgis.h:2798
@ Default
Allow raster-based rendering in situations where it is required for correct rendering or where it wil...
Definition qgis.h:2799
@ PreferVector
Prefer vector-based rendering, when the result will still be visually near-identical to a raster-base...
Definition qgis.h:2800
@ ForceVector
Always force vector-based rendering, even when the result will be visually different to a raster-base...
Definition qgis.h:2801
static QString version()
Version string.
Definition qgis.cpp:682
@ Millimeters
Millimeters.
Definition qgis.h:5361
@ Pixels
Pixels.
Definition qgis.h:5368
@ Inches
Inches.
Definition qgis.h:5364
@ GeometrySimplification
The geometries can be simplified using the current map2pixel context state.
Definition qgis.h:3132
@ SnappedToGridGlobal
Snap to a global grid based on the tolerance. Good for consistent results for incoming vertices,...
Definition qgis.h:3118
@ Warning
Warning message.
Definition qgis.h:162
QFlags< LayoutRenderFlag > LayoutRenderFlags
Flags for controlling how a layout is rendered.
Definition qgis.h:5423
TextRenderFormat
Options for rendering text.
Definition qgis.h:2923
@ AlwaysOutlines
Always render text using path objects (AKA outlines/curves). This setting guarantees the best quality...
Definition qgis.h:2924
@ Cmyk
CMYK color model.
Definition qgis.h:6346
@ Rgb
RGB color model.
Definition qgis.h:6345
@ PreferredGdal
Preferred format for conversion of CRS to WKT for use with the GDAL library.
Definition qgis.h:2530
@ SynchronousLegendGraphics
Query legend graphics synchronously.
Definition qgis.h:5409
@ LosslessImageRendering
Render images losslessly whenever possible, instead of the default lossy jpeg rendering used for some...
Definition qgis.h:5407
@ Antialiasing
Use antialiasing when drawing items.
Definition qgis.h:5397
@ RenderLabelsByMapLayer
When rendering map items to multi-layered exports, render labels belonging to different layers into s...
Definition qgis.h:5405
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 QgsLayout * layout()=0
Returns the layout associated with the iterator.
virtual bool next()=0
Iterates to next feature, returning false if no more features exist to iterate over.
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.
QString toWkt(Qgis::CrsWktVariant variant=Qgis::CrsWktVariant::Wkt1Gdal, 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:44
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:65
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.
QString errorMessage() const
Returns a string describing the last error encountered during an export.
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.
static const QgsSettingsEntryBool * settingOpenAfterExportingPdf
Settings entry - Whether to automatically open pdfs after exporting them.
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.
static const QgsSettingsEntryInteger * settingImageQuality
Settings entry - Image quality for lossy formats.
QImage renderPageToImage(int page, QSize imageSize=QSize(), double dpi=-1) const
Renders a full page to an image.
QgsLayoutExporter(QgsLayout *layout)
Constructor for QgsLayoutExporter, for the specified 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.
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.
static const QgsSettingsEntryBool * settingOpenAfterExportingImage
Settings entry - Whether to automatically open images after exporting them.
static const QgsSettingsEntryBool * settingOpenAfterExportingSvg
Settings entry - Whether to automatically open svgs after exporting them.
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.
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.
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.
Provides a method of storing measurements for use in QGIS layouts using a variety of different measur...
Provides a method of storing points, consisting of an x and y coordinate, for use in QGIS layouts.
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.
Qgis::LayoutRenderFlags flags() const
Returns the current combination of flags used for rendering the layout.
void setRasterizedRenderingPolicy(Qgis::RasterizedRenderingPolicy policy)
Sets the policy controlling when rasterization of content during renders is permitted.
double dpi() const
Returns the dpi for outputting the layout.
void setPredefinedScales(const QVector< qreal > &scales)
Sets the list of predefined scales to use with the layout.
void setMaskSettings(const QgsMaskRenderSettings &settings)
Sets the mask render settings, which control how masks are drawn and behave during map renders.
void setFlags(Qgis::LayoutRenderFlags flags)
Sets the combination of flags that will be used for rendering the layout.
Provides a method of storing sizes, consisting of a width and height, for use in QGIS layouts.
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:50
QgsLayoutRenderContext & renderContext()
Returns a reference to the layout's render context, which stores information relating to the current ...
Line string geometry type, with support for z-dimension and m-values.
Base class for all map layer types.
Definition qgsmaplayer.h:83
double top() const
Returns the top margin.
Definition qgsmargins.h:76
double right() const
Returns the right margin.
Definition qgsmargins.h:82
double bottom() const
Returns the bottom margin.
Definition qgsmargins.h:88
double left() const
Returns the left margin.
Definition qgsmargins.h:70
Contains settings regarding how masks are calculated and handled during a map render.
void setSimplificationTolerance(double tolerance)
Sets a simplification tolerance (in painter units) to use for on-the-fly simplification of mask paths...
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
void setExteriorRing(QgsCurve *ring) override
Sets the exterior ring of the polygon.
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:113
QgsProjectMetadata metadata
Definition qgsproject.h:127
A rectangle specified with double values.
double xMinimum
double yMinimum
double yMaximum
QgsPointXY center
A boolean settings entry.
An integer settings entry.
static QgsSettingsTreeNode * sTreeLayout
Contains settings for simplifying geometries fetched from a vector layer.
void setThreshold(float threshold)
Sets the simplification threshold of the vector layer managed.
void setForceLocalOptimization(bool localOptimization)
Sets where the simplification executes, after fetch the geometries from provider, or when supported,...
void setSimplifyHints(Qgis::VectorRenderingSimplificationFlags simplifyHints)
Sets the simplification hints of the vector layer managed.
void setSimplifyAlgorithm(Qgis::VectorSimplificationAlgorithm simplifyAlgorithm)
Sets the local simplification algorithm of the vector layer managed.
std::unique_ptr< std::remove_pointer< GDALDatasetH >::type, GDALDatasetCloser > dataset_unique_ptr
Scoped GDAL dataset.
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:7504
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7503
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6975
QString nameForLayerWithItems(const QList< QGraphicsItem * > &items, unsigned int layerId)
#define QgsDebugError(str)
Definition qgslogger.h:59
Contains details of a particular input component to be used during PDF composition.
QString mapLayerId
Associated map layer ID, or an empty string if this component layer is not associated with a map laye...
QString group
Optional group name, for arranging layers in top-level groups.
QString name
User-friendly name for the generated PDF layer.
QPainter::CompositionMode compositionMode
Component composition mode.
QString sourcePdfPath
File path to the (already created) PDF to use as the source for this component layer.
Contains details of a control point used during georeferencing Geospatial PDF outputs.
QMap< QString, QString > customLayerTreeGroups
Optional map of map layer ID to custom logical layer tree group in created PDF file.
QMap< QString, bool > initialLayerVisibility
Optional map of map layer ID to initial visibility state.
QList< QgsAbstractGeospatialPdfExporter::GeoReferencedSection > georeferencedSections
List of georeferenced sections.
QStringList layerOrder
Optional list of layer IDs, in the order desired to appear in the generated Geospatial PDF file.
QMap< QString, QString > layerIdToPdfLayerTreeNameMap
Optional map of map layer ID to custom layer tree name to show in the created PDF file.
QgsAbstractMetadataBase::KeywordMap keywords
Metadata keyword map.
QStringList layerTreeGroupOrder
Specifies the ordering of layer tree groups in the generated Geospatial PDF file.
QDateTime creationDateTime
Metadata creation datetime.
bool includeFeatures
true if feature vector information (such as attributes) should be exported.
bool useIso32000ExtensionFormatGeoreferencing
true if ISO32000 extension format georeferencing should be used.
QSet< QString > mutuallyExclusiveGroups
Contains a list of group names which should be considered as mutually exclusive.
QgsCoordinateReferenceSystem crs
Coordinate reference system for georeferenced section.
QList< QgsAbstractGeospatialPdfExporter::ControlPoint > controlPoints
List of control points corresponding to this georeferenced section.
QgsPolygon pageBoundsPolygon
Bounds of the georeferenced section on the page, in millimeters, as a free-form polygon.
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...
int quality
Image quality, typically used for JPEG compression (whose quality ranges from 1 to 100) if quality is...
Qgis::LayoutRenderFlags 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 Geospatial PDF 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.
Qgis::LayoutRenderFlags flags
Layout context flags, which control how the export will be created.
bool writeGeoPdf
true if geospatial PDF 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.
Qgis::LayoutRenderFlags flags
Layout context flags, which control how the export will be created.
double dpi
Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.
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::LayoutRenderFlags flags
Layout context flags, which control how the export will be created.
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.
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.
QString groupName
Associated group name, if this layer is associated with an export group.