QGIS API Documentation 3.39.0-Master (47f7b3a4989)
Loading...
Searching...
No Matches
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 , mPreviousMaskSettings( layout->renderContext().maskSettings() )
341 , mExportThemes( layout->renderContext().exportThemes() )
342 , mPredefinedScales( layout->renderContext().predefinedScales() )
343 {
344 }
346
347 ~LayoutContextSettingsRestorer()
348 {
349 mLayout->renderContext().setDpi( mPreviousDpi );
350 mLayout->renderContext().setFlags( mPreviousFlags );
351 mLayout->renderContext().setTextRenderFormat( mPreviousTextFormat );
353 mLayout->renderContext().setCurrentExportLayer( mPreviousExportLayer );
355 mLayout->renderContext().setSimplifyMethod( mPreviousSimplifyMethod );
356 mLayout->renderContext().setMaskSettings( mPreviousMaskSettings );
357 mLayout->renderContext().setExportThemes( mExportThemes );
358 mLayout->renderContext().setPredefinedScales( mPredefinedScales );
359 }
360
361 LayoutContextSettingsRestorer( const LayoutContextSettingsRestorer &other ) = delete;
362 LayoutContextSettingsRestorer &operator=( const LayoutContextSettingsRestorer &other ) = delete;
363
364 private:
365 QgsLayout *mLayout = nullptr;
366 double mPreviousDpi = 0;
369 int mPreviousExportLayer = 0;
370 QgsVectorSimplifyMethod mPreviousSimplifyMethod;
371 QgsMaskRenderSettings mPreviousMaskSettings;
372 QStringList mExportThemes;
373 QVector< double > mPredefinedScales;
374
375};
377
379{
380 if ( !mLayout )
381 return PrintError;
382
383 ImageExportSettings settings = s;
384 if ( settings.dpi <= 0 )
385 settings.dpi = mLayout->renderContext().dpi();
386
387 mErrorFileName.clear();
388
389 int worldFilePageNo = -1;
390 if ( QgsLayoutItemMap *referenceMap = mLayout->referenceMap() )
391 {
392 worldFilePageNo = referenceMap->page();
393 }
394
395 QFileInfo fi( filePath );
396 QDir dir;
397 if ( !dir.exists( fi.absolutePath() ) )
398 {
399 dir.mkpath( fi.absolutePath() );
400 }
401
402 PageExportDetails pageDetails;
403 pageDetails.directory = fi.path();
404 pageDetails.baseName = fi.completeBaseName();
405 pageDetails.extension = fi.suffix();
406
407 LayoutContextPreviewSettingRestorer restorer( mLayout );
408 ( void )restorer;
409 LayoutContextSettingsRestorer dpiRestorer( mLayout );
410 ( void )dpiRestorer;
411 mLayout->renderContext().setDpi( settings.dpi );
412 mLayout->renderContext().setFlags( settings.flags );
413 mLayout->renderContext().setPredefinedScales( settings.predefinedMapScales );
414
415 QList< int > pages;
416 if ( settings.pages.empty() )
417 {
418 for ( int page = 0; page < mLayout->pageCollection()->pageCount(); ++page )
419 pages << page;
420 }
421 else
422 {
423 for ( int page : std::as_const( settings.pages ) )
424 {
425 if ( page >= 0 && page < mLayout->pageCollection()->pageCount() )
426 pages << page;
427 }
428 }
429
430 for ( int page : std::as_const( pages ) )
431 {
432 if ( !mLayout->pageCollection()->shouldExportPage( page ) )
433 {
434 continue;
435 }
436
437 bool skip = false;
438 QRectF bounds;
439 QImage image = createImage( settings, page, bounds, skip );
440
441 if ( skip )
442 continue; // should skip this page, e.g. null size
443
444 pageDetails.page = page;
445 QString outputFilePath = generateFileName( pageDetails );
446
447 if ( image.isNull() )
448 {
449 mErrorFileName = outputFilePath;
450 return MemoryError;
451 }
452
453 if ( !saveImage( image, outputFilePath, pageDetails.extension, settings.exportMetadata ? mLayout->project() : nullptr ) )
454 {
455 mErrorFileName = outputFilePath;
456 return FileError;
457 }
458
459 const bool shouldGeoreference = ( page == worldFilePageNo );
460 if ( shouldGeoreference )
461 {
462 georeferenceOutputPrivate( outputFilePath, nullptr, bounds, settings.dpi, shouldGeoreference );
463
464 if ( settings.generateWorldFile )
465 {
466 // should generate world file for this page
467 double a, b, c, d, e, f;
468 if ( bounds.isValid() )
469 computeWorldFileParameters( bounds, a, b, c, d, e, f, settings.dpi );
470 else
471 computeWorldFileParameters( a, b, c, d, e, f, settings.dpi );
472
473 QFileInfo fi( outputFilePath );
474 // build the world file name
475 QString outputSuffix = fi.suffix();
476 QString worldFileName = fi.absolutePath() + '/' + fi.completeBaseName() + '.'
477 + outputSuffix.at( 0 ) + outputSuffix.at( fi.suffix().size() - 1 ) + 'w';
478
479 writeWorldFile( worldFileName, a, b, c, d, e, f );
480 }
481 }
482
483 }
484 captureLabelingResults();
485 return Success;
486}
487
488QgsLayoutExporter::ExportResult QgsLayoutExporter::exportToImage( QgsAbstractLayoutIterator *iterator, const QString &baseFilePath, const QString &extension, const QgsLayoutExporter::ImageExportSettings &settings, QString &error, QgsFeedback *feedback )
489{
490 error.clear();
491
492 if ( !iterator->beginRender() )
493 return IteratorError;
494
495 int total = iterator->count();
496 double step = total > 0 ? 100.0 / total : 100.0;
497 int i = 0;
498 while ( iterator->next() )
499 {
500 if ( feedback )
501 {
502 if ( total > 0 )
503 feedback->setProperty( "progress", QObject::tr( "Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
504 else
505 feedback->setProperty( "progress", QObject::tr( "Exporting section %1" ).arg( i + 1 ).arg( total ) );
506 feedback->setProgress( step * i );
507 }
508 if ( feedback && feedback->isCanceled() )
509 {
510 iterator->endRender();
511 return Canceled;
512 }
513
514 QgsLayoutExporter exporter( iterator->layout() );
515 QString filePath = iterator->filePath( baseFilePath, extension );
516 ExportResult result = exporter.exportToImage( filePath, settings );
517 if ( result != Success )
518 {
519 if ( result == FileError )
520 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 ) );
521 else
522 error = exporter.errorMessage();
523 iterator->endRender();
524 return result;
525 }
526 i++;
527 }
528
529 if ( feedback )
530 {
531 feedback->setProgress( 100 );
532 }
533
534 iterator->endRender();
535 return Success;
536}
537
539{
540 if ( !mLayout || mLayout->pageCollection()->pageCount() == 0 )
541 return PrintError;
542
543 PdfExportSettings settings = s;
544 if ( settings.dpi <= 0 )
545 settings.dpi = mLayout->renderContext().dpi();
546
547 mErrorFileName.clear();
548
549 LayoutContextPreviewSettingRestorer restorer( mLayout );
550 ( void )restorer;
551 LayoutContextSettingsRestorer contextRestorer( mLayout );
552 ( void )contextRestorer;
553 mLayout->renderContext().setDpi( settings.dpi );
554 mLayout->renderContext().setPredefinedScales( settings.predefinedMapScales );
555 mLayout->renderContext().setMaskSettings( createExportMaskSettings() );
556
557 if ( settings.simplifyGeometries )
558 {
559 mLayout->renderContext().setSimplifyMethod( createExportSimplifyMethod() );
560 }
561
562 std::unique_ptr< QgsLayoutGeoPdfExporter > geoPdfExporter;
563 if ( settings.writeGeoPdf || settings.exportLayersAsSeperateFiles ) //#spellok
564 geoPdfExporter = std::make_unique< QgsLayoutGeoPdfExporter >( mLayout );
565
566 mLayout->renderContext().setFlags( settings.flags );
567
568 // If we are not printing as raster, temporarily disable advanced effects
569 // as QPrinter does not support composition modes and can result
570 // in items missing from the output
571 mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagUseAdvancedEffects, !settings.forceVectorOutput );
572 mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagForceVectorOutput, settings.forceVectorOutput );
573
574 // Force synchronous legend graphics requests. Necessary for WMS GetPrint,
575 // as otherwise processing the request ends before remote graphics are downloaded.
576 mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagSynchronousLegendGraphics, true );
577
578 mLayout->renderContext().setTextRenderFormat( settings.textRenderFormat );
579 mLayout->renderContext().setExportThemes( settings.exportThemes );
580
581 ExportResult result = Success;
582 if ( settings.writeGeoPdf || settings.exportLayersAsSeperateFiles ) //#spellok
583 {
584 mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagRenderLabelsByMapLayer, true );
585
586 // here we need to export layers to individual PDFs
587 PdfExportSettings subSettings = settings;
588 subSettings.writeGeoPdf = false;
589 subSettings.exportLayersAsSeperateFiles = false; //#spellok
590
591 const QList<QGraphicsItem *> items = mLayout->items( Qt::AscendingOrder );
592
593 QList< QgsLayoutGeoPdfExporter::ComponentLayerDetail > pdfComponents;
594
595 const QDir baseDir = settings.exportLayersAsSeperateFiles ? QFileInfo( filePath ).dir() : QDir(); //#spellok
596 const QString baseFileName = settings.exportLayersAsSeperateFiles ? QFileInfo( filePath ).completeBaseName() : QString(); //#spellok
597
598 auto exportFunc = [this, &subSettings, &pdfComponents, &geoPdfExporter, &settings, &baseDir, &baseFileName]( unsigned int layerId, const QgsLayoutItem::ExportLayerDetail & layerDetail )->QgsLayoutExporter::ExportResult
599 {
600 ExportResult layerExportResult = Success;
602 component.name = layerDetail.name;
603 component.mapLayerId = layerDetail.mapLayerId;
604 component.opacity = layerDetail.opacity;
605 component.compositionMode = layerDetail.compositionMode;
606 component.group = layerDetail.mapTheme;
607 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' ) ) );
608 pdfComponents << component;
609 QPdfWriter printer = QPdfWriter( component.sourcePdfPath );
610 preparePrintAsPdf( mLayout, &printer, component.sourcePdfPath );
611 preparePrint( mLayout, &printer, false );
612 QPainter p;
613 if ( !p.begin( &printer ) )
614 {
615 //error beginning print
616 return FileError;
617 }
618 p.setRenderHint( QPainter::LosslessImageRendering, mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagLosslessImageRendering );
619 layerExportResult = printPrivate( &printer, p, false, subSettings.dpi, subSettings.rasterizeWholeImage );
620 p.end();
621 return layerExportResult;
622 };
623 result = handleLayeredExport( items, exportFunc );
624 if ( result != Success )
625 return result;
626
627 if ( settings.writeGeoPdf )
628 {
630 details.dpi = settings.dpi;
631 // TODO - multipages
632 QgsLayoutSize pageSize = mLayout->pageCollection()->page( 0 )->sizeWithUnits();
633 QgsLayoutSize pageSizeMM = mLayout->renderContext().measurementConverter().convert( pageSize, Qgis::LayoutUnit::Millimeters );
634 details.pageSizeMm = pageSizeMM.toQSizeF();
635
636 if ( settings.exportMetadata )
637 {
638 // copy layout metadata to GeoPDF export settings
639 details.author = mLayout->project()->metadata().author();
640 details.producer = QStringLiteral( "QGIS %1" ).arg( Qgis::version() );
641 details.creator = QStringLiteral( "QGIS %1" ).arg( Qgis::version() );
642 details.creationDateTime = mLayout->project()->metadata().creationDateTime();
643 details.subject = mLayout->project()->metadata().abstract();
644 details.title = mLayout->project()->metadata().title();
645 details.keywords = mLayout->project()->metadata().keywords();
646 }
647
648 const QList< QgsMapLayer * > layers = mLayout->project()->mapLayers().values();
649 for ( const QgsMapLayer *layer : layers )
650 {
651 details.layerIdToPdfLayerTreeNameMap.insert( layer->id(), layer->name() );
652 }
653
654 if ( settings.appendGeoreference )
655 {
656 // setup georeferencing
657 QList< QgsLayoutItemMap * > maps;
658 mLayout->layoutItems( maps );
659 for ( QgsLayoutItemMap *map : std::as_const( maps ) )
660 {
662 georef.crs = map->crs();
663
664 const QPointF topLeft = map->mapToScene( QPointF( 0, 0 ) );
665 const QPointF topRight = map->mapToScene( QPointF( map->rect().width(), 0 ) );
666 const QPointF bottomLeft = map->mapToScene( QPointF( 0, map->rect().height() ) );
667 const QPointF bottomRight = map->mapToScene( QPointF( map->rect().width(), map->rect().height() ) );
668 const QgsLayoutPoint topLeftMm = mLayout->convertFromLayoutUnits( topLeft, Qgis::LayoutUnit::Millimeters );
669 const QgsLayoutPoint topRightMm = mLayout->convertFromLayoutUnits( topRight, Qgis::LayoutUnit::Millimeters );
670 const QgsLayoutPoint bottomLeftMm = mLayout->convertFromLayoutUnits( bottomLeft, Qgis::LayoutUnit::Millimeters );
671 const QgsLayoutPoint bottomRightMm = mLayout->convertFromLayoutUnits( bottomRight, Qgis::LayoutUnit::Millimeters );
672
673 georef.pageBoundsPolygon.setExteriorRing( new QgsLineString( QVector< QgsPointXY >() << QgsPointXY( topLeftMm.x(), topLeftMm.y() )
674 << QgsPointXY( topRightMm.x(), topRightMm.y() )
675 << QgsPointXY( bottomRightMm.x(), bottomRightMm.y() )
676 << QgsPointXY( bottomLeftMm.x(), bottomLeftMm.y() )
677 << QgsPointXY( topLeftMm.x(), topLeftMm.y() ) ) );
678
679 georef.controlPoints.reserve( 4 );
680 const QTransform t = map->layoutToMapCoordsTransform();
681 const QgsPointXY topLeftMap = t.map( topLeft );
682 const QgsPointXY topRightMap = t.map( topRight );
683 const QgsPointXY bottomLeftMap = t.map( bottomLeft );
684 const QgsPointXY bottomRightMap = t.map( bottomRight );
685
686 georef.controlPoints << QgsAbstractGeoPdfExporter::ControlPoint( QgsPointXY( topLeftMm.x(), topLeftMm.y() ), topLeftMap );
687 georef.controlPoints << QgsAbstractGeoPdfExporter::ControlPoint( QgsPointXY( topRightMm.x(), topRightMm.y() ), topRightMap );
688 georef.controlPoints << QgsAbstractGeoPdfExporter::ControlPoint( QgsPointXY( bottomLeftMm.x(), bottomLeftMm.y() ), bottomLeftMap );
689 georef.controlPoints << QgsAbstractGeoPdfExporter::ControlPoint( QgsPointXY( bottomRightMm.x(), bottomRightMm.y() ), bottomRightMap );
690 details.georeferencedSections << georef;
691 }
692 }
693
694 details.customLayerTreeGroups = geoPdfExporter->customLayerTreeGroups();
695 details.initialLayerVisibility = geoPdfExporter->initialLayerVisibility();
696 details.layerOrder = geoPdfExporter->layerOrder();
697 details.layerTreeGroupOrder = geoPdfExporter->layerTreeGroupOrder();
698 details.includeFeatures = settings.includeGeoPdfFeatures;
699 details.useOgcBestPracticeFormatGeoreferencing = settings.useOgcBestPracticeFormatGeoreferencing;
700 details.useIso32000ExtensionFormatGeoreferencing = settings.useIso32000ExtensionFormatGeoreferencing;
701
702 if ( !geoPdfExporter->finalize( pdfComponents, filePath, details ) )
703 {
704 result = PrintError;
705 mErrorMessage = geoPdfExporter->errorMessage();
706 }
707 }
708 else
709 {
710 result = Success;
711 }
712 }
713 else
714 {
715 QPdfWriter printer = QPdfWriter( filePath );
716 preparePrintAsPdf( mLayout, &printer, filePath );
717 preparePrint( mLayout, &printer, false );
718 QPainter p;
719 if ( !p.begin( &printer ) )
720 {
721 //error beginning print
722 return FileError;
723 }
724 p.setRenderHint( QPainter::LosslessImageRendering, mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagLosslessImageRendering );
725 result = printPrivate( &printer, p, false, settings.dpi, settings.rasterizeWholeImage );
726 p.end();
727
728 bool shouldAppendGeoreference = settings.appendGeoreference && mLayout && mLayout->referenceMap() && mLayout->referenceMap()->page() == 0;
729 if ( settings.appendGeoreference || settings.exportMetadata )
730 {
731 georeferenceOutputPrivate( filePath, nullptr, QRectF(), settings.dpi, shouldAppendGeoreference, settings.exportMetadata );
732 }
733 }
734 captureLabelingResults();
735 return result;
736}
737
739{
740 error.clear();
741
742 if ( !iterator->beginRender() )
743 return IteratorError;
744
745 PdfExportSettings settings = s;
746
747 QPdfWriter printer = QPdfWriter( fileName );
748 QPainter p;
749
750 int total = iterator->count();
751 double step = total > 0 ? 100.0 / total : 100.0;
752 int i = 0;
753 bool first = true;
754 while ( iterator->next() )
755 {
756 if ( feedback )
757 {
758 if ( total > 0 )
759 feedback->setProperty( "progress", QObject::tr( "Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
760 else
761 feedback->setProperty( "progress", QObject::tr( "Exporting section %1" ).arg( i + 1 ) );
762 feedback->setProgress( step * i );
763 }
764 if ( feedback && feedback->isCanceled() )
765 {
766 iterator->endRender();
767 return Canceled;
768 }
769
770 if ( s.dpi <= 0 )
771 settings.dpi = iterator->layout()->renderContext().dpi();
772
773 LayoutContextPreviewSettingRestorer restorer( iterator->layout() );
774 ( void )restorer;
775 LayoutContextSettingsRestorer contextRestorer( iterator->layout() );
776 ( void )contextRestorer;
777 iterator->layout()->renderContext().setDpi( settings.dpi );
778
779 iterator->layout()->renderContext().setFlags( settings.flags );
781 iterator->layout()->renderContext().setMaskSettings( createExportMaskSettings() );
782
783 if ( settings.simplifyGeometries )
784 {
785 iterator->layout()->renderContext().setSimplifyMethod( createExportSimplifyMethod() );
786 }
787
788 // If we are not printing as raster, temporarily disable advanced effects
789 // as QPrinter does not support composition modes and can result
790 // in items missing from the output
792
794
795 iterator->layout()->renderContext().setTextRenderFormat( settings.textRenderFormat );
796
797 if ( first )
798 {
799 preparePrintAsPdf( iterator->layout(), &printer, fileName );
800 preparePrint( iterator->layout(), &printer, false );
801
802 if ( !p.begin( &printer ) )
803 {
804 //error beginning print
805 return PrintError;
806 }
807 p.setRenderHint( QPainter::LosslessImageRendering, iterator->layout()->renderContext().flags() & QgsLayoutRenderContext::FlagLosslessImageRendering );
808 }
809
810 QgsLayoutExporter exporter( iterator->layout() );
811
812 ExportResult result = exporter.printPrivate( &printer, p, !first, settings.dpi, settings.rasterizeWholeImage );
813 if ( result != Success )
814 {
815 if ( result == FileError )
816 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 ) );
817 else
818 error = exporter.errorMessage();
819
820 iterator->endRender();
821 return result;
822 }
823 first = false;
824 i++;
825 }
826
827 if ( feedback )
828 {
829 feedback->setProgress( 100 );
830 }
831
832 iterator->endRender();
833 return Success;
834}
835
837{
838 error.clear();
839
840 if ( !iterator->beginRender() )
841 return IteratorError;
842
843 int total = iterator->count();
844 double step = total > 0 ? 100.0 / total : 100.0;
845 int i = 0;
846 while ( iterator->next() )
847 {
848 if ( feedback )
849 {
850 if ( total > 0 )
851 feedback->setProperty( "progress", QObject::tr( "Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
852 else
853 feedback->setProperty( "progress", QObject::tr( "Exporting section %1" ).arg( i + 1 ).arg( total ) );
854 feedback->setProgress( step * i );
855 }
856 if ( feedback && feedback->isCanceled() )
857 {
858 iterator->endRender();
859 return Canceled;
860 }
861
862 QString filePath = iterator->filePath( baseFilePath, QStringLiteral( "pdf" ) );
863
864 QgsLayoutExporter exporter( iterator->layout() );
865 ExportResult result = exporter.exportToPdf( filePath, settings );
866 if ( result != Success )
867 {
868 if ( result == FileError )
869 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 ) );
870 else
871 error = exporter.errorMessage();
872 iterator->endRender();
873 return result;
874 }
875 i++;
876 }
877
878 if ( feedback )
879 {
880 feedback->setProgress( 100 );
881 }
882
883 iterator->endRender();
884 return Success;
885}
886
887#if defined( HAVE_QTPRINTER )
888QgsLayoutExporter::ExportResult QgsLayoutExporter::print( QPrinter &printer, const QgsLayoutExporter::PrintExportSettings &s )
889{
890 if ( !mLayout )
891 return PrintError;
892
894 if ( settings.dpi <= 0 )
895 settings.dpi = mLayout->renderContext().dpi();
896
897 mErrorFileName.clear();
898
899 LayoutContextPreviewSettingRestorer restorer( mLayout );
900 ( void )restorer;
901 LayoutContextSettingsRestorer contextRestorer( mLayout );
902 ( void )contextRestorer;
903 mLayout->renderContext().setDpi( settings.dpi );
904
905 mLayout->renderContext().setFlags( settings.flags );
906 mLayout->renderContext().setPredefinedScales( settings.predefinedMapScales );
907 // If we are not printing as raster, temporarily disable advanced effects
908 // as QPrinter does not support composition modes and can result
909 // in items missing from the output
910 mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagUseAdvancedEffects, !settings.rasterizeWholeImage );
911
912 preparePrint( mLayout, &printer, true );
913 QPainter p;
914 if ( !p.begin( &printer ) )
915 {
916 //error beginning print
917 return PrintError;
918 }
919 p.setRenderHint( QPainter::LosslessImageRendering, mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagLosslessImageRendering );
920 ExportResult result = printPrivate( &printer, p, false, settings.dpi, settings.rasterizeWholeImage );
921 p.end();
922
923 captureLabelingResults();
924 return result;
925}
926
927QgsLayoutExporter::ExportResult QgsLayoutExporter::print( QgsAbstractLayoutIterator *iterator, QPrinter &printer, const QgsLayoutExporter::PrintExportSettings &s, QString &error, QgsFeedback *feedback )
928{
929 error.clear();
930
931 if ( !iterator->beginRender() )
932 return IteratorError;
933
934 PrintExportSettings settings = s;
935
936 QPainter p;
937
938 int total = iterator->count();
939 double step = total > 0 ? 100.0 / total : 100.0;
940 int i = 0;
941 bool first = true;
942 while ( iterator->next() )
943 {
944 if ( feedback )
945 {
946 if ( total > 0 )
947 feedback->setProperty( "progress", QObject::tr( "Printing %1 of %2" ).arg( i + 1 ).arg( total ) );
948 else
949 feedback->setProperty( "progress", QObject::tr( "Printing section %1" ).arg( i + 1 ).arg( total ) );
950 feedback->setProgress( step * i );
951 }
952 if ( feedback && feedback->isCanceled() )
953 {
954 iterator->endRender();
955 return Canceled;
956 }
957
958 if ( s.dpi <= 0 )
959 settings.dpi = iterator->layout()->renderContext().dpi();
960
961 LayoutContextPreviewSettingRestorer restorer( iterator->layout() );
962 ( void )restorer;
963 LayoutContextSettingsRestorer contextRestorer( iterator->layout() );
964 ( void )contextRestorer;
965 iterator->layout()->renderContext().setDpi( settings.dpi );
966
967 iterator->layout()->renderContext().setFlags( settings.flags );
968 iterator->layout()->renderContext().setPredefinedScales( settings.predefinedMapScales );
969
970 // If we are not printing as raster, temporarily disable advanced effects
971 // as QPrinter does not support composition modes and can result
972 // in items missing from the output
973 iterator->layout()->renderContext().setFlag( QgsLayoutRenderContext::FlagUseAdvancedEffects, !settings.rasterizeWholeImage );
974
975 if ( first )
976 {
977 preparePrint( iterator->layout(), &printer, true );
978
979 if ( !p.begin( &printer ) )
980 {
981 //error beginning print
982 return PrintError;
983 }
984 p.setRenderHint( QPainter::LosslessImageRendering, iterator->layout()->renderContext().flags() & QgsLayoutRenderContext::FlagLosslessImageRendering );
985 }
986
987 QgsLayoutExporter exporter( iterator->layout() );
988
989 ExportResult result = exporter.printPrivate( &printer, p, !first, settings.dpi, settings.rasterizeWholeImage );
990 if ( result != Success )
991 {
992 iterator->endRender();
993 error = exporter.errorMessage();
994 return result;
995 }
996 first = false;
997 i++;
998 }
999
1000 if ( feedback )
1001 {
1002 feedback->setProgress( 100 );
1003 }
1004
1005 iterator->endRender();
1006 return Success;
1007}
1008#endif // HAVE_QTPRINTER
1009
1011{
1012 if ( !mLayout )
1013 return PrintError;
1014
1015 SvgExportSettings settings = s;
1016 if ( settings.dpi <= 0 )
1017 settings.dpi = mLayout->renderContext().dpi();
1018
1019 mErrorFileName.clear();
1020
1021 LayoutContextPreviewSettingRestorer restorer( mLayout );
1022 ( void )restorer;
1023 LayoutContextSettingsRestorer contextRestorer( mLayout );
1024 ( void )contextRestorer;
1025 mLayout->renderContext().setDpi( settings.dpi );
1026
1027 mLayout->renderContext().setFlags( settings.flags );
1028 mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagForceVectorOutput, settings.forceVectorOutput );
1029 mLayout->renderContext().setTextRenderFormat( s.textRenderFormat );
1030 mLayout->renderContext().setPredefinedScales( settings.predefinedMapScales );
1031 mLayout->renderContext().setMaskSettings( createExportMaskSettings() );
1032
1033 if ( settings.simplifyGeometries )
1034 {
1035 mLayout->renderContext().setSimplifyMethod( createExportSimplifyMethod() );
1036 }
1037
1038 QFileInfo fi( filePath );
1039 PageExportDetails pageDetails;
1040 pageDetails.directory = fi.path();
1041 pageDetails.baseName = fi.baseName();
1042 pageDetails.extension = fi.completeSuffix();
1043
1044 double inchesToLayoutUnits = mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 1, Qgis::LayoutUnit::Inches ) );
1045
1046 for ( int i = 0; i < mLayout->pageCollection()->pageCount(); ++i )
1047 {
1048 if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1049 {
1050 continue;
1051 }
1052
1053 pageDetails.page = i;
1054 QString fileName = generateFileName( pageDetails );
1055
1056 QgsLayoutItemPage *pageItem = mLayout->pageCollection()->page( i );
1057 QRectF bounds;
1058 if ( settings.cropToContents )
1059 {
1060 if ( mLayout->pageCollection()->pageCount() == 1 )
1061 {
1062 // single page, so include everything
1063 bounds = mLayout->layoutBounds( true );
1064 }
1065 else
1066 {
1067 // multi page, so just clip to items on current page
1068 bounds = mLayout->pageItemBounds( i, true );
1069 }
1070 bounds = bounds.adjusted( -settings.cropMargins.left(),
1071 -settings.cropMargins.top(),
1072 settings.cropMargins.right(),
1073 settings.cropMargins.bottom() );
1074 }
1075 else
1076 {
1077 bounds = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() );
1078 }
1079
1080 //width in pixel
1081 int width = static_cast< int >( bounds.width() * settings.dpi / inchesToLayoutUnits );
1082 //height in pixel
1083 int height = static_cast< int >( bounds.height() * settings.dpi / inchesToLayoutUnits );
1084 if ( width == 0 || height == 0 )
1085 {
1086 //invalid size, skip this page
1087 continue;
1088 }
1089
1090 if ( settings.exportAsLayers )
1091 {
1092 mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagRenderLabelsByMapLayer, settings.exportLabelsToSeparateLayers );
1093 const QRectF paperRect = QRectF( pageItem->pos().x(),
1094 pageItem->pos().y(),
1095 pageItem->rect().width(),
1096 pageItem->rect().height() );
1097 QDomDocument svg;
1098 QDomNode svgDocRoot;
1099 const QList<QGraphicsItem *> items = mLayout->items( paperRect,
1100 Qt::IntersectsItemBoundingRect,
1101 Qt::AscendingOrder );
1102
1103 auto exportFunc = [this, &settings, width, height, i, bounds, fileName, &svg, &svgDocRoot]( unsigned int layerId, const QgsLayoutItem::ExportLayerDetail & layerDetail )->QgsLayoutExporter::ExportResult
1104 {
1105 return renderToLayeredSvg( settings, width, height, i, bounds, fileName, layerId, layerDetail.name, svg, svgDocRoot, settings.exportMetadata );
1106 };
1107 ExportResult res = handleLayeredExport( items, exportFunc );
1108 if ( res != Success )
1109 return res;
1110
1111 if ( settings.exportMetadata )
1112 appendMetadataToSvg( svg );
1113
1114 QFile out( fileName );
1115 bool openOk = out.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate );
1116 if ( !openOk )
1117 {
1118 mErrorFileName = fileName;
1119 return FileError;
1120 }
1121
1122 out.write( svg.toByteArray() );
1123 }
1124 else
1125 {
1126 QBuffer svgBuffer;
1127 {
1128 QSvgGenerator generator;
1129 if ( settings.exportMetadata )
1130 {
1131 generator.setTitle( mLayout->project()->metadata().title() );
1132 generator.setDescription( mLayout->project()->metadata().abstract() );
1133 }
1134 generator.setOutputDevice( &svgBuffer );
1135 generator.setSize( QSize( width, height ) );
1136 generator.setViewBox( QRect( 0, 0, width, height ) );
1137 generator.setResolution( static_cast< int >( std::round( settings.dpi ) ) );
1138
1139 QPainter p;
1140 bool createOk = p.begin( &generator );
1141 if ( !createOk )
1142 {
1143 mErrorFileName = fileName;
1144 return FileError;
1145 }
1146
1147 if ( settings.cropToContents )
1148 renderRegion( &p, bounds );
1149 else
1150 renderPage( &p, i );
1151
1152 p.end();
1153 }
1154 {
1155 svgBuffer.close();
1156 svgBuffer.open( QIODevice::ReadOnly );
1157 QDomDocument svg;
1158 QString errorMsg;
1159 int errorLine;
1160 if ( ! svg.setContent( &svgBuffer, false, &errorMsg, &errorLine ) )
1161 {
1162 mErrorFileName = fileName;
1163 return SvgLayerError;
1164 }
1165
1166 if ( settings.exportMetadata )
1167 appendMetadataToSvg( svg );
1168
1169 QFile out( fileName );
1170 bool openOk = out.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate );
1171 if ( !openOk )
1172 {
1173 mErrorFileName = fileName;
1174 return FileError;
1175 }
1176
1177 out.write( svg.toByteArray() );
1178 }
1179 }
1180 }
1181 captureLabelingResults();
1182 return Success;
1183}
1184
1186{
1187 error.clear();
1188
1189 if ( !iterator->beginRender() )
1190 return IteratorError;
1191
1192 int total = iterator->count();
1193 double step = total > 0 ? 100.0 / total : 100.0;
1194 int i = 0;
1195 while ( iterator->next() )
1196 {
1197 if ( feedback )
1198 {
1199 if ( total > 0 )
1200 feedback->setProperty( "progress", QObject::tr( "Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
1201 else
1202 feedback->setProperty( "progress", QObject::tr( "Exporting section %1" ).arg( i + 1 ).arg( total ) );
1203
1204 feedback->setProgress( step * i );
1205 }
1206 if ( feedback && feedback->isCanceled() )
1207 {
1208 iterator->endRender();
1209 return Canceled;
1210 }
1211
1212 QString filePath = iterator->filePath( baseFilePath, QStringLiteral( "svg" ) );
1213
1214 QgsLayoutExporter exporter( iterator->layout() );
1215 ExportResult result = exporter.exportToSvg( filePath, settings );
1216 if ( result != Success )
1217 {
1218 if ( result == FileError )
1219 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 ) );
1220 else
1221 error = exporter.errorMessage();
1222 iterator->endRender();
1223 return result;
1224 }
1225 i++;
1226 }
1227
1228 if ( feedback )
1229 {
1230 feedback->setProgress( 100 );
1231 }
1232
1233 iterator->endRender();
1234 return Success;
1235
1236}
1237
1238QMap<QString, QgsLabelingResults *> QgsLayoutExporter::labelingResults()
1239{
1240 return mLabelingResults;
1241}
1242
1243QMap<QString, QgsLabelingResults *> QgsLayoutExporter::takeLabelingResults()
1244{
1245 QMap<QString, QgsLabelingResults *> res;
1246 std::swap( mLabelingResults, res );
1247 return res;
1248}
1249
1250void QgsLayoutExporter::preparePrintAsPdf( QgsLayout *layout, QPagedPaintDevice *device, const QString &filePath )
1251{
1252 QFileInfo fi( filePath );
1253 QDir dir;
1254 if ( !dir.exists( fi.absolutePath() ) )
1255 {
1256 dir.mkpath( fi.absolutePath() );
1257 }
1258
1259 updatePrinterPageSize( layout, device, firstPageToBeExported( layout ) );
1260
1261 // TODO: add option for this in layout
1262 // May not work on Windows or non-X11 Linux. Works fine on Mac using QPrinter::NativeFormat
1263 //printer.setFontEmbeddingEnabled( true );
1264
1265#if defined(HAS_KDE_QT5_PDF_TRANSFORM_FIX) || QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
1266 // paint engine hack not required, fixed upstream
1267#else
1268 QgsPaintEngineHack::fixEngineFlags( device->paintEngine() );
1269#endif
1270}
1271
1272void QgsLayoutExporter::preparePrint( QgsLayout *layout, QPagedPaintDevice *device, bool setFirstPageSize )
1273{
1274 if ( QPdfWriter *pdf = dynamic_cast<QPdfWriter *>( device ) )
1275 {
1276 pdf->setResolution( static_cast< int>( std::round( layout->renderContext().dpi() ) ) );
1277 }
1278#if defined( HAVE_QTPRINTER )
1279 else if ( QPrinter *printer = dynamic_cast<QPrinter *>( device ) )
1280 {
1281 printer->setFullPage( true );
1282 printer->setColorMode( QPrinter::Color );
1283 //set user-defined resolution
1284 printer->setResolution( static_cast< int>( std::round( layout->renderContext().dpi() ) ) );
1285 }
1286#endif
1287
1288 if ( setFirstPageSize )
1289 {
1290 updatePrinterPageSize( layout, device, firstPageToBeExported( layout ) );
1291 }
1292}
1293
1294QgsLayoutExporter::ExportResult QgsLayoutExporter::print( QPagedPaintDevice *device )
1295{
1296 if ( mLayout->pageCollection()->pageCount() == 0 )
1297 return PrintError;
1298
1299 preparePrint( mLayout, device, true );
1300 QPainter p;
1301 if ( !p.begin( device ) )
1302 {
1303 //error beginning print
1304 return PrintError;
1305 }
1306
1307 printPrivate( device, p );
1308 p.end();
1309 return Success;
1310}
1311
1312QgsLayoutExporter::ExportResult QgsLayoutExporter::printPrivate( QPagedPaintDevice *device, QPainter &painter, bool startNewPage, double dpi, bool rasterize )
1313{
1314 // layout starts page numbering at 0
1315 int fromPage = 0;
1316 int toPage = mLayout->pageCollection()->pageCount() - 1;
1317
1318#if defined( HAVE_QTPRINTER )
1319 if ( QPrinter *printer = dynamic_cast<QPrinter *>( device ) )
1320 {
1321 if ( printer->fromPage() >= 1 )
1322 fromPage = printer->fromPage() - 1;
1323 if ( printer->toPage() >= 1 )
1324 toPage = printer->toPage() - 1;
1325 }
1326#endif
1327
1328 bool pageExported = false;
1329 if ( rasterize )
1330 {
1331 for ( int i = fromPage; i <= toPage; ++i )
1332 {
1333 if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1334 {
1335 continue;
1336 }
1337
1338 updatePrinterPageSize( mLayout, device, i );
1339 if ( ( pageExported && i > fromPage ) || startNewPage )
1340 {
1341 device->newPage();
1342 }
1343
1344 QImage image = renderPageToImage( i, QSize(), dpi );
1345 if ( !image.isNull() )
1346 {
1347 QRectF targetArea( 0, 0, image.width(), image.height() );
1348 painter.drawImage( targetArea, image, targetArea );
1349 }
1350 else
1351 {
1352 return MemoryError;
1353 }
1354 pageExported = true;
1355 }
1356 }
1357 else
1358 {
1359 for ( int i = fromPage; i <= toPage; ++i )
1360 {
1361 if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1362 {
1363 continue;
1364 }
1365
1366 updatePrinterPageSize( mLayout, device, i );
1367
1368 if ( ( pageExported && i > fromPage ) || startNewPage )
1369 {
1370 device->newPage();
1371 }
1372 renderPage( &painter, i );
1373 pageExported = true;
1374 }
1375 }
1376 return Success;
1377}
1378
1379void QgsLayoutExporter::updatePrinterPageSize( QgsLayout *layout, QPagedPaintDevice *device, int page )
1380{
1381 QgsLayoutSize pageSize = layout->pageCollection()->page( page )->sizeWithUnits();
1383
1384 QPageLayout pageLayout( QPageSize( pageSizeMM.toQSizeF(), QPageSize::Millimeter ),
1385 QPageLayout::Portrait,
1386 QMarginsF( 0, 0, 0, 0 ) );
1387 pageLayout.setMode( QPageLayout::FullPageMode );
1388 device->setPageLayout( pageLayout );
1389 device->setPageMargins( QMarginsF( 0, 0, 0, 0 ) );
1390
1391#if defined( HAVE_QTPRINTER )
1392 if ( QPrinter *printer = dynamic_cast<QPrinter *>( device ) )
1393 {
1394 printer->setFullPage( true );
1395 }
1396#endif
1397}
1398
1399QgsLayoutExporter::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
1400{
1401 QBuffer svgBuffer;
1402 {
1403 QSvgGenerator generator;
1404 if ( includeMetadata )
1405 {
1406 if ( const QgsMasterLayoutInterface *l = dynamic_cast< const QgsMasterLayoutInterface * >( mLayout.data() ) )
1407 generator.setTitle( l->name() );
1408 else if ( mLayout->project() )
1409 generator.setTitle( mLayout->project()->title() );
1410 }
1411
1412 generator.setOutputDevice( &svgBuffer );
1413 generator.setSize( QSize( static_cast< int >( std::round( width ) ),
1414 static_cast< int >( std::round( height ) ) ) );
1415 generator.setViewBox( QRect( 0, 0,
1416 static_cast< int >( std::round( width ) ),
1417 static_cast< int >( std::round( height ) ) ) );
1418 generator.setResolution( static_cast< int >( std::round( settings.dpi ) ) ); //because the rendering is done in mm, convert the dpi
1419
1420 QPainter svgPainter( &generator );
1421 if ( settings.cropToContents )
1422 renderRegion( &svgPainter, bounds );
1423 else
1424 renderPage( &svgPainter, page );
1425 }
1426
1427// post-process svg output to create groups in a single svg file
1428// we create inkscape layers since it's nice and clean and free
1429// and fully svg compatible
1430 {
1431 svgBuffer.close();
1432 svgBuffer.open( QIODevice::ReadOnly );
1433 QDomDocument doc;
1434 QString errorMsg;
1435 int errorLine;
1436 if ( ! doc.setContent( &svgBuffer, false, &errorMsg, &errorLine ) )
1437 {
1438 mErrorFileName = filename;
1439 return SvgLayerError;
1440 }
1441 if ( 1 == svgLayerId )
1442 {
1443 svg = QDomDocument( doc.doctype() );
1444 svg.appendChild( svg.importNode( doc.firstChild(), false ) );
1445 svgDocRoot = svg.importNode( doc.elementsByTagName( QStringLiteral( "svg" ) ).at( 0 ), false );
1446 svgDocRoot.toElement().setAttribute( QStringLiteral( "xmlns:inkscape" ), QStringLiteral( "http://www.inkscape.org/namespaces/inkscape" ) );
1447 svg.appendChild( svgDocRoot );
1448 }
1449 QDomNode mainGroup = svg.importNode( doc.elementsByTagName( QStringLiteral( "g" ) ).at( 0 ), true );
1450 mainGroup.toElement().setAttribute( QStringLiteral( "id" ), layerName );
1451 mainGroup.toElement().setAttribute( QStringLiteral( "inkscape:label" ), layerName );
1452 mainGroup.toElement().setAttribute( QStringLiteral( "inkscape:groupmode" ), QStringLiteral( "layer" ) );
1453 QDomNode defs = svg.importNode( doc.elementsByTagName( QStringLiteral( "defs" ) ).at( 0 ), true );
1454 svgDocRoot.appendChild( defs );
1455 svgDocRoot.appendChild( mainGroup );
1456 }
1457 return Success;
1458}
1459
1460void QgsLayoutExporter::appendMetadataToSvg( QDomDocument &svg ) const
1461{
1462 const QgsProjectMetadata &metadata = mLayout->project()->metadata();
1463 QDomElement metadataElement = svg.createElement( QStringLiteral( "metadata" ) );
1464 QDomElement rdfElement = svg.createElement( QStringLiteral( "rdf:RDF" ) );
1465 rdfElement.setAttribute( QStringLiteral( "xmlns:rdf" ), QStringLiteral( "http://www.w3.org/1999/02/22-rdf-syntax-ns#" ) );
1466 rdfElement.setAttribute( QStringLiteral( "xmlns:rdfs" ), QStringLiteral( "http://www.w3.org/2000/01/rdf-schema#" ) );
1467 rdfElement.setAttribute( QStringLiteral( "xmlns:dc" ), QStringLiteral( "http://purl.org/dc/elements/1.1/" ) );
1468 QDomElement descriptionElement = svg.createElement( QStringLiteral( "rdf:Description" ) );
1469 QDomElement workElement = svg.createElement( QStringLiteral( "cc:Work" ) );
1470 workElement.setAttribute( QStringLiteral( "rdf:about" ), QString() );
1471
1472 auto addTextNode = [&workElement, &descriptionElement, &svg]( const QString & tag, const QString & value )
1473 {
1474 // inkscape compatible
1475 QDomElement element = svg.createElement( tag );
1476 QDomText t = svg.createTextNode( value );
1477 element.appendChild( t );
1478 workElement.appendChild( element );
1479
1480 // svg spec compatible
1481 descriptionElement.setAttribute( tag, value );
1482 };
1483
1484 addTextNode( QStringLiteral( "dc:format" ), QStringLiteral( "image/svg+xml" ) );
1485 addTextNode( QStringLiteral( "dc:title" ), metadata.title() );
1486 addTextNode( QStringLiteral( "dc:date" ), metadata.creationDateTime().toString( Qt::ISODate ) );
1487 addTextNode( QStringLiteral( "dc:identifier" ), metadata.identifier() );
1488 addTextNode( QStringLiteral( "dc:description" ), metadata.abstract() );
1489
1490 auto addAgentNode = [&workElement, &descriptionElement, &svg]( const QString & tag, const QString & value )
1491 {
1492 // inkscape compatible
1493 QDomElement inkscapeElement = svg.createElement( tag );
1494 QDomElement agentElement = svg.createElement( QStringLiteral( "cc:Agent" ) );
1495 QDomElement titleElement = svg.createElement( QStringLiteral( "dc:title" ) );
1496 QDomText t = svg.createTextNode( value );
1497 titleElement.appendChild( t );
1498 agentElement.appendChild( titleElement );
1499 inkscapeElement.appendChild( agentElement );
1500 workElement.appendChild( inkscapeElement );
1501
1502 // svg spec compatible
1503 QDomElement bagElement = svg.createElement( QStringLiteral( "rdf:Bag" ) );
1504 QDomElement liElement = svg.createElement( QStringLiteral( "rdf:li" ) );
1505 t = svg.createTextNode( value );
1506 liElement.appendChild( t );
1507 bagElement.appendChild( liElement );
1508
1509 QDomElement element = svg.createElement( tag );
1510 element.appendChild( bagElement );
1511 descriptionElement.appendChild( element );
1512 };
1513
1514 addAgentNode( QStringLiteral( "dc:creator" ), metadata.author() );
1515 addAgentNode( QStringLiteral( "dc:publisher" ), QStringLiteral( "QGIS %1" ).arg( Qgis::version() ) );
1516
1517 // keywords
1518 {
1519 QDomElement element = svg.createElement( QStringLiteral( "dc:subject" ) );
1520 QDomElement bagElement = svg.createElement( QStringLiteral( "rdf:Bag" ) );
1521 QgsAbstractMetadataBase::KeywordMap keywords = metadata.keywords();
1522 for ( auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
1523 {
1524 const QStringList words = it.value();
1525 for ( const QString &keyword : words )
1526 {
1527 QDomElement liElement = svg.createElement( QStringLiteral( "rdf:li" ) );
1528 QDomText t = svg.createTextNode( keyword );
1529 liElement.appendChild( t );
1530 bagElement.appendChild( liElement );
1531 }
1532 }
1533 element.appendChild( bagElement );
1534 workElement.appendChild( element );
1535 descriptionElement.appendChild( element );
1536 }
1537
1538 rdfElement.appendChild( descriptionElement );
1539 rdfElement.appendChild( workElement );
1540 metadataElement.appendChild( rdfElement );
1541 svg.documentElement().appendChild( metadataElement );
1542 svg.documentElement().setAttribute( QStringLiteral( "xmlns:cc" ), QStringLiteral( "http://creativecommons.org/ns#" ) );
1543}
1544
1545std::unique_ptr<double[]> QgsLayoutExporter::computeGeoTransform( const QgsLayoutItemMap *map, const QRectF &region, double dpi ) const
1546{
1547 if ( !map )
1548 map = mLayout->referenceMap();
1549
1550 if ( !map )
1551 return nullptr;
1552
1553 if ( dpi < 0 )
1554 dpi = mLayout->renderContext().dpi();
1555
1556 // calculate region of composition to export (in mm)
1557 QRectF exportRegion = region;
1558 if ( !exportRegion.isValid() )
1559 {
1560 int pageNumber = map->page();
1561
1562 QgsLayoutItemPage *page = mLayout->pageCollection()->page( pageNumber );
1563 double pageY = page->pos().y();
1564 QSizeF pageSize = page->rect().size();
1565 exportRegion = QRectF( 0, pageY, pageSize.width(), pageSize.height() );
1566 }
1567
1568 // map rectangle (in mm)
1569 QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
1570
1571 // destination width/height in mm
1572 double outputHeightMM = exportRegion.height();
1573 double outputWidthMM = exportRegion.width();
1574
1575 // map properties
1576 QgsRectangle mapExtent = map->extent();
1577 double mapXCenter = mapExtent.center().x();
1578 double mapYCenter = mapExtent.center().y();
1579 double alpha = - map->mapRotation() / 180 * M_PI;
1580 double sinAlpha = std::sin( alpha );
1581 double cosAlpha = std::cos( alpha );
1582
1583 // get the extent (in map units) for the exported region
1584 QPointF mapItemPos = map->pos();
1585 //adjust item position so it is relative to export region
1586 mapItemPos.rx() -= exportRegion.left();
1587 mapItemPos.ry() -= exportRegion.top();
1588
1589 // calculate extent of entire page in map units
1590 double xRatio = mapExtent.width() / mapItemSceneRect.width();
1591 double yRatio = mapExtent.height() / mapItemSceneRect.height();
1592 double xmin = mapExtent.xMinimum() - mapItemPos.x() * xRatio;
1593 double ymax = mapExtent.yMaximum() + mapItemPos.y() * yRatio;
1594 QgsRectangle paperExtent( xmin, ymax - outputHeightMM * yRatio, xmin + outputWidthMM * xRatio, ymax );
1595
1596 // calculate origin of page
1597 double X0 = paperExtent.xMinimum();
1598 double Y0 = paperExtent.yMaximum();
1599
1600 if ( !qgsDoubleNear( alpha, 0.0 ) )
1601 {
1602 // translate origin to account for map rotation
1603 double X1 = X0 - mapXCenter;
1604 double Y1 = Y0 - mapYCenter;
1605 double X2 = X1 * cosAlpha + Y1 * sinAlpha;
1606 double Y2 = -X1 * sinAlpha + Y1 * cosAlpha;
1607 X0 = X2 + mapXCenter;
1608 Y0 = Y2 + mapYCenter;
1609 }
1610
1611 // calculate scaling of pixels
1612 int pageWidthPixels = static_cast< int >( dpi * outputWidthMM / 25.4 );
1613 int pageHeightPixels = static_cast< int >( dpi * outputHeightMM / 25.4 );
1614 double pixelWidthScale = paperExtent.width() / pageWidthPixels;
1615 double pixelHeightScale = paperExtent.height() / pageHeightPixels;
1616
1617 // transform matrix
1618 std::unique_ptr<double[]> t( new double[6] );
1619 t[0] = X0;
1620 t[1] = cosAlpha * pixelWidthScale;
1621 t[2] = -sinAlpha * pixelWidthScale;
1622 t[3] = Y0;
1623 t[4] = -sinAlpha * pixelHeightScale;
1624 t[5] = -cosAlpha * pixelHeightScale;
1625
1626 return t;
1627}
1628
1629void QgsLayoutExporter::writeWorldFile( const QString &worldFileName, double a, double b, double c, double d, double e, double f ) const
1630{
1631 QFile worldFile( worldFileName );
1632 if ( !worldFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
1633 {
1634 return;
1635 }
1636 QTextStream fout( &worldFile );
1637
1638 // QString::number does not use locale settings (for the decimal point)
1639 // which is what we want here
1640 fout << QString::number( a, 'f', 12 ) << "\r\n";
1641 fout << QString::number( d, 'f', 12 ) << "\r\n";
1642 fout << QString::number( b, 'f', 12 ) << "\r\n";
1643 fout << QString::number( e, 'f', 12 ) << "\r\n";
1644 fout << QString::number( c, 'f', 12 ) << "\r\n";
1645 fout << QString::number( f, 'f', 12 ) << "\r\n";
1646}
1647
1648bool QgsLayoutExporter::georeferenceOutput( const QString &file, QgsLayoutItemMap *map, const QRectF &exportRegion, double dpi ) const
1649{
1650 return georeferenceOutputPrivate( file, map, exportRegion, dpi, false );
1651}
1652
1653bool QgsLayoutExporter::georeferenceOutputPrivate( const QString &file, QgsLayoutItemMap *map, const QRectF &exportRegion, double dpi, bool includeGeoreference, bool includeMetadata ) const
1654{
1655 if ( !mLayout )
1656 return false;
1657
1658 if ( !map && includeGeoreference )
1659 map = mLayout->referenceMap();
1660
1661 std::unique_ptr<double[]> t;
1662
1663 if ( map && includeGeoreference )
1664 {
1665 if ( dpi < 0 )
1666 dpi = mLayout->renderContext().dpi();
1667
1668 t = computeGeoTransform( map, exportRegion, dpi );
1669 }
1670
1671 // important - we need to manually specify the DPI in advance, as GDAL will otherwise
1672 // assume a DPI of 150
1673 CPLSetConfigOption( "GDAL_PDF_DPI", QString::number( dpi ).toUtf8().constData() );
1674 gdal::dataset_unique_ptr outputDS( GDALOpen( file.toUtf8().constData(), GA_Update ) );
1675 if ( outputDS )
1676 {
1677 if ( t )
1678 GDALSetGeoTransform( outputDS.get(), t.get() );
1679
1680 if ( includeMetadata )
1681 {
1682 QString creationDateString;
1683 const QDateTime creationDateTime = mLayout->project()->metadata().creationDateTime();
1684 if ( creationDateTime.isValid() )
1685 {
1686 creationDateString = QStringLiteral( "D:%1" ).arg( mLayout->project()->metadata().creationDateTime().toString( QStringLiteral( "yyyyMMddHHmmss" ) ) );
1687 if ( creationDateTime.timeZone().isValid() )
1688 {
1689 int offsetFromUtc = creationDateTime.timeZone().offsetFromUtc( creationDateTime );
1690 creationDateString += ( offsetFromUtc >= 0 ) ? '+' : '-';
1691 offsetFromUtc = std::abs( offsetFromUtc );
1692 int offsetHours = offsetFromUtc / 3600;
1693 int offsetMins = ( offsetFromUtc % 3600 ) / 60;
1694 creationDateString += QStringLiteral( "%1'%2'" ).arg( offsetHours ).arg( offsetMins );
1695 }
1696 }
1697 GDALSetMetadataItem( outputDS.get(), "CREATION_DATE", creationDateString.toUtf8().constData(), nullptr );
1698
1699 GDALSetMetadataItem( outputDS.get(), "AUTHOR", mLayout->project()->metadata().author().toUtf8().constData(), nullptr );
1700 const QString creator = QStringLiteral( "QGIS %1" ).arg( Qgis::version() );
1701 GDALSetMetadataItem( outputDS.get(), "CREATOR", creator.toUtf8().constData(), nullptr );
1702 GDALSetMetadataItem( outputDS.get(), "PRODUCER", creator.toUtf8().constData(), nullptr );
1703 GDALSetMetadataItem( outputDS.get(), "SUBJECT", mLayout->project()->metadata().abstract().toUtf8().constData(), nullptr );
1704 GDALSetMetadataItem( outputDS.get(), "TITLE", mLayout->project()->metadata().title().toUtf8().constData(), nullptr );
1705
1706 const QgsAbstractMetadataBase::KeywordMap keywords = mLayout->project()->metadata().keywords();
1707 QStringList allKeywords;
1708 for ( auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
1709 {
1710 allKeywords.append( QStringLiteral( "%1: %2" ).arg( it.key(), it.value().join( ',' ) ) );
1711 }
1712 const QString keywordString = allKeywords.join( ';' );
1713 GDALSetMetadataItem( outputDS.get(), "KEYWORDS", keywordString.toUtf8().constData(), nullptr );
1714 }
1715
1716 if ( t )
1717 GDALSetProjection( outputDS.get(), map->crs().toWkt( Qgis::CrsWktVariant::PreferredGdal ).toLocal8Bit().constData() );
1718 }
1719 CPLSetConfigOption( "GDAL_PDF_DPI", nullptr );
1720
1721 return true;
1722}
1723
1724QString nameForLayerWithItems( const QList< QGraphicsItem * > &items, unsigned int layerId )
1725{
1726 if ( items.count() == 1 )
1727 {
1728 if ( QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( items.at( 0 ) ) )
1729 {
1730 QString name = layoutItem->displayName();
1731 // cleanup default item ID format
1732 if ( name.startsWith( '<' ) && name.endsWith( '>' ) )
1733 name = name.mid( 1, name.length() - 2 );
1734 return name;
1735 }
1736 }
1737 else if ( items.count() > 1 )
1738 {
1739 QStringList currentLayerItemTypes;
1740 for ( QGraphicsItem *item : items )
1741 {
1742 if ( QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item ) )
1743 {
1744 const QString itemType = QgsApplication::layoutItemRegistry()->itemMetadata( layoutItem->type() )->visibleName();
1745 const QString itemTypePlural = QgsApplication::layoutItemRegistry()->itemMetadata( layoutItem->type() )->visiblePluralName();
1746 if ( !currentLayerItemTypes.contains( itemType ) && !currentLayerItemTypes.contains( itemTypePlural ) )
1747 currentLayerItemTypes << itemType;
1748 else if ( currentLayerItemTypes.contains( itemType ) )
1749 {
1750 currentLayerItemTypes.replace( currentLayerItemTypes.indexOf( itemType ), itemTypePlural );
1751 }
1752 }
1753 else
1754 {
1755 if ( !currentLayerItemTypes.contains( QObject::tr( "Other" ) ) )
1756 currentLayerItemTypes.append( QObject::tr( "Other" ) );
1757 }
1758 }
1759 return currentLayerItemTypes.join( QLatin1String( ", " ) );
1760 }
1761 return QObject::tr( "Layer %1" ).arg( layerId );
1762}
1763
1764QgsLayoutExporter::ExportResult QgsLayoutExporter::handleLayeredExport( const QList<QGraphicsItem *> &items,
1765 const std::function<QgsLayoutExporter::ExportResult( unsigned int, const QgsLayoutItem::ExportLayerDetail & )> &exportFunc )
1766{
1767 LayoutItemHider itemHider( items );
1768 ( void )itemHider;
1769
1770 int prevType = -1;
1772 unsigned int layerId = 1;
1774 itemHider.hideAll();
1775 const QList< QGraphicsItem * > itemsToIterate = itemHider.itemsToIterate();
1776 QList< QGraphicsItem * > currentLayerItems;
1777 for ( QGraphicsItem *item : itemsToIterate )
1778 {
1779 QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item );
1780
1781 bool canPlaceInExistingLayer = false;
1782 if ( layoutItem )
1783 {
1784 switch ( layoutItem->exportLayerBehavior() )
1785 {
1787 {
1788 switch ( prevItemBehavior )
1789 {
1791 canPlaceInExistingLayer = true;
1792 break;
1793
1795 canPlaceInExistingLayer = prevType == -1 || prevType == layoutItem->type();
1796 break;
1797
1800 canPlaceInExistingLayer = false;
1801 break;
1802 }
1803 break;
1804 }
1805
1807 {
1808 switch ( prevItemBehavior )
1809 {
1812 canPlaceInExistingLayer = prevType == -1 || prevType == layoutItem->type();
1813 break;
1814
1817 canPlaceInExistingLayer = false;
1818 break;
1819 }
1820 break;
1821 }
1822
1824 {
1825 canPlaceInExistingLayer = false;
1826 break;
1827 }
1828
1830 canPlaceInExistingLayer = false;
1831 break;
1832 }
1833 prevItemBehavior = layoutItem->exportLayerBehavior();
1834 prevType = layoutItem->type();
1835 }
1836 else
1837 {
1838 prevItemBehavior = QgsLayoutItem::MustPlaceInOwnLayer;
1839 }
1840
1841 if ( canPlaceInExistingLayer )
1842 {
1843 currentLayerItems << item;
1844 item->show();
1845 }
1846 else
1847 {
1848 if ( !currentLayerItems.isEmpty() )
1849 {
1850 layerDetails.name = nameForLayerWithItems( currentLayerItems, layerId );
1851
1852 ExportResult result = exportFunc( layerId, layerDetails );
1853 if ( result != Success )
1854 return result;
1855 layerId++;
1856 currentLayerItems.clear();
1857 }
1858
1859 itemHider.hideAll();
1860 item->show();
1861
1862 if ( layoutItem && layoutItem->exportLayerBehavior() == QgsLayoutItem::ItemContainsSubLayers )
1863 {
1864 int layoutItemLayerIdx = 0;
1866 mLayout->renderContext().setCurrentExportLayer( layoutItemLayerIdx );
1868 layoutItem->startLayeredExport();
1869 while ( layoutItem->nextExportPart() )
1870 {
1872 mLayout->renderContext().setCurrentExportLayer( layoutItemLayerIdx );
1874
1875 layerDetails = layoutItem->exportLayerDetails();
1876 ExportResult result = exportFunc( layerId, layerDetails );
1877 if ( result != Success )
1878 return result;
1879 layerId++;
1880
1881 layoutItemLayerIdx++;
1882 }
1883 layerDetails.mapLayerId.clear();
1885 mLayout->renderContext().setCurrentExportLayer( -1 );
1887 layoutItem->stopLayeredExport();
1888 currentLayerItems.clear();
1889 }
1890 else
1891 {
1892 currentLayerItems << item;
1893 }
1894 }
1895 }
1896 if ( !currentLayerItems.isEmpty() )
1897 {
1898 layerDetails.name = nameForLayerWithItems( currentLayerItems, layerId );
1899 ExportResult result = exportFunc( layerId, layerDetails );
1900 if ( result != Success )
1901 return result;
1902 }
1903 return Success;
1904}
1905
1906QgsVectorSimplifyMethod QgsLayoutExporter::createExportSimplifyMethod()
1907{
1908 QgsVectorSimplifyMethod simplifyMethod;
1910 simplifyMethod.setForceLocalOptimization( true );
1911 // we use SnappedToGridGlobal, because it avoids gaps and slivers between previously adjacent polygons
1913 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.
1914 return simplifyMethod;
1915}
1916
1917QgsMaskRenderSettings QgsLayoutExporter::createExportMaskSettings()
1918{
1919 QgsMaskRenderSettings settings;
1920 // this is quite a conservative setting -- I think we could make this more aggressive and get smaller file sizes
1921 // without too much loss of quality...
1922 settings.setSimplificationTolerance( 0.5 );
1923 return settings;
1924}
1925
1926void QgsLayoutExporter::computeWorldFileParameters( double &a, double &b, double &c, double &d, double &e, double &f, double dpi ) const
1927{
1928 if ( !mLayout )
1929 return;
1930
1931 QgsLayoutItemMap *map = mLayout->referenceMap();
1932 if ( !map )
1933 {
1934 return;
1935 }
1936
1937 int pageNumber = map->page();
1938 QgsLayoutItemPage *page = mLayout->pageCollection()->page( pageNumber );
1939 double pageY = page->pos().y();
1940 QSizeF pageSize = page->rect().size();
1941 QRectF pageRect( 0, pageY, pageSize.width(), pageSize.height() );
1942 computeWorldFileParameters( pageRect, a, b, c, d, e, f, dpi );
1943}
1944
1945void QgsLayoutExporter::computeWorldFileParameters( const QRectF &exportRegion, double &a, double &b, double &c, double &d, double &e, double &f, double dpi ) const
1946{
1947 if ( !mLayout )
1948 return;
1949
1950 // World file parameters : affine transformation parameters from pixel coordinates to map coordinates
1951 QgsLayoutItemMap *map = mLayout->referenceMap();
1952 if ( !map )
1953 {
1954 return;
1955 }
1956
1957 double destinationHeight = exportRegion.height();
1958 double destinationWidth = exportRegion.width();
1959
1960 QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
1961 QgsRectangle mapExtent = map->extent();
1962
1963 double alpha = map->mapRotation() / 180 * M_PI;
1964
1965 double xRatio = mapExtent.width() / mapItemSceneRect.width();
1966 double yRatio = mapExtent.height() / mapItemSceneRect.height();
1967
1968 double xCenter = mapExtent.center().x();
1969 double yCenter = mapExtent.center().y();
1970
1971 // get the extent (in map units) for the region
1972 QPointF mapItemPos = map->pos();
1973 //adjust item position so it is relative to export region
1974 mapItemPos.rx() -= exportRegion.left();
1975 mapItemPos.ry() -= exportRegion.top();
1976
1977 double xmin = mapExtent.xMinimum() - mapItemPos.x() * xRatio;
1978 double ymax = mapExtent.yMaximum() + mapItemPos.y() * yRatio;
1979 QgsRectangle paperExtent( xmin, ymax - destinationHeight * yRatio, xmin + destinationWidth * xRatio, ymax );
1980
1981 double X0 = paperExtent.xMinimum();
1982 double Y0 = paperExtent.yMinimum();
1983
1984 if ( dpi < 0 )
1985 dpi = mLayout->renderContext().dpi();
1986
1987 int widthPx = static_cast< int >( dpi * destinationWidth / 25.4 );
1988 int heightPx = static_cast< int >( dpi * destinationHeight / 25.4 );
1989
1990 double Ww = paperExtent.width() / widthPx;
1991 double Hh = paperExtent.height() / heightPx;
1992
1993 // scaling matrix
1994 double s[6];
1995 s[0] = Ww;
1996 s[1] = 0;
1997 s[2] = X0;
1998 s[3] = 0;
1999 s[4] = -Hh;
2000 s[5] = Y0 + paperExtent.height();
2001
2002 // rotation matrix
2003 double r[6];
2004 r[0] = std::cos( alpha );
2005 r[1] = -std::sin( alpha );
2006 r[2] = xCenter * ( 1 - std::cos( alpha ) ) + yCenter * std::sin( alpha );
2007 r[3] = std::sin( alpha );
2008 r[4] = std::cos( alpha );
2009 r[5] = - xCenter * std::sin( alpha ) + yCenter * ( 1 - std::cos( alpha ) );
2010
2011 // result = rotation x scaling = rotation(scaling(X))
2012 a = r[0] * s[0] + r[1] * s[3];
2013 b = r[0] * s[1] + r[1] * s[4];
2014 c = r[0] * s[2] + r[1] * s[5] + r[2];
2015 d = r[3] * s[0] + r[4] * s[3];
2016 e = r[3] * s[1] + r[4] * s[4];
2017 f = r[3] * s[2] + r[4] * s[5] + r[5];
2018}
2019
2021{
2022 if ( !layout )
2023 return false;
2024
2025 QList< QgsLayoutItem *> items;
2026 layout->layoutItems( items );
2027
2028 for ( QgsLayoutItem *currentItem : std::as_const( items ) )
2029 {
2030 // ignore invisible items, they won't affect the output in any way...
2031 if ( currentItem->isVisible() && currentItem->requiresRasterization() )
2032 return true;
2033 }
2034 return false;
2035}
2036
2038{
2039 if ( !layout )
2040 return false;
2041
2042 QList< QgsLayoutItem *> items;
2043 layout->layoutItems( items );
2044
2045 for ( QgsLayoutItem *currentItem : std::as_const( items ) )
2046 {
2047 // ignore invisible items, they won't affect the output in any way...
2048 if ( currentItem->isVisible() && currentItem->containsAdvancedEffects() )
2049 return true;
2050 }
2051 return false;
2052}
2053
2054QImage QgsLayoutExporter::createImage( const QgsLayoutExporter::ImageExportSettings &settings, int page, QRectF &bounds, bool &skipPage ) const
2055{
2056 bounds = QRectF();
2057 skipPage = false;
2058
2059 if ( settings.cropToContents )
2060 {
2061 if ( mLayout->pageCollection()->pageCount() == 1 )
2062 {
2063 // single page, so include everything
2064 bounds = mLayout->layoutBounds( true );
2065 }
2066 else
2067 {
2068 // multi page, so just clip to items on current page
2069 bounds = mLayout->pageItemBounds( page, true );
2070 }
2071 if ( bounds.width() <= 0 || bounds.height() <= 0 )
2072 {
2073 //invalid size, skip page
2074 skipPage = true;
2075 return QImage();
2076 }
2077
2078 double pixelToLayoutUnits = mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 1, Qgis::LayoutUnit::Pixels ) );
2079 bounds = bounds.adjusted( -settings.cropMargins.left() * pixelToLayoutUnits,
2080 -settings.cropMargins.top() * pixelToLayoutUnits,
2081 settings.cropMargins.right() * pixelToLayoutUnits,
2082 settings.cropMargins.bottom() * pixelToLayoutUnits );
2083 return renderRegionToImage( bounds, QSize(), settings.dpi );
2084 }
2085 else
2086 {
2087 return renderPageToImage( page, settings.imageSize, settings.dpi );
2088 }
2089}
2090
2091int QgsLayoutExporter::firstPageToBeExported( QgsLayout *layout )
2092{
2093 const int pageCount = layout->pageCollection()->pageCount();
2094 for ( int i = 0; i < pageCount; ++i )
2095 {
2096 if ( !layout->pageCollection()->shouldExportPage( i ) )
2097 {
2098 continue;
2099 }
2100
2101 return i;
2102 }
2103 return 0; // shouldn't really matter -- we aren't exporting ANY pages!
2104}
2105
2107{
2108 if ( details.page == 0 )
2109 {
2110 return details.directory + '/' + details.baseName + '.' + details.extension;
2111 }
2112 else
2113 {
2114 return details.directory + '/' + details.baseName + '_' + QString::number( details.page + 1 ) + '.' + details.extension;
2115 }
2116}
2117
2118void QgsLayoutExporter::captureLabelingResults()
2119{
2120 qDeleteAll( mLabelingResults );
2121 mLabelingResults.clear();
2122
2123 QList< QgsLayoutItemMap * > maps;
2124 mLayout->layoutItems( maps );
2125
2126 for ( QgsLayoutItemMap *map : std::as_const( maps ) )
2127 {
2128 mLabelingResults[ map->uuid() ] = map->mExportLabelingResults.release();
2129 }
2130}
2131
2132bool QgsLayoutExporter::saveImage( const QImage &image, const QString &imageFilename, const QString &imageFormat, QgsProject *projectForMetadata )
2133{
2134 QImageWriter w( imageFilename, imageFormat.toLocal8Bit().constData() );
2135 if ( imageFormat.compare( QLatin1String( "tiff" ), Qt::CaseInsensitive ) == 0 || imageFormat.compare( QLatin1String( "tif" ), Qt::CaseInsensitive ) == 0 )
2136 {
2137 w.setCompression( 1 ); //use LZW compression
2138 }
2139 if ( projectForMetadata )
2140 {
2141 w.setText( QStringLiteral( "Author" ), projectForMetadata->metadata().author() );
2142 const QString creator = QStringLiteral( "QGIS %1" ).arg( Qgis::version() );
2143 w.setText( QStringLiteral( "Creator" ), creator );
2144 w.setText( QStringLiteral( "Producer" ), creator );
2145 w.setText( QStringLiteral( "Subject" ), projectForMetadata->metadata().abstract() );
2146 w.setText( QStringLiteral( "Created" ), projectForMetadata->metadata().creationDateTime().toString( Qt::ISODate ) );
2147 w.setText( QStringLiteral( "Title" ), projectForMetadata->metadata().title() );
2148
2149 const QgsAbstractMetadataBase::KeywordMap keywords = projectForMetadata->metadata().keywords();
2150 QStringList allKeywords;
2151 for ( auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
2152 {
2153 allKeywords.append( QStringLiteral( "%1: %2" ).arg( it.key(), it.value().join( ',' ) ) );
2154 }
2155 const QString keywordString = allKeywords.join( ';' );
2156 w.setText( QStringLiteral( "Keywords" ), keywordString );
2157 }
2158 return w.write( image );
2159}
static QString version()
Version string.
Definition qgis.cpp:258
@ Millimeters
Millimeters.
@ 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,...
@ Warning
Warning message.
Definition qgis.h:101
TextRenderFormat
Options for rendering text.
Definition qgis.h:2409
@ 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.
QString errorMessage() const
Returns a string describing the last error encountered during an export.
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 setMaskSettings(const QgsMaskRenderSettings &settings)
Sets the mask render settings, which control how masks are drawn and behave during map renders.
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...
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 ...
QgsLayoutPageCollection * pageCollection()
Returns a pointer to the layout's page collection, which stores and manages page items in the layout.
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.
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
Contains settings regarding how masks are calculated and handled during a map render.
void setSimplificationTolerance(double tolerance)
Sets a simplification tolerance (in painter units) to use for on-the-fly simplification of mask paths...
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
double x
Definition qgspointxy.h:63
void setExteriorRing(QgsCurve *ring) override
Sets the exterior ring of the polygon.
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.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
double width() const
Returns the width of the rectangle.
double yMaximum() const
Returns the y maximum value (top side of rectangle).
QgsPointXY center() const
Returns the center point of the rectangle.
double height() const
Returns the height of the rectangle.
A boolean settings entry.
static QgsSettingsTreeNode * sTreeLayout
This class contains information how to simplify geometries fetched from a vector layer.
void setThreshold(float threshold)
Sets the simplification threshold of the vector layer managed.
void setForceLocalOptimization(bool localOptimization)
Sets where the simplification executes, after fetch the geometries from provider, or when supported,...
void setSimplifyHints(Qgis::VectorRenderingSimplificationFlags simplifyHints)
Sets the simplification hints of the vector layer managed.
void setSimplifyAlgorithm(Qgis::VectorSimplificationAlgorithm simplifyAlgorithm)
Sets the local simplification algorithm of the vector layer managed.
std::unique_ptr< std::remove_pointer< GDALDatasetH >::type, GDALDatasetCloser > dataset_unique_ptr
Scoped GDAL dataset.
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:6042
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6041
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:5465
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.
QStringList layerTreeGroupOrder
Specifies the ordering of layer tree groups in the generated GeoPDF file.
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.