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