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