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