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