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