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