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