QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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#ifndef QT_NO_PRINTER
19
20#include "qgslayout.h"
21#include "qgslayoutitemmap.h"
23#include "qgsogrutils.h"
24#include "qgspaintenginehack.h"
27#include "qgsfeedback.h"
29#include "qgslinestring.h"
30#include "qgsmessagelog.h"
31#include "qgslabelingresults.h"
32#include <QImageWriter>
33#include <QSize>
34#include <QSvgGenerator>
35#include <QBuffer>
36#include <QTimeZone>
37#include <QTextStream>
38
39#include "gdal.h"
40#include "cpl_conv.h"
41
43class LayoutContextPreviewSettingRestorer
44{
45 public:
46
47 LayoutContextPreviewSettingRestorer( QgsLayout *layout )
48 : mLayout( layout )
49 , mPreviousSetting( layout->renderContext().mIsPreviewRender )
50 {
51 mLayout->renderContext().mIsPreviewRender = false;
52 }
53
54 ~LayoutContextPreviewSettingRestorer()
55 {
56 mLayout->renderContext().mIsPreviewRender = mPreviousSetting;
57 }
58
59 LayoutContextPreviewSettingRestorer( const LayoutContextPreviewSettingRestorer &other ) = delete;
60 LayoutContextPreviewSettingRestorer &operator=( const LayoutContextPreviewSettingRestorer &other ) = delete;
61
62 private:
63 QgsLayout *mLayout = nullptr;
64 bool mPreviousSetting = false;
65};
66
67class LayoutGuideHider
68{
69 public:
70
71 LayoutGuideHider( QgsLayout *layout )
72 : mLayout( layout )
73 {
74 const QList< QgsLayoutGuide * > guides = mLayout->guides().guides();
75 for ( QgsLayoutGuide *guide : guides )
76 {
77 mPrevVisibility.insert( guide, guide->item()->isVisible() );
78 guide->item()->setVisible( false );
79 }
80 }
81
82 ~LayoutGuideHider()
83 {
84 for ( auto it = mPrevVisibility.constBegin(); it != mPrevVisibility.constEnd(); ++it )
85 {
86 it.key()->item()->setVisible( it.value() );
87 }
88 }
89
90 LayoutGuideHider( const LayoutGuideHider &other ) = delete;
91 LayoutGuideHider &operator=( const LayoutGuideHider &other ) = delete;
92
93 private:
94 QgsLayout *mLayout = nullptr;
95 QHash< QgsLayoutGuide *, bool > mPrevVisibility;
96};
97
98class LayoutItemHider
99{
100 public:
101 explicit LayoutItemHider( const QList<QGraphicsItem *> &items )
102 {
103 mItemsToIterate.reserve( items.count() );
104 for ( QGraphicsItem *item : items )
105 {
106 const bool isVisible = item->isVisible();
107 mPrevVisibility[item] = isVisible;
108 if ( isVisible )
109 mItemsToIterate.append( item );
110 if ( QgsLayoutItem *layoutItem = dynamic_cast< QgsLayoutItem * >( item ) )
111 layoutItem->setProperty( "wasVisible", isVisible );
112
113 item->hide();
114 }
115 }
116
117 void hideAll()
118 {
119 for ( auto it = mPrevVisibility.constBegin(); it != mPrevVisibility.constEnd(); ++it )
120 {
121 it.key()->hide();
122 }
123 }
124
125 ~LayoutItemHider()
126 {
127 for ( auto it = mPrevVisibility.constBegin(); it != mPrevVisibility.constEnd(); ++it )
128 {
129 it.key()->setVisible( it.value() );
130 if ( QgsLayoutItem *layoutItem = dynamic_cast< QgsLayoutItem * >( it.key() ) )
131 layoutItem->setProperty( "wasVisible", QVariant() );
132 }
133 }
134
135 QList< QGraphicsItem * > itemsToIterate() const { return mItemsToIterate; }
136
137 LayoutItemHider( const LayoutItemHider &other ) = delete;
138 LayoutItemHider &operator=( const LayoutItemHider &other ) = delete;
139
140 private:
141
142 QList<QGraphicsItem * > mItemsToIterate;
143 QHash<QGraphicsItem *, bool> mPrevVisibility;
144};
145
147
149 : mLayout( layout )
150{
151
152}
153
155{
156 qDeleteAll( mLabelingResults );
157}
158
160{
161 return mLayout;
162}
163
164void QgsLayoutExporter::renderPage( QPainter *painter, int page ) const
165{
166 if ( !mLayout )
167 return;
168
169 if ( mLayout->pageCollection()->pageCount() <= page || page < 0 )
170 {
171 return;
172 }
173
174 QgsLayoutItemPage *pageItem = mLayout->pageCollection()->page( page );
175 if ( !pageItem )
176 {
177 return;
178 }
179
180 LayoutContextPreviewSettingRestorer restorer( mLayout );
181 ( void )restorer;
182
183 QRectF paperRect = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() );
184 renderRegion( painter, paperRect );
185}
186
187QImage QgsLayoutExporter::renderPageToImage( int page, QSize imageSize, double dpi ) const
188{
189 if ( !mLayout )
190 return QImage();
191
192 if ( mLayout->pageCollection()->pageCount() <= page || page < 0 )
193 {
194 return QImage();
195 }
196
197 QgsLayoutItemPage *pageItem = mLayout->pageCollection()->page( page );
198 if ( !pageItem )
199 {
200 return QImage();
201 }
202
203 LayoutContextPreviewSettingRestorer restorer( mLayout );
204 ( void )restorer;
205
206 QRectF paperRect = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() );
207
208 const double imageAspectRatio = static_cast< double >( imageSize.width() ) / imageSize.height();
209 const double paperAspectRatio = paperRect.width() / paperRect.height();
210 if ( imageSize.isValid() && ( !qgsDoubleNear( imageAspectRatio, paperAspectRatio, 0.008 ) ) )
211 {
212 // specified image size is wrong aspect ratio for paper rect - so ignore it and just use dpi
213 // this can happen e.g. as a result of data defined page sizes
214 // see https://github.com/qgis/QGIS/issues/26422
215 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 );
216 imageSize = QSize();
217 }
218
219 return renderRegionToImage( paperRect, imageSize, dpi );
220}
221
223class LayoutItemCacheSettingRestorer
224{
225 public:
226
227 LayoutItemCacheSettingRestorer( QgsLayout *layout )
228 : mLayout( layout )
229 {
230 const QList< QGraphicsItem * > items = mLayout->items();
231 for ( QGraphicsItem *item : items )
232 {
233 mPrevCacheMode.insert( item, item->cacheMode() );
234 item->setCacheMode( QGraphicsItem::NoCache );
235 }
236 }
237
238 ~LayoutItemCacheSettingRestorer()
239 {
240 for ( auto it = mPrevCacheMode.constBegin(); it != mPrevCacheMode.constEnd(); ++it )
241 {
242 it.key()->setCacheMode( it.value() );
243 }
244 }
245
246 LayoutItemCacheSettingRestorer( const LayoutItemCacheSettingRestorer &other ) = delete;
247 LayoutItemCacheSettingRestorer &operator=( const LayoutItemCacheSettingRestorer &other ) = delete;
248
249 private:
250 QgsLayout *mLayout = nullptr;
251 QHash< QGraphicsItem *, QGraphicsItem::CacheMode > mPrevCacheMode;
252};
253
255
256void QgsLayoutExporter::renderRegion( QPainter *painter, const QRectF &region ) const
257{
258 QPaintDevice *paintDevice = painter->device();
259 if ( !paintDevice || !mLayout )
260 {
261 return;
262 }
263
264 LayoutItemCacheSettingRestorer cacheRestorer( mLayout );
265 ( void )cacheRestorer;
266 LayoutContextPreviewSettingRestorer restorer( mLayout );
267 ( void )restorer;
268 LayoutGuideHider guideHider( mLayout );
269 ( void ) guideHider;
270
271 painter->setRenderHint( QPainter::Antialiasing, mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagAntialiasing );
272
273 mLayout->render( painter, QRectF( 0, 0, paintDevice->width(), paintDevice->height() ), region );
274}
275
276QImage QgsLayoutExporter::renderRegionToImage( const QRectF &region, QSize imageSize, double dpi ) const
277{
278 if ( !mLayout )
279 return QImage();
280
281 LayoutContextPreviewSettingRestorer restorer( mLayout );
282 ( void )restorer;
283
284 double resolution = mLayout->renderContext().dpi();
285 double oneInchInLayoutUnits = mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 1, QgsUnitTypes::LayoutInches ) );
286 if ( imageSize.isValid() )
287 {
288 //output size in pixels specified, calculate resolution using average of
289 //derived x/y dpi
290 resolution = ( imageSize.width() / region.width()
291 + imageSize.height() / region.height() ) / 2.0 * oneInchInLayoutUnits;
292 }
293 else if ( dpi > 0 )
294 {
295 //dpi overridden by function parameters
296 resolution = dpi;
297 }
298
299 int width = imageSize.isValid() ? imageSize.width()
300 : static_cast< int >( resolution * region.width() / oneInchInLayoutUnits );
301 int height = imageSize.isValid() ? imageSize.height()
302 : static_cast< int >( resolution * region.height() / oneInchInLayoutUnits );
303
304 QImage image( QSize( width, height ), QImage::Format_ARGB32 );
305 if ( !image.isNull() )
306 {
307 // see https://doc.qt.io/qt-5/qpainter.html#limitations
308 if ( width > 32768 || height > 32768 )
309 QgsMessageLog::logMessage( QObject::tr( "Error: output width or height is larger than 32768 pixel, result will be clipped" ) );
310 image.setDotsPerMeterX( static_cast< int >( std::round( resolution / 25.4 * 1000 ) ) );
311 image.setDotsPerMeterY( static_cast< int>( std::round( resolution / 25.4 * 1000 ) ) );
312 image.fill( Qt::transparent );
313 QPainter imagePainter( &image );
314 renderRegion( &imagePainter, region );
315 if ( !imagePainter.isActive() )
316 return QImage();
317 }
318
319 return image;
320}
321
323class LayoutContextSettingsRestorer
324{
325 public:
326
328 LayoutContextSettingsRestorer( QgsLayout *layout )
329 : mLayout( layout )
330 , mPreviousDpi( layout->renderContext().dpi() )
331 , mPreviousFlags( layout->renderContext().flags() )
332 , mPreviousTextFormat( layout->renderContext().textRenderFormat() )
333 , mPreviousExportLayer( layout->renderContext().currentExportLayer() )
334 , mPreviousSimplifyMethod( layout->renderContext().simplifyMethod() )
335 , mExportThemes( layout->renderContext().exportThemes() )
336 , mPredefinedScales( layout->renderContext().predefinedScales() )
337 {
338 }
340
341 ~LayoutContextSettingsRestorer()
342 {
343 mLayout->renderContext().setDpi( mPreviousDpi );
344 mLayout->renderContext().setFlags( mPreviousFlags );
345 mLayout->renderContext().setTextRenderFormat( mPreviousTextFormat );
347 mLayout->renderContext().setCurrentExportLayer( mPreviousExportLayer );
349 mLayout->renderContext().setSimplifyMethod( mPreviousSimplifyMethod );
350 mLayout->renderContext().setExportThemes( mExportThemes );
351 mLayout->renderContext().setPredefinedScales( mPredefinedScales );
352 }
353
354 LayoutContextSettingsRestorer( const LayoutContextSettingsRestorer &other ) = delete;
355 LayoutContextSettingsRestorer &operator=( const LayoutContextSettingsRestorer &other ) = delete;
356
357 private:
358 QgsLayout *mLayout = nullptr;
359 double mPreviousDpi = 0;
360 QgsLayoutRenderContext::Flags mPreviousFlags = QgsLayoutRenderContext::Flags();
361 Qgis::TextRenderFormat mPreviousTextFormat = Qgis::TextRenderFormat::AlwaysOutlines;
362 int mPreviousExportLayer = 0;
363 QgsVectorSimplifyMethod mPreviousSimplifyMethod;
364 QStringList mExportThemes;
365 QVector< double > mPredefinedScales;
366
367};
369
371{
372 if ( !mLayout )
373 return PrintError;
374
375 ImageExportSettings settings = s;
376 if ( settings.dpi <= 0 )
377 settings.dpi = mLayout->renderContext().dpi();
378
379 mErrorFileName.clear();
380
381 int worldFilePageNo = -1;
382 if ( QgsLayoutItemMap *referenceMap = mLayout->referenceMap() )
383 {
384 worldFilePageNo = referenceMap->page();
385 }
386
387 QFileInfo fi( filePath );
388 QDir dir;
389 if ( !dir.exists( fi.absolutePath() ) )
390 {
391 dir.mkpath( fi.absolutePath() );
392 }
393
394 PageExportDetails pageDetails;
395 pageDetails.directory = fi.path();
396 pageDetails.baseName = fi.completeBaseName();
397 pageDetails.extension = fi.suffix();
398
399 LayoutContextPreviewSettingRestorer restorer( mLayout );
400 ( void )restorer;
401 LayoutContextSettingsRestorer dpiRestorer( mLayout );
402 ( void )dpiRestorer;
403 mLayout->renderContext().setDpi( settings.dpi );
404 mLayout->renderContext().setFlags( settings.flags );
405 mLayout->renderContext().setPredefinedScales( settings.predefinedMapScales );
406
407 QList< int > pages;
408 if ( settings.pages.empty() )
409 {
410 for ( int page = 0; page < mLayout->pageCollection()->pageCount(); ++page )
411 pages << page;
412 }
413 else
414 {
415 for ( int page : std::as_const( settings.pages ) )
416 {
417 if ( page >= 0 && page < mLayout->pageCollection()->pageCount() )
418 pages << page;
419 }
420 }
421
422 for ( int page : std::as_const( pages ) )
423 {
424 if ( !mLayout->pageCollection()->shouldExportPage( page ) )
425 {
426 continue;
427 }
428
429 bool skip = false;
430 QRectF bounds;
431 QImage image = createImage( settings, page, bounds, skip );
432
433 if ( skip )
434 continue; // should skip this page, e.g. null size
435
436 pageDetails.page = page;
437 QString outputFilePath = generateFileName( pageDetails );
438
439 if ( image.isNull() )
440 {
441 mErrorFileName = outputFilePath;
442 return MemoryError;
443 }
444
445 if ( !saveImage( image, outputFilePath, pageDetails.extension, settings.exportMetadata ? mLayout->project() : nullptr ) )
446 {
447 mErrorFileName = outputFilePath;
448 return FileError;
449 }
450
451 const bool shouldGeoreference = ( page == worldFilePageNo );
452 if ( shouldGeoreference )
453 {
454 georeferenceOutputPrivate( outputFilePath, nullptr, bounds, settings.dpi, shouldGeoreference );
455
456 if ( settings.generateWorldFile )
457 {
458 // should generate world file for this page
459 double a, b, c, d, e, f;
460 if ( bounds.isValid() )
461 computeWorldFileParameters( bounds, a, b, c, d, e, f, settings.dpi );
462 else
463 computeWorldFileParameters( a, b, c, d, e, f, settings.dpi );
464
465 QFileInfo fi( outputFilePath );
466 // build the world file name
467 QString outputSuffix = fi.suffix();
468 QString worldFileName = fi.absolutePath() + '/' + fi.completeBaseName() + '.'
469 + outputSuffix.at( 0 ) + outputSuffix.at( fi.suffix().size() - 1 ) + 'w';
470
471 writeWorldFile( worldFileName, a, b, c, d, e, f );
472 }
473 }
474
475 }
476 captureLabelingResults();
477 return Success;
478}
479
480QgsLayoutExporter::ExportResult QgsLayoutExporter::exportToImage( QgsAbstractLayoutIterator *iterator, const QString &baseFilePath, const QString &extension, const QgsLayoutExporter::ImageExportSettings &settings, QString &error, QgsFeedback *feedback )
481{
482 error.clear();
483
484 if ( !iterator->beginRender() )
485 return IteratorError;
486
487 int total = iterator->count();
488 double step = total > 0 ? 100.0 / total : 100.0;
489 int i = 0;
490 while ( iterator->next() )
491 {
492 if ( feedback )
493 {
494 if ( total > 0 )
495 feedback->setProperty( "progress", QObject::tr( "Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
496 else
497 feedback->setProperty( "progress", QObject::tr( "Exporting section %1" ).arg( i + 1 ).arg( total ) );
498 feedback->setProgress( step * i );
499 }
500 if ( feedback && feedback->isCanceled() )
501 {
502 iterator->endRender();
503 return Canceled;
504 }
505
506 QgsLayoutExporter exporter( iterator->layout() );
507 QString filePath = iterator->filePath( baseFilePath, extension );
508 ExportResult result = exporter.exportToImage( filePath, settings );
509 if ( result != Success )
510 {
511 if ( result == FileError )
512 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 ) );
513 iterator->endRender();
514 return result;
515 }
516 i++;
517 }
518
519 if ( feedback )
520 {
521 feedback->setProgress( 100 );
522 }
523
524 iterator->endRender();
525 return Success;
526}
527
529{
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, QgsUnitTypes::LayoutMillimeters );
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, QgsUnitTypes::LayoutMillimeters );
653 const QgsLayoutPoint topRightMm = mLayout->convertFromLayoutUnits( topRight, QgsUnitTypes::LayoutMillimeters );
654 const QgsLayoutPoint bottomLeftMm = mLayout->convertFromLayoutUnits( bottomLeft, QgsUnitTypes::LayoutMillimeters );
655 const QgsLayoutPoint bottomRightMm = mLayout->convertFromLayoutUnits( bottomRight, QgsUnitTypes::LayoutMillimeters );
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}
717
719{
720 error.clear();
721
722 if ( !iterator->beginRender() )
723 return IteratorError;
724
725 PdfExportSettings settings = s;
726
727 QPrinter printer;
728 QPainter p;
729
730 int total = iterator->count();
731 double step = total > 0 ? 100.0 / total : 100.0;
732 int i = 0;
733 bool first = true;
734 while ( iterator->next() )
735 {
736 if ( feedback )
737 {
738 if ( total > 0 )
739 feedback->setProperty( "progress", QObject::tr( "Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
740 else
741 feedback->setProperty( "progress", QObject::tr( "Exporting section %1" ).arg( i + 1 ) );
742 feedback->setProgress( step * i );
743 }
744 if ( feedback && feedback->isCanceled() )
745 {
746 iterator->endRender();
747 return Canceled;
748 }
749
750 if ( s.dpi <= 0 )
751 settings.dpi = iterator->layout()->renderContext().dpi();
752
753 LayoutContextPreviewSettingRestorer restorer( iterator->layout() );
754 ( void )restorer;
755 LayoutContextSettingsRestorer contextRestorer( iterator->layout() );
756 ( void )contextRestorer;
757 iterator->layout()->renderContext().setDpi( settings.dpi );
758
759 iterator->layout()->renderContext().setFlags( settings.flags );
761
762 if ( settings.simplifyGeometries )
763 {
764 iterator->layout()->renderContext().setSimplifyMethod( createExportSimplifyMethod() );
765 }
766
767 // If we are not printing as raster, temporarily disable advanced effects
768 // as QPrinter does not support composition modes and can result
769 // in items missing from the output
771
773
774 iterator->layout()->renderContext().setTextRenderFormat( settings.textRenderFormat );
775
776 if ( first )
777 {
778 preparePrintAsPdf( iterator->layout(), printer, fileName );
779 preparePrint( iterator->layout(), printer, false );
780
781 if ( !p.begin( &printer ) )
782 {
783 //error beginning print
784 return PrintError;
785 }
786 p.setRenderHint( QPainter::LosslessImageRendering, iterator->layout()->renderContext().flags() & QgsLayoutRenderContext::FlagLosslessImageRendering );
787 }
788
789 QgsLayoutExporter exporter( iterator->layout() );
790
791 ExportResult result = exporter.printPrivate( printer, p, !first, settings.dpi, settings.rasterizeWholeImage );
792 if ( result != Success )
793 {
794 if ( result == FileError )
795 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 ) );
796 iterator->endRender();
797 return result;
798 }
799 first = false;
800 i++;
801 }
802
803 if ( feedback )
804 {
805 feedback->setProgress( 100 );
806 }
807
808 iterator->endRender();
809 return Success;
810}
811
813{
814 error.clear();
815
816 if ( !iterator->beginRender() )
817 return IteratorError;
818
819 int total = iterator->count();
820 double step = total > 0 ? 100.0 / total : 100.0;
821 int i = 0;
822 while ( iterator->next() )
823 {
824 if ( feedback )
825 {
826 if ( total > 0 )
827 feedback->setProperty( "progress", QObject::tr( "Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
828 else
829 feedback->setProperty( "progress", QObject::tr( "Exporting section %1" ).arg( i + 1 ).arg( total ) );
830 feedback->setProgress( step * i );
831 }
832 if ( feedback && feedback->isCanceled() )
833 {
834 iterator->endRender();
835 return Canceled;
836 }
837
838 QString filePath = iterator->filePath( baseFilePath, QStringLiteral( "pdf" ) );
839
840 QgsLayoutExporter exporter( iterator->layout() );
841 ExportResult result = exporter.exportToPdf( filePath, settings );
842 if ( result != Success )
843 {
844 if ( result == FileError )
845 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 ) );
846 iterator->endRender();
847 return result;
848 }
849 i++;
850 }
851
852 if ( feedback )
853 {
854 feedback->setProgress( 100 );
855 }
856
857 iterator->endRender();
858 return Success;
859}
860
862{
863 if ( !mLayout )
864 return PrintError;
865
867 if ( settings.dpi <= 0 )
868 settings.dpi = mLayout->renderContext().dpi();
869
870 mErrorFileName.clear();
871
872 LayoutContextPreviewSettingRestorer restorer( mLayout );
873 ( void )restorer;
874 LayoutContextSettingsRestorer contextRestorer( mLayout );
875 ( void )contextRestorer;
876 mLayout->renderContext().setDpi( settings.dpi );
877
878 mLayout->renderContext().setFlags( settings.flags );
879 mLayout->renderContext().setPredefinedScales( settings.predefinedMapScales );
880 // If we are not printing as raster, temporarily disable advanced effects
881 // as QPrinter does not support composition modes and can result
882 // in items missing from the output
883 mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagUseAdvancedEffects, !settings.rasterizeWholeImage );
884
885 preparePrint( mLayout, printer, true );
886 QPainter p;
887 if ( !p.begin( &printer ) )
888 {
889 //error beginning print
890 return PrintError;
891 }
892 p.setRenderHint( QPainter::LosslessImageRendering, mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagLosslessImageRendering );
893 ExportResult result = printPrivate( printer, p, false, settings.dpi, settings.rasterizeWholeImage );
894 p.end();
895
896 captureLabelingResults();
897 return result;
898}
899
901{
902 error.clear();
903
904 if ( !iterator->beginRender() )
905 return IteratorError;
906
907 PrintExportSettings settings = s;
908
909 QPainter p;
910
911 int total = iterator->count();
912 double step = total > 0 ? 100.0 / total : 100.0;
913 int i = 0;
914 bool first = true;
915 while ( iterator->next() )
916 {
917 if ( feedback )
918 {
919 if ( total > 0 )
920 feedback->setProperty( "progress", QObject::tr( "Printing %1 of %2" ).arg( i + 1 ).arg( total ) );
921 else
922 feedback->setProperty( "progress", QObject::tr( "Printing section %1" ).arg( i + 1 ).arg( total ) );
923 feedback->setProgress( step * i );
924 }
925 if ( feedback && feedback->isCanceled() )
926 {
927 iterator->endRender();
928 return Canceled;
929 }
930
931 if ( s.dpi <= 0 )
932 settings.dpi = iterator->layout()->renderContext().dpi();
933
934 LayoutContextPreviewSettingRestorer restorer( iterator->layout() );
935 ( void )restorer;
936 LayoutContextSettingsRestorer contextRestorer( iterator->layout() );
937 ( void )contextRestorer;
938 iterator->layout()->renderContext().setDpi( settings.dpi );
939
940 iterator->layout()->renderContext().setFlags( settings.flags );
942
943 // If we are not printing as raster, temporarily disable advanced effects
944 // as QPrinter does not support composition modes and can result
945 // in items missing from the output
947
948 if ( first )
949 {
950 preparePrint( iterator->layout(), printer, true );
951
952 if ( !p.begin( &printer ) )
953 {
954 //error beginning print
955 return PrintError;
956 }
957 p.setRenderHint( QPainter::LosslessImageRendering, iterator->layout()->renderContext().flags() & QgsLayoutRenderContext::FlagLosslessImageRendering );
958 }
959
960 QgsLayoutExporter exporter( iterator->layout() );
961
962 ExportResult result = exporter.printPrivate( printer, p, !first, settings.dpi, settings.rasterizeWholeImage );
963 if ( result != Success )
964 {
965 iterator->endRender();
966 return result;
967 }
968 first = false;
969 i++;
970 }
971
972 if ( feedback )
973 {
974 feedback->setProgress( 100 );
975 }
976
977 iterator->endRender();
978 return Success;
979}
980
982{
983 if ( !mLayout )
984 return PrintError;
985
986 SvgExportSettings settings = s;
987 if ( settings.dpi <= 0 )
988 settings.dpi = mLayout->renderContext().dpi();
989
990 mErrorFileName.clear();
991
992 LayoutContextPreviewSettingRestorer restorer( mLayout );
993 ( void )restorer;
994 LayoutContextSettingsRestorer contextRestorer( mLayout );
995 ( void )contextRestorer;
996 mLayout->renderContext().setDpi( settings.dpi );
997
998 mLayout->renderContext().setFlags( settings.flags );
999 mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagForceVectorOutput, settings.forceVectorOutput );
1000 mLayout->renderContext().setTextRenderFormat( s.textRenderFormat );
1001 mLayout->renderContext().setPredefinedScales( settings.predefinedMapScales );
1002
1003 if ( settings.simplifyGeometries )
1004 {
1005 mLayout->renderContext().setSimplifyMethod( createExportSimplifyMethod() );
1006 }
1007
1008 QFileInfo fi( filePath );
1009 PageExportDetails pageDetails;
1010 pageDetails.directory = fi.path();
1011 pageDetails.baseName = fi.baseName();
1012 pageDetails.extension = fi.completeSuffix();
1013
1014 double inchesToLayoutUnits = mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 1, QgsUnitTypes::LayoutInches ) );
1015
1016 for ( int i = 0; i < mLayout->pageCollection()->pageCount(); ++i )
1017 {
1018 if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1019 {
1020 continue;
1021 }
1022
1023 pageDetails.page = i;
1024 QString fileName = generateFileName( pageDetails );
1025
1026 QgsLayoutItemPage *pageItem = mLayout->pageCollection()->page( i );
1027 QRectF bounds;
1028 if ( settings.cropToContents )
1029 {
1030 if ( mLayout->pageCollection()->pageCount() == 1 )
1031 {
1032 // single page, so include everything
1033 bounds = mLayout->layoutBounds( true );
1034 }
1035 else
1036 {
1037 // multi page, so just clip to items on current page
1038 bounds = mLayout->pageItemBounds( i, true );
1039 }
1040 bounds = bounds.adjusted( -settings.cropMargins.left(),
1041 -settings.cropMargins.top(),
1042 settings.cropMargins.right(),
1043 settings.cropMargins.bottom() );
1044 }
1045 else
1046 {
1047 bounds = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() );
1048 }
1049
1050 //width in pixel
1051 int width = static_cast< int >( bounds.width() * settings.dpi / inchesToLayoutUnits );
1052 //height in pixel
1053 int height = static_cast< int >( bounds.height() * settings.dpi / inchesToLayoutUnits );
1054 if ( width == 0 || height == 0 )
1055 {
1056 //invalid size, skip this page
1057 continue;
1058 }
1059
1060 if ( settings.exportAsLayers )
1061 {
1062 mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagRenderLabelsByMapLayer, settings.exportLabelsToSeparateLayers );
1063 const QRectF paperRect = QRectF( pageItem->pos().x(),
1064 pageItem->pos().y(),
1065 pageItem->rect().width(),
1066 pageItem->rect().height() );
1067 QDomDocument svg;
1068 QDomNode svgDocRoot;
1069 const QList<QGraphicsItem *> items = mLayout->items( paperRect,
1070 Qt::IntersectsItemBoundingRect,
1071 Qt::AscendingOrder );
1072
1073 auto exportFunc = [this, &settings, width, height, i, bounds, fileName, &svg, &svgDocRoot]( unsigned int layerId, const QgsLayoutItem::ExportLayerDetail & layerDetail )->QgsLayoutExporter::ExportResult
1074 {
1075 return renderToLayeredSvg( settings, width, height, i, bounds, fileName, layerId, layerDetail.name, svg, svgDocRoot, settings.exportMetadata );
1076 };
1077 ExportResult res = handleLayeredExport( items, exportFunc );
1078 if ( res != Success )
1079 return res;
1080
1081 if ( settings.exportMetadata )
1082 appendMetadataToSvg( svg );
1083
1084 QFile out( fileName );
1085 bool openOk = out.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate );
1086 if ( !openOk )
1087 {
1088 mErrorFileName = fileName;
1089 return FileError;
1090 }
1091
1092 out.write( svg.toByteArray() );
1093 }
1094 else
1095 {
1096 QBuffer svgBuffer;
1097 {
1098 QSvgGenerator generator;
1099 if ( settings.exportMetadata )
1100 {
1101 generator.setTitle( mLayout->project()->metadata().title() );
1102 generator.setDescription( mLayout->project()->metadata().abstract() );
1103 }
1104 generator.setOutputDevice( &svgBuffer );
1105 generator.setSize( QSize( width, height ) );
1106 generator.setViewBox( QRect( 0, 0, width, height ) );
1107 generator.setResolution( static_cast< int >( std::round( settings.dpi ) ) );
1108
1109 QPainter p;
1110 bool createOk = p.begin( &generator );
1111 if ( !createOk )
1112 {
1113 mErrorFileName = fileName;
1114 return FileError;
1115 }
1116
1117 if ( settings.cropToContents )
1118 renderRegion( &p, bounds );
1119 else
1120 renderPage( &p, i );
1121
1122 p.end();
1123 }
1124 {
1125 svgBuffer.close();
1126 svgBuffer.open( QIODevice::ReadOnly );
1127 QDomDocument svg;
1128 QString errorMsg;
1129 int errorLine;
1130 if ( ! svg.setContent( &svgBuffer, false, &errorMsg, &errorLine ) )
1131 {
1132 mErrorFileName = fileName;
1133 return SvgLayerError;
1134 }
1135
1136 if ( settings.exportMetadata )
1137 appendMetadataToSvg( svg );
1138
1139 QFile out( fileName );
1140 bool openOk = out.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate );
1141 if ( !openOk )
1142 {
1143 mErrorFileName = fileName;
1144 return FileError;
1145 }
1146
1147 out.write( svg.toByteArray() );
1148 }
1149 }
1150 }
1151 captureLabelingResults();
1152 return Success;
1153}
1154
1156{
1157 error.clear();
1158
1159 if ( !iterator->beginRender() )
1160 return IteratorError;
1161
1162 int total = iterator->count();
1163 double step = total > 0 ? 100.0 / total : 100.0;
1164 int i = 0;
1165 while ( iterator->next() )
1166 {
1167 if ( feedback )
1168 {
1169 if ( total > 0 )
1170 feedback->setProperty( "progress", QObject::tr( "Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
1171 else
1172 feedback->setProperty( "progress", QObject::tr( "Exporting section %1" ).arg( i + 1 ).arg( total ) );
1173
1174 feedback->setProgress( step * i );
1175 }
1176 if ( feedback && feedback->isCanceled() )
1177 {
1178 iterator->endRender();
1179 return Canceled;
1180 }
1181
1182 QString filePath = iterator->filePath( baseFilePath, QStringLiteral( "svg" ) );
1183
1184 QgsLayoutExporter exporter( iterator->layout() );
1185 ExportResult result = exporter.exportToSvg( filePath, settings );
1186 if ( result != Success )
1187 {
1188 if ( result == FileError )
1189 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 ) );
1190 iterator->endRender();
1191 return result;
1192 }
1193 i++;
1194 }
1195
1196 if ( feedback )
1197 {
1198 feedback->setProgress( 100 );
1199 }
1200
1201 iterator->endRender();
1202 return Success;
1203
1204}
1205
1206QMap<QString, QgsLabelingResults *> QgsLayoutExporter::labelingResults()
1207{
1208 return mLabelingResults;
1209}
1210
1211QMap<QString, QgsLabelingResults *> QgsLayoutExporter::takeLabelingResults()
1212{
1213 QMap<QString, QgsLabelingResults *> res;
1214 std::swap( mLabelingResults, res );
1215 return res;
1216}
1217
1218void QgsLayoutExporter::preparePrintAsPdf( QgsLayout *layout, QPrinter &printer, const QString &filePath )
1219{
1220 QFileInfo fi( filePath );
1221 QDir dir;
1222 if ( !dir.exists( fi.absolutePath() ) )
1223 {
1224 dir.mkpath( fi.absolutePath() );
1225 }
1226
1227 printer.setOutputFileName( filePath );
1228 printer.setOutputFormat( QPrinter::PdfFormat );
1229
1230 updatePrinterPageSize( layout, printer, firstPageToBeExported( layout ) );
1231
1232 // TODO: add option for this in layout
1233 // May not work on Windows or non-X11 Linux. Works fine on Mac using QPrinter::NativeFormat
1234 //printer.setFontEmbeddingEnabled( true );
1235
1236#if defined(HAS_KDE_QT5_PDF_TRANSFORM_FIX) || QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
1237 // paint engine hack not required, fixed upstream
1238#else
1239 QgsPaintEngineHack::fixEngineFlags( printer.paintEngine() );
1240#endif
1241}
1242
1243void QgsLayoutExporter::preparePrint( QgsLayout *layout, QPrinter &printer, bool setFirstPageSize )
1244{
1245 printer.setFullPage( true );
1246 printer.setColorMode( QPrinter::Color );
1247
1248 //set user-defined resolution
1249 printer.setResolution( static_cast< int>( std::round( layout->renderContext().dpi() ) ) );
1250
1251 if ( setFirstPageSize )
1252 {
1253 updatePrinterPageSize( layout, printer, firstPageToBeExported( layout ) );
1254 }
1255}
1256
1258{
1259 if ( mLayout->pageCollection()->pageCount() == 0 )
1260 return PrintError;
1261
1262 preparePrint( mLayout, printer, true );
1263 QPainter p;
1264 if ( !p.begin( &printer ) )
1265 {
1266 //error beginning print
1267 return PrintError;
1268 }
1269
1270 printPrivate( printer, p );
1271 p.end();
1272 return Success;
1273}
1274
1275QgsLayoutExporter::ExportResult QgsLayoutExporter::printPrivate( QPrinter &printer, QPainter &painter, bool startNewPage, double dpi, bool rasterize )
1276{
1277 //layout starts page numbering at 0
1278 int fromPage = ( printer.fromPage() < 1 ) ? 0 : printer.fromPage() - 1;
1279 int toPage = ( printer.toPage() < 1 ) ? mLayout->pageCollection()->pageCount() - 1 : printer.toPage() - 1;
1280
1281 bool pageExported = false;
1282 if ( rasterize )
1283 {
1284 for ( int i = fromPage; i <= toPage; ++i )
1285 {
1286 if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1287 {
1288 continue;
1289 }
1290
1291 updatePrinterPageSize( mLayout, printer, i );
1292 if ( ( pageExported && i > fromPage ) || startNewPage )
1293 {
1294 printer.newPage();
1295 }
1296
1297 QImage image = renderPageToImage( i, QSize(), dpi );
1298 if ( !image.isNull() )
1299 {
1300 QRectF targetArea( 0, 0, image.width(), image.height() );
1301 painter.drawImage( targetArea, image, targetArea );
1302 }
1303 else
1304 {
1305 return MemoryError;
1306 }
1307 pageExported = true;
1308 }
1309 }
1310 else
1311 {
1312 for ( int i = fromPage; i <= toPage; ++i )
1313 {
1314 if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1315 {
1316 continue;
1317 }
1318
1319 updatePrinterPageSize( mLayout, printer, i );
1320
1321 if ( ( pageExported && i > fromPage ) || startNewPage )
1322 {
1323 printer.newPage();
1324 }
1325 renderPage( &painter, i );
1326 pageExported = true;
1327 }
1328 }
1329 return Success;
1330}
1331
1332void QgsLayoutExporter::updatePrinterPageSize( QgsLayout *layout, QPrinter &printer, int page )
1333{
1334 QgsLayoutSize pageSize = layout->pageCollection()->page( page )->sizeWithUnits();
1336
1337 QPageLayout pageLayout( QPageSize( pageSizeMM.toQSizeF(), QPageSize::Millimeter ),
1338 QPageLayout::Portrait,
1339 QMarginsF( 0, 0, 0, 0 ) );
1340 pageLayout.setMode( QPageLayout::FullPageMode );
1341 printer.setPageLayout( pageLayout );
1342 printer.setFullPage( true );
1343 printer.setPageMargins( QMarginsF( 0, 0, 0, 0 ) );
1344}
1345
1346QgsLayoutExporter::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
1347{
1348 QBuffer svgBuffer;
1349 {
1350 QSvgGenerator generator;
1351 if ( includeMetadata )
1352 {
1353 if ( const QgsMasterLayoutInterface *l = dynamic_cast< const QgsMasterLayoutInterface * >( mLayout.data() ) )
1354 generator.setTitle( l->name() );
1355 else if ( mLayout->project() )
1356 generator.setTitle( mLayout->project()->title() );
1357 }
1358
1359 generator.setOutputDevice( &svgBuffer );
1360 generator.setSize( QSize( static_cast< int >( std::round( width ) ),
1361 static_cast< int >( std::round( height ) ) ) );
1362 generator.setViewBox( QRect( 0, 0,
1363 static_cast< int >( std::round( width ) ),
1364 static_cast< int >( std::round( height ) ) ) );
1365 generator.setResolution( static_cast< int >( std::round( settings.dpi ) ) ); //because the rendering is done in mm, convert the dpi
1366
1367 QPainter svgPainter( &generator );
1368 if ( settings.cropToContents )
1369 renderRegion( &svgPainter, bounds );
1370 else
1371 renderPage( &svgPainter, page );
1372 }
1373
1374// post-process svg output to create groups in a single svg file
1375// we create inkscape layers since it's nice and clean and free
1376// and fully svg compatible
1377 {
1378 svgBuffer.close();
1379 svgBuffer.open( QIODevice::ReadOnly );
1380 QDomDocument doc;
1381 QString errorMsg;
1382 int errorLine;
1383 if ( ! doc.setContent( &svgBuffer, false, &errorMsg, &errorLine ) )
1384 {
1385 mErrorFileName = filename;
1386 return SvgLayerError;
1387 }
1388 if ( 1 == svgLayerId )
1389 {
1390 svg = QDomDocument( doc.doctype() );
1391 svg.appendChild( svg.importNode( doc.firstChild(), false ) );
1392 svgDocRoot = svg.importNode( doc.elementsByTagName( QStringLiteral( "svg" ) ).at( 0 ), false );
1393 svgDocRoot.toElement().setAttribute( QStringLiteral( "xmlns:inkscape" ), QStringLiteral( "http://www.inkscape.org/namespaces/inkscape" ) );
1394 svg.appendChild( svgDocRoot );
1395 }
1396 QDomNode mainGroup = svg.importNode( doc.elementsByTagName( QStringLiteral( "g" ) ).at( 0 ), true );
1397 mainGroup.toElement().setAttribute( QStringLiteral( "id" ), layerName );
1398 mainGroup.toElement().setAttribute( QStringLiteral( "inkscape:label" ), layerName );
1399 mainGroup.toElement().setAttribute( QStringLiteral( "inkscape:groupmode" ), QStringLiteral( "layer" ) );
1400 QDomNode defs = svg.importNode( doc.elementsByTagName( QStringLiteral( "defs" ) ).at( 0 ), true );
1401 svgDocRoot.appendChild( defs );
1402 svgDocRoot.appendChild( mainGroup );
1403 }
1404 return Success;
1405}
1406
1407void QgsLayoutExporter::appendMetadataToSvg( QDomDocument &svg ) const
1408{
1409 const QgsProjectMetadata &metadata = mLayout->project()->metadata();
1410 QDomElement metadataElement = svg.createElement( QStringLiteral( "metadata" ) );
1411 QDomElement rdfElement = svg.createElement( QStringLiteral( "rdf:RDF" ) );
1412 rdfElement.setAttribute( QStringLiteral( "xmlns:rdf" ), QStringLiteral( "http://www.w3.org/1999/02/22-rdf-syntax-ns#" ) );
1413 rdfElement.setAttribute( QStringLiteral( "xmlns:rdfs" ), QStringLiteral( "http://www.w3.org/2000/01/rdf-schema#" ) );
1414 rdfElement.setAttribute( QStringLiteral( "xmlns:dc" ), QStringLiteral( "http://purl.org/dc/elements/1.1/" ) );
1415 QDomElement descriptionElement = svg.createElement( QStringLiteral( "rdf:Description" ) );
1416 QDomElement workElement = svg.createElement( QStringLiteral( "cc:Work" ) );
1417 workElement.setAttribute( QStringLiteral( "rdf:about" ), QString() );
1418
1419 auto addTextNode = [&workElement, &descriptionElement, &svg]( const QString & tag, const QString & value )
1420 {
1421 // inkscape compatible
1422 QDomElement element = svg.createElement( tag );
1423 QDomText t = svg.createTextNode( value );
1424 element.appendChild( t );
1425 workElement.appendChild( element );
1426
1427 // svg spec compatible
1428 descriptionElement.setAttribute( tag, value );
1429 };
1430
1431 addTextNode( QStringLiteral( "dc:format" ), QStringLiteral( "image/svg+xml" ) );
1432 addTextNode( QStringLiteral( "dc:title" ), metadata.title() );
1433 addTextNode( QStringLiteral( "dc:date" ), metadata.creationDateTime().toString( Qt::ISODate ) );
1434 addTextNode( QStringLiteral( "dc:identifier" ), metadata.identifier() );
1435 addTextNode( QStringLiteral( "dc:description" ), metadata.abstract() );
1436
1437 auto addAgentNode = [&workElement, &descriptionElement, &svg]( const QString & tag, const QString & value )
1438 {
1439 // inkscape compatible
1440 QDomElement inkscapeElement = svg.createElement( tag );
1441 QDomElement agentElement = svg.createElement( QStringLiteral( "cc:Agent" ) );
1442 QDomElement titleElement = svg.createElement( QStringLiteral( "dc:title" ) );
1443 QDomText t = svg.createTextNode( value );
1444 titleElement.appendChild( t );
1445 agentElement.appendChild( titleElement );
1446 inkscapeElement.appendChild( agentElement );
1447 workElement.appendChild( inkscapeElement );
1448
1449 // svg spec compatible
1450 QDomElement bagElement = svg.createElement( QStringLiteral( "rdf:Bag" ) );
1451 QDomElement liElement = svg.createElement( QStringLiteral( "rdf:li" ) );
1452 t = svg.createTextNode( value );
1453 liElement.appendChild( t );
1454 bagElement.appendChild( liElement );
1455
1456 QDomElement element = svg.createElement( tag );
1457 element.appendChild( bagElement );
1458 descriptionElement.appendChild( element );
1459 };
1460
1461 addAgentNode( QStringLiteral( "dc:creator" ), metadata.author() );
1462 addAgentNode( QStringLiteral( "dc:publisher" ), QStringLiteral( "QGIS %1" ).arg( Qgis::version() ) );
1463
1464 // keywords
1465 {
1466 QDomElement element = svg.createElement( QStringLiteral( "dc:subject" ) );
1467 QDomElement bagElement = svg.createElement( QStringLiteral( "rdf:Bag" ) );
1468 QgsAbstractMetadataBase::KeywordMap keywords = metadata.keywords();
1469 for ( auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
1470 {
1471 const QStringList words = it.value();
1472 for ( const QString &keyword : words )
1473 {
1474 QDomElement liElement = svg.createElement( QStringLiteral( "rdf:li" ) );
1475 QDomText t = svg.createTextNode( keyword );
1476 liElement.appendChild( t );
1477 bagElement.appendChild( liElement );
1478 }
1479 }
1480 element.appendChild( bagElement );
1481 workElement.appendChild( element );
1482 descriptionElement.appendChild( element );
1483 }
1484
1485 rdfElement.appendChild( descriptionElement );
1486 rdfElement.appendChild( workElement );
1487 metadataElement.appendChild( rdfElement );
1488 svg.documentElement().appendChild( metadataElement );
1489 svg.documentElement().setAttribute( QStringLiteral( "xmlns:cc" ), QStringLiteral( "http://creativecommons.org/ns#" ) );
1490}
1491
1492std::unique_ptr<double[]> QgsLayoutExporter::computeGeoTransform( const QgsLayoutItemMap *map, const QRectF &region, double dpi ) const
1493{
1494 if ( !map )
1495 map = mLayout->referenceMap();
1496
1497 if ( !map )
1498 return nullptr;
1499
1500 if ( dpi < 0 )
1501 dpi = mLayout->renderContext().dpi();
1502
1503 // calculate region of composition to export (in mm)
1504 QRectF exportRegion = region;
1505 if ( !exportRegion.isValid() )
1506 {
1507 int pageNumber = map->page();
1508
1509 QgsLayoutItemPage *page = mLayout->pageCollection()->page( pageNumber );
1510 double pageY = page->pos().y();
1511 QSizeF pageSize = page->rect().size();
1512 exportRegion = QRectF( 0, pageY, pageSize.width(), pageSize.height() );
1513 }
1514
1515 // map rectangle (in mm)
1516 QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
1517
1518 // destination width/height in mm
1519 double outputHeightMM = exportRegion.height();
1520 double outputWidthMM = exportRegion.width();
1521
1522 // map properties
1523 QgsRectangle mapExtent = map->extent();
1524 double mapXCenter = mapExtent.center().x();
1525 double mapYCenter = mapExtent.center().y();
1526 double alpha = - map->mapRotation() / 180 * M_PI;
1527 double sinAlpha = std::sin( alpha );
1528 double cosAlpha = std::cos( alpha );
1529
1530 // get the extent (in map units) for the exported region
1531 QPointF mapItemPos = map->pos();
1532 //adjust item position so it is relative to export region
1533 mapItemPos.rx() -= exportRegion.left();
1534 mapItemPos.ry() -= exportRegion.top();
1535
1536 // calculate extent of entire page in map units
1537 double xRatio = mapExtent.width() / mapItemSceneRect.width();
1538 double yRatio = mapExtent.height() / mapItemSceneRect.height();
1539 double xmin = mapExtent.xMinimum() - mapItemPos.x() * xRatio;
1540 double ymax = mapExtent.yMaximum() + mapItemPos.y() * yRatio;
1541 QgsRectangle paperExtent( xmin, ymax - outputHeightMM * yRatio, xmin + outputWidthMM * xRatio, ymax );
1542
1543 // calculate origin of page
1544 double X0 = paperExtent.xMinimum();
1545 double Y0 = paperExtent.yMaximum();
1546
1547 if ( !qgsDoubleNear( alpha, 0.0 ) )
1548 {
1549 // translate origin to account for map rotation
1550 double X1 = X0 - mapXCenter;
1551 double Y1 = Y0 - mapYCenter;
1552 double X2 = X1 * cosAlpha + Y1 * sinAlpha;
1553 double Y2 = -X1 * sinAlpha + Y1 * cosAlpha;
1554 X0 = X2 + mapXCenter;
1555 Y0 = Y2 + mapYCenter;
1556 }
1557
1558 // calculate scaling of pixels
1559 int pageWidthPixels = static_cast< int >( dpi * outputWidthMM / 25.4 );
1560 int pageHeightPixels = static_cast< int >( dpi * outputHeightMM / 25.4 );
1561 double pixelWidthScale = paperExtent.width() / pageWidthPixels;
1562 double pixelHeightScale = paperExtent.height() / pageHeightPixels;
1563
1564 // transform matrix
1565 std::unique_ptr<double[]> t( new double[6] );
1566 t[0] = X0;
1567 t[1] = cosAlpha * pixelWidthScale;
1568 t[2] = -sinAlpha * pixelWidthScale;
1569 t[3] = Y0;
1570 t[4] = -sinAlpha * pixelHeightScale;
1571 t[5] = -cosAlpha * pixelHeightScale;
1572
1573 return t;
1574}
1575
1576void QgsLayoutExporter::writeWorldFile( const QString &worldFileName, double a, double b, double c, double d, double e, double f ) const
1577{
1578 QFile worldFile( worldFileName );
1579 if ( !worldFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
1580 {
1581 return;
1582 }
1583 QTextStream fout( &worldFile );
1584
1585 // QString::number does not use locale settings (for the decimal point)
1586 // which is what we want here
1587 fout << QString::number( a, 'f', 12 ) << "\r\n";
1588 fout << QString::number( d, 'f', 12 ) << "\r\n";
1589 fout << QString::number( b, 'f', 12 ) << "\r\n";
1590 fout << QString::number( e, 'f', 12 ) << "\r\n";
1591 fout << QString::number( c, 'f', 12 ) << "\r\n";
1592 fout << QString::number( f, 'f', 12 ) << "\r\n";
1593}
1594
1595bool QgsLayoutExporter::georeferenceOutput( const QString &file, QgsLayoutItemMap *map, const QRectF &exportRegion, double dpi ) const
1596{
1597 return georeferenceOutputPrivate( file, map, exportRegion, dpi, false );
1598}
1599
1600bool QgsLayoutExporter::georeferenceOutputPrivate( const QString &file, QgsLayoutItemMap *map, const QRectF &exportRegion, double dpi, bool includeGeoreference, bool includeMetadata ) const
1601{
1602 if ( !mLayout )
1603 return false;
1604
1605 if ( !map && includeGeoreference )
1606 map = mLayout->referenceMap();
1607
1608 std::unique_ptr<double[]> t;
1609
1610 if ( map && includeGeoreference )
1611 {
1612 if ( dpi < 0 )
1613 dpi = mLayout->renderContext().dpi();
1614
1615 t = computeGeoTransform( map, exportRegion, dpi );
1616 }
1617
1618 // important - we need to manually specify the DPI in advance, as GDAL will otherwise
1619 // assume a DPI of 150
1620 CPLSetConfigOption( "GDAL_PDF_DPI", QString::number( dpi ).toLocal8Bit().constData() );
1621 gdal::dataset_unique_ptr outputDS( GDALOpen( file.toLocal8Bit().constData(), GA_Update ) );
1622 if ( outputDS )
1623 {
1624 if ( t )
1625 GDALSetGeoTransform( outputDS.get(), t.get() );
1626
1627 if ( includeMetadata )
1628 {
1629 QString creationDateString;
1630 const QDateTime creationDateTime = mLayout->project()->metadata().creationDateTime();
1631 if ( creationDateTime.isValid() )
1632 {
1633 creationDateString = QStringLiteral( "D:%1" ).arg( mLayout->project()->metadata().creationDateTime().toString( QStringLiteral( "yyyyMMddHHmmss" ) ) );
1634 if ( creationDateTime.timeZone().isValid() )
1635 {
1636 int offsetFromUtc = creationDateTime.timeZone().offsetFromUtc( creationDateTime );
1637 creationDateString += ( offsetFromUtc >= 0 ) ? '+' : '-';
1638 offsetFromUtc = std::abs( offsetFromUtc );
1639 int offsetHours = offsetFromUtc / 3600;
1640 int offsetMins = ( offsetFromUtc % 3600 ) / 60;
1641 creationDateString += QStringLiteral( "%1'%2'" ).arg( offsetHours ).arg( offsetMins );
1642 }
1643 }
1644 GDALSetMetadataItem( outputDS.get(), "CREATION_DATE", creationDateString.toUtf8().constData(), nullptr );
1645
1646 GDALSetMetadataItem( outputDS.get(), "AUTHOR", mLayout->project()->metadata().author().toUtf8().constData(), nullptr );
1647 const QString creator = QStringLiteral( "QGIS %1" ).arg( Qgis::version() );
1648 GDALSetMetadataItem( outputDS.get(), "CREATOR", creator.toUtf8().constData(), nullptr );
1649 GDALSetMetadataItem( outputDS.get(), "PRODUCER", creator.toUtf8().constData(), nullptr );
1650 GDALSetMetadataItem( outputDS.get(), "SUBJECT", mLayout->project()->metadata().abstract().toUtf8().constData(), nullptr );
1651 GDALSetMetadataItem( outputDS.get(), "TITLE", mLayout->project()->metadata().title().toUtf8().constData(), nullptr );
1652
1653 const QgsAbstractMetadataBase::KeywordMap keywords = mLayout->project()->metadata().keywords();
1654 QStringList allKeywords;
1655 for ( auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
1656 {
1657 allKeywords.append( QStringLiteral( "%1: %2" ).arg( it.key(), it.value().join( ',' ) ) );
1658 }
1659 const QString keywordString = allKeywords.join( ';' );
1660 GDALSetMetadataItem( outputDS.get(), "KEYWORDS", keywordString.toUtf8().constData(), nullptr );
1661 }
1662
1663 if ( t )
1664 GDALSetProjection( outputDS.get(), map->crs().toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED_GDAL ).toLocal8Bit().constData() );
1665 }
1666 CPLSetConfigOption( "GDAL_PDF_DPI", nullptr );
1667
1668 return true;
1669}
1670
1671QString nameForLayerWithItems( const QList< QGraphicsItem * > &items, unsigned int layerId )
1672{
1673 if ( items.count() == 1 )
1674 {
1675 if ( QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( items.at( 0 ) ) )
1676 {
1677 QString name = layoutItem->displayName();
1678 // cleanup default item ID format
1679 if ( name.startsWith( '<' ) && name.endsWith( '>' ) )
1680 name = name.mid( 1, name.length() - 2 );
1681 return name;
1682 }
1683 }
1684 else if ( items.count() > 1 )
1685 {
1686 QStringList currentLayerItemTypes;
1687 for ( QGraphicsItem *item : items )
1688 {
1689 if ( QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item ) )
1690 {
1691 const QString itemType = QgsApplication::layoutItemRegistry()->itemMetadata( layoutItem->type() )->visibleName();
1692 const QString itemTypePlural = QgsApplication::layoutItemRegistry()->itemMetadata( layoutItem->type() )->visiblePluralName();
1693 if ( !currentLayerItemTypes.contains( itemType ) && !currentLayerItemTypes.contains( itemTypePlural ) )
1694 currentLayerItemTypes << itemType;
1695 else if ( currentLayerItemTypes.contains( itemType ) )
1696 {
1697 currentLayerItemTypes.replace( currentLayerItemTypes.indexOf( itemType ), itemTypePlural );
1698 }
1699 }
1700 else
1701 {
1702 if ( !currentLayerItemTypes.contains( QObject::tr( "Other" ) ) )
1703 currentLayerItemTypes.append( QObject::tr( "Other" ) );
1704 }
1705 }
1706 return currentLayerItemTypes.join( QLatin1String( ", " ) );
1707 }
1708 return QObject::tr( "Layer %1" ).arg( layerId );
1709}
1710
1711QgsLayoutExporter::ExportResult QgsLayoutExporter::handleLayeredExport( const QList<QGraphicsItem *> &items,
1712 const std::function<QgsLayoutExporter::ExportResult( unsigned int, const QgsLayoutItem::ExportLayerDetail & )> &exportFunc )
1713{
1714 LayoutItemHider itemHider( items );
1715 ( void )itemHider;
1716
1717 int prevType = -1;
1719 unsigned int layerId = 1;
1721 itemHider.hideAll();
1722 const QList< QGraphicsItem * > itemsToIterate = itemHider.itemsToIterate();
1723 QList< QGraphicsItem * > currentLayerItems;
1724 for ( QGraphicsItem *item : itemsToIterate )
1725 {
1726 QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item );
1727
1728 bool canPlaceInExistingLayer = false;
1729 if ( layoutItem )
1730 {
1731 switch ( layoutItem->exportLayerBehavior() )
1732 {
1734 {
1735 switch ( prevItemBehavior )
1736 {
1738 canPlaceInExistingLayer = true;
1739 break;
1740
1742 canPlaceInExistingLayer = prevType == -1 || prevType == layoutItem->type();
1743 break;
1744
1747 canPlaceInExistingLayer = false;
1748 break;
1749 }
1750 break;
1751 }
1752
1754 {
1755 switch ( prevItemBehavior )
1756 {
1759 canPlaceInExistingLayer = prevType == -1 || prevType == layoutItem->type();
1760 break;
1761
1764 canPlaceInExistingLayer = false;
1765 break;
1766 }
1767 break;
1768 }
1769
1771 {
1772 canPlaceInExistingLayer = false;
1773 break;
1774 }
1775
1777 canPlaceInExistingLayer = false;
1778 break;
1779 }
1780 prevItemBehavior = layoutItem->exportLayerBehavior();
1781 prevType = layoutItem->type();
1782 }
1783 else
1784 {
1785 prevItemBehavior = QgsLayoutItem::MustPlaceInOwnLayer;
1786 }
1787
1788 if ( canPlaceInExistingLayer )
1789 {
1790 currentLayerItems << item;
1791 item->show();
1792 }
1793 else
1794 {
1795 if ( !currentLayerItems.isEmpty() )
1796 {
1797 layerDetails.name = nameForLayerWithItems( currentLayerItems, layerId );
1798
1799 ExportResult result = exportFunc( layerId, layerDetails );
1800 if ( result != Success )
1801 return result;
1802 layerId++;
1803 currentLayerItems.clear();
1804 }
1805
1806 itemHider.hideAll();
1807 item->show();
1808
1809 if ( layoutItem && layoutItem->exportLayerBehavior() == QgsLayoutItem::ItemContainsSubLayers )
1810 {
1811 int layoutItemLayerIdx = 0;
1813 mLayout->renderContext().setCurrentExportLayer( layoutItemLayerIdx );
1815 layoutItem->startLayeredExport();
1816 while ( layoutItem->nextExportPart() )
1817 {
1819 mLayout->renderContext().setCurrentExportLayer( layoutItemLayerIdx );
1821
1822 layerDetails = layoutItem->exportLayerDetails();
1823 ExportResult result = exportFunc( layerId, layerDetails );
1824 if ( result != Success )
1825 return result;
1826 layerId++;
1827
1828 layoutItemLayerIdx++;
1829 }
1830 layerDetails.mapLayerId.clear();
1832 mLayout->renderContext().setCurrentExportLayer( -1 );
1834 layoutItem->stopLayeredExport();
1835 currentLayerItems.clear();
1836 }
1837 else
1838 {
1839 currentLayerItems << item;
1840 }
1841 }
1842 }
1843 if ( !currentLayerItems.isEmpty() )
1844 {
1845 layerDetails.name = nameForLayerWithItems( currentLayerItems, layerId );
1846 ExportResult result = exportFunc( layerId, layerDetails );
1847 if ( result != Success )
1848 return result;
1849 }
1850 return Success;
1851}
1852
1853QgsVectorSimplifyMethod QgsLayoutExporter::createExportSimplifyMethod()
1854{
1855 QgsVectorSimplifyMethod simplifyMethod;
1857 simplifyMethod.setForceLocalOptimization( true );
1858 // we use SnappedToGridGlobal, because it avoids gaps and slivers between previously adjacent polygons
1860 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.
1861 return simplifyMethod;
1862}
1863
1864void QgsLayoutExporter::computeWorldFileParameters( double &a, double &b, double &c, double &d, double &e, double &f, double dpi ) const
1865{
1866 if ( !mLayout )
1867 return;
1868
1869 QgsLayoutItemMap *map = mLayout->referenceMap();
1870 if ( !map )
1871 {
1872 return;
1873 }
1874
1875 int pageNumber = map->page();
1876 QgsLayoutItemPage *page = mLayout->pageCollection()->page( pageNumber );
1877 double pageY = page->pos().y();
1878 QSizeF pageSize = page->rect().size();
1879 QRectF pageRect( 0, pageY, pageSize.width(), pageSize.height() );
1880 computeWorldFileParameters( pageRect, a, b, c, d, e, f, dpi );
1881}
1882
1883void QgsLayoutExporter::computeWorldFileParameters( const QRectF &exportRegion, double &a, double &b, double &c, double &d, double &e, double &f, double dpi ) const
1884{
1885 if ( !mLayout )
1886 return;
1887
1888 // World file parameters : affine transformation parameters from pixel coordinates to map coordinates
1889 QgsLayoutItemMap *map = mLayout->referenceMap();
1890 if ( !map )
1891 {
1892 return;
1893 }
1894
1895 double destinationHeight = exportRegion.height();
1896 double destinationWidth = exportRegion.width();
1897
1898 QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
1899 QgsRectangle mapExtent = map->extent();
1900
1901 double alpha = map->mapRotation() / 180 * M_PI;
1902
1903 double xRatio = mapExtent.width() / mapItemSceneRect.width();
1904 double yRatio = mapExtent.height() / mapItemSceneRect.height();
1905
1906 double xCenter = mapExtent.center().x();
1907 double yCenter = mapExtent.center().y();
1908
1909 // get the extent (in map units) for the region
1910 QPointF mapItemPos = map->pos();
1911 //adjust item position so it is relative to export region
1912 mapItemPos.rx() -= exportRegion.left();
1913 mapItemPos.ry() -= exportRegion.top();
1914
1915 double xmin = mapExtent.xMinimum() - mapItemPos.x() * xRatio;
1916 double ymax = mapExtent.yMaximum() + mapItemPos.y() * yRatio;
1917 QgsRectangle paperExtent( xmin, ymax - destinationHeight * yRatio, xmin + destinationWidth * xRatio, ymax );
1918
1919 double X0 = paperExtent.xMinimum();
1920 double Y0 = paperExtent.yMinimum();
1921
1922 if ( dpi < 0 )
1923 dpi = mLayout->renderContext().dpi();
1924
1925 int widthPx = static_cast< int >( dpi * destinationWidth / 25.4 );
1926 int heightPx = static_cast< int >( dpi * destinationHeight / 25.4 );
1927
1928 double Ww = paperExtent.width() / widthPx;
1929 double Hh = paperExtent.height() / heightPx;
1930
1931 // scaling matrix
1932 double s[6];
1933 s[0] = Ww;
1934 s[1] = 0;
1935 s[2] = X0;
1936 s[3] = 0;
1937 s[4] = -Hh;
1938 s[5] = Y0 + paperExtent.height();
1939
1940 // rotation matrix
1941 double r[6];
1942 r[0] = std::cos( alpha );
1943 r[1] = -std::sin( alpha );
1944 r[2] = xCenter * ( 1 - std::cos( alpha ) ) + yCenter * std::sin( alpha );
1945 r[3] = std::sin( alpha );
1946 r[4] = std::cos( alpha );
1947 r[5] = - xCenter * std::sin( alpha ) + yCenter * ( 1 - std::cos( alpha ) );
1948
1949 // result = rotation x scaling = rotation(scaling(X))
1950 a = r[0] * s[0] + r[1] * s[3];
1951 b = r[0] * s[1] + r[1] * s[4];
1952 c = r[0] * s[2] + r[1] * s[5] + r[2];
1953 d = r[3] * s[0] + r[4] * s[3];
1954 e = r[3] * s[1] + r[4] * s[4];
1955 f = r[3] * s[2] + r[4] * s[5] + r[5];
1956}
1957
1959{
1960 if ( !layout )
1961 return false;
1962
1963 QList< QgsLayoutItem *> items;
1964 layout->layoutItems( items );
1965
1966 for ( QgsLayoutItem *currentItem : std::as_const( items ) )
1967 {
1968 // ignore invisible items, they won't affect the output in any way...
1969 if ( currentItem->isVisible() && currentItem->requiresRasterization() )
1970 return true;
1971 }
1972 return false;
1973}
1974
1976{
1977 if ( !layout )
1978 return false;
1979
1980 QList< QgsLayoutItem *> items;
1981 layout->layoutItems( items );
1982
1983 for ( QgsLayoutItem *currentItem : std::as_const( items ) )
1984 {
1985 // ignore invisible items, they won't affect the output in any way...
1986 if ( currentItem->isVisible() && currentItem->containsAdvancedEffects() )
1987 return true;
1988 }
1989 return false;
1990}
1991
1992QImage QgsLayoutExporter::createImage( const QgsLayoutExporter::ImageExportSettings &settings, int page, QRectF &bounds, bool &skipPage ) const
1993{
1994 bounds = QRectF();
1995 skipPage = false;
1996
1997 if ( settings.cropToContents )
1998 {
1999 if ( mLayout->pageCollection()->pageCount() == 1 )
2000 {
2001 // single page, so include everything
2002 bounds = mLayout->layoutBounds( true );
2003 }
2004 else
2005 {
2006 // multi page, so just clip to items on current page
2007 bounds = mLayout->pageItemBounds( page, true );
2008 }
2009 if ( bounds.width() <= 0 || bounds.height() <= 0 )
2010 {
2011 //invalid size, skip page
2012 skipPage = true;
2013 return QImage();
2014 }
2015
2016 double pixelToLayoutUnits = mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 1, QgsUnitTypes::LayoutPixels ) );
2017 bounds = bounds.adjusted( -settings.cropMargins.left() * pixelToLayoutUnits,
2018 -settings.cropMargins.top() * pixelToLayoutUnits,
2019 settings.cropMargins.right() * pixelToLayoutUnits,
2020 settings.cropMargins.bottom() * pixelToLayoutUnits );
2021 return renderRegionToImage( bounds, QSize(), settings.dpi );
2022 }
2023 else
2024 {
2025 return renderPageToImage( page, settings.imageSize, settings.dpi );
2026 }
2027}
2028
2029int QgsLayoutExporter::firstPageToBeExported( QgsLayout *layout )
2030{
2031 const int pageCount = layout->pageCollection()->pageCount();
2032 for ( int i = 0; i < pageCount; ++i )
2033 {
2034 if ( !layout->pageCollection()->shouldExportPage( i ) )
2035 {
2036 continue;
2037 }
2038
2039 return i;
2040 }
2041 return 0; // shouldn't really matter -- we aren't exporting ANY pages!
2042}
2043
2045{
2046 if ( details.page == 0 )
2047 {
2048 return details.directory + '/' + details.baseName + '.' + details.extension;
2049 }
2050 else
2051 {
2052 return details.directory + '/' + details.baseName + '_' + QString::number( details.page + 1 ) + '.' + details.extension;
2053 }
2054}
2055
2056void QgsLayoutExporter::captureLabelingResults()
2057{
2058 qDeleteAll( mLabelingResults );
2059 mLabelingResults.clear();
2060
2061 QList< QgsLayoutItemMap * > maps;
2062 mLayout->layoutItems( maps );
2063
2064 for ( QgsLayoutItemMap *map : std::as_const( maps ) )
2065 {
2066 mLabelingResults[ map->uuid() ] = map->mExportLabelingResults.release();
2067 }
2068}
2069
2070bool QgsLayoutExporter::saveImage( const QImage &image, const QString &imageFilename, const QString &imageFormat, QgsProject *projectForMetadata )
2071{
2072 QImageWriter w( imageFilename, imageFormat.toLocal8Bit().constData() );
2073 if ( imageFormat.compare( QLatin1String( "tiff" ), Qt::CaseInsensitive ) == 0 || imageFormat.compare( QLatin1String( "tif" ), Qt::CaseInsensitive ) == 0 )
2074 {
2075 w.setCompression( 1 ); //use LZW compression
2076 }
2077 if ( projectForMetadata )
2078 {
2079 w.setText( QStringLiteral( "Author" ), projectForMetadata->metadata().author() );
2080 const QString creator = QStringLiteral( "QGIS %1" ).arg( Qgis::version() );
2081 w.setText( QStringLiteral( "Creator" ), creator );
2082 w.setText( QStringLiteral( "Producer" ), creator );
2083 w.setText( QStringLiteral( "Subject" ), projectForMetadata->metadata().abstract() );
2084 w.setText( QStringLiteral( "Created" ), projectForMetadata->metadata().creationDateTime().toString( Qt::ISODate ) );
2085 w.setText( QStringLiteral( "Title" ), projectForMetadata->metadata().title() );
2086
2087 const QgsAbstractMetadataBase::KeywordMap keywords = projectForMetadata->metadata().keywords();
2088 QStringList allKeywords;
2089 for ( auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
2090 {
2091 allKeywords.append( QStringLiteral( "%1: %2" ).arg( it.key(), it.value().join( ',' ) ) );
2092 }
2093 const QString keywordString = allKeywords.join( ';' );
2094 w.setText( QStringLiteral( "Keywords" ), keywordString );
2095 }
2096 return w.write( image );
2097}
2098
2099#endif // ! QT_NO_PRINTER
static QString version()
Version string.
Definition: qgis.cpp:277
TextRenderFormat
Options for rendering text.
Definition: qgis.h:1416
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, QgsUnitTypes::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:51
QgsLayoutRenderContext & renderContext()
Returns a reference to the layout's render context, which stores information relating to the current ...
Definition: qgslayout.cpp:359
QgsLayoutPageCollection * pageCollection()
Returns a pointer to the layout's page collection, which stores and manages page items in the layout.
Definition: qgslayout.cpp:459
void layoutItems(QList< T * > &itemList) const
Returns a list of layout items of a specific type.
Definition: qgslayout.h:122
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:104
QgsProjectMetadata metadata
Definition: qgsproject.h:117
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
@ LayoutInches
Inches.
Definition: qgsunittypes.h:186
@ LayoutMillimeters
Millimeters.
Definition: qgsunittypes.h:183
@ LayoutPixels
Pixels.
Definition: qgsunittypes.h:190
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:140
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:3061
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:3060
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2527
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.