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