QGIS API Documentation 3.40.0-Bratislava (b56115d8743)
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< QgsLayoutGeospatialPdfExporter > geospatialPdfExporter;
569 if ( settings.writeGeoPdf || settings.exportLayersAsSeperateFiles ) //#spellok
570 geospatialPdfExporter = std::make_unique< QgsLayoutGeospatialPdfExporter >( 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< QgsLayoutGeospatialPdfExporter::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, &geospatialPdfExporter, &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 ? geospatialPdfExporter->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 geospatial PDF 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 << QgsAbstractGeospatialPdfExporter::ControlPoint( QgsPointXY( topLeftMm.x(), topLeftMm.y() ), topLeftMap );
706 georef.controlPoints << QgsAbstractGeospatialPdfExporter::ControlPoint( QgsPointXY( topRightMm.x(), topRightMm.y() ), topRightMap );
707 georef.controlPoints << QgsAbstractGeospatialPdfExporter::ControlPoint( QgsPointXY( bottomLeftMm.x(), bottomLeftMm.y() ), bottomLeftMap );
708 georef.controlPoints << QgsAbstractGeospatialPdfExporter::ControlPoint( QgsPointXY( bottomRightMm.x(), bottomRightMm.y() ), bottomRightMap );
709 details.georeferencedSections << georef;
710 }
711 }
712
713 details.customLayerTreeGroups = geospatialPdfExporter->customLayerTreeGroups();
714 details.initialLayerVisibility = geospatialPdfExporter->initialLayerVisibility();
715 details.layerOrder = geospatialPdfExporter->layerOrder();
716 details.layerTreeGroupOrder = geospatialPdfExporter->layerTreeGroupOrder();
717 details.includeFeatures = settings.includeGeoPdfFeatures;
718 details.useOgcBestPracticeFormatGeoreferencing = settings.useOgcBestPracticeFormatGeoreferencing;
719 details.useIso32000ExtensionFormatGeoreferencing = settings.useIso32000ExtensionFormatGeoreferencing;
720
721 if ( !geospatialPdfExporter->finalize( pdfComponents, filePath, details ) )
722 {
723 result = PrintError;
724 mErrorMessage = geospatialPdfExporter->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 outputIntent.setOutputCondition( colorSpace.description() );
1315
1316 // There is no way to actually get the color space registry identifier or even
1317 // the registry it comes from.
1318 outputIntent.setOutputConditionIdentifier( QStringLiteral( "Unknown identifier" ) );
1319 outputIntent.setRegistryName( QStringLiteral( "Unknown registry" ) );
1320 device->setOutputIntent( outputIntent );
1321
1322 // PDF/X-4 standard allows PDF to be printing ready and is only possible if a color space has been set
1323 pdfVersion = QPagedPaintDevice::PdfVersion_X4;
1324 }
1325 }
1326
1327#endif
1328
1329 device->setPdfVersion( pdfVersion );
1330 setXmpMetadata( device, layout );
1331
1332 // TODO: add option for this in layout
1333 // May not work on Windows or non-X11 Linux. Works fine on Mac using QPrinter::NativeFormat
1334 //printer.setFontEmbeddingEnabled( true );
1335
1336#if defined(HAS_KDE_QT5_PDF_TRANSFORM_FIX) || QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
1337 // paint engine hack not required, fixed upstream
1338#else
1339 QgsPaintEngineHack::fixEngineFlags( static_cast<QPaintDevice *>( device )->paintEngine() );
1340#endif
1341}
1342
1343void QgsLayoutExporter::preparePrint( QgsLayout *layout, QPagedPaintDevice *device, bool setFirstPageSize )
1344{
1345 if ( QPdfWriter *pdf = dynamic_cast<QPdfWriter *>( device ) )
1346 {
1347 pdf->setResolution( static_cast< int>( std::round( layout->renderContext().dpi() ) ) );
1348 }
1349#if defined( HAVE_QTPRINTER )
1350 else if ( QPrinter *printer = dynamic_cast<QPrinter *>( device ) )
1351 {
1352 printer->setFullPage( true );
1353 printer->setColorMode( QPrinter::Color );
1354 //set user-defined resolution
1355 printer->setResolution( static_cast< int>( std::round( layout->renderContext().dpi() ) ) );
1356 }
1357#endif
1358
1359 if ( setFirstPageSize )
1360 {
1361 updatePrinterPageSize( layout, device, firstPageToBeExported( layout ) );
1362 }
1363}
1364
1365QgsLayoutExporter::ExportResult QgsLayoutExporter::print( QPagedPaintDevice *device )
1366{
1367 if ( mLayout->pageCollection()->pageCount() == 0 )
1368 return PrintError;
1369
1370 preparePrint( mLayout, device, true );
1371 QPainter p;
1372 if ( !p.begin( device ) )
1373 {
1374 //error beginning print
1375 return PrintError;
1376 }
1377
1378 printPrivate( device, p );
1379 p.end();
1380 return Success;
1381}
1382
1383QgsLayoutExporter::ExportResult QgsLayoutExporter::printPrivate( QPagedPaintDevice *device, QPainter &painter, bool startNewPage, double dpi, bool rasterize )
1384{
1385 // layout starts page numbering at 0
1386 int fromPage = 0;
1387 int toPage = mLayout->pageCollection()->pageCount() - 1;
1388
1389#if defined( HAVE_QTPRINTER )
1390 if ( QPrinter *printer = dynamic_cast<QPrinter *>( device ) )
1391 {
1392 if ( printer->fromPage() >= 1 )
1393 fromPage = printer->fromPage() - 1;
1394 if ( printer->toPage() >= 1 )
1395 toPage = printer->toPage() - 1;
1396 }
1397#endif
1398
1399 bool pageExported = false;
1400 if ( rasterize )
1401 {
1402 for ( int i = fromPage; i <= toPage; ++i )
1403 {
1404 if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1405 {
1406 continue;
1407 }
1408
1409 updatePrinterPageSize( mLayout, device, i );
1410 if ( ( pageExported && i > fromPage ) || startNewPage )
1411 {
1412 device->newPage();
1413 }
1414
1415 QImage image = renderPageToImage( i, QSize(), dpi );
1416 if ( !image.isNull() )
1417 {
1418 QRectF targetArea( 0, 0, image.width(), image.height() );
1419 painter.drawImage( targetArea, image, targetArea );
1420 }
1421 else
1422 {
1423 return MemoryError;
1424 }
1425 pageExported = true;
1426 }
1427 }
1428 else
1429 {
1430 for ( int i = fromPage; i <= toPage; ++i )
1431 {
1432 if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1433 {
1434 continue;
1435 }
1436
1437 updatePrinterPageSize( mLayout, device, i );
1438
1439 if ( ( pageExported && i > fromPage ) || startNewPage )
1440 {
1441 device->newPage();
1442 }
1443 renderPage( &painter, i );
1444 pageExported = true;
1445 }
1446 }
1447 return Success;
1448}
1449
1450void QgsLayoutExporter::updatePrinterPageSize( QgsLayout *layout, QPagedPaintDevice *device, int page )
1451{
1452 QgsLayoutSize pageSize = layout->pageCollection()->page( page )->sizeWithUnits();
1454
1455 QPageLayout pageLayout( QPageSize( pageSizeMM.toQSizeF(), QPageSize::Millimeter ),
1456 QPageLayout::Portrait,
1457 QMarginsF( 0, 0, 0, 0 ) );
1458 pageLayout.setMode( QPageLayout::FullPageMode );
1459 device->setPageLayout( pageLayout );
1460 device->setPageMargins( QMarginsF( 0, 0, 0, 0 ) );
1461
1462#if defined( HAVE_QTPRINTER )
1463 if ( QPrinter *printer = dynamic_cast<QPrinter *>( device ) )
1464 {
1465 printer->setFullPage( true );
1466 }
1467#endif
1468}
1469
1470QgsLayoutExporter::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
1471{
1472 QBuffer svgBuffer;
1473 {
1474 QSvgGenerator generator;
1475 if ( includeMetadata )
1476 {
1477 if ( const QgsMasterLayoutInterface *l = dynamic_cast< const QgsMasterLayoutInterface * >( mLayout.data() ) )
1478 generator.setTitle( l->name() );
1479 else if ( mLayout->project() )
1480 generator.setTitle( mLayout->project()->title() );
1481 }
1482
1483 generator.setOutputDevice( &svgBuffer );
1484 generator.setSize( QSize( static_cast< int >( std::round( width ) ),
1485 static_cast< int >( std::round( height ) ) ) );
1486 generator.setViewBox( QRect( 0, 0,
1487 static_cast< int >( std::round( width ) ),
1488 static_cast< int >( std::round( height ) ) ) );
1489 generator.setResolution( static_cast< int >( std::round( settings.dpi ) ) ); //because the rendering is done in mm, convert the dpi
1490
1491 QPainter svgPainter( &generator );
1492 if ( settings.cropToContents )
1493 renderRegion( &svgPainter, bounds );
1494 else
1495 renderPage( &svgPainter, page );
1496 }
1497
1498// post-process svg output to create groups in a single svg file
1499// we create inkscape layers since it's nice and clean and free
1500// and fully svg compatible
1501 {
1502 svgBuffer.close();
1503 svgBuffer.open( QIODevice::ReadOnly );
1504 QDomDocument doc;
1505 QString errorMsg;
1506 int errorLine;
1507 if ( ! doc.setContent( &svgBuffer, false, &errorMsg, &errorLine ) )
1508 {
1509 mErrorFileName = filename;
1510 return SvgLayerError;
1511 }
1512 if ( 1 == svgLayerId )
1513 {
1514 svg = QDomDocument( doc.doctype() );
1515 svg.appendChild( svg.importNode( doc.firstChild(), false ) );
1516 svgDocRoot = svg.importNode( doc.elementsByTagName( QStringLiteral( "svg" ) ).at( 0 ), false );
1517 svgDocRoot.toElement().setAttribute( QStringLiteral( "xmlns:inkscape" ), QStringLiteral( "http://www.inkscape.org/namespaces/inkscape" ) );
1518 svg.appendChild( svgDocRoot );
1519 }
1520 QDomNode mainGroup = svg.importNode( doc.elementsByTagName( QStringLiteral( "g" ) ).at( 0 ), true );
1521 mainGroup.toElement().setAttribute( QStringLiteral( "id" ), layerName );
1522 mainGroup.toElement().setAttribute( QStringLiteral( "inkscape:label" ), layerName );
1523 mainGroup.toElement().setAttribute( QStringLiteral( "inkscape:groupmode" ), QStringLiteral( "layer" ) );
1524 QDomNode defs = svg.importNode( doc.elementsByTagName( QStringLiteral( "defs" ) ).at( 0 ), true );
1525 svgDocRoot.appendChild( defs );
1526 svgDocRoot.appendChild( mainGroup );
1527 }
1528 return Success;
1529}
1530
1531void QgsLayoutExporter::appendMetadataToSvg( QDomDocument &svg ) const
1532{
1533 const QgsProjectMetadata &metadata = mLayout->project()->metadata();
1534 QDomElement metadataElement = svg.createElement( QStringLiteral( "metadata" ) );
1535 QDomElement rdfElement = svg.createElement( QStringLiteral( "rdf:RDF" ) );
1536 rdfElement.setAttribute( QStringLiteral( "xmlns:rdf" ), QStringLiteral( "http://www.w3.org/1999/02/22-rdf-syntax-ns#" ) );
1537 rdfElement.setAttribute( QStringLiteral( "xmlns:rdfs" ), QStringLiteral( "http://www.w3.org/2000/01/rdf-schema#" ) );
1538 rdfElement.setAttribute( QStringLiteral( "xmlns:dc" ), QStringLiteral( "http://purl.org/dc/elements/1.1/" ) );
1539 QDomElement descriptionElement = svg.createElement( QStringLiteral( "rdf:Description" ) );
1540 QDomElement workElement = svg.createElement( QStringLiteral( "cc:Work" ) );
1541 workElement.setAttribute( QStringLiteral( "rdf:about" ), QString() );
1542
1543 auto addTextNode = [&workElement, &descriptionElement, &svg]( const QString & tag, const QString & value )
1544 {
1545 // inkscape compatible
1546 QDomElement element = svg.createElement( tag );
1547 QDomText t = svg.createTextNode( value );
1548 element.appendChild( t );
1549 workElement.appendChild( element );
1550
1551 // svg spec compatible
1552 descriptionElement.setAttribute( tag, value );
1553 };
1554
1555 addTextNode( QStringLiteral( "dc:format" ), QStringLiteral( "image/svg+xml" ) );
1556 addTextNode( QStringLiteral( "dc:title" ), metadata.title() );
1557 addTextNode( QStringLiteral( "dc:date" ), metadata.creationDateTime().toString( Qt::ISODate ) );
1558 addTextNode( QStringLiteral( "dc:identifier" ), metadata.identifier() );
1559 addTextNode( QStringLiteral( "dc:description" ), metadata.abstract() );
1560
1561 auto addAgentNode = [&workElement, &descriptionElement, &svg]( const QString & tag, const QString & value )
1562 {
1563 // inkscape compatible
1564 QDomElement inkscapeElement = svg.createElement( tag );
1565 QDomElement agentElement = svg.createElement( QStringLiteral( "cc:Agent" ) );
1566 QDomElement titleElement = svg.createElement( QStringLiteral( "dc:title" ) );
1567 QDomText t = svg.createTextNode( value );
1568 titleElement.appendChild( t );
1569 agentElement.appendChild( titleElement );
1570 inkscapeElement.appendChild( agentElement );
1571 workElement.appendChild( inkscapeElement );
1572
1573 // svg spec compatible
1574 QDomElement bagElement = svg.createElement( QStringLiteral( "rdf:Bag" ) );
1575 QDomElement liElement = svg.createElement( QStringLiteral( "rdf:li" ) );
1576 t = svg.createTextNode( value );
1577 liElement.appendChild( t );
1578 bagElement.appendChild( liElement );
1579
1580 QDomElement element = svg.createElement( tag );
1581 element.appendChild( bagElement );
1582 descriptionElement.appendChild( element );
1583 };
1584
1585 addAgentNode( QStringLiteral( "dc:creator" ), metadata.author() );
1586 addAgentNode( QStringLiteral( "dc:publisher" ), getCreator() );
1587
1588 // keywords
1589 {
1590 QDomElement element = svg.createElement( QStringLiteral( "dc:subject" ) );
1591 QDomElement bagElement = svg.createElement( QStringLiteral( "rdf:Bag" ) );
1592 QgsAbstractMetadataBase::KeywordMap keywords = metadata.keywords();
1593 for ( auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
1594 {
1595 const QStringList words = it.value();
1596 for ( const QString &keyword : words )
1597 {
1598 QDomElement liElement = svg.createElement( QStringLiteral( "rdf:li" ) );
1599 QDomText t = svg.createTextNode( keyword );
1600 liElement.appendChild( t );
1601 bagElement.appendChild( liElement );
1602 }
1603 }
1604 element.appendChild( bagElement );
1605 workElement.appendChild( element );
1606 descriptionElement.appendChild( element );
1607 }
1608
1609 rdfElement.appendChild( descriptionElement );
1610 rdfElement.appendChild( workElement );
1611 metadataElement.appendChild( rdfElement );
1612 svg.documentElement().appendChild( metadataElement );
1613 svg.documentElement().setAttribute( QStringLiteral( "xmlns:cc" ), QStringLiteral( "http://creativecommons.org/ns#" ) );
1614}
1615
1616std::unique_ptr<double[]> QgsLayoutExporter::computeGeoTransform( const QgsLayoutItemMap *map, const QRectF &region, double dpi ) const
1617{
1618 if ( !map )
1619 map = mLayout->referenceMap();
1620
1621 if ( !map )
1622 return nullptr;
1623
1624 if ( dpi < 0 )
1625 dpi = mLayout->renderContext().dpi();
1626
1627 // calculate region of composition to export (in mm)
1628 QRectF exportRegion = region;
1629 if ( !exportRegion.isValid() )
1630 {
1631 int pageNumber = map->page();
1632
1633 QgsLayoutItemPage *page = mLayout->pageCollection()->page( pageNumber );
1634 double pageY = page->pos().y();
1635 QSizeF pageSize = page->rect().size();
1636 exportRegion = QRectF( 0, pageY, pageSize.width(), pageSize.height() );
1637 }
1638
1639 // map rectangle (in mm)
1640 QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
1641
1642 // destination width/height in mm
1643 double outputHeightMM = exportRegion.height();
1644 double outputWidthMM = exportRegion.width();
1645
1646 // map properties
1647 QgsRectangle mapExtent = map->extent();
1648 double mapXCenter = mapExtent.center().x();
1649 double mapYCenter = mapExtent.center().y();
1650 double alpha = - map->mapRotation() / 180 * M_PI;
1651 double sinAlpha = std::sin( alpha );
1652 double cosAlpha = std::cos( alpha );
1653
1654 // get the extent (in map units) for the exported region
1655 QPointF mapItemPos = map->pos();
1656 //adjust item position so it is relative to export region
1657 mapItemPos.rx() -= exportRegion.left();
1658 mapItemPos.ry() -= exportRegion.top();
1659
1660 // calculate extent of entire page in map units
1661 double xRatio = mapExtent.width() / mapItemSceneRect.width();
1662 double yRatio = mapExtent.height() / mapItemSceneRect.height();
1663 double xmin = mapExtent.xMinimum() - mapItemPos.x() * xRatio;
1664 double ymax = mapExtent.yMaximum() + mapItemPos.y() * yRatio;
1665 QgsRectangle paperExtent( xmin, ymax - outputHeightMM * yRatio, xmin + outputWidthMM * xRatio, ymax );
1666
1667 // calculate origin of page
1668 double X0 = paperExtent.xMinimum();
1669 double Y0 = paperExtent.yMaximum();
1670
1671 if ( !qgsDoubleNear( alpha, 0.0 ) )
1672 {
1673 // translate origin to account for map rotation
1674 double X1 = X0 - mapXCenter;
1675 double Y1 = Y0 - mapYCenter;
1676 double X2 = X1 * cosAlpha + Y1 * sinAlpha;
1677 double Y2 = -X1 * sinAlpha + Y1 * cosAlpha;
1678 X0 = X2 + mapXCenter;
1679 Y0 = Y2 + mapYCenter;
1680 }
1681
1682 // calculate scaling of pixels
1683 int pageWidthPixels = static_cast< int >( dpi * outputWidthMM / 25.4 );
1684 int pageHeightPixels = static_cast< int >( dpi * outputHeightMM / 25.4 );
1685 double pixelWidthScale = paperExtent.width() / pageWidthPixels;
1686 double pixelHeightScale = paperExtent.height() / pageHeightPixels;
1687
1688 // transform matrix
1689 std::unique_ptr<double[]> t( new double[6] );
1690 t[0] = X0;
1691 t[1] = cosAlpha * pixelWidthScale;
1692 t[2] = -sinAlpha * pixelWidthScale;
1693 t[3] = Y0;
1694 t[4] = -sinAlpha * pixelHeightScale;
1695 t[5] = -cosAlpha * pixelHeightScale;
1696
1697 return t;
1698}
1699
1700void QgsLayoutExporter::writeWorldFile( const QString &worldFileName, double a, double b, double c, double d, double e, double f ) const
1701{
1702 QFile worldFile( worldFileName );
1703 if ( !worldFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
1704 {
1705 return;
1706 }
1707 QTextStream fout( &worldFile );
1708
1709 // QString::number does not use locale settings (for the decimal point)
1710 // which is what we want here
1711 fout << QString::number( a, 'f', 12 ) << "\r\n";
1712 fout << QString::number( d, 'f', 12 ) << "\r\n";
1713 fout << QString::number( b, 'f', 12 ) << "\r\n";
1714 fout << QString::number( e, 'f', 12 ) << "\r\n";
1715 fout << QString::number( c, 'f', 12 ) << "\r\n";
1716 fout << QString::number( f, 'f', 12 ) << "\r\n";
1717}
1718
1719bool QgsLayoutExporter::georeferenceOutput( const QString &file, QgsLayoutItemMap *map, const QRectF &exportRegion, double dpi ) const
1720{
1721 return georeferenceOutputPrivate( file, map, exportRegion, dpi, false );
1722}
1723
1724bool QgsLayoutExporter::georeferenceOutputPrivate( const QString &file, QgsLayoutItemMap *map, const QRectF &exportRegion, double dpi, bool includeGeoreference, bool includeMetadata ) const
1725{
1726 if ( !mLayout )
1727 return false;
1728
1729 if ( !map && includeGeoreference )
1730 map = mLayout->referenceMap();
1731
1732 std::unique_ptr<double[]> t;
1733
1734 if ( map && includeGeoreference )
1735 {
1736 if ( dpi < 0 )
1737 dpi = mLayout->renderContext().dpi();
1738
1739 t = computeGeoTransform( map, exportRegion, dpi );
1740 }
1741
1742 // important - we need to manually specify the DPI in advance, as GDAL will otherwise
1743 // assume a DPI of 150
1744 CPLSetConfigOption( "GDAL_PDF_DPI", QString::number( dpi ).toUtf8().constData() );
1745 gdal::dataset_unique_ptr outputDS( GDALOpen( file.toUtf8().constData(), GA_Update ) );
1746 if ( outputDS )
1747 {
1748 if ( t )
1749 GDALSetGeoTransform( outputDS.get(), t.get() );
1750
1751 if ( includeMetadata )
1752 {
1753 QString creationDateString;
1754 const QDateTime creationDateTime = mLayout->project()->metadata().creationDateTime();
1755 if ( creationDateTime.isValid() )
1756 {
1757 creationDateString = QStringLiteral( "D:%1" ).arg( mLayout->project()->metadata().creationDateTime().toString( QStringLiteral( "yyyyMMddHHmmss" ) ) );
1758 if ( creationDateTime.timeZone().isValid() )
1759 {
1760 int offsetFromUtc = creationDateTime.timeZone().offsetFromUtc( creationDateTime );
1761 creationDateString += ( offsetFromUtc >= 0 ) ? '+' : '-';
1762 offsetFromUtc = std::abs( offsetFromUtc );
1763 int offsetHours = offsetFromUtc / 3600;
1764 int offsetMins = ( offsetFromUtc % 3600 ) / 60;
1765 creationDateString += QStringLiteral( "%1'%2'" ).arg( offsetHours ).arg( offsetMins );
1766 }
1767 }
1768 GDALSetMetadataItem( outputDS.get(), "CREATION_DATE", creationDateString.toUtf8().constData(), nullptr );
1769
1770 GDALSetMetadataItem( outputDS.get(), "AUTHOR", mLayout->project()->metadata().author().toUtf8().constData(), nullptr );
1771 const QString creator = getCreator();
1772 GDALSetMetadataItem( outputDS.get(), "CREATOR", creator.toUtf8().constData(), nullptr );
1773 GDALSetMetadataItem( outputDS.get(), "PRODUCER", creator.toUtf8().constData(), nullptr );
1774 GDALSetMetadataItem( outputDS.get(), "SUBJECT", mLayout->project()->metadata().abstract().toUtf8().constData(), nullptr );
1775 GDALSetMetadataItem( outputDS.get(), "TITLE", mLayout->project()->metadata().title().toUtf8().constData(), nullptr );
1776
1777 const QgsAbstractMetadataBase::KeywordMap keywords = mLayout->project()->metadata().keywords();
1778 QStringList allKeywords;
1779 for ( auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
1780 {
1781 allKeywords.append( QStringLiteral( "%1: %2" ).arg( it.key(), it.value().join( ',' ) ) );
1782 }
1783 const QString keywordString = allKeywords.join( ';' );
1784 GDALSetMetadataItem( outputDS.get(), "KEYWORDS", keywordString.toUtf8().constData(), nullptr );
1785 }
1786
1787 if ( t )
1788 GDALSetProjection( outputDS.get(), map->crs().toWkt( Qgis::CrsWktVariant::PreferredGdal ).toLocal8Bit().constData() );
1789 }
1790 CPLSetConfigOption( "GDAL_PDF_DPI", nullptr );
1791
1792 return true;
1793}
1794
1795QString nameForLayerWithItems( const QList< QGraphicsItem * > &items, unsigned int layerId )
1796{
1797 if ( items.count() == 1 )
1798 {
1799 if ( QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( items.at( 0 ) ) )
1800 {
1801 QString name = layoutItem->displayName();
1802 // cleanup default item ID format
1803 if ( name.startsWith( '<' ) && name.endsWith( '>' ) )
1804 name = name.mid( 1, name.length() - 2 );
1805 return name;
1806 }
1807 }
1808 else if ( items.count() > 1 )
1809 {
1810 QStringList currentLayerItemTypes;
1811 for ( QGraphicsItem *item : items )
1812 {
1813 if ( QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item ) )
1814 {
1815 const QString itemType = QgsApplication::layoutItemRegistry()->itemMetadata( layoutItem->type() )->visibleName();
1816 const QString itemTypePlural = QgsApplication::layoutItemRegistry()->itemMetadata( layoutItem->type() )->visiblePluralName();
1817 if ( !currentLayerItemTypes.contains( itemType ) && !currentLayerItemTypes.contains( itemTypePlural ) )
1818 currentLayerItemTypes << itemType;
1819 else if ( currentLayerItemTypes.contains( itemType ) )
1820 {
1821 currentLayerItemTypes.replace( currentLayerItemTypes.indexOf( itemType ), itemTypePlural );
1822 }
1823 }
1824 else
1825 {
1826 if ( !currentLayerItemTypes.contains( QObject::tr( "Other" ) ) )
1827 currentLayerItemTypes.append( QObject::tr( "Other" ) );
1828 }
1829 }
1830 return currentLayerItemTypes.join( QLatin1String( ", " ) );
1831 }
1832 return QObject::tr( "Layer %1" ).arg( layerId );
1833}
1834
1835QgsLayoutExporter::ExportResult QgsLayoutExporter::handleLayeredExport( const QList<QGraphicsItem *> &items,
1836 const std::function<QgsLayoutExporter::ExportResult( unsigned int, const QgsLayoutItem::ExportLayerDetail & )> &exportFunc,
1837 const std::function<QString( QgsLayoutItem *item )> &getItemExportGroupFunc )
1838{
1839 LayoutItemHider itemHider( items );
1840 ( void )itemHider;
1841
1842 int prevType = -1;
1844 QString previousItemGroup;
1845 unsigned int layerId = 1;
1847 itemHider.hideAll();
1848 const QList< QGraphicsItem * > itemsToIterate = itemHider.itemsToIterate();
1849 QList< QGraphicsItem * > currentLayerItems;
1850 for ( QGraphicsItem *item : itemsToIterate )
1851 {
1852 QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item );
1853
1854 bool canPlaceInExistingLayer = false;
1855 QString thisItemExportGroupName;
1856 if ( layoutItem )
1857 {
1858 QgsLayoutItem::ExportLayerBehavior itemExportBehavior = layoutItem->exportLayerBehavior();
1859 thisItemExportGroupName = getItemExportGroupFunc( layoutItem );
1860 if ( !thisItemExportGroupName.isEmpty() )
1861 {
1862 if ( thisItemExportGroupName != previousItemGroup && !currentLayerItems.empty() )
1863 itemExportBehavior = QgsLayoutItem::MustPlaceInOwnLayer;
1864 else
1865 layerDetails.groupName = thisItemExportGroupName;
1866 }
1867
1868 switch ( itemExportBehavior )
1869 {
1871 {
1872 switch ( prevItemBehavior )
1873 {
1875 canPlaceInExistingLayer = true;
1876 break;
1877
1879 canPlaceInExistingLayer = prevType == -1 || prevType == layoutItem->type();
1880 break;
1881
1884 canPlaceInExistingLayer = false;
1885 break;
1886 }
1887 break;
1888 }
1889
1891 {
1892 switch ( prevItemBehavior )
1893 {
1896 canPlaceInExistingLayer = prevType == -1 || prevType == layoutItem->type();
1897 break;
1898
1901 canPlaceInExistingLayer = false;
1902 break;
1903 }
1904 break;
1905 }
1906
1908 {
1909 canPlaceInExistingLayer = false;
1910 break;
1911 }
1912
1914 canPlaceInExistingLayer = false;
1915 break;
1916 }
1917 prevItemBehavior = itemExportBehavior;
1918 prevType = layoutItem->type();
1919 previousItemGroup = thisItemExportGroupName;
1920 }
1921 else
1922 {
1923 prevItemBehavior = QgsLayoutItem::MustPlaceInOwnLayer;
1924 previousItemGroup.clear();
1925 }
1926
1927 if ( canPlaceInExistingLayer )
1928 {
1929 currentLayerItems << item;
1930 item->show();
1931 }
1932 else
1933 {
1934 if ( !currentLayerItems.isEmpty() )
1935 {
1936 layerDetails.name = nameForLayerWithItems( currentLayerItems, layerId );
1937
1938 ExportResult result = exportFunc( layerId, layerDetails );
1939 if ( result != Success )
1940 return result;
1941 layerId++;
1942 currentLayerItems.clear();
1943 }
1944
1945 itemHider.hideAll();
1946 item->show();
1947
1948 if ( layoutItem && layoutItem->exportLayerBehavior() == QgsLayoutItem::ItemContainsSubLayers )
1949 {
1950 int layoutItemLayerIdx = 0;
1952 mLayout->renderContext().setCurrentExportLayer( layoutItemLayerIdx );
1954 layoutItem->startLayeredExport();
1955 while ( layoutItem->nextExportPart() )
1956 {
1958 mLayout->renderContext().setCurrentExportLayer( layoutItemLayerIdx );
1960
1961 layerDetails = layoutItem->exportLayerDetails();
1962 ExportResult result = exportFunc( layerId, layerDetails );
1963 if ( result != Success )
1964 return result;
1965 layerId++;
1966
1967 layoutItemLayerIdx++;
1968 }
1969 layerDetails.mapLayerId.clear();
1971 mLayout->renderContext().setCurrentExportLayer( -1 );
1973 layoutItem->stopLayeredExport();
1974 currentLayerItems.clear();
1975 }
1976 else
1977 {
1978 currentLayerItems << item;
1979 }
1980 layerDetails.groupName = thisItemExportGroupName;
1981 }
1982 }
1983 if ( !currentLayerItems.isEmpty() )
1984 {
1985 layerDetails.name = nameForLayerWithItems( currentLayerItems, layerId );
1986 ExportResult result = exportFunc( layerId, layerDetails );
1987 if ( result != Success )
1988 return result;
1989 }
1990 return Success;
1991}
1992
1993QgsVectorSimplifyMethod QgsLayoutExporter::createExportSimplifyMethod()
1994{
1995 QgsVectorSimplifyMethod simplifyMethod;
1997 simplifyMethod.setForceLocalOptimization( true );
1998 // we use SnappedToGridGlobal, because it avoids gaps and slivers between previously adjacent polygons
2000 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.
2001 return simplifyMethod;
2002}
2003
2004QgsMaskRenderSettings QgsLayoutExporter::createExportMaskSettings()
2005{
2006 QgsMaskRenderSettings settings;
2007 // this is quite a conservative setting -- I think we could make this more aggressive and get smaller file sizes
2008 // without too much loss of quality...
2009 settings.setSimplificationTolerance( 0.5 );
2010 return settings;
2011}
2012
2013void QgsLayoutExporter::computeWorldFileParameters( double &a, double &b, double &c, double &d, double &e, double &f, double dpi ) const
2014{
2015 if ( !mLayout )
2016 return;
2017
2018 QgsLayoutItemMap *map = mLayout->referenceMap();
2019 if ( !map )
2020 {
2021 return;
2022 }
2023
2024 int pageNumber = map->page();
2025 QgsLayoutItemPage *page = mLayout->pageCollection()->page( pageNumber );
2026 double pageY = page->pos().y();
2027 QSizeF pageSize = page->rect().size();
2028 QRectF pageRect( 0, pageY, pageSize.width(), pageSize.height() );
2029 computeWorldFileParameters( pageRect, a, b, c, d, e, f, dpi );
2030}
2031
2032void QgsLayoutExporter::computeWorldFileParameters( const QRectF &exportRegion, double &a, double &b, double &c, double &d, double &e, double &f, double dpi ) const
2033{
2034 if ( !mLayout )
2035 return;
2036
2037 // World file parameters : affine transformation parameters from pixel coordinates to map coordinates
2038 QgsLayoutItemMap *map = mLayout->referenceMap();
2039 if ( !map )
2040 {
2041 return;
2042 }
2043
2044 double destinationHeight = exportRegion.height();
2045 double destinationWidth = exportRegion.width();
2046
2047 QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
2048 QgsRectangle mapExtent = map->extent();
2049
2050 double alpha = map->mapRotation() / 180 * M_PI;
2051
2052 double xRatio = mapExtent.width() / mapItemSceneRect.width();
2053 double yRatio = mapExtent.height() / mapItemSceneRect.height();
2054
2055 double xCenter = mapExtent.center().x();
2056 double yCenter = mapExtent.center().y();
2057
2058 // get the extent (in map units) for the region
2059 QPointF mapItemPos = map->pos();
2060 //adjust item position so it is relative to export region
2061 mapItemPos.rx() -= exportRegion.left();
2062 mapItemPos.ry() -= exportRegion.top();
2063
2064 double xmin = mapExtent.xMinimum() - mapItemPos.x() * xRatio;
2065 double ymax = mapExtent.yMaximum() + mapItemPos.y() * yRatio;
2066 QgsRectangle paperExtent( xmin, ymax - destinationHeight * yRatio, xmin + destinationWidth * xRatio, ymax );
2067
2068 double X0 = paperExtent.xMinimum();
2069 double Y0 = paperExtent.yMinimum();
2070
2071 if ( dpi < 0 )
2072 dpi = mLayout->renderContext().dpi();
2073
2074 int widthPx = static_cast< int >( dpi * destinationWidth / 25.4 );
2075 int heightPx = static_cast< int >( dpi * destinationHeight / 25.4 );
2076
2077 double Ww = paperExtent.width() / widthPx;
2078 double Hh = paperExtent.height() / heightPx;
2079
2080 // scaling matrix
2081 double s[6];
2082 s[0] = Ww;
2083 s[1] = 0;
2084 s[2] = X0;
2085 s[3] = 0;
2086 s[4] = -Hh;
2087 s[5] = Y0 + paperExtent.height();
2088
2089 // rotation matrix
2090 double r[6];
2091 r[0] = std::cos( alpha );
2092 r[1] = -std::sin( alpha );
2093 r[2] = xCenter * ( 1 - std::cos( alpha ) ) + yCenter * std::sin( alpha );
2094 r[3] = std::sin( alpha );
2095 r[4] = std::cos( alpha );
2096 r[5] = - xCenter * std::sin( alpha ) + yCenter * ( 1 - std::cos( alpha ) );
2097
2098 // result = rotation x scaling = rotation(scaling(X))
2099 a = r[0] * s[0] + r[1] * s[3];
2100 b = r[0] * s[1] + r[1] * s[4];
2101 c = r[0] * s[2] + r[1] * s[5] + r[2];
2102 d = r[3] * s[0] + r[4] * s[3];
2103 e = r[3] * s[1] + r[4] * s[4];
2104 f = r[3] * s[2] + r[4] * s[5] + r[5];
2105}
2106
2108{
2109 if ( !layout )
2110 return false;
2111
2112 QList< QgsLayoutItem *> items;
2113 layout->layoutItems( items );
2114
2115 for ( QgsLayoutItem *currentItem : std::as_const( items ) )
2116 {
2117 // ignore invisible items, they won't affect the output in any way...
2118 if ( currentItem->isVisible() && currentItem->requiresRasterization() )
2119 return true;
2120 }
2121 return false;
2122}
2123
2125{
2126 if ( !layout )
2127 return false;
2128
2129 QList< QgsLayoutItem *> items;
2130 layout->layoutItems( items );
2131
2132 for ( QgsLayoutItem *currentItem : std::as_const( items ) )
2133 {
2134 // ignore invisible items, they won't affect the output in any way...
2135 if ( currentItem->isVisible() && currentItem->containsAdvancedEffects() )
2136 return true;
2137 }
2138 return false;
2139}
2140
2141QImage QgsLayoutExporter::createImage( const QgsLayoutExporter::ImageExportSettings &settings, int page, QRectF &bounds, bool &skipPage ) const
2142{
2143 bounds = QRectF();
2144 skipPage = false;
2145
2146 if ( settings.cropToContents )
2147 {
2148 if ( mLayout->pageCollection()->pageCount() == 1 )
2149 {
2150 // single page, so include everything
2151 bounds = mLayout->layoutBounds( true );
2152 }
2153 else
2154 {
2155 // multi page, so just clip to items on current page
2156 bounds = mLayout->pageItemBounds( page, true );
2157 }
2158 if ( bounds.width() <= 0 || bounds.height() <= 0 )
2159 {
2160 //invalid size, skip page
2161 skipPage = true;
2162 return QImage();
2163 }
2164
2165 double pixelToLayoutUnits = mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 1, Qgis::LayoutUnit::Pixels ) );
2166 bounds = bounds.adjusted( -settings.cropMargins.left() * pixelToLayoutUnits,
2167 -settings.cropMargins.top() * pixelToLayoutUnits,
2168 settings.cropMargins.right() * pixelToLayoutUnits,
2169 settings.cropMargins.bottom() * pixelToLayoutUnits );
2170 return renderRegionToImage( bounds, QSize(), settings.dpi );
2171 }
2172 else
2173 {
2174 return renderPageToImage( page, settings.imageSize, settings.dpi );
2175 }
2176}
2177
2178int QgsLayoutExporter::firstPageToBeExported( QgsLayout *layout )
2179{
2180 const int pageCount = layout->pageCollection()->pageCount();
2181 for ( int i = 0; i < pageCount; ++i )
2182 {
2183 if ( !layout->pageCollection()->shouldExportPage( i ) )
2184 {
2185 continue;
2186 }
2187
2188 return i;
2189 }
2190 return 0; // shouldn't really matter -- we aren't exporting ANY pages!
2191}
2192
2194{
2195 if ( details.page == 0 )
2196 {
2197 return details.directory + '/' + details.baseName + '.' + details.extension;
2198 }
2199 else
2200 {
2201 return details.directory + '/' + details.baseName + '_' + QString::number( details.page + 1 ) + '.' + details.extension;
2202 }
2203}
2204
2205void QgsLayoutExporter::captureLabelingResults()
2206{
2207 qDeleteAll( mLabelingResults );
2208 mLabelingResults.clear();
2209
2210 QList< QgsLayoutItemMap * > maps;
2211 mLayout->layoutItems( maps );
2212
2213 for ( QgsLayoutItemMap *map : std::as_const( maps ) )
2214 {
2215 mLabelingResults[ map->uuid() ] = map->mExportLabelingResults.release();
2216 }
2217}
2218
2219bool QgsLayoutExporter::saveImage( const QImage &image, const QString &imageFilename, const QString &imageFormat, QgsProject *projectForMetadata )
2220{
2221 QImageWriter w( imageFilename, imageFormat.toLocal8Bit().constData() );
2222 if ( imageFormat.compare( QLatin1String( "tiff" ), Qt::CaseInsensitive ) == 0 || imageFormat.compare( QLatin1String( "tif" ), Qt::CaseInsensitive ) == 0 )
2223 {
2224 w.setCompression( 1 ); //use LZW compression
2225 }
2226 if ( projectForMetadata )
2227 {
2228 w.setText( QStringLiteral( "Author" ), projectForMetadata->metadata().author() );
2229 const QString creator = getCreator();
2230 w.setText( QStringLiteral( "Creator" ), creator );
2231 w.setText( QStringLiteral( "Producer" ), creator );
2232 w.setText( QStringLiteral( "Subject" ), projectForMetadata->metadata().abstract() );
2233 w.setText( QStringLiteral( "Created" ), projectForMetadata->metadata().creationDateTime().toString( Qt::ISODate ) );
2234 w.setText( QStringLiteral( "Title" ), projectForMetadata->metadata().title() );
2235
2236 const QgsAbstractMetadataBase::KeywordMap keywords = projectForMetadata->metadata().keywords();
2237 QStringList allKeywords;
2238 for ( auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
2239 {
2240 allKeywords.append( QStringLiteral( "%1: %2" ).arg( it.key(), it.value().join( ',' ) ) );
2241 }
2242 const QString keywordString = allKeywords.join( ';' );
2243 w.setText( QStringLiteral( "Keywords" ), keywordString );
2244 }
2245 return w.write( image );
2246}
2247
2248QString QgsLayoutExporter::getCreator()
2249{
2250 return QStringLiteral( "QGIS %1" ).arg( Qgis::version() );
2251}
2252
2253void QgsLayoutExporter::setXmpMetadata( QPdfWriter *pdfWriter, QgsLayout *layout )
2254{
2255#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
2256 QUuid documentId = pdfWriter->documentId();
2257#else
2258 QUuid documentId = QUuid::createUuid();
2259#endif
2260
2261 // XMP metadata date format differs from PDF dictionary one
2262 const QDateTime creationDateTime = layout->project() ? layout->project()->metadata().creationDateTime() : QDateTime();
2263 const QString metaDataDate = creationDateTime.isValid() ? creationDateTime.toOffsetFromUtc( creationDateTime.offsetFromUtc() ).toString( Qt::ISODate ) : QString();
2264 const QString title = pdfWriter->title();
2265 const QString creator = getCreator();
2266 const QString producer = creator;
2267 const QString author = layout->project() ? layout->project()->metadata().author() : QString();
2268
2269 // heavily inspired from qpdf.cpp QPdfEnginePrivate::writeXmpDocumentMetaData
2270
2271 const QLatin1String xmlNS( "http://www.w3.org/XML/1998/namespace" );
2272 const QLatin1String adobeNS( "adobe:ns:meta/" );
2273 const QLatin1String rdfNS( "http://www.w3.org/1999/02/22-rdf-syntax-ns#" );
2274 const QLatin1String dcNS( "http://purl.org/dc/elements/1.1/" );
2275 const QLatin1String xmpNS( "http://ns.adobe.com/xap/1.0/" );
2276 const QLatin1String xmpMMNS( "http://ns.adobe.com/xap/1.0/mm/" );
2277 const QLatin1String pdfNS( "http://ns.adobe.com/pdf/1.3/" );
2278 const QLatin1String pdfaidNS( "http://www.aiim.org/pdfa/ns/id/" );
2279 const QLatin1String pdfxidNS( "http://www.npes.org/pdfx/ns/id/" );
2280
2281 QByteArray xmpMetadata;
2282 QBuffer output( &xmpMetadata );
2283 output.open( QIODevice::WriteOnly );
2284 output.write( "<?xpacket begin='' ?>" );
2285
2286 QXmlStreamWriter w( &output );
2287 w.setAutoFormatting( true );
2288 w.writeNamespace( adobeNS, "x" ); //#spellok
2289 w.writeNamespace( rdfNS, "rdf" ); //#spellok
2290 w.writeNamespace( dcNS, "dc" ); //#spellok
2291 w.writeNamespace( xmpNS, "xmp" ); //#spellok
2292 w.writeNamespace( xmpMMNS, "xmpMM" ); //#spellok
2293 w.writeNamespace( pdfNS, "pdf" ); //#spellok
2294 w.writeNamespace( pdfaidNS, "pdfaid" ); //#spellok
2295 w.writeNamespace( pdfxidNS, "pdfxid" ); //#spellok
2296
2297 w.writeStartElement( adobeNS, "xmpmeta" );
2298 w.writeStartElement( rdfNS, "RDF" );
2299
2300 // DC
2301 w.writeStartElement( rdfNS, "Description" );
2302 w.writeAttribute( rdfNS, "about", "" );
2303 w.writeStartElement( dcNS, "title" );
2304 w.writeStartElement( rdfNS, "Alt" );
2305 w.writeStartElement( rdfNS, "li" );
2306 w.writeAttribute( xmlNS, "lang", "x-default" );
2307 w.writeCharacters( title );
2308 w.writeEndElement();
2309 w.writeEndElement();
2310 w.writeEndElement();
2311
2312 w.writeStartElement( dcNS, "creator" );
2313 w.writeStartElement( rdfNS, "Seq" );
2314 w.writeStartElement( rdfNS, "li" );
2315 w.writeCharacters( author );
2316 w.writeEndElement();
2317 w.writeEndElement();
2318 w.writeEndElement();
2319
2320 w.writeEndElement();
2321
2322 // PDF
2323 w.writeStartElement( rdfNS, "Description" );
2324 w.writeAttribute( rdfNS, "about", "" );
2325 w.writeAttribute( pdfNS, "Producer", producer );
2326 w.writeAttribute( pdfNS, "Trapped", "False" );
2327 w.writeEndElement();
2328
2329 // XMP
2330 w.writeStartElement( rdfNS, "Description" );
2331 w.writeAttribute( rdfNS, "about", "" );
2332 w.writeAttribute( xmpNS, "CreatorTool", creator );
2333 w.writeAttribute( xmpNS, "CreateDate", metaDataDate );
2334 w.writeAttribute( xmpNS, "ModifyDate", metaDataDate );
2335 w.writeAttribute( xmpNS, "MetadataDate", metaDataDate );
2336 w.writeEndElement();
2337
2338 // XMPMM
2339 w.writeStartElement( rdfNS, "Description" );
2340 w.writeAttribute( rdfNS, "about", "" );
2341 w.writeAttribute( xmpMMNS, "DocumentID", "uuid:" + documentId.toString( QUuid::WithoutBraces ) );
2342 w.writeAttribute( xmpMMNS, "VersionID", "1" );
2343 w.writeAttribute( xmpMMNS, "RenditionClass", "default" );
2344 w.writeEndElement();
2345
2346#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
2347
2348 // Version-specific
2349 switch ( pdfWriter->pdfVersion() )
2350 {
2351 case QPagedPaintDevice::PdfVersion_1_4:
2352 case QPagedPaintDevice::PdfVersion_A1b: // A1b and 1.6 are not used by QGIS
2353 case QPagedPaintDevice::PdfVersion_1_6:
2354 break;
2355 case QPagedPaintDevice::PdfVersion_X4:
2356 w.writeStartElement( rdfNS, "Description" );
2357 w.writeAttribute( rdfNS, "about", "" );
2358 w.writeAttribute( pdfxidNS, "GTS_PDFXVersion", "PDF/X-4" );
2359 w.writeEndElement();
2360 break;
2361 }
2362
2363#endif
2364
2365 w.writeEndElement(); // </RDF>
2366 w.writeEndElement(); // </xmpmeta>
2367
2368 w.writeEndDocument();
2369 output.write( "<?xpacket end='w'?>" );
2370
2371 pdfWriter->setDocumentXmpMetadata( xmpMetadata );
2372}
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:2624
@ 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:6494
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6493
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:5917
QString nameForLayerWithItems(const QList< QGraphicsItem * > &items, unsigned int layerId)
Contains details of a particular input component to be used during PDF composition.
QString mapLayerId
Associated map layer ID, or an empty string if this component layer is not associated with a map laye...
QString group
Optional group name, for arranging layers in top-level groups.
QString name
User-friendly name for the generated PDF layer.
QPainter::CompositionMode compositionMode
Component composition mode.
QString sourcePdfPath
File path to the (already created) PDF to use as the source for this component layer.
Contains details of a control point used during georeferencing Geospatial PDF outputs.
QMap< QString, QString > customLayerTreeGroups
Optional map of map layer ID to custom logical layer tree group in created PDF file.
QMap< QString, bool > initialLayerVisibility
Optional map of map layer ID to initial visibility state.
QList< QgsAbstractGeospatialPdfExporter::GeoReferencedSection > georeferencedSections
List of georeferenced sections.
QStringList layerOrder
Optional list of layer IDs, in the order desired to appear in the generated Geospatial PDF file.
QMap< QString, QString > layerIdToPdfLayerTreeNameMap
Optional map of map layer ID to custom layer tree name to show in the created PDF file.
QgsAbstractMetadataBase::KeywordMap keywords
Metadata keyword map.
bool useOgcBestPracticeFormatGeoreferencing
true if OGC "best practice" format georeferencing should be used.
QStringList layerTreeGroupOrder
Specifies the ordering of layer tree groups in the generated Geospatial PDF file.
QDateTime creationDateTime
Metadata creation datetime.
bool includeFeatures
true if feature vector information (such as attributes) should be exported.
bool useIso32000ExtensionFormatGeoreferencing
true if ISO32000 extension format georeferencing should be used.
QSet< QString > mutuallyExclusiveGroups
Contains a list of group names which should be considered as mutually exclusive.
QgsCoordinateReferenceSystem crs
Coordinate reference system for georeferenced section.
QList< QgsAbstractGeospatialPdfExporter::ControlPoint > controlPoints
List of control points corresponding to this georeferenced section.
QgsPolygon pageBoundsPolygon
Bounds of the georeferenced section on the page, in millimeters, as a free-form polygon.
Contains settings relating to exporting layouts to raster images.
QgsMargins cropMargins
Crop to content margins, in pixels.
QList< int > pages
List of specific pages to export, or an empty list to export all pages.
bool generateWorldFile
Set to true to generate an external world file alongside exported images.
QSize imageSize
Manual size in pixels for output image.
bool exportMetadata
Indicates whether image export should include metadata generated from the layout's project's metadata...
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 Geospatial PDF layer groups.
bool exportMetadata
Indicates whether PDF export should include metadata generated from the layout's project's metadata.
bool appendGeoreference
Indicates whether PDF export should append georeference data.
QgsLayoutRenderContext::Flags flags
Layout context flags, which control how the export will be created.
bool writeGeoPdf
true if geospatial PDF files should be created, instead of normal PDF files.
double dpi
Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.
QVector< qreal > predefinedMapScales
A list of predefined scales to use with the layout.
bool exportLayersAsSeperateFiles
true if individual layers from the layout should be rendered to separate PDF files.
bool simplifyGeometries
Indicates whether vector geometries should be simplified to avoid redundant extraneous detail,...
Qgis::TextRenderFormat textRenderFormat
Text rendering format, which controls how text should be rendered in the export (e....
Contains settings relating to printing layouts.
QVector< qreal > predefinedMapScales
A list of predefined scales to use with the layout.
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.