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