QGIS API Documentation 3.32.0-Lima (311a8cb8a6)
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 if ( !mLayout || mLayout->pageCollection()->pageCount() == 0 )
530 return PrintError;
531
532 PdfExportSettings settings = s;
533 if ( settings.dpi <= 0 )
534 settings.dpi = mLayout->renderContext().dpi();
535
536 mErrorFileName.clear();
537
538 LayoutContextPreviewSettingRestorer restorer( mLayout );
539 ( void )restorer;
540 LayoutContextSettingsRestorer contextRestorer( mLayout );
541 ( void )contextRestorer;
542 mLayout->renderContext().setDpi( settings.dpi );
543 mLayout->renderContext().setPredefinedScales( settings.predefinedMapScales );
544
545 if ( settings.simplifyGeometries )
546 {
547 mLayout->renderContext().setSimplifyMethod( createExportSimplifyMethod() );
548 }
549
550 std::unique_ptr< QgsLayoutGeoPdfExporter > geoPdfExporter;
551 if ( settings.writeGeoPdf || settings.exportLayersAsSeperateFiles ) //#spellok
552 geoPdfExporter = std::make_unique< QgsLayoutGeoPdfExporter >( mLayout );
553
554 mLayout->renderContext().setFlags( settings.flags );
555
556 // If we are not printing as raster, temporarily disable advanced effects
557 // as QPrinter does not support composition modes and can result
558 // in items missing from the output
559 mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagUseAdvancedEffects, !settings.forceVectorOutput );
560 mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagForceVectorOutput, settings.forceVectorOutput );
561 mLayout->renderContext().setTextRenderFormat( settings.textRenderFormat );
562 mLayout->renderContext().setExportThemes( settings.exportThemes );
563
564 ExportResult result = Success;
565 if ( settings.writeGeoPdf || settings.exportLayersAsSeperateFiles ) //#spellok
566 {
567 mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagRenderLabelsByMapLayer, true );
568
569 // here we need to export layers to individual PDFs
570 PdfExportSettings subSettings = settings;
571 subSettings.writeGeoPdf = false;
572 subSettings.exportLayersAsSeperateFiles = false; //#spellok
573
574 const QList<QGraphicsItem *> items = mLayout->items( Qt::AscendingOrder );
575
576 QList< QgsLayoutGeoPdfExporter::ComponentLayerDetail > pdfComponents;
577
578 const QDir baseDir = settings.exportLayersAsSeperateFiles ? QFileInfo( filePath ).dir() : QDir(); //#spellok
579 const QString baseFileName = settings.exportLayersAsSeperateFiles ? QFileInfo( filePath ).completeBaseName() : QString(); //#spellok
580
581 auto exportFunc = [this, &subSettings, &pdfComponents, &geoPdfExporter, &settings, &baseDir, &baseFileName]( unsigned int layerId, const QgsLayoutItem::ExportLayerDetail & layerDetail )->QgsLayoutExporter::ExportResult
582 {
583 ExportResult layerExportResult = Success;
585 component.name = layerDetail.name;
586 component.mapLayerId = layerDetail.mapLayerId;
587 component.opacity = layerDetail.opacity;
588 component.compositionMode = layerDetail.compositionMode;
589 component.group = layerDetail.mapTheme;
590 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' ) ) );
591 pdfComponents << component;
592 QPdfWriter printer = QPdfWriter( component.sourcePdfPath );
593 preparePrintAsPdf( mLayout, &printer, component.sourcePdfPath );
594 preparePrint( mLayout, &printer, false );
595 QPainter p;
596 if ( !p.begin( &printer ) )
597 {
598 //error beginning print
599 return FileError;
600 }
601 p.setRenderHint( QPainter::LosslessImageRendering, mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagLosslessImageRendering );
602 layerExportResult = printPrivate( &printer, p, false, subSettings.dpi, subSettings.rasterizeWholeImage );
603 p.end();
604 return layerExportResult;
605 };
606 result = handleLayeredExport( items, exportFunc );
607 if ( result != Success )
608 return result;
609
610 if ( settings.writeGeoPdf )
611 {
613 details.dpi = settings.dpi;
614 // TODO - multipages
615 QgsLayoutSize pageSize = mLayout->pageCollection()->page( 0 )->sizeWithUnits();
616 QgsLayoutSize pageSizeMM = mLayout->renderContext().measurementConverter().convert( pageSize, Qgis::LayoutUnit::Millimeters );
617 details.pageSizeMm = pageSizeMM.toQSizeF();
618
619 if ( settings.exportMetadata )
620 {
621 // copy layout metadata to GeoPDF export settings
622 details.author = mLayout->project()->metadata().author();
623 details.producer = QStringLiteral( "QGIS %1" ).arg( Qgis::version() );
624 details.creator = QStringLiteral( "QGIS %1" ).arg( Qgis::version() );
625 details.creationDateTime = mLayout->project()->metadata().creationDateTime();
626 details.subject = mLayout->project()->metadata().abstract();
627 details.title = mLayout->project()->metadata().title();
628 details.keywords = mLayout->project()->metadata().keywords();
629 }
630
631 const QList< QgsMapLayer * > layers = mLayout->project()->mapLayers().values();
632 for ( const QgsMapLayer *layer : layers )
633 {
634 details.layerIdToPdfLayerTreeNameMap.insert( layer->id(), layer->name() );
635 }
636
637 if ( settings.appendGeoreference )
638 {
639 // setup georeferencing
640 QList< QgsLayoutItemMap * > maps;
641 mLayout->layoutItems( maps );
642 for ( QgsLayoutItemMap *map : std::as_const( maps ) )
643 {
645 georef.crs = map->crs();
646
647 const QPointF topLeft = map->mapToScene( QPointF( 0, 0 ) );
648 const QPointF topRight = map->mapToScene( QPointF( map->rect().width(), 0 ) );
649 const QPointF bottomLeft = map->mapToScene( QPointF( 0, map->rect().height() ) );
650 const QPointF bottomRight = map->mapToScene( QPointF( map->rect().width(), map->rect().height() ) );
651 const QgsLayoutPoint topLeftMm = mLayout->convertFromLayoutUnits( topLeft, Qgis::LayoutUnit::Millimeters );
652 const QgsLayoutPoint topRightMm = mLayout->convertFromLayoutUnits( topRight, Qgis::LayoutUnit::Millimeters );
653 const QgsLayoutPoint bottomLeftMm = mLayout->convertFromLayoutUnits( bottomLeft, Qgis::LayoutUnit::Millimeters );
654 const QgsLayoutPoint bottomRightMm = mLayout->convertFromLayoutUnits( bottomRight, Qgis::LayoutUnit::Millimeters );
655
656 georef.pageBoundsPolygon.setExteriorRing( new QgsLineString( QVector< QgsPointXY >() << QgsPointXY( topLeftMm.x(), topLeftMm.y() )
657 << QgsPointXY( topRightMm.x(), topRightMm.y() )
658 << QgsPointXY( bottomRightMm.x(), bottomRightMm.y() )
659 << QgsPointXY( bottomLeftMm.x(), bottomLeftMm.y() )
660 << QgsPointXY( topLeftMm.x(), topLeftMm.y() ) ) );
661
662 georef.controlPoints.reserve( 4 );
663 const QTransform t = map->layoutToMapCoordsTransform();
664 const QgsPointXY topLeftMap = t.map( topLeft );
665 const QgsPointXY topRightMap = t.map( topRight );
666 const QgsPointXY bottomLeftMap = t.map( bottomLeft );
667 const QgsPointXY bottomRightMap = t.map( bottomRight );
668
669 georef.controlPoints << QgsAbstractGeoPdfExporter::ControlPoint( QgsPointXY( topLeftMm.x(), topLeftMm.y() ), topLeftMap );
670 georef.controlPoints << QgsAbstractGeoPdfExporter::ControlPoint( QgsPointXY( topRightMm.x(), topRightMm.y() ), topRightMap );
671 georef.controlPoints << QgsAbstractGeoPdfExporter::ControlPoint( QgsPointXY( bottomLeftMm.x(), bottomLeftMm.y() ), bottomLeftMap );
672 georef.controlPoints << QgsAbstractGeoPdfExporter::ControlPoint( QgsPointXY( bottomRightMm.x(), bottomRightMm.y() ), bottomRightMap );
673 details.georeferencedSections << georef;
674 }
675 }
676
677 details.customLayerTreeGroups = geoPdfExporter->customLayerTreeGroups();
678 details.initialLayerVisibility = geoPdfExporter->initialLayerVisibility();
679 details.layerOrder = geoPdfExporter->layerOrder();
680 details.includeFeatures = settings.includeGeoPdfFeatures;
681 details.useOgcBestPracticeFormatGeoreferencing = settings.useOgcBestPracticeFormatGeoreferencing;
682 details.useIso32000ExtensionFormatGeoreferencing = settings.useIso32000ExtensionFormatGeoreferencing;
683
684 if ( !geoPdfExporter->finalize( pdfComponents, filePath, details ) )
685 result = PrintError;
686 }
687 else
688 {
689 result = Success;
690 }
691 }
692 else
693 {
694 QPdfWriter printer = QPdfWriter( filePath );
695 preparePrintAsPdf( mLayout, &printer, filePath );
696 preparePrint( mLayout, &printer, false );
697 QPainter p;
698 if ( !p.begin( &printer ) )
699 {
700 //error beginning print
701 return FileError;
702 }
703 p.setRenderHint( QPainter::LosslessImageRendering, mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagLosslessImageRendering );
704 result = printPrivate( &printer, p, false, settings.dpi, settings.rasterizeWholeImage );
705 p.end();
706
707 bool shouldAppendGeoreference = settings.appendGeoreference && mLayout && mLayout->referenceMap() && mLayout->referenceMap()->page() == 0;
708 if ( settings.appendGeoreference || settings.exportMetadata )
709 {
710 georeferenceOutputPrivate( filePath, nullptr, QRectF(), settings.dpi, shouldAppendGeoreference, settings.exportMetadata );
711 }
712 }
713 captureLabelingResults();
714 return result;
715}
716
718{
719 error.clear();
720
721 if ( !iterator->beginRender() )
722 return IteratorError;
723
724 PdfExportSettings settings = s;
725
726 QPdfWriter printer = QPdfWriter( fileName );
727 QPainter p;
728
729 int total = iterator->count();
730 double step = total > 0 ? 100.0 / total : 100.0;
731 int i = 0;
732 bool first = true;
733 while ( iterator->next() )
734 {
735 if ( feedback )
736 {
737 if ( total > 0 )
738 feedback->setProperty( "progress", QObject::tr( "Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
739 else
740 feedback->setProperty( "progress", QObject::tr( "Exporting section %1" ).arg( i + 1 ) );
741 feedback->setProgress( step * i );
742 }
743 if ( feedback && feedback->isCanceled() )
744 {
745 iterator->endRender();
746 return Canceled;
747 }
748
749 if ( s.dpi <= 0 )
750 settings.dpi = iterator->layout()->renderContext().dpi();
751
752 LayoutContextPreviewSettingRestorer restorer( iterator->layout() );
753 ( void )restorer;
754 LayoutContextSettingsRestorer contextRestorer( iterator->layout() );
755 ( void )contextRestorer;
756 iterator->layout()->renderContext().setDpi( settings.dpi );
757
758 iterator->layout()->renderContext().setFlags( settings.flags );
760
761 if ( settings.simplifyGeometries )
762 {
763 iterator->layout()->renderContext().setSimplifyMethod( createExportSimplifyMethod() );
764 }
765
766 // If we are not printing as raster, temporarily disable advanced effects
767 // as QPrinter does not support composition modes and can result
768 // in items missing from the output
770
772
773 iterator->layout()->renderContext().setTextRenderFormat( settings.textRenderFormat );
774
775 if ( first )
776 {
777 preparePrintAsPdf( iterator->layout(), &printer, fileName );
778 preparePrint( iterator->layout(), &printer, false );
779
780 if ( !p.begin( &printer ) )
781 {
782 //error beginning print
783 return PrintError;
784 }
785 p.setRenderHint( QPainter::LosslessImageRendering, iterator->layout()->renderContext().flags() & QgsLayoutRenderContext::FlagLosslessImageRendering );
786 }
787
788 QgsLayoutExporter exporter( iterator->layout() );
789
790 ExportResult result = exporter.printPrivate( &printer, p, !first, settings.dpi, settings.rasterizeWholeImage );
791 if ( result != Success )
792 {
793 if ( result == FileError )
794 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 ) );
795 iterator->endRender();
796 return result;
797 }
798 first = false;
799 i++;
800 }
801
802 if ( feedback )
803 {
804 feedback->setProgress( 100 );
805 }
806
807 iterator->endRender();
808 return Success;
809}
810
812{
813 error.clear();
814
815 if ( !iterator->beginRender() )
816 return IteratorError;
817
818 int total = iterator->count();
819 double step = total > 0 ? 100.0 / total : 100.0;
820 int i = 0;
821 while ( iterator->next() )
822 {
823 if ( feedback )
824 {
825 if ( total > 0 )
826 feedback->setProperty( "progress", QObject::tr( "Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
827 else
828 feedback->setProperty( "progress", QObject::tr( "Exporting section %1" ).arg( i + 1 ).arg( total ) );
829 feedback->setProgress( step * i );
830 }
831 if ( feedback && feedback->isCanceled() )
832 {
833 iterator->endRender();
834 return Canceled;
835 }
836
837 QString filePath = iterator->filePath( baseFilePath, QStringLiteral( "pdf" ) );
838
839 QgsLayoutExporter exporter( iterator->layout() );
840 ExportResult result = exporter.exportToPdf( filePath, settings );
841 if ( result != Success )
842 {
843 if ( result == FileError )
844 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 ) );
845 iterator->endRender();
846 return result;
847 }
848 i++;
849 }
850
851 if ( feedback )
852 {
853 feedback->setProgress( 100 );
854 }
855
856 iterator->endRender();
857 return Success;
858}
859
860#ifndef QT_NO_PRINTER
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#endif // QT_NO_PRINTER
981
983{
984 if ( !mLayout )
985 return PrintError;
986
987 SvgExportSettings settings = s;
988 if ( settings.dpi <= 0 )
989 settings.dpi = mLayout->renderContext().dpi();
990
991 mErrorFileName.clear();
992
993 LayoutContextPreviewSettingRestorer restorer( mLayout );
994 ( void )restorer;
995 LayoutContextSettingsRestorer contextRestorer( mLayout );
996 ( void )contextRestorer;
997 mLayout->renderContext().setDpi( settings.dpi );
998
999 mLayout->renderContext().setFlags( settings.flags );
1000 mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagForceVectorOutput, settings.forceVectorOutput );
1001 mLayout->renderContext().setTextRenderFormat( s.textRenderFormat );
1002 mLayout->renderContext().setPredefinedScales( settings.predefinedMapScales );
1003
1004 if ( settings.simplifyGeometries )
1005 {
1006 mLayout->renderContext().setSimplifyMethod( createExportSimplifyMethod() );
1007 }
1008
1009 QFileInfo fi( filePath );
1010 PageExportDetails pageDetails;
1011 pageDetails.directory = fi.path();
1012 pageDetails.baseName = fi.baseName();
1013 pageDetails.extension = fi.completeSuffix();
1014
1015 double inchesToLayoutUnits = mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 1, Qgis::LayoutUnit::Inches ) );
1016
1017 for ( int i = 0; i < mLayout->pageCollection()->pageCount(); ++i )
1018 {
1019 if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1020 {
1021 continue;
1022 }
1023
1024 pageDetails.page = i;
1025 QString fileName = generateFileName( pageDetails );
1026
1027 QgsLayoutItemPage *pageItem = mLayout->pageCollection()->page( i );
1028 QRectF bounds;
1029 if ( settings.cropToContents )
1030 {
1031 if ( mLayout->pageCollection()->pageCount() == 1 )
1032 {
1033 // single page, so include everything
1034 bounds = mLayout->layoutBounds( true );
1035 }
1036 else
1037 {
1038 // multi page, so just clip to items on current page
1039 bounds = mLayout->pageItemBounds( i, true );
1040 }
1041 bounds = bounds.adjusted( -settings.cropMargins.left(),
1042 -settings.cropMargins.top(),
1043 settings.cropMargins.right(),
1044 settings.cropMargins.bottom() );
1045 }
1046 else
1047 {
1048 bounds = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() );
1049 }
1050
1051 //width in pixel
1052 int width = static_cast< int >( bounds.width() * settings.dpi / inchesToLayoutUnits );
1053 //height in pixel
1054 int height = static_cast< int >( bounds.height() * settings.dpi / inchesToLayoutUnits );
1055 if ( width == 0 || height == 0 )
1056 {
1057 //invalid size, skip this page
1058 continue;
1059 }
1060
1061 if ( settings.exportAsLayers )
1062 {
1063 mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagRenderLabelsByMapLayer, settings.exportLabelsToSeparateLayers );
1064 const QRectF paperRect = QRectF( pageItem->pos().x(),
1065 pageItem->pos().y(),
1066 pageItem->rect().width(),
1067 pageItem->rect().height() );
1068 QDomDocument svg;
1069 QDomNode svgDocRoot;
1070 const QList<QGraphicsItem *> items = mLayout->items( paperRect,
1071 Qt::IntersectsItemBoundingRect,
1072 Qt::AscendingOrder );
1073
1074 auto exportFunc = [this, &settings, width, height, i, bounds, fileName, &svg, &svgDocRoot]( unsigned int layerId, const QgsLayoutItem::ExportLayerDetail & layerDetail )->QgsLayoutExporter::ExportResult
1075 {
1076 return renderToLayeredSvg( settings, width, height, i, bounds, fileName, layerId, layerDetail.name, svg, svgDocRoot, settings.exportMetadata );
1077 };
1078 ExportResult res = handleLayeredExport( items, exportFunc );
1079 if ( res != Success )
1080 return res;
1081
1082 if ( settings.exportMetadata )
1083 appendMetadataToSvg( svg );
1084
1085 QFile out( fileName );
1086 bool openOk = out.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate );
1087 if ( !openOk )
1088 {
1089 mErrorFileName = fileName;
1090 return FileError;
1091 }
1092
1093 out.write( svg.toByteArray() );
1094 }
1095 else
1096 {
1097 QBuffer svgBuffer;
1098 {
1099 QSvgGenerator generator;
1100 if ( settings.exportMetadata )
1101 {
1102 generator.setTitle( mLayout->project()->metadata().title() );
1103 generator.setDescription( mLayout->project()->metadata().abstract() );
1104 }
1105 generator.setOutputDevice( &svgBuffer );
1106 generator.setSize( QSize( width, height ) );
1107 generator.setViewBox( QRect( 0, 0, width, height ) );
1108 generator.setResolution( static_cast< int >( std::round( settings.dpi ) ) );
1109
1110 QPainter p;
1111 bool createOk = p.begin( &generator );
1112 if ( !createOk )
1113 {
1114 mErrorFileName = fileName;
1115 return FileError;
1116 }
1117
1118 if ( settings.cropToContents )
1119 renderRegion( &p, bounds );
1120 else
1121 renderPage( &p, i );
1122
1123 p.end();
1124 }
1125 {
1126 svgBuffer.close();
1127 svgBuffer.open( QIODevice::ReadOnly );
1128 QDomDocument svg;
1129 QString errorMsg;
1130 int errorLine;
1131 if ( ! svg.setContent( &svgBuffer, false, &errorMsg, &errorLine ) )
1132 {
1133 mErrorFileName = fileName;
1134 return SvgLayerError;
1135 }
1136
1137 if ( settings.exportMetadata )
1138 appendMetadataToSvg( svg );
1139
1140 QFile out( fileName );
1141 bool openOk = out.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate );
1142 if ( !openOk )
1143 {
1144 mErrorFileName = fileName;
1145 return FileError;
1146 }
1147
1148 out.write( svg.toByteArray() );
1149 }
1150 }
1151 }
1152 captureLabelingResults();
1153 return Success;
1154}
1155
1157{
1158 error.clear();
1159
1160 if ( !iterator->beginRender() )
1161 return IteratorError;
1162
1163 int total = iterator->count();
1164 double step = total > 0 ? 100.0 / total : 100.0;
1165 int i = 0;
1166 while ( iterator->next() )
1167 {
1168 if ( feedback )
1169 {
1170 if ( total > 0 )
1171 feedback->setProperty( "progress", QObject::tr( "Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
1172 else
1173 feedback->setProperty( "progress", QObject::tr( "Exporting section %1" ).arg( i + 1 ).arg( total ) );
1174
1175 feedback->setProgress( step * i );
1176 }
1177 if ( feedback && feedback->isCanceled() )
1178 {
1179 iterator->endRender();
1180 return Canceled;
1181 }
1182
1183 QString filePath = iterator->filePath( baseFilePath, QStringLiteral( "svg" ) );
1184
1185 QgsLayoutExporter exporter( iterator->layout() );
1186 ExportResult result = exporter.exportToSvg( filePath, settings );
1187 if ( result != Success )
1188 {
1189 if ( result == FileError )
1190 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 ) );
1191 iterator->endRender();
1192 return result;
1193 }
1194 i++;
1195 }
1196
1197 if ( feedback )
1198 {
1199 feedback->setProgress( 100 );
1200 }
1201
1202 iterator->endRender();
1203 return Success;
1204
1205}
1206
1207QMap<QString, QgsLabelingResults *> QgsLayoutExporter::labelingResults()
1208{
1209 return mLabelingResults;
1210}
1211
1212QMap<QString, QgsLabelingResults *> QgsLayoutExporter::takeLabelingResults()
1213{
1214 QMap<QString, QgsLabelingResults *> res;
1215 std::swap( mLabelingResults, res );
1216 return res;
1217}
1218
1219void QgsLayoutExporter::preparePrintAsPdf( QgsLayout *layout, QPagedPaintDevice *device, const QString &filePath )
1220{
1221 QFileInfo fi( filePath );
1222 QDir dir;
1223 if ( !dir.exists( fi.absolutePath() ) )
1224 {
1225 dir.mkpath( fi.absolutePath() );
1226 }
1227
1228 updatePrinterPageSize( layout, device, firstPageToBeExported( layout ) );
1229
1230 // TODO: add option for this in layout
1231 // May not work on Windows or non-X11 Linux. Works fine on Mac using QPrinter::NativeFormat
1232 //printer.setFontEmbeddingEnabled( true );
1233
1234#if defined(HAS_KDE_QT5_PDF_TRANSFORM_FIX) || QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
1235 // paint engine hack not required, fixed upstream
1236#else
1237 QgsPaintEngineHack::fixEngineFlags( device->paintEngine() );
1238#endif
1239}
1240
1241void QgsLayoutExporter::preparePrint( QgsLayout *layout, QPagedPaintDevice *device, bool setFirstPageSize )
1242{
1243 if ( QPdfWriter *pdf = dynamic_cast<QPdfWriter *>( device ) )
1244 {
1245 pdf->setResolution( static_cast< int>( std::round( layout->renderContext().dpi() ) ) );
1246 }
1247#ifndef QT_NO_PRINTER
1248 else if ( QPrinter *printer = dynamic_cast<QPrinter *>( device ) )
1249 {
1250 printer->setFullPage( true );
1251 printer->setColorMode( QPrinter::Color );
1252 //set user-defined resolution
1253 printer->setResolution( static_cast< int>( std::round( layout->renderContext().dpi() ) ) );
1254 }
1255#endif
1256
1257 if ( setFirstPageSize )
1258 {
1259 updatePrinterPageSize( layout, device, firstPageToBeExported( layout ) );
1260 }
1261}
1262
1264{
1265 if ( mLayout->pageCollection()->pageCount() == 0 )
1266 return PrintError;
1267
1268 preparePrint( mLayout, device, true );
1269 QPainter p;
1270 if ( !p.begin( device ) )
1271 {
1272 //error beginning print
1273 return PrintError;
1274 }
1275
1276 printPrivate( device, p );
1277 p.end();
1278 return Success;
1279}
1280
1281QgsLayoutExporter::ExportResult QgsLayoutExporter::printPrivate( QPagedPaintDevice *device, QPainter &painter, bool startNewPage, double dpi, bool rasterize )
1282{
1283 // layout starts page numbering at 0
1284 int fromPage = 0;
1285 int toPage = mLayout->pageCollection()->pageCount() - 1;
1286
1287#ifndef QT_NO_PRINTER
1288 if ( QPrinter *printer = dynamic_cast<QPrinter *>( device ) )
1289 {
1290 if ( printer->fromPage() >= 1 )
1291 fromPage = printer->fromPage() - 1;
1292 if ( printer->toPage() >= 1 )
1293 toPage = printer->toPage() - 1;
1294 }
1295#endif
1296
1297 bool pageExported = false;
1298 if ( rasterize )
1299 {
1300 for ( int i = fromPage; i <= toPage; ++i )
1301 {
1302 if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1303 {
1304 continue;
1305 }
1306
1307 updatePrinterPageSize( mLayout, device, i );
1308 if ( ( pageExported && i > fromPage ) || startNewPage )
1309 {
1310 device->newPage();
1311 }
1312
1313 QImage image = renderPageToImage( i, QSize(), dpi );
1314 if ( !image.isNull() )
1315 {
1316 QRectF targetArea( 0, 0, image.width(), image.height() );
1317 painter.drawImage( targetArea, image, targetArea );
1318 }
1319 else
1320 {
1321 return MemoryError;
1322 }
1323 pageExported = true;
1324 }
1325 }
1326 else
1327 {
1328 for ( int i = fromPage; i <= toPage; ++i )
1329 {
1330 if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1331 {
1332 continue;
1333 }
1334
1335 updatePrinterPageSize( mLayout, device, i );
1336
1337 if ( ( pageExported && i > fromPage ) || startNewPage )
1338 {
1339 device->newPage();
1340 }
1341 renderPage( &painter, i );
1342 pageExported = true;
1343 }
1344 }
1345 return Success;
1346}
1347
1348void QgsLayoutExporter::updatePrinterPageSize( QgsLayout *layout, QPagedPaintDevice *device, int page )
1349{
1350 QgsLayoutSize pageSize = layout->pageCollection()->page( page )->sizeWithUnits();
1351 QgsLayoutSize pageSizeMM = layout->renderContext().measurementConverter().convert( pageSize, Qgis::LayoutUnit::Millimeters );
1352
1353 QPageLayout pageLayout( QPageSize( pageSizeMM.toQSizeF(), QPageSize::Millimeter ),
1354 QPageLayout::Portrait,
1355 QMarginsF( 0, 0, 0, 0 ) );
1356 pageLayout.setMode( QPageLayout::FullPageMode );
1357 device->setPageLayout( pageLayout );
1358 device->setPageMargins( QMarginsF( 0, 0, 0, 0 ) );
1359
1360#ifndef QT_NO_PRINTER
1361 if ( QPrinter *printer = dynamic_cast<QPrinter *>( device ) )
1362 {
1363 printer->setFullPage( true );
1364 }
1365#endif
1366}
1367
1368QgsLayoutExporter::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
1369{
1370 QBuffer svgBuffer;
1371 {
1372 QSvgGenerator generator;
1373 if ( includeMetadata )
1374 {
1375 if ( const QgsMasterLayoutInterface *l = dynamic_cast< const QgsMasterLayoutInterface * >( mLayout.data() ) )
1376 generator.setTitle( l->name() );
1377 else if ( mLayout->project() )
1378 generator.setTitle( mLayout->project()->title() );
1379 }
1380
1381 generator.setOutputDevice( &svgBuffer );
1382 generator.setSize( QSize( static_cast< int >( std::round( width ) ),
1383 static_cast< int >( std::round( height ) ) ) );
1384 generator.setViewBox( QRect( 0, 0,
1385 static_cast< int >( std::round( width ) ),
1386 static_cast< int >( std::round( height ) ) ) );
1387 generator.setResolution( static_cast< int >( std::round( settings.dpi ) ) ); //because the rendering is done in mm, convert the dpi
1388
1389 QPainter svgPainter( &generator );
1390 if ( settings.cropToContents )
1391 renderRegion( &svgPainter, bounds );
1392 else
1393 renderPage( &svgPainter, page );
1394 }
1395
1396// post-process svg output to create groups in a single svg file
1397// we create inkscape layers since it's nice and clean and free
1398// and fully svg compatible
1399 {
1400 svgBuffer.close();
1401 svgBuffer.open( QIODevice::ReadOnly );
1402 QDomDocument doc;
1403 QString errorMsg;
1404 int errorLine;
1405 if ( ! doc.setContent( &svgBuffer, false, &errorMsg, &errorLine ) )
1406 {
1407 mErrorFileName = filename;
1408 return SvgLayerError;
1409 }
1410 if ( 1 == svgLayerId )
1411 {
1412 svg = QDomDocument( doc.doctype() );
1413 svg.appendChild( svg.importNode( doc.firstChild(), false ) );
1414 svgDocRoot = svg.importNode( doc.elementsByTagName( QStringLiteral( "svg" ) ).at( 0 ), false );
1415 svgDocRoot.toElement().setAttribute( QStringLiteral( "xmlns:inkscape" ), QStringLiteral( "http://www.inkscape.org/namespaces/inkscape" ) );
1416 svg.appendChild( svgDocRoot );
1417 }
1418 QDomNode mainGroup = svg.importNode( doc.elementsByTagName( QStringLiteral( "g" ) ).at( 0 ), true );
1419 mainGroup.toElement().setAttribute( QStringLiteral( "id" ), layerName );
1420 mainGroup.toElement().setAttribute( QStringLiteral( "inkscape:label" ), layerName );
1421 mainGroup.toElement().setAttribute( QStringLiteral( "inkscape:groupmode" ), QStringLiteral( "layer" ) );
1422 QDomNode defs = svg.importNode( doc.elementsByTagName( QStringLiteral( "defs" ) ).at( 0 ), true );
1423 svgDocRoot.appendChild( defs );
1424 svgDocRoot.appendChild( mainGroup );
1425 }
1426 return Success;
1427}
1428
1429void QgsLayoutExporter::appendMetadataToSvg( QDomDocument &svg ) const
1430{
1431 const QgsProjectMetadata &metadata = mLayout->project()->metadata();
1432 QDomElement metadataElement = svg.createElement( QStringLiteral( "metadata" ) );
1433 QDomElement rdfElement = svg.createElement( QStringLiteral( "rdf:RDF" ) );
1434 rdfElement.setAttribute( QStringLiteral( "xmlns:rdf" ), QStringLiteral( "http://www.w3.org/1999/02/22-rdf-syntax-ns#" ) );
1435 rdfElement.setAttribute( QStringLiteral( "xmlns:rdfs" ), QStringLiteral( "http://www.w3.org/2000/01/rdf-schema#" ) );
1436 rdfElement.setAttribute( QStringLiteral( "xmlns:dc" ), QStringLiteral( "http://purl.org/dc/elements/1.1/" ) );
1437 QDomElement descriptionElement = svg.createElement( QStringLiteral( "rdf:Description" ) );
1438 QDomElement workElement = svg.createElement( QStringLiteral( "cc:Work" ) );
1439 workElement.setAttribute( QStringLiteral( "rdf:about" ), QString() );
1440
1441 auto addTextNode = [&workElement, &descriptionElement, &svg]( const QString & tag, const QString & value )
1442 {
1443 // inkscape compatible
1444 QDomElement element = svg.createElement( tag );
1445 QDomText t = svg.createTextNode( value );
1446 element.appendChild( t );
1447 workElement.appendChild( element );
1448
1449 // svg spec compatible
1450 descriptionElement.setAttribute( tag, value );
1451 };
1452
1453 addTextNode( QStringLiteral( "dc:format" ), QStringLiteral( "image/svg+xml" ) );
1454 addTextNode( QStringLiteral( "dc:title" ), metadata.title() );
1455 addTextNode( QStringLiteral( "dc:date" ), metadata.creationDateTime().toString( Qt::ISODate ) );
1456 addTextNode( QStringLiteral( "dc:identifier" ), metadata.identifier() );
1457 addTextNode( QStringLiteral( "dc:description" ), metadata.abstract() );
1458
1459 auto addAgentNode = [&workElement, &descriptionElement, &svg]( const QString & tag, const QString & value )
1460 {
1461 // inkscape compatible
1462 QDomElement inkscapeElement = svg.createElement( tag );
1463 QDomElement agentElement = svg.createElement( QStringLiteral( "cc:Agent" ) );
1464 QDomElement titleElement = svg.createElement( QStringLiteral( "dc:title" ) );
1465 QDomText t = svg.createTextNode( value );
1466 titleElement.appendChild( t );
1467 agentElement.appendChild( titleElement );
1468 inkscapeElement.appendChild( agentElement );
1469 workElement.appendChild( inkscapeElement );
1470
1471 // svg spec compatible
1472 QDomElement bagElement = svg.createElement( QStringLiteral( "rdf:Bag" ) );
1473 QDomElement liElement = svg.createElement( QStringLiteral( "rdf:li" ) );
1474 t = svg.createTextNode( value );
1475 liElement.appendChild( t );
1476 bagElement.appendChild( liElement );
1477
1478 QDomElement element = svg.createElement( tag );
1479 element.appendChild( bagElement );
1480 descriptionElement.appendChild( element );
1481 };
1482
1483 addAgentNode( QStringLiteral( "dc:creator" ), metadata.author() );
1484 addAgentNode( QStringLiteral( "dc:publisher" ), QStringLiteral( "QGIS %1" ).arg( Qgis::version() ) );
1485
1486 // keywords
1487 {
1488 QDomElement element = svg.createElement( QStringLiteral( "dc:subject" ) );
1489 QDomElement bagElement = svg.createElement( QStringLiteral( "rdf:Bag" ) );
1490 QgsAbstractMetadataBase::KeywordMap keywords = metadata.keywords();
1491 for ( auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
1492 {
1493 const QStringList words = it.value();
1494 for ( const QString &keyword : words )
1495 {
1496 QDomElement liElement = svg.createElement( QStringLiteral( "rdf:li" ) );
1497 QDomText t = svg.createTextNode( keyword );
1498 liElement.appendChild( t );
1499 bagElement.appendChild( liElement );
1500 }
1501 }
1502 element.appendChild( bagElement );
1503 workElement.appendChild( element );
1504 descriptionElement.appendChild( element );
1505 }
1506
1507 rdfElement.appendChild( descriptionElement );
1508 rdfElement.appendChild( workElement );
1509 metadataElement.appendChild( rdfElement );
1510 svg.documentElement().appendChild( metadataElement );
1511 svg.documentElement().setAttribute( QStringLiteral( "xmlns:cc" ), QStringLiteral( "http://creativecommons.org/ns#" ) );
1512}
1513
1514std::unique_ptr<double[]> QgsLayoutExporter::computeGeoTransform( const QgsLayoutItemMap *map, const QRectF &region, double dpi ) const
1515{
1516 if ( !map )
1517 map = mLayout->referenceMap();
1518
1519 if ( !map )
1520 return nullptr;
1521
1522 if ( dpi < 0 )
1523 dpi = mLayout->renderContext().dpi();
1524
1525 // calculate region of composition to export (in mm)
1526 QRectF exportRegion = region;
1527 if ( !exportRegion.isValid() )
1528 {
1529 int pageNumber = map->page();
1530
1531 QgsLayoutItemPage *page = mLayout->pageCollection()->page( pageNumber );
1532 double pageY = page->pos().y();
1533 QSizeF pageSize = page->rect().size();
1534 exportRegion = QRectF( 0, pageY, pageSize.width(), pageSize.height() );
1535 }
1536
1537 // map rectangle (in mm)
1538 QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
1539
1540 // destination width/height in mm
1541 double outputHeightMM = exportRegion.height();
1542 double outputWidthMM = exportRegion.width();
1543
1544 // map properties
1545 QgsRectangle mapExtent = map->extent();
1546 double mapXCenter = mapExtent.center().x();
1547 double mapYCenter = mapExtent.center().y();
1548 double alpha = - map->mapRotation() / 180 * M_PI;
1549 double sinAlpha = std::sin( alpha );
1550 double cosAlpha = std::cos( alpha );
1551
1552 // get the extent (in map units) for the exported region
1553 QPointF mapItemPos = map->pos();
1554 //adjust item position so it is relative to export region
1555 mapItemPos.rx() -= exportRegion.left();
1556 mapItemPos.ry() -= exportRegion.top();
1557
1558 // calculate extent of entire page in map units
1559 double xRatio = mapExtent.width() / mapItemSceneRect.width();
1560 double yRatio = mapExtent.height() / mapItemSceneRect.height();
1561 double xmin = mapExtent.xMinimum() - mapItemPos.x() * xRatio;
1562 double ymax = mapExtent.yMaximum() + mapItemPos.y() * yRatio;
1563 QgsRectangle paperExtent( xmin, ymax - outputHeightMM * yRatio, xmin + outputWidthMM * xRatio, ymax );
1564
1565 // calculate origin of page
1566 double X0 = paperExtent.xMinimum();
1567 double Y0 = paperExtent.yMaximum();
1568
1569 if ( !qgsDoubleNear( alpha, 0.0 ) )
1570 {
1571 // translate origin to account for map rotation
1572 double X1 = X0 - mapXCenter;
1573 double Y1 = Y0 - mapYCenter;
1574 double X2 = X1 * cosAlpha + Y1 * sinAlpha;
1575 double Y2 = -X1 * sinAlpha + Y1 * cosAlpha;
1576 X0 = X2 + mapXCenter;
1577 Y0 = Y2 + mapYCenter;
1578 }
1579
1580 // calculate scaling of pixels
1581 int pageWidthPixels = static_cast< int >( dpi * outputWidthMM / 25.4 );
1582 int pageHeightPixels = static_cast< int >( dpi * outputHeightMM / 25.4 );
1583 double pixelWidthScale = paperExtent.width() / pageWidthPixels;
1584 double pixelHeightScale = paperExtent.height() / pageHeightPixels;
1585
1586 // transform matrix
1587 std::unique_ptr<double[]> t( new double[6] );
1588 t[0] = X0;
1589 t[1] = cosAlpha * pixelWidthScale;
1590 t[2] = -sinAlpha * pixelWidthScale;
1591 t[3] = Y0;
1592 t[4] = -sinAlpha * pixelHeightScale;
1593 t[5] = -cosAlpha * pixelHeightScale;
1594
1595 return t;
1596}
1597
1598void QgsLayoutExporter::writeWorldFile( const QString &worldFileName, double a, double b, double c, double d, double e, double f ) const
1599{
1600 QFile worldFile( worldFileName );
1601 if ( !worldFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
1602 {
1603 return;
1604 }
1605 QTextStream fout( &worldFile );
1606
1607 // QString::number does not use locale settings (for the decimal point)
1608 // which is what we want here
1609 fout << QString::number( a, 'f', 12 ) << "\r\n";
1610 fout << QString::number( d, 'f', 12 ) << "\r\n";
1611 fout << QString::number( b, 'f', 12 ) << "\r\n";
1612 fout << QString::number( e, 'f', 12 ) << "\r\n";
1613 fout << QString::number( c, 'f', 12 ) << "\r\n";
1614 fout << QString::number( f, 'f', 12 ) << "\r\n";
1615}
1616
1617bool QgsLayoutExporter::georeferenceOutput( const QString &file, QgsLayoutItemMap *map, const QRectF &exportRegion, double dpi ) const
1618{
1619 return georeferenceOutputPrivate( file, map, exportRegion, dpi, false );
1620}
1621
1622bool QgsLayoutExporter::georeferenceOutputPrivate( const QString &file, QgsLayoutItemMap *map, const QRectF &exportRegion, double dpi, bool includeGeoreference, bool includeMetadata ) const
1623{
1624 if ( !mLayout )
1625 return false;
1626
1627 if ( !map && includeGeoreference )
1628 map = mLayout->referenceMap();
1629
1630 std::unique_ptr<double[]> t;
1631
1632 if ( map && includeGeoreference )
1633 {
1634 if ( dpi < 0 )
1635 dpi = mLayout->renderContext().dpi();
1636
1637 t = computeGeoTransform( map, exportRegion, dpi );
1638 }
1639
1640 // important - we need to manually specify the DPI in advance, as GDAL will otherwise
1641 // assume a DPI of 150
1642 CPLSetConfigOption( "GDAL_PDF_DPI", QString::number( dpi ).toUtf8().constData() );
1643 gdal::dataset_unique_ptr outputDS( GDALOpen( file.toUtf8().constData(), GA_Update ) );
1644 if ( outputDS )
1645 {
1646 if ( t )
1647 GDALSetGeoTransform( outputDS.get(), t.get() );
1648
1649 if ( includeMetadata )
1650 {
1651 QString creationDateString;
1652 const QDateTime creationDateTime = mLayout->project()->metadata().creationDateTime();
1653 if ( creationDateTime.isValid() )
1654 {
1655 creationDateString = QStringLiteral( "D:%1" ).arg( mLayout->project()->metadata().creationDateTime().toString( QStringLiteral( "yyyyMMddHHmmss" ) ) );
1656 if ( creationDateTime.timeZone().isValid() )
1657 {
1658 int offsetFromUtc = creationDateTime.timeZone().offsetFromUtc( creationDateTime );
1659 creationDateString += ( offsetFromUtc >= 0 ) ? '+' : '-';
1660 offsetFromUtc = std::abs( offsetFromUtc );
1661 int offsetHours = offsetFromUtc / 3600;
1662 int offsetMins = ( offsetFromUtc % 3600 ) / 60;
1663 creationDateString += QStringLiteral( "%1'%2'" ).arg( offsetHours ).arg( offsetMins );
1664 }
1665 }
1666 GDALSetMetadataItem( outputDS.get(), "CREATION_DATE", creationDateString.toUtf8().constData(), nullptr );
1667
1668 GDALSetMetadataItem( outputDS.get(), "AUTHOR", mLayout->project()->metadata().author().toUtf8().constData(), nullptr );
1669 const QString creator = QStringLiteral( "QGIS %1" ).arg( Qgis::version() );
1670 GDALSetMetadataItem( outputDS.get(), "CREATOR", creator.toUtf8().constData(), nullptr );
1671 GDALSetMetadataItem( outputDS.get(), "PRODUCER", creator.toUtf8().constData(), nullptr );
1672 GDALSetMetadataItem( outputDS.get(), "SUBJECT", mLayout->project()->metadata().abstract().toUtf8().constData(), nullptr );
1673 GDALSetMetadataItem( outputDS.get(), "TITLE", mLayout->project()->metadata().title().toUtf8().constData(), nullptr );
1674
1675 const QgsAbstractMetadataBase::KeywordMap keywords = mLayout->project()->metadata().keywords();
1676 QStringList allKeywords;
1677 for ( auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
1678 {
1679 allKeywords.append( QStringLiteral( "%1: %2" ).arg( it.key(), it.value().join( ',' ) ) );
1680 }
1681 const QString keywordString = allKeywords.join( ';' );
1682 GDALSetMetadataItem( outputDS.get(), "KEYWORDS", keywordString.toUtf8().constData(), nullptr );
1683 }
1684
1685 if ( t )
1686 GDALSetProjection( outputDS.get(), map->crs().toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED_GDAL ).toLocal8Bit().constData() );
1687 }
1688 CPLSetConfigOption( "GDAL_PDF_DPI", nullptr );
1689
1690 return true;
1691}
1692
1693QString nameForLayerWithItems( const QList< QGraphicsItem * > &items, unsigned int layerId )
1694{
1695 if ( items.count() == 1 )
1696 {
1697 if ( QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( items.at( 0 ) ) )
1698 {
1699 QString name = layoutItem->displayName();
1700 // cleanup default item ID format
1701 if ( name.startsWith( '<' ) && name.endsWith( '>' ) )
1702 name = name.mid( 1, name.length() - 2 );
1703 return name;
1704 }
1705 }
1706 else if ( items.count() > 1 )
1707 {
1708 QStringList currentLayerItemTypes;
1709 for ( QGraphicsItem *item : items )
1710 {
1711 if ( QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item ) )
1712 {
1713 const QString itemType = QgsApplication::layoutItemRegistry()->itemMetadata( layoutItem->type() )->visibleName();
1714 const QString itemTypePlural = QgsApplication::layoutItemRegistry()->itemMetadata( layoutItem->type() )->visiblePluralName();
1715 if ( !currentLayerItemTypes.contains( itemType ) && !currentLayerItemTypes.contains( itemTypePlural ) )
1716 currentLayerItemTypes << itemType;
1717 else if ( currentLayerItemTypes.contains( itemType ) )
1718 {
1719 currentLayerItemTypes.replace( currentLayerItemTypes.indexOf( itemType ), itemTypePlural );
1720 }
1721 }
1722 else
1723 {
1724 if ( !currentLayerItemTypes.contains( QObject::tr( "Other" ) ) )
1725 currentLayerItemTypes.append( QObject::tr( "Other" ) );
1726 }
1727 }
1728 return currentLayerItemTypes.join( QLatin1String( ", " ) );
1729 }
1730 return QObject::tr( "Layer %1" ).arg( layerId );
1731}
1732
1733QgsLayoutExporter::ExportResult QgsLayoutExporter::handleLayeredExport( const QList<QGraphicsItem *> &items,
1734 const std::function<QgsLayoutExporter::ExportResult( unsigned int, const QgsLayoutItem::ExportLayerDetail & )> &exportFunc )
1735{
1736 LayoutItemHider itemHider( items );
1737 ( void )itemHider;
1738
1739 int prevType = -1;
1741 unsigned int layerId = 1;
1743 itemHider.hideAll();
1744 const QList< QGraphicsItem * > itemsToIterate = itemHider.itemsToIterate();
1745 QList< QGraphicsItem * > currentLayerItems;
1746 for ( QGraphicsItem *item : itemsToIterate )
1747 {
1748 QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item );
1749
1750 bool canPlaceInExistingLayer = false;
1751 if ( layoutItem )
1752 {
1753 switch ( layoutItem->exportLayerBehavior() )
1754 {
1756 {
1757 switch ( prevItemBehavior )
1758 {
1760 canPlaceInExistingLayer = true;
1761 break;
1762
1764 canPlaceInExistingLayer = prevType == -1 || prevType == layoutItem->type();
1765 break;
1766
1769 canPlaceInExistingLayer = false;
1770 break;
1771 }
1772 break;
1773 }
1774
1776 {
1777 switch ( prevItemBehavior )
1778 {
1781 canPlaceInExistingLayer = prevType == -1 || prevType == layoutItem->type();
1782 break;
1783
1786 canPlaceInExistingLayer = false;
1787 break;
1788 }
1789 break;
1790 }
1791
1793 {
1794 canPlaceInExistingLayer = false;
1795 break;
1796 }
1797
1799 canPlaceInExistingLayer = false;
1800 break;
1801 }
1802 prevItemBehavior = layoutItem->exportLayerBehavior();
1803 prevType = layoutItem->type();
1804 }
1805 else
1806 {
1807 prevItemBehavior = QgsLayoutItem::MustPlaceInOwnLayer;
1808 }
1809
1810 if ( canPlaceInExistingLayer )
1811 {
1812 currentLayerItems << item;
1813 item->show();
1814 }
1815 else
1816 {
1817 if ( !currentLayerItems.isEmpty() )
1818 {
1819 layerDetails.name = nameForLayerWithItems( currentLayerItems, layerId );
1820
1821 ExportResult result = exportFunc( layerId, layerDetails );
1822 if ( result != Success )
1823 return result;
1824 layerId++;
1825 currentLayerItems.clear();
1826 }
1827
1828 itemHider.hideAll();
1829 item->show();
1830
1831 if ( layoutItem && layoutItem->exportLayerBehavior() == QgsLayoutItem::ItemContainsSubLayers )
1832 {
1833 int layoutItemLayerIdx = 0;
1835 mLayout->renderContext().setCurrentExportLayer( layoutItemLayerIdx );
1837 layoutItem->startLayeredExport();
1838 while ( layoutItem->nextExportPart() )
1839 {
1841 mLayout->renderContext().setCurrentExportLayer( layoutItemLayerIdx );
1843
1844 layerDetails = layoutItem->exportLayerDetails();
1845 ExportResult result = exportFunc( layerId, layerDetails );
1846 if ( result != Success )
1847 return result;
1848 layerId++;
1849
1850 layoutItemLayerIdx++;
1851 }
1852 layerDetails.mapLayerId.clear();
1854 mLayout->renderContext().setCurrentExportLayer( -1 );
1856 layoutItem->stopLayeredExport();
1857 currentLayerItems.clear();
1858 }
1859 else
1860 {
1861 currentLayerItems << item;
1862 }
1863 }
1864 }
1865 if ( !currentLayerItems.isEmpty() )
1866 {
1867 layerDetails.name = nameForLayerWithItems( currentLayerItems, layerId );
1868 ExportResult result = exportFunc( layerId, layerDetails );
1869 if ( result != Success )
1870 return result;
1871 }
1872 return Success;
1873}
1874
1875QgsVectorSimplifyMethod QgsLayoutExporter::createExportSimplifyMethod()
1876{
1877 QgsVectorSimplifyMethod simplifyMethod;
1879 simplifyMethod.setForceLocalOptimization( true );
1880 // we use SnappedToGridGlobal, because it avoids gaps and slivers between previously adjacent polygons
1882 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.
1883 return simplifyMethod;
1884}
1885
1886void QgsLayoutExporter::computeWorldFileParameters( double &a, double &b, double &c, double &d, double &e, double &f, double dpi ) const
1887{
1888 if ( !mLayout )
1889 return;
1890
1891 QgsLayoutItemMap *map = mLayout->referenceMap();
1892 if ( !map )
1893 {
1894 return;
1895 }
1896
1897 int pageNumber = map->page();
1898 QgsLayoutItemPage *page = mLayout->pageCollection()->page( pageNumber );
1899 double pageY = page->pos().y();
1900 QSizeF pageSize = page->rect().size();
1901 QRectF pageRect( 0, pageY, pageSize.width(), pageSize.height() );
1902 computeWorldFileParameters( pageRect, a, b, c, d, e, f, dpi );
1903}
1904
1905void QgsLayoutExporter::computeWorldFileParameters( const QRectF &exportRegion, double &a, double &b, double &c, double &d, double &e, double &f, double dpi ) const
1906{
1907 if ( !mLayout )
1908 return;
1909
1910 // World file parameters : affine transformation parameters from pixel coordinates to map coordinates
1911 QgsLayoutItemMap *map = mLayout->referenceMap();
1912 if ( !map )
1913 {
1914 return;
1915 }
1916
1917 double destinationHeight = exportRegion.height();
1918 double destinationWidth = exportRegion.width();
1919
1920 QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
1921 QgsRectangle mapExtent = map->extent();
1922
1923 double alpha = map->mapRotation() / 180 * M_PI;
1924
1925 double xRatio = mapExtent.width() / mapItemSceneRect.width();
1926 double yRatio = mapExtent.height() / mapItemSceneRect.height();
1927
1928 double xCenter = mapExtent.center().x();
1929 double yCenter = mapExtent.center().y();
1930
1931 // get the extent (in map units) for the region
1932 QPointF mapItemPos = map->pos();
1933 //adjust item position so it is relative to export region
1934 mapItemPos.rx() -= exportRegion.left();
1935 mapItemPos.ry() -= exportRegion.top();
1936
1937 double xmin = mapExtent.xMinimum() - mapItemPos.x() * xRatio;
1938 double ymax = mapExtent.yMaximum() + mapItemPos.y() * yRatio;
1939 QgsRectangle paperExtent( xmin, ymax - destinationHeight * yRatio, xmin + destinationWidth * xRatio, ymax );
1940
1941 double X0 = paperExtent.xMinimum();
1942 double Y0 = paperExtent.yMinimum();
1943
1944 if ( dpi < 0 )
1945 dpi = mLayout->renderContext().dpi();
1946
1947 int widthPx = static_cast< int >( dpi * destinationWidth / 25.4 );
1948 int heightPx = static_cast< int >( dpi * destinationHeight / 25.4 );
1949
1950 double Ww = paperExtent.width() / widthPx;
1951 double Hh = paperExtent.height() / heightPx;
1952
1953 // scaling matrix
1954 double s[6];
1955 s[0] = Ww;
1956 s[1] = 0;
1957 s[2] = X0;
1958 s[3] = 0;
1959 s[4] = -Hh;
1960 s[5] = Y0 + paperExtent.height();
1961
1962 // rotation matrix
1963 double r[6];
1964 r[0] = std::cos( alpha );
1965 r[1] = -std::sin( alpha );
1966 r[2] = xCenter * ( 1 - std::cos( alpha ) ) + yCenter * std::sin( alpha );
1967 r[3] = std::sin( alpha );
1968 r[4] = std::cos( alpha );
1969 r[5] = - xCenter * std::sin( alpha ) + yCenter * ( 1 - std::cos( alpha ) );
1970
1971 // result = rotation x scaling = rotation(scaling(X))
1972 a = r[0] * s[0] + r[1] * s[3];
1973 b = r[0] * s[1] + r[1] * s[4];
1974 c = r[0] * s[2] + r[1] * s[5] + r[2];
1975 d = r[3] * s[0] + r[4] * s[3];
1976 e = r[3] * s[1] + r[4] * s[4];
1977 f = r[3] * s[2] + r[4] * s[5] + r[5];
1978}
1979
1981{
1982 if ( !layout )
1983 return false;
1984
1985 QList< QgsLayoutItem *> items;
1986 layout->layoutItems( items );
1987
1988 for ( QgsLayoutItem *currentItem : std::as_const( items ) )
1989 {
1990 // ignore invisible items, they won't affect the output in any way...
1991 if ( currentItem->isVisible() && currentItem->requiresRasterization() )
1992 return true;
1993 }
1994 return false;
1995}
1996
1998{
1999 if ( !layout )
2000 return false;
2001
2002 QList< QgsLayoutItem *> items;
2003 layout->layoutItems( items );
2004
2005 for ( QgsLayoutItem *currentItem : std::as_const( items ) )
2006 {
2007 // ignore invisible items, they won't affect the output in any way...
2008 if ( currentItem->isVisible() && currentItem->containsAdvancedEffects() )
2009 return true;
2010 }
2011 return false;
2012}
2013
2014QImage QgsLayoutExporter::createImage( const QgsLayoutExporter::ImageExportSettings &settings, int page, QRectF &bounds, bool &skipPage ) const
2015{
2016 bounds = QRectF();
2017 skipPage = false;
2018
2019 if ( settings.cropToContents )
2020 {
2021 if ( mLayout->pageCollection()->pageCount() == 1 )
2022 {
2023 // single page, so include everything
2024 bounds = mLayout->layoutBounds( true );
2025 }
2026 else
2027 {
2028 // multi page, so just clip to items on current page
2029 bounds = mLayout->pageItemBounds( page, true );
2030 }
2031 if ( bounds.width() <= 0 || bounds.height() <= 0 )
2032 {
2033 //invalid size, skip page
2034 skipPage = true;
2035 return QImage();
2036 }
2037
2038 double pixelToLayoutUnits = mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 1, Qgis::LayoutUnit::Pixels ) );
2039 bounds = bounds.adjusted( -settings.cropMargins.left() * pixelToLayoutUnits,
2040 -settings.cropMargins.top() * pixelToLayoutUnits,
2041 settings.cropMargins.right() * pixelToLayoutUnits,
2042 settings.cropMargins.bottom() * pixelToLayoutUnits );
2043 return renderRegionToImage( bounds, QSize(), settings.dpi );
2044 }
2045 else
2046 {
2047 return renderPageToImage( page, settings.imageSize, settings.dpi );
2048 }
2049}
2050
2051int QgsLayoutExporter::firstPageToBeExported( QgsLayout *layout )
2052{
2053 const int pageCount = layout->pageCollection()->pageCount();
2054 for ( int i = 0; i < pageCount; ++i )
2055 {
2056 if ( !layout->pageCollection()->shouldExportPage( i ) )
2057 {
2058 continue;
2059 }
2060
2061 return i;
2062 }
2063 return 0; // shouldn't really matter -- we aren't exporting ANY pages!
2064}
2065
2067{
2068 if ( details.page == 0 )
2069 {
2070 return details.directory + '/' + details.baseName + '.' + details.extension;
2071 }
2072 else
2073 {
2074 return details.directory + '/' + details.baseName + '_' + QString::number( details.page + 1 ) + '.' + details.extension;
2075 }
2076}
2077
2078void QgsLayoutExporter::captureLabelingResults()
2079{
2080 qDeleteAll( mLabelingResults );
2081 mLabelingResults.clear();
2082
2083 QList< QgsLayoutItemMap * > maps;
2084 mLayout->layoutItems( maps );
2085
2086 for ( QgsLayoutItemMap *map : std::as_const( maps ) )
2087 {
2088 mLabelingResults[ map->uuid() ] = map->mExportLabelingResults.release();
2089 }
2090}
2091
2092bool QgsLayoutExporter::saveImage( const QImage &image, const QString &imageFilename, const QString &imageFormat, QgsProject *projectForMetadata )
2093{
2094 QImageWriter w( imageFilename, imageFormat.toLocal8Bit().constData() );
2095 if ( imageFormat.compare( QLatin1String( "tiff" ), Qt::CaseInsensitive ) == 0 || imageFormat.compare( QLatin1String( "tif" ), Qt::CaseInsensitive ) == 0 )
2096 {
2097 w.setCompression( 1 ); //use LZW compression
2098 }
2099 if ( projectForMetadata )
2100 {
2101 w.setText( QStringLiteral( "Author" ), projectForMetadata->metadata().author() );
2102 const QString creator = QStringLiteral( "QGIS %1" ).arg( Qgis::version() );
2103 w.setText( QStringLiteral( "Creator" ), creator );
2104 w.setText( QStringLiteral( "Producer" ), creator );
2105 w.setText( QStringLiteral( "Subject" ), projectForMetadata->metadata().abstract() );
2106 w.setText( QStringLiteral( "Created" ), projectForMetadata->metadata().creationDateTime().toString( Qt::ISODate ) );
2107 w.setText( QStringLiteral( "Title" ), projectForMetadata->metadata().title() );
2108
2109 const QgsAbstractMetadataBase::KeywordMap keywords = projectForMetadata->metadata().keywords();
2110 QStringList allKeywords;
2111 for ( auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
2112 {
2113 allKeywords.append( QStringLiteral( "%1: %2" ).arg( it.key(), it.value().join( ',' ) ) );
2114 }
2115 const QString keywordString = allKeywords.join( ';' );
2116 w.setText( QStringLiteral( "Keywords" ), keywordString );
2117 }
2118 return w.write( image );
2119}
static QString version()
Version string.
Definition: qgis.cpp:258
TextRenderFormat
Options for rendering text.
Definition: qgis.h:1885
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:367
QgsLayoutPageCollection * pageCollection()
Returns a pointer to the layout's page collection, which stores and manages page items in the layout.
Definition: qgslayout.cpp:467
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:107
QgsProjectMetadata metadata
Definition: qgsproject.h:120
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:4572
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:4571
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:3988
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.