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