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