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