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