36#include <QImageWriter>
39#include <QSvgGenerator>
43using namespace Qt::StringLiterals;
45#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
47#include <QPdfOutputIntent>
49#include <QXmlStreamWriter>
55class LayoutContextPreviewSettingRestorer
59 LayoutContextPreviewSettingRestorer( QgsLayout *layout )
61 , mPreviousSetting( layout->renderContext().mIsPreviewRender )
63 mLayout->renderContext().mIsPreviewRender =
false;
66 ~LayoutContextPreviewSettingRestorer()
68 mLayout->renderContext().mIsPreviewRender = mPreviousSetting;
71 LayoutContextPreviewSettingRestorer(
const LayoutContextPreviewSettingRestorer &other ) =
delete;
72 LayoutContextPreviewSettingRestorer &operator=(
const LayoutContextPreviewSettingRestorer &other ) =
delete;
75 QgsLayout *mLayout =
nullptr;
76 bool mPreviousSetting =
false;
83 LayoutGuideHider( QgsLayout *layout )
86 const QList< QgsLayoutGuide * > guides = mLayout->guides().guides();
87 for ( QgsLayoutGuide *guide : guides )
89 mPrevVisibility.insert( guide, guide->item()->isVisible() );
90 guide->item()->setVisible(
false );
96 for (
auto it = mPrevVisibility.constBegin(); it != mPrevVisibility.constEnd(); ++it )
98 it.key()->item()->setVisible( it.value() );
102 LayoutGuideHider(
const LayoutGuideHider &other ) =
delete;
103 LayoutGuideHider &operator=(
const LayoutGuideHider &other ) =
delete;
106 QgsLayout *mLayout =
nullptr;
107 QHash< QgsLayoutGuide *, bool > mPrevVisibility;
113 explicit LayoutItemHider(
const QList<QGraphicsItem *> &items )
115 mItemsToIterate.reserve( items.count() );
116 for ( QGraphicsItem *item : items )
118 const bool isVisible = item->isVisible();
119 mPrevVisibility[item] = isVisible;
121 mItemsToIterate.append( item );
122 if ( QgsLayoutItem *layoutItem =
dynamic_cast< QgsLayoutItem *
>( item ) )
123 layoutItem->setProperty(
"wasVisible", isVisible );
131 for (
auto it = mPrevVisibility.constBegin(); it != mPrevVisibility.constEnd(); ++it )
139 for (
auto it = mPrevVisibility.constBegin(); it != mPrevVisibility.constEnd(); ++it )
141 it.key()->setVisible( it.value() );
142 if ( QgsLayoutItem *layoutItem =
dynamic_cast< QgsLayoutItem *
>( it.key() ) )
143 layoutItem->setProperty(
"wasVisible", QVariant() );
147 QList< QGraphicsItem * > itemsToIterate()
const {
return mItemsToIterate; }
149 LayoutItemHider(
const LayoutItemHider &other ) =
delete;
150 LayoutItemHider &operator=(
const LayoutItemHider &other ) =
delete;
154 QList<QGraphicsItem * > mItemsToIterate;
155 QHash<QGraphicsItem *, bool> mPrevVisibility;
173 qDeleteAll( mLabelingResults );
186 if ( mLayout->pageCollection()->pageCount() <= page || page < 0 )
197 LayoutContextPreviewSettingRestorer restorer( mLayout );
200 QRectF paperRect = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() );
209 if ( mLayout->pageCollection()->pageCount() <= page || page < 0 )
220 LayoutContextPreviewSettingRestorer restorer( mLayout );
223 QRectF paperRect = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() );
225 const double imageAspectRatio =
static_cast< double >( imageSize.width() ) / imageSize.height();
226 const double paperAspectRatio = paperRect.width() / paperRect.height();
227 if ( imageSize.isValid() && ( !
qgsDoubleNear( imageAspectRatio, paperAspectRatio, 0.008 ) ) )
232 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 ) ), u
"Layout"_s,
Qgis::MessageLevel::Warning );
240class LayoutItemCacheSettingRestorer
244 LayoutItemCacheSettingRestorer(
QgsLayout *layout )
247 const QList< QGraphicsItem * > items = mLayout->items();
248 for ( QGraphicsItem *item : items )
250 mPrevCacheMode.insert( item, item->cacheMode() );
251 item->setCacheMode( QGraphicsItem::NoCache );
255 ~LayoutItemCacheSettingRestorer()
257 for (
auto it = mPrevCacheMode.constBegin(); it != mPrevCacheMode.constEnd(); ++it )
259 it.key()->setCacheMode( it.value() );
263 LayoutItemCacheSettingRestorer(
const LayoutItemCacheSettingRestorer &other ) =
delete;
264 LayoutItemCacheSettingRestorer &operator=(
const LayoutItemCacheSettingRestorer &other ) =
delete;
267 QgsLayout *mLayout =
nullptr;
268 QHash< QGraphicsItem *, QGraphicsItem::CacheMode > mPrevCacheMode;
275 QPaintDevice *paintDevice = painter->device();
276 if ( !paintDevice || !mLayout )
281 LayoutItemCacheSettingRestorer cacheRestorer( mLayout );
282 ( void )cacheRestorer;
283 LayoutContextPreviewSettingRestorer restorer( mLayout );
285 LayoutGuideHider guideHider( mLayout );
290 mLayout->render( painter, QRectF( 0, 0, paintDevice->width(), paintDevice->height() ), region );
298 LayoutContextPreviewSettingRestorer restorer( mLayout );
301 double resolution = mLayout->renderContext().dpi();
303 if ( imageSize.isValid() )
307 resolution = ( imageSize.width() / region.width()
308 + imageSize.height() / region.height() ) / 2.0 * oneInchInLayoutUnits;
316 int width = imageSize.isValid() ? imageSize.width()
317 :
static_cast< int >( resolution * region.width() / oneInchInLayoutUnits );
318 int height = imageSize.isValid() ? imageSize.height()
319 :
static_cast< int >( resolution * region.height() / oneInchInLayoutUnits );
321 QImage image( QSize( width, height ), QImage::Format_ARGB32 );
322 if ( !image.isNull() )
325 if ( width > 32768 || height > 32768 )
326 QgsMessageLog::logMessage( QObject::tr(
"Error: output width or height is larger than 32768 pixel, result will be clipped" ) );
327 image.setDotsPerMeterX(
static_cast< int >( std::round( resolution / 25.4 * 1000 ) ) );
328 image.setDotsPerMeterY(
static_cast< int>( std::round( resolution / 25.4 * 1000 ) ) );
329 image.fill( Qt::transparent );
330 QPainter imagePainter( &image );
332 if ( !imagePainter.isActive() )
340class LayoutContextSettingsRestorer
345 LayoutContextSettingsRestorer(
QgsLayout *layout )
347 , mPreviousDpi( layout->renderContext().dpi() )
348 , mPreviousFlags( layout->renderContext().flags() )
349 , mPreviousRasterPolicy( layout->renderContext().rasterizedRenderingPolicy() )
350 , mPreviousTextFormat( layout->renderContext().textRenderFormat() )
351 , mPreviousExportLayer( layout->renderContext().currentExportLayer() )
352 , mPreviousSimplifyMethod( layout->renderContext().simplifyMethod() )
353 , mPreviousMaskSettings( layout->renderContext().maskSettings() )
354 , mExportThemes( layout->renderContext().exportThemes() )
355 , mPredefinedScales( layout->renderContext().predefinedScales() )
360 ~LayoutContextSettingsRestorer()
362 mLayout->renderContext().setDpi( mPreviousDpi );
363 mLayout->renderContext().setFlags( mPreviousFlags );
364 mLayout->renderContext().setRasterizedRenderingPolicy( mPreviousRasterPolicy );
365 mLayout->renderContext().setTextRenderFormat( mPreviousTextFormat );
367 mLayout->renderContext().setCurrentExportLayer( mPreviousExportLayer );
369 mLayout->renderContext().setSimplifyMethod( mPreviousSimplifyMethod );
370 mLayout->renderContext().setMaskSettings( mPreviousMaskSettings );
371 mLayout->renderContext().setExportThemes( mExportThemes );
372 mLayout->renderContext().setPredefinedScales( mPredefinedScales );
375 LayoutContextSettingsRestorer(
const LayoutContextSettingsRestorer &other ) =
delete;
376 LayoutContextSettingsRestorer &operator=(
const LayoutContextSettingsRestorer &other ) =
delete;
379 QgsLayout *mLayout =
nullptr;
380 double mPreviousDpi = 0;
384 int mPreviousExportLayer = 0;
385 QgsVectorSimplifyMethod mPreviousSimplifyMethod;
386 QgsMaskRenderSettings mPreviousMaskSettings;
387 QStringList mExportThemes;
388 QVector< double > mPredefinedScales;
399 if ( settings.
dpi <= 0 )
400 settings.
dpi = mLayout->renderContext().dpi();
402 mErrorFileName.clear();
404 int worldFilePageNo = -1;
407 worldFilePageNo = referenceMap->page();
410 QFileInfo fi( filePath );
412 if ( !dir.exists( fi.absolutePath() ) )
414 dir.mkpath( fi.absolutePath() );
419 pageDetails.
baseName = fi.completeBaseName();
422 LayoutContextPreviewSettingRestorer restorer( mLayout );
424 LayoutContextSettingsRestorer dpiRestorer( mLayout );
426 mLayout->renderContext().setDpi( settings.
dpi );
427 mLayout->renderContext().setFlags( settings.
flags );
432 if ( settings.
pages.empty() )
434 for (
int page = 0; page < mLayout->pageCollection()->pageCount(); ++page )
439 for (
int page : std::as_const( settings.
pages ) )
441 if ( page >= 0 && page < mLayout->pageCollection()->pageCount() )
446 for (
int page : std::as_const( pages ) )
448 if ( !mLayout->pageCollection()->shouldExportPage( page ) )
455 QImage image = createImage( settings, page, bounds, skip );
460 pageDetails.
page = page;
463 if ( image.isNull() )
465 mErrorFileName = outputFilePath;
471 mErrorFileName = outputFilePath;
475 const bool shouldGeoreference = ( page == worldFilePageNo );
476 if ( shouldGeoreference )
478 georeferenceOutputPrivate( outputFilePath,
nullptr, bounds, settings.
dpi, shouldGeoreference );
483 double a, b,
c, d, e, f;
484 if ( bounds.isValid() )
489 QFileInfo fi( outputFilePath );
491 QString outputSuffix = fi.suffix();
492 QString worldFileName = fi.absolutePath() +
'/' + fi.completeBaseName() +
'.'
493 + outputSuffix.at( 0 ) + outputSuffix.at( fi.suffix().size() - 1 ) +
'w';
495 writeWorldFile( worldFileName, a, b,
c, d, e, f );
500 captureLabelingResults();
511 int total = iterator->
count();
512 double step = total > 0 ? 100.0 / total : 100.0;
514 while ( iterator->
next() )
519 feedback->setProperty(
"progress", QObject::tr(
"Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
521 feedback->setProperty(
"progress", QObject::tr(
"Exporting section %1" ).arg( i + 1 ) );
531 QString filePath = iterator->
filePath( baseFilePath, extension );
536 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 ) );
556 if ( !mLayout || mLayout->pageCollection()->pageCount() == 0 )
560 if ( settings.
dpi <= 0 )
561 settings.
dpi = mLayout->renderContext().dpi();
563 mErrorFileName.clear();
565 LayoutContextPreviewSettingRestorer restorer( mLayout );
567 LayoutContextSettingsRestorer contextRestorer( mLayout );
568 ( void )contextRestorer;
569 mLayout->renderContext().setDpi( settings.
dpi );
571 mLayout->renderContext().setMaskSettings( createExportMaskSettings() );
575 mLayout->renderContext().setSimplifyMethod( createExportSimplifyMethod() );
578 std::unique_ptr< QgsLayoutGeospatialPdfExporter > geospatialPdfExporter;
580 geospatialPdfExporter = std::make_unique< QgsLayoutGeospatialPdfExporter >( mLayout );
582 mLayout->renderContext().setFlags( settings.
flags );
605 mLayout->renderContext().setExportThemes( settings.
exportThemes );
617 const QList<QGraphicsItem *> items = mLayout->items( Qt::AscendingOrder );
619 QList< QgsLayoutGeospatialPdfExporter::ComponentLayerDetail > pdfComponents;
624 QSet<QString> mutuallyExclusiveGroups;
630 component.
name = layerDetail.name;
631 component.
mapLayerId = layerDetail.mapLayerId;
632 component.
opacity = layerDetail.opacity;
634 component.
group = layerDetail.groupName;
635 if ( !layerDetail.mapTheme.isEmpty() )
637 component.
group = layerDetail.mapTheme;
638 mutuallyExclusiveGroups.insert( layerDetail.mapTheme );
641 component.
sourcePdfPath = settings.writeGeoPdf ? geospatialPdfExporter->generateTemporaryFilepath( u
"layer_%1.pdf"_s.arg( layerId ) ) : baseDir.filePath( u
"%1_%2.pdf"_s.arg( baseFileName ).arg( layerId, 4, 10, QChar(
'0' ) ) );
642 pdfComponents << component;
644 preparePrintAsPdf( mLayout, &printer, component.
sourcePdfPath );
645 preparePrint( mLayout, &printer,
false );
647 if ( !p.begin( &printer ) )
655 return layerExportResult;
657 auto getExportGroupNameFunc = [](
QgsLayoutItem * item )->QString
659 return item->customProperty( u
"pdfExportGroup"_s ).toString();
661 result = handleLayeredExport( items, exportFunc, getExportGroupNameFunc );
665 if ( settings.writeGeoPdf )
668 details.
dpi = settings.dpi;
670 QgsLayoutSize pageSize = mLayout->pageCollection()->page( 0 )->sizeWithUnits();
675 if ( settings.exportMetadata )
678 details.
author = mLayout->project()->metadata().author();
680 details.
creator = getCreator();
681 details.
creationDateTime = mLayout->project()->metadata().creationDateTime();
682 details.
subject = mLayout->project()->metadata().abstract();
683 details.
title = mLayout->project()->metadata().title();
684 details.
keywords = mLayout->project()->metadata().keywords();
687 const QList< QgsMapLayer * > layers = mLayout->project()->mapLayers().values();
693 if ( settings.appendGeoreference )
696 QList< QgsLayoutItemMap * > maps;
697 mLayout->layoutItems( maps );
701 georef.
crs = map->crs();
703 const QPointF topLeft = map->mapToScene( QPointF( 0, 0 ) );
704 const QPointF topRight = map->mapToScene( QPointF( map->rect().width(), 0 ) );
705 const QPointF bottomLeft = map->mapToScene( QPointF( 0, map->rect().height() ) );
706 const QPointF bottomRight = map->mapToScene( QPointF( map->rect().width(), map->rect().height() ) );
719 const QTransform t = map->layoutToMapCoordsTransform();
720 const QgsPointXY topLeftMap = t.map( topLeft );
721 const QgsPointXY topRightMap = t.map( topRight );
722 const QgsPointXY bottomLeftMap = t.map( bottomLeft );
723 const QgsPointXY bottomRightMap = t.map( bottomRight );
735 details.
layerOrder = geospatialPdfExporter->layerOrder();
740 if ( !geospatialPdfExporter->finalize( pdfComponents, filePath, details ) )
743 mErrorMessage = geospatialPdfExporter->errorMessage();
753 QPdfWriter printer = QPdfWriter( filePath );
754 preparePrintAsPdf( mLayout, &printer, filePath );
755 preparePrint( mLayout, &printer,
false );
757 if ( !p.begin( &printer ) )
766 bool shouldAppendGeoreference = settings.
appendGeoreference && mLayout && mLayout->referenceMap() && mLayout->referenceMap()->page() == 0;
769 georeferenceOutputPrivate( filePath,
nullptr, QRectF(), settings.
dpi, shouldAppendGeoreference, settings.
exportMetadata );
772 captureLabelingResults();
785 QPdfWriter printer = QPdfWriter( fileName );
788 int total = iterator->
count();
789 double step = total > 0 ? 100.0 / total : 100.0;
792 while ( iterator->
next() )
797 feedback->setProperty(
"progress", QObject::tr(
"Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
799 feedback->setProperty(
"progress", QObject::tr(
"Exporting section %1" ).arg( i + 1 ) );
811 LayoutContextPreviewSettingRestorer restorer( iterator->
layout() );
813 LayoutContextSettingsRestorer contextRestorer( iterator->
layout() );
814 ( void )contextRestorer;
841 preparePrintAsPdf( iterator->
layout(), &printer, fileName );
842 preparePrint( iterator->
layout(), &printer,
false );
844 if ( !p.begin( &printer ) )
858 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 ) );
885 int total = iterator->
count();
886 double step = total > 0 ? 100.0 / total : 100.0;
888 while ( iterator->
next() )
893 feedback->setProperty(
"progress", QObject::tr(
"Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
895 feedback->setProperty(
"progress", QObject::tr(
"Exporting section %1" ).arg( i + 1 ) );
904 QString filePath = iterator->
filePath( baseFilePath, u
"pdf"_s );
911 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 ) );
929#if defined( HAVE_QTPRINTER )
936 if ( settings.
dpi <= 0 )
937 settings.
dpi = mLayout->renderContext().dpi();
939 mErrorFileName.clear();
941 LayoutContextPreviewSettingRestorer restorer( mLayout );
943 LayoutContextSettingsRestorer contextRestorer( mLayout );
944 ( void )contextRestorer;
945 mLayout->renderContext().setDpi( settings.
dpi );
947 mLayout->renderContext().setFlags( settings.
flags );
954 preparePrint( mLayout, &printer,
true );
956 if ( !p.begin( &printer ) )
965 captureLabelingResults();
980 int total = iterator->
count();
981 double step = total > 0 ? 100.0 / total : 100.0;
984 while ( iterator->
next() )
989 feedback->setProperty(
"progress", QObject::tr(
"Printing %1 of %2" ).arg( i + 1 ).arg( total ) );
991 feedback->setProperty(
"progress", QObject::tr(
"Printing section %1" ).arg( i + 1 ) );
1003 LayoutContextPreviewSettingRestorer restorer( iterator->
layout() );
1005 LayoutContextSettingsRestorer contextRestorer( iterator->
layout() );
1006 ( void )contextRestorer;
1015 if ( !settings.rasterizeWholeImage )
1020 preparePrint( iterator->
layout(), &printer,
true );
1022 if ( !p.begin( &printer ) )
1032 ExportResult result = exporter.printPrivate( &printer, p, !first, settings.dpi, settings.rasterizeWholeImage );
1036 error = exporter.errorMessage();
1059 if ( settings.
dpi <= 0 )
1060 settings.
dpi = mLayout->renderContext().dpi();
1062 mErrorFileName.clear();
1064 LayoutContextPreviewSettingRestorer restorer( mLayout );
1066 LayoutContextSettingsRestorer contextRestorer( mLayout );
1067 ( void )contextRestorer;
1068 mLayout->renderContext().setDpi( settings.
dpi );
1070 mLayout->renderContext().setFlags( settings.
flags );
1085 mLayout->renderContext().setMaskSettings( createExportMaskSettings() );
1089 mLayout->renderContext().setSimplifyMethod( createExportSimplifyMethod() );
1092 QFileInfo fi( filePath );
1095 pageDetails.
baseName = fi.baseName();
1096 pageDetails.
extension = fi.completeSuffix();
1100 for (
int i = 0; i < mLayout->pageCollection()->pageCount(); ++i )
1102 if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1107 pageDetails.
page = i;
1114 if ( mLayout->pageCollection()->pageCount() == 1 )
1117 bounds = mLayout->layoutBounds(
true );
1122 bounds = mLayout->pageItemBounds( i,
true );
1131 bounds = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() );
1135 int width =
static_cast< int >( bounds.width() * settings.
dpi / inchesToLayoutUnits );
1137 int height =
static_cast< int >( bounds.height() * settings.
dpi / inchesToLayoutUnits );
1138 if ( width == 0 || height == 0 )
1147 const QRectF paperRect = QRectF( pageItem->pos().x(),
1148 pageItem->pos().y(),
1149 pageItem->rect().width(),
1150 pageItem->rect().height() );
1152 QDomNode svgDocRoot;
1153 const QList<QGraphicsItem *> items = mLayout->items( paperRect,
1154 Qt::IntersectsItemBoundingRect,
1155 Qt::AscendingOrder );
1159 return renderToLayeredSvg( settings, width, height, i, bounds, fileName, layerId, layerDetail.name, svg, svgDocRoot, settings.
exportMetadata );
1161 auto getExportGroupNameFunc = [](
QgsLayoutItem * )->QString
1165 ExportResult res = handleLayeredExport( items, exportFunc, getExportGroupNameFunc );
1170 appendMetadataToSvg( svg );
1172 QFile out( fileName );
1173 bool openOk = out.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate );
1176 mErrorFileName = fileName;
1180 out.write( svg.toByteArray() );
1186 QSvgGenerator generator;
1189 generator.setTitle( mLayout->project()->metadata().title() );
1190 generator.setDescription( mLayout->project()->metadata().abstract() );
1192 generator.setOutputDevice( &svgBuffer );
1193 generator.setSize( QSize( width, height ) );
1194 generator.setViewBox( QRect( 0, 0, width, height ) );
1195 generator.setResolution(
static_cast< int >( std::round( settings.
dpi ) ) );
1198 bool createOk = p.begin( &generator );
1201 mErrorFileName = fileName;
1214 svgBuffer.open( QIODevice::ReadOnly );
1218 if ( ! svg.setContent( &svgBuffer,
false, &errorMsg, &errorLine ) )
1220 mErrorFileName = fileName;
1225 appendMetadataToSvg( svg );
1227 QFile out( fileName );
1228 bool openOk = out.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate );
1231 mErrorFileName = fileName;
1235 out.write( svg.toByteArray() );
1239 captureLabelingResults();
1250 int total = iterator->
count();
1251 double step = total > 0 ? 100.0 / total : 100.0;
1253 while ( iterator->
next() )
1258 feedback->setProperty(
"progress", QObject::tr(
"Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
1260 feedback->setProperty(
"progress", QObject::tr(
"Exporting section %1" ).arg( i + 1 ) );
1270 QString filePath = iterator->
filePath( baseFilePath, u
"svg"_s );
1277 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 ) );
1298 return mLabelingResults;
1303 QMap<QString, QgsLabelingResults *> res;
1304 std::swap( mLabelingResults, res );
1308void QgsLayoutExporter::preparePrintAsPdf(
QgsLayout *layout, QPdfWriter *device,
const QString &filePath )
1310 QFileInfo fi( filePath );
1312 if ( !dir.exists( fi.absolutePath() ) )
1314 dir.mkpath( fi.absolutePath() );
1317 updatePrinterPageSize(
layout, device, firstPageToBeExported(
layout ) );
1320 const QString title = !
layout->project() ||
layout->project()->metadata().title().isEmpty() ?
1321 fi.baseName() :
layout->project()->metadata().title();
1323 device->setTitle( title );
1325 QPagedPaintDevice::PdfVersion pdfVersion = QPagedPaintDevice::PdfVersion_1_4;
1327#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
1329 if (
const QgsProjectStyleSettings *styleSettings = (
layout->project() ?
layout->project()->styleSettings() :
nullptr ) )
1333 switch ( styleSettings->colorModel() )
1336 device->setColorModel( QPdfWriter::ColorModel::CMYK );
1340 device->setColorModel( QPdfWriter::ColorModel::RGB );
1344 const QColorSpace colorSpace = styleSettings->colorSpace();
1345 if ( colorSpace.isValid() )
1347 QPdfOutputIntent outputIntent;
1348 outputIntent.setOutputProfile( colorSpace );
1349 outputIntent.setOutputCondition( colorSpace.description() );
1353 outputIntent.setOutputConditionIdentifier( u
"Unknown identifier"_s );
1354 outputIntent.setRegistryName( u
"Unknown registry"_s );
1355 device->setOutputIntent( outputIntent );
1358 pdfVersion = QPagedPaintDevice::PdfVersion_X4;
1364 device->setPdfVersion( pdfVersion );
1365 setXmpMetadata( device,
layout );
1371#if QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
1378void QgsLayoutExporter::preparePrint(
QgsLayout *layout, QPagedPaintDevice *device,
bool setFirstPageSize )
1380 if ( QPdfWriter *pdf =
dynamic_cast<QPdfWriter *
>( device ) )
1382 pdf->setResolution(
static_cast< int>( std::round(
layout->renderContext().dpi() ) ) );
1384#if defined( HAVE_QTPRINTER )
1385 else if ( QPrinter *printer =
dynamic_cast<QPrinter *
>( device ) )
1387 printer->setFullPage(
true );
1388 printer->setColorMode( QPrinter::Color );
1390 printer->setResolution(
static_cast< int>( std::round(
layout->renderContext().dpi() ) ) );
1394 if ( setFirstPageSize )
1396 updatePrinterPageSize(
layout, device, firstPageToBeExported(
layout ) );
1402 if ( mLayout->pageCollection()->pageCount() == 0 )
1405 preparePrint( mLayout, device,
true );
1407 if ( !p.begin( device ) )
1413 printPrivate( device, p );
1418QgsLayoutExporter::ExportResult QgsLayoutExporter::printPrivate( QPagedPaintDevice *device, QPainter &painter,
bool startNewPage,
double dpi,
bool rasterize )
1422 int toPage = mLayout->pageCollection()->pageCount() - 1;
1424#if defined( HAVE_QTPRINTER )
1425 if ( QPrinter *printer =
dynamic_cast<QPrinter *
>( device ) )
1427 if ( printer->fromPage() >= 1 )
1428 fromPage = printer->fromPage() - 1;
1429 if ( printer->toPage() >= 1 )
1430 toPage = printer->toPage() - 1;
1434 bool pageExported =
false;
1437 for (
int i = fromPage; i <= toPage; ++i )
1439 if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1444 updatePrinterPageSize( mLayout, device, i );
1445 if ( ( pageExported && i > fromPage ) || startNewPage )
1451 if ( !image.isNull() )
1453 QRectF targetArea( 0, 0, image.width(), image.height() );
1454 painter.drawImage( targetArea, image, targetArea );
1460 pageExported =
true;
1465 for (
int i = fromPage; i <= toPage; ++i )
1467 if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1472 updatePrinterPageSize( mLayout, device, i );
1474 if ( ( pageExported && i > fromPage ) || startNewPage )
1479 pageExported =
true;
1485void QgsLayoutExporter::updatePrinterPageSize(
QgsLayout *layout, QPagedPaintDevice *device,
int page )
1487 QgsLayoutSize pageSize =
layout->pageCollection()->page( page )->sizeWithUnits();
1490 QPageLayout pageLayout( QPageSize( pageSizeMM.
toQSizeF(), QPageSize::Millimeter ),
1491 QPageLayout::Portrait,
1492 QMarginsF( 0, 0, 0, 0 ) );
1493 pageLayout.setMode( QPageLayout::FullPageMode );
1494 device->setPageLayout( pageLayout );
1495 device->setPageMargins( QMarginsF( 0, 0, 0, 0 ) );
1497#if defined( HAVE_QTPRINTER )
1498 if ( QPrinter *printer =
dynamic_cast<QPrinter *
>( device ) )
1500 printer->setFullPage(
true );
1505QgsLayoutExporter::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
1509 QSvgGenerator generator;
1510 if ( includeMetadata )
1512 if (
const QgsMasterLayoutInterface *l =
dynamic_cast< const QgsMasterLayoutInterface *
>( mLayout.data() ) )
1513 generator.setTitle( l->name() );
1514 else if ( mLayout->project() )
1515 generator.setTitle( mLayout->project()->title() );
1518 generator.setOutputDevice( &svgBuffer );
1519 generator.setSize( QSize(
static_cast< int >( std::round( width ) ),
1520 static_cast< int >( std::round( height ) ) ) );
1521 generator.setViewBox( QRect( 0, 0,
1522 static_cast< int >( std::round( width ) ),
1523 static_cast< int >( std::round( height ) ) ) );
1524 generator.setResolution(
static_cast< int >( std::round( settings.dpi ) ) );
1526 QPainter svgPainter( &generator );
1527 if ( settings.cropToContents )
1538 svgBuffer.open( QIODevice::ReadOnly );
1542 if ( ! doc.setContent( &svgBuffer,
false, &errorMsg, &errorLine ) )
1544 mErrorFileName = filename;
1547 if ( 1 == svgLayerId )
1549 svg = QDomDocument( doc.doctype() );
1550 svg.appendChild( svg.importNode( doc.firstChild(),
false ) );
1551 svgDocRoot = svg.importNode( doc.elementsByTagName( u
"svg"_s ).at( 0 ),
false );
1552 svgDocRoot.toElement().setAttribute( u
"xmlns:inkscape"_s, u
"http://www.inkscape.org/namespaces/inkscape"_s );
1553 svg.appendChild( svgDocRoot );
1555 QDomNode mainGroup = svg.importNode( doc.elementsByTagName( u
"g"_s ).at( 0 ),
true );
1556 mainGroup.toElement().setAttribute( u
"id"_s, layerName );
1557 mainGroup.toElement().setAttribute( u
"inkscape:label"_s, layerName );
1558 mainGroup.toElement().setAttribute( u
"inkscape:groupmode"_s, u
"layer"_s );
1559 QDomNode defs = svg.importNode( doc.elementsByTagName( u
"defs"_s ).at( 0 ),
true );
1560 svgDocRoot.appendChild( defs );
1561 svgDocRoot.appendChild( mainGroup );
1566void QgsLayoutExporter::appendMetadataToSvg( QDomDocument &svg )
const
1568 const QgsProjectMetadata &metadata = mLayout->project()->metadata();
1569 QDomElement metadataElement = svg.createElement( u
"metadata"_s );
1570 QDomElement rdfElement = svg.createElement( u
"rdf:RDF"_s );
1571 rdfElement.setAttribute( u
"xmlns:rdf"_s, u
"http://www.w3.org/1999/02/22-rdf-syntax-ns#"_s );
1572 rdfElement.setAttribute( u
"xmlns:rdfs"_s, u
"http://www.w3.org/2000/01/rdf-schema#"_s );
1573 rdfElement.setAttribute( u
"xmlns:dc"_s, u
"http://purl.org/dc/elements/1.1/"_s );
1574 QDomElement descriptionElement = svg.createElement( u
"rdf:Description"_s );
1575 QDomElement workElement = svg.createElement( u
"cc:Work"_s );
1576 workElement.setAttribute( u
"rdf:about"_s, QString() );
1578 auto addTextNode = [&workElement, &descriptionElement, &svg](
const QString & tag,
const QString & value )
1581 QDomElement element = svg.createElement( tag );
1582 QDomText t = svg.createTextNode( value );
1583 element.appendChild( t );
1584 workElement.appendChild( element );
1587 descriptionElement.setAttribute( tag, value );
1590 addTextNode( u
"dc:format"_s, u
"image/svg+xml"_s );
1591 addTextNode( u
"dc:title"_s, metadata.
title() );
1592 addTextNode( u
"dc:date"_s, metadata.
creationDateTime().toString( Qt::ISODate ) );
1593 addTextNode( u
"dc:identifier"_s, metadata.
identifier() );
1594 addTextNode( u
"dc:description"_s, metadata.
abstract() );
1596 auto addAgentNode = [&workElement, &descriptionElement, &svg](
const QString & tag,
const QString & value )
1599 QDomElement inkscapeElement = svg.createElement( tag );
1600 QDomElement agentElement = svg.createElement( u
"cc:Agent"_s );
1601 QDomElement titleElement = svg.createElement( u
"dc:title"_s );
1602 QDomText t = svg.createTextNode( value );
1603 titleElement.appendChild( t );
1604 agentElement.appendChild( titleElement );
1605 inkscapeElement.appendChild( agentElement );
1606 workElement.appendChild( inkscapeElement );
1609 QDomElement bagElement = svg.createElement( u
"rdf:Bag"_s );
1610 QDomElement liElement = svg.createElement( u
"rdf:li"_s );
1611 t = svg.createTextNode( value );
1612 liElement.appendChild( t );
1613 bagElement.appendChild( liElement );
1615 QDomElement element = svg.createElement( tag );
1616 element.appendChild( bagElement );
1617 descriptionElement.appendChild( element );
1620 addAgentNode( u
"dc:creator"_s, metadata.
author() );
1621 addAgentNode( u
"dc:publisher"_s, getCreator() );
1625 QDomElement element = svg.createElement( u
"dc:subject"_s );
1626 QDomElement bagElement = svg.createElement( u
"rdf:Bag"_s );
1628 for (
auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
1630 const QStringList words = it.value();
1631 for (
const QString &keyword : words )
1633 QDomElement liElement = svg.createElement( u
"rdf:li"_s );
1634 QDomText t = svg.createTextNode( keyword );
1635 liElement.appendChild( t );
1636 bagElement.appendChild( liElement );
1639 element.appendChild( bagElement );
1640 workElement.appendChild( element );
1641 descriptionElement.appendChild( element );
1644 rdfElement.appendChild( descriptionElement );
1645 rdfElement.appendChild( workElement );
1646 metadataElement.appendChild( rdfElement );
1647 svg.documentElement().appendChild( metadataElement );
1648 svg.documentElement().setAttribute( u
"xmlns:cc"_s, u
"http://creativecommons.org/ns#"_s );
1651std::unique_ptr<double[]> QgsLayoutExporter::computeGeoTransform(
const QgsLayoutItemMap *map,
const QRectF ®ion,
double dpi )
const
1654 map = mLayout->referenceMap();
1660 dpi = mLayout->renderContext().dpi();
1663 QRectF exportRegion = region;
1664 if ( !exportRegion.isValid() )
1666 int pageNumber = map->
page();
1668 QgsLayoutItemPage *page = mLayout->pageCollection()->page( pageNumber );
1669 double pageY = page->pos().y();
1670 QSizeF pageSize = page->rect().size();
1671 exportRegion = QRectF( 0, pageY, pageSize.width(), pageSize.height() );
1675 QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
1678 double outputHeightMM = exportRegion.height();
1679 double outputWidthMM = exportRegion.width();
1682 QgsRectangle mapExtent = map->
extent();
1683 double mapXCenter = mapExtent.
center().
x();
1684 double mapYCenter = mapExtent.
center().
y();
1686 double sinAlpha = std::sin( alpha );
1687 double cosAlpha = std::cos( alpha );
1690 QPointF mapItemPos = map->pos();
1692 mapItemPos.rx() -= exportRegion.left();
1693 mapItemPos.ry() -= exportRegion.top();
1696 double xRatio = mapExtent.
width() / mapItemSceneRect.width();
1697 double yRatio = mapExtent.
height() / mapItemSceneRect.height();
1698 double xmin = mapExtent.
xMinimum() - mapItemPos.x() * xRatio;
1699 double ymax = mapExtent.
yMaximum() + mapItemPos.y() * yRatio;
1700 QgsRectangle paperExtent( xmin, ymax - outputHeightMM * yRatio, xmin + outputWidthMM * xRatio, ymax );
1703 double X0 = paperExtent.xMinimum();
1704 double Y0 = paperExtent.yMaximum();
1709 double X1 = X0 - mapXCenter;
1710 double Y1 = Y0 - mapYCenter;
1711 double X2 = X1 * cosAlpha + Y1 * sinAlpha;
1712 double Y2 = -X1 * sinAlpha + Y1 * cosAlpha;
1713 X0 = X2 + mapXCenter;
1714 Y0 = Y2 + mapYCenter;
1718 int pageWidthPixels =
static_cast< int >( dpi * outputWidthMM / 25.4 );
1719 int pageHeightPixels =
static_cast< int >( dpi * outputHeightMM / 25.4 );
1720 double pixelWidthScale = paperExtent.width() / pageWidthPixels;
1721 double pixelHeightScale = paperExtent.height() / pageHeightPixels;
1724 std::unique_ptr<double[]> t(
new double[6] );
1726 t[1] = cosAlpha * pixelWidthScale;
1727 t[2] = -sinAlpha * pixelWidthScale;
1729 t[4] = -sinAlpha * pixelHeightScale;
1730 t[5] = -cosAlpha * pixelHeightScale;
1735void QgsLayoutExporter::writeWorldFile(
const QString &worldFileName,
double a,
double b,
double c,
double d,
double e,
double f )
const
1737 QFile worldFile( worldFileName );
1738 if ( !worldFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
1742 QTextStream fout( &worldFile );
1746 fout << QString::number( a,
'f', 12 ) <<
"\r\n";
1747 fout << QString::number( d,
'f', 12 ) <<
"\r\n";
1748 fout << QString::number( b,
'f', 12 ) <<
"\r\n";
1749 fout << QString::number( e,
'f', 12 ) <<
"\r\n";
1750 fout << QString::number(
c,
'f', 12 ) <<
"\r\n";
1751 fout << QString::number( f,
'f', 12 ) <<
"\r\n";
1756 return georeferenceOutputPrivate( file, map, exportRegion, dpi,
false );
1759bool QgsLayoutExporter::georeferenceOutputPrivate(
const QString &file,
QgsLayoutItemMap *map,
const QRectF &exportRegion,
double dpi,
bool includeGeoreference,
bool includeMetadata )
const
1764 if ( !map && includeGeoreference )
1765 map = mLayout->referenceMap();
1767 std::unique_ptr<double[]> t;
1769 if ( map && includeGeoreference )
1772 dpi = mLayout->renderContext().dpi();
1774 t = computeGeoTransform( map, exportRegion, dpi );
1779 CPLSetConfigOption(
"GDAL_PDF_DPI", QString::number( dpi ).toUtf8().constData() );
1784 GDALSetGeoTransform( outputDS.get(), t.get() );
1786 if ( includeMetadata )
1788 QString creationDateString;
1789 const QDateTime creationDateTime = mLayout->project()->metadata().creationDateTime();
1790#if QT_FEATURE_timezone > 0
1791 if ( creationDateTime.isValid() )
1793 creationDateString = u
"D:%1"_s.arg( mLayout->project()->metadata().creationDateTime().toString( u
"yyyyMMddHHmmss"_s ) );
1794 if ( creationDateTime.timeZone().isValid() )
1796 int offsetFromUtc = creationDateTime.timeZone().offsetFromUtc( creationDateTime );
1797 creationDateString += ( offsetFromUtc >= 0 ) ?
'+' :
'-';
1798 offsetFromUtc = std::abs( offsetFromUtc );
1799 int offsetHours = offsetFromUtc / 3600;
1800 int offsetMins = ( offsetFromUtc % 3600 ) / 60;
1801 creationDateString += u
"%1'%2'"_s.arg( offsetHours ).arg( offsetMins );
1805 QgsDebugError( u
"Qt is built without timezone support, skipping timezone for pdf export"_s );
1807 GDALSetMetadataItem( outputDS.get(),
"CREATION_DATE", creationDateString.toUtf8().constData(),
nullptr );
1809 GDALSetMetadataItem( outputDS.get(),
"AUTHOR", mLayout->project()->metadata().author().toUtf8().constData(),
nullptr );
1810 const QString creator = getCreator();
1811 GDALSetMetadataItem( outputDS.get(),
"CREATOR", creator.toUtf8().constData(),
nullptr );
1812 GDALSetMetadataItem( outputDS.get(),
"PRODUCER", creator.toUtf8().constData(),
nullptr );
1813 GDALSetMetadataItem( outputDS.get(),
"SUBJECT", mLayout->project()->metadata().abstract().toUtf8().constData(),
nullptr );
1814 GDALSetMetadataItem( outputDS.get(),
"TITLE", mLayout->project()->metadata().title().toUtf8().constData(),
nullptr );
1817 QStringList allKeywords;
1818 for (
auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
1820 allKeywords.append( u
"%1: %2"_s.arg( it.key(), it.value().join(
',' ) ) );
1822 const QString keywordString = allKeywords.join(
';' );
1823 GDALSetMetadataItem( outputDS.get(),
"KEYWORDS", keywordString.toUtf8().constData(),
nullptr );
1829 CPLSetConfigOption(
"GDAL_PDF_DPI",
nullptr );
1836 if ( items.count() == 1 )
1840 QString name = layoutItem->displayName();
1842 if ( name.startsWith(
'<' ) && name.endsWith(
'>' ) )
1843 name = name.mid( 1, name.length() - 2 );
1847 else if ( items.count() > 1 )
1849 QStringList currentLayerItemTypes;
1850 for ( QGraphicsItem *item : items )
1856 if ( !currentLayerItemTypes.contains( itemType ) && !currentLayerItemTypes.contains( itemTypePlural ) )
1857 currentLayerItemTypes << itemType;
1858 else if ( currentLayerItemTypes.contains( itemType ) )
1860 currentLayerItemTypes.replace( currentLayerItemTypes.indexOf( itemType ), itemTypePlural );
1865 if ( !currentLayerItemTypes.contains( QObject::tr(
"Other" ) ) )
1866 currentLayerItemTypes.append( QObject::tr(
"Other" ) );
1869 return currentLayerItemTypes.join(
", "_L1 );
1871 return QObject::tr(
"Layer %1" ).arg( layerId );
1876 const std::function<QString(
QgsLayoutItem *item )> &getItemExportGroupFunc )
1878 LayoutItemHider itemHider( items );
1883 QString previousItemGroup;
1884 unsigned int layerId = 1;
1885 QgsLayoutItem::ExportLayerDetail layerDetails;
1886 itemHider.hideAll();
1887 const QList< QGraphicsItem * > itemsToIterate = itemHider.itemsToIterate();
1888 QList< QGraphicsItem * > currentLayerItems;
1889 for ( QGraphicsItem *item : itemsToIterate )
1891 QgsLayoutItem *layoutItem =
dynamic_cast<QgsLayoutItem *
>( item );
1893 bool canPlaceInExistingLayer =
false;
1894 QString thisItemExportGroupName;
1898 thisItemExportGroupName = getItemExportGroupFunc( layoutItem );
1899 if ( !thisItemExportGroupName.isEmpty() )
1901 if ( thisItemExportGroupName != previousItemGroup && !currentLayerItems.empty() )
1904 layerDetails.
groupName = thisItemExportGroupName;
1907 switch ( itemExportBehavior )
1911 switch ( prevItemBehavior )
1914 canPlaceInExistingLayer =
true;
1918 canPlaceInExistingLayer = prevType == -1 || prevType == layoutItem->
type();
1923 canPlaceInExistingLayer =
false;
1931 switch ( prevItemBehavior )
1935 canPlaceInExistingLayer = prevType == -1 || prevType == layoutItem->
type();
1940 canPlaceInExistingLayer =
false;
1948 canPlaceInExistingLayer =
false;
1953 canPlaceInExistingLayer =
false;
1956 prevItemBehavior = itemExportBehavior;
1957 prevType = layoutItem->
type();
1958 previousItemGroup = thisItemExportGroupName;
1963 previousItemGroup.clear();
1966 if ( canPlaceInExistingLayer )
1968 currentLayerItems << item;
1973 if ( !currentLayerItems.isEmpty() )
1977 ExportResult result = exportFunc( layerId, layerDetails );
1981 currentLayerItems.clear();
1984 itemHider.hideAll();
1989 int layoutItemLayerIdx = 0;
1991 mLayout->renderContext().setCurrentExportLayer( layoutItemLayerIdx );
1997 mLayout->renderContext().setCurrentExportLayer( layoutItemLayerIdx );
2001 ExportResult result = exportFunc( layerId, layerDetails );
2006 layoutItemLayerIdx++;
2008 layerDetails.mapLayerId.clear();
2010 mLayout->renderContext().setCurrentExportLayer( -1 );
2013 currentLayerItems.clear();
2017 currentLayerItems << item;
2019 layerDetails.groupName = thisItemExportGroupName;
2022 if ( !currentLayerItems.isEmpty() )
2025 ExportResult result = exportFunc( layerId, layerDetails );
2034 QgsVectorSimplifyMethod simplifyMethod;
2040 return simplifyMethod;
2045 QgsMaskRenderSettings settings;
2063 int pageNumber = map->
page();
2065 double pageY = page->pos().y();
2066 QSizeF pageSize = page->rect().size();
2067 QRectF pageRect( 0, pageY, pageSize.width(), pageSize.height() );
2083 double destinationHeight = exportRegion.height();
2084 double destinationWidth = exportRegion.width();
2086 QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
2091 double xRatio = mapExtent.
width() / mapItemSceneRect.width();
2092 double yRatio = mapExtent.
height() / mapItemSceneRect.height();
2094 double xCenter = mapExtent.
center().
x();
2095 double yCenter = mapExtent.
center().
y();
2098 QPointF mapItemPos = map->pos();
2100 mapItemPos.rx() -= exportRegion.left();
2101 mapItemPos.ry() -= exportRegion.top();
2103 double xmin = mapExtent.
xMinimum() - mapItemPos.x() * xRatio;
2104 double ymax = mapExtent.
yMaximum() + mapItemPos.y() * yRatio;
2105 QgsRectangle paperExtent( xmin, ymax - destinationHeight * yRatio, xmin + destinationWidth * xRatio, ymax );
2107 double X0 = paperExtent.
xMinimum();
2108 double Y0 = paperExtent.
yMinimum();
2111 dpi = mLayout->renderContext().dpi();
2113 int widthPx =
static_cast< int >( dpi * destinationWidth / 25.4 );
2114 int heightPx =
static_cast< int >( dpi * destinationHeight / 25.4 );
2116 double Ww = paperExtent.
width() / widthPx;
2117 double Hh = paperExtent.
height() / heightPx;
2126 s[5] = Y0 + paperExtent.
height();
2130 r[0] = std::cos( alpha );
2131 r[1] = -std::sin( alpha );
2132 r[2] = xCenter * ( 1 - std::cos( alpha ) ) + yCenter * std::sin( alpha );
2133 r[3] = std::sin( alpha );
2134 r[4] = std::cos( alpha );
2135 r[5] = - xCenter * std::sin( alpha ) + yCenter * ( 1 - std::cos( alpha ) );
2138 a = r[0] * s[0] + r[1] * s[3];
2139 b = r[0] * s[1] + r[1] * s[4];
2140 c = r[0] * s[2] + r[1] * s[5] + r[2];
2141 d = r[3] * s[0] + r[4] * s[3];
2142 e = r[3] * s[1] + r[4] * s[4];
2143 f = r[3] * s[2] + r[4] * s[5] + r[5];
2151 QList< QgsLayoutItem *> items;
2152 layout->layoutItems( items );
2157 if ( currentItem->isVisible() && currentItem->requiresRasterization() )
2168 QList< QgsLayoutItem *> items;
2169 layout->layoutItems( items );
2174 if ( currentItem->isVisible() && currentItem->containsAdvancedEffects() )
2187 if ( mLayout->pageCollection()->pageCount() == 1 )
2190 bounds = mLayout->layoutBounds(
true );
2195 bounds = mLayout->pageItemBounds( page,
true );
2197 if ( bounds.width() <= 0 || bounds.height() <= 0 )
2205 bounds = bounds.adjusted( -settings.
cropMargins.
left() * pixelToLayoutUnits,
2217int QgsLayoutExporter::firstPageToBeExported(
QgsLayout *layout )
2219 const int pageCount =
layout->pageCollection()->pageCount();
2220 for (
int i = 0; i < pageCount; ++i )
2222 if ( !
layout->pageCollection()->shouldExportPage( i ) )
2234 if ( details.
page == 0 )
2244void QgsLayoutExporter::captureLabelingResults()
2246 qDeleteAll( mLabelingResults );
2247 mLabelingResults.clear();
2249 QList< QgsLayoutItemMap * > maps;
2250 mLayout->layoutItems( maps );
2254 mLabelingResults[ map->
uuid() ] = map->mExportLabelingResults.release();
2258bool QgsLayoutExporter::saveImage(
const QImage &image,
const QString &imageFilename,
const QString &imageFormat,
QgsProject *projectForMetadata,
int quality )
2260 QImageWriter w( imageFilename, imageFormat.toLocal8Bit().constData() );
2261 if ( imageFormat.compare(
"tiff"_L1, Qt::CaseInsensitive ) == 0 || imageFormat.compare(
"tif"_L1, Qt::CaseInsensitive ) == 0 )
2263 w.setCompression( 1 );
2267 w.setQuality( quality );
2269 if ( projectForMetadata )
2271 w.setText( u
"Author"_s, projectForMetadata->
metadata().
author() );
2272 const QString creator = getCreator();
2273 w.setText( u
"Creator"_s, creator );
2274 w.setText( u
"Producer"_s, creator );
2277 w.setText( u
"Title"_s, projectForMetadata->
metadata().
title() );
2280 QStringList allKeywords;
2281 for (
auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
2283 allKeywords.append( u
"%1: %2"_s.arg( it.key(), it.value().join(
',' ) ) );
2285 const QString keywordString = allKeywords.join(
';' );
2286 w.setText( u
"Keywords"_s, keywordString );
2288 return w.write( image );
2291QString QgsLayoutExporter::getCreator()
2296void QgsLayoutExporter::setXmpMetadata( QPdfWriter *pdfWriter,
QgsLayout *layout )
2298#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
2299 QUuid documentId = pdfWriter->documentId();
2301 QUuid documentId = QUuid::createUuid();
2305 const QDateTime creationDateTime =
layout->project() ?
layout->project()->metadata().creationDateTime() : QDateTime();
2306 const QString metaDataDate = creationDateTime.isValid() ? creationDateTime.toOffsetFromUtc( creationDateTime.offsetFromUtc() ).toString( Qt::ISODate ) : QString();
2307 const QString title = pdfWriter->title();
2308 const QString creator = getCreator();
2309 const QString producer = creator;
2310 const QString author =
layout->project() ?
layout->project()->metadata().author() : QString();
2314 const QLatin1String xmlNS(
"http://www.w3.org/XML/1998/namespace" );
2315 const QLatin1String adobeNS(
"adobe:ns:meta/" );
2316 const QLatin1String rdfNS(
"http://www.w3.org/1999/02/22-rdf-syntax-ns#" );
2317 const QLatin1String dcNS(
"http://purl.org/dc/elements/1.1/" );
2318 const QLatin1String xmpNS(
"http://ns.adobe.com/xap/1.0/" );
2319 const QLatin1String xmpMMNS(
"http://ns.adobe.com/xap/1.0/mm/" );
2320 const QLatin1String pdfNS(
"http://ns.adobe.com/pdf/1.3/" );
2321 const QLatin1String pdfaidNS(
"http://www.aiim.org/pdfa/ns/id/" );
2323 QByteArray xmpMetadata;
2324 QBuffer output( &xmpMetadata );
2325 output.open( QIODevice::WriteOnly );
2326 output.write(
"<?xpacket begin='' ?>" );
2328 QXmlStreamWriter w( &output );
2329 w.setAutoFormatting(
true );
2330 w.writeNamespace( adobeNS,
"x" );
2331 w.writeNamespace( rdfNS,
"rdf" );
2332 w.writeNamespace( dcNS,
"dc" );
2333 w.writeNamespace( xmpNS,
"xmp" );
2334 w.writeNamespace( xmpMMNS,
"xmpMM" );
2335 w.writeNamespace( pdfNS,
"pdf" );
2336 w.writeNamespace( pdfaidNS,
"pdfaid" );
2338 w.writeStartElement( adobeNS,
"xmpmeta" );
2339 w.writeStartElement( rdfNS,
"RDF" );
2342 w.writeStartElement( rdfNS,
"Description" );
2343 w.writeAttribute( rdfNS,
"about",
"" );
2344 w.writeStartElement( dcNS,
"title" );
2345 w.writeStartElement( rdfNS,
"Alt" );
2346 w.writeStartElement( rdfNS,
"li" );
2347 w.writeAttribute( xmlNS,
"lang",
"x-default" );
2348 w.writeCharacters( title );
2349 w.writeEndElement();
2350 w.writeEndElement();
2351 w.writeEndElement();
2353 w.writeStartElement( dcNS,
"creator" );
2354 w.writeStartElement( rdfNS,
"Seq" );
2355 w.writeStartElement( rdfNS,
"li" );
2356 w.writeCharacters( author );
2357 w.writeEndElement();
2358 w.writeEndElement();
2359 w.writeEndElement();
2361 w.writeEndElement();
2364 w.writeStartElement( rdfNS,
"Description" );
2365 w.writeAttribute( rdfNS,
"about",
"" );
2366 w.writeAttribute( pdfNS,
"Producer", producer );
2367 w.writeAttribute( pdfNS,
"Trapped",
"False" );
2368 w.writeEndElement();
2371 w.writeStartElement( rdfNS,
"Description" );
2372 w.writeAttribute( rdfNS,
"about",
"" );
2373 w.writeAttribute( xmpNS,
"CreatorTool", creator );
2374 w.writeAttribute( xmpNS,
"CreateDate", metaDataDate );
2375 w.writeAttribute( xmpNS,
"ModifyDate", metaDataDate );
2376 w.writeAttribute( xmpNS,
"MetadataDate", metaDataDate );
2377 w.writeEndElement();
2380 w.writeStartElement( rdfNS,
"Description" );
2381 w.writeAttribute( rdfNS,
"about",
"" );
2382 w.writeAttribute( xmpMMNS,
"DocumentID",
"uuid:" + documentId.toString( QUuid::WithoutBraces ) );
2383 w.writeAttribute( xmpMMNS,
"VersionID",
"1" );
2384 w.writeAttribute( xmpMMNS,
"RenditionClass",
"default" );
2385 w.writeEndElement();
2387#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
2390 switch ( pdfWriter->pdfVersion() )
2392 case QPagedPaintDevice::PdfVersion_1_4:
2393 case QPagedPaintDevice::PdfVersion_A1b:
2394 case QPagedPaintDevice::PdfVersion_1_6:
2396 case QPagedPaintDevice::PdfVersion_X4:
2397 const QLatin1String pdfxidNS(
"http://www.npes.org/pdfx/ns/id/" );
2398 w.writeNamespace( pdfxidNS,
"pdfxid" );
2399 w.writeStartElement( rdfNS,
"Description" );
2400 w.writeAttribute( rdfNS,
"about",
"" );
2401 w.writeAttribute( pdfxidNS,
"GTS_PDFXVersion",
"PDF/X-4" );
2402 w.writeEndElement();
2408 w.writeEndElement();
2409 w.writeEndElement();
2411 w.writeEndDocument();
2412 output.write(
"<?xpacket end='w'?>" );
2414 pdfWriter->setDocumentXmpMetadata( xmpMetadata );
RasterizedRenderingPolicy
Policies controlling when rasterisation of content during renders is permitted.
@ Default
Allow raster-based rendering in situations where it is required for correct rendering or where it wil...
@ PreferVector
Prefer vector-based rendering, when the result will still be visually near-identical to a raster-base...
@ ForceVector
Always force vector-based rendering, even when the result will be visually different to a raster-base...
static QString version()
Version string.
@ 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.
QFlags< LayoutRenderFlag > LayoutRenderFlags
Flags for controlling how a layout is rendered.
TextRenderFormat
Options for rendering text.
@ 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.
@ SynchronousLegendGraphics
Query legend graphics synchronously.
@ LosslessImageRendering
Render images losslessly whenever possible, instead of the default lossy jpeg rendering used for some...
@ Antialiasing
Use antialiasing when drawing items.
@ RenderLabelsByMapLayer
When rendering map items to multi-layered exports, render labels belonging to different layers into s...
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.
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.
bool isCanceled() const
Tells whether the operation has been canceled already.
void setProgress(double progress)
Sets the current progress for the feedback object.
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.
virtual ~QgsLayoutExporter()
QImage renderRegionToImage(const QRectF ®ion, 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.
static const QgsSettingsEntryInteger * settingImageQuality
Settings entry - Image quality for lossy formats.
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 ®ion) const
Renders a region from the layout to a painter.
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.
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.
Provides a method of storing measurements for use in QGIS layouts using a variety of different measur...
Provides a method of storing points, consisting of an x and y coordinate, for use in QGIS layouts.
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.
Qgis::LayoutRenderFlags flags() const
Returns the current combination of flags used for rendering the layout.
void setRasterizedRenderingPolicy(Qgis::RasterizedRenderingPolicy policy)
Sets the policy controlling when rasterization of content during renders is permitted.
double dpi() const
Returns the dpi for outputting the layout.
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(Qgis::LayoutRenderFlags flags)
Sets the combination of flags that will be used for rendering the layout.
Provides a method of storing sizes, consisting of a width and height, for use in QGIS layouts.
QSizeF toQSizeF() const
Converts the layout size to a QSizeF.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
QgsLayoutRenderContext & renderContext()
Returns a reference to the layout's render context, which stores information relating to the current ...
Line string geometry type, with support for z-dimension and m-values.
Base class for all map layer types.
double top() const
Returns the top margin.
double right() const
Returns the right margin.
double bottom() const
Returns the bottom margin.
double left() const
Returns the left margin.
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...
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
Adds a message to the log instance (and creates it if necessary).
static void fixEngineFlags(QPaintEngine *engine)
void setExteriorRing(QgsCurve *ring) override
Sets the exterior ring of the polygon.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
QgsProjectMetadata metadata
A rectangle specified with double values.
A boolean settings entry.
An integer settings entry.
static QgsSettingsTreeNode * sTreeLayout
Contains settings for simplifying 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
#define Q_NOWARN_DEPRECATED_PUSH
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
QString nameForLayerWithItems(const QList< QGraphicsItem * > &items, unsigned int layerId)
#define QgsDebugError(str)
Contains details of a particular input component to be used during PDF composition.
QString mapLayerId
Associated map layer ID, or an empty string if this component layer is not associated with a map laye...
QString group
Optional group name, for arranging layers in top-level groups.
double opacity
Component opacity.
QString name
User-friendly name for the generated PDF layer.
QPainter::CompositionMode compositionMode
Component composition mode.
QString sourcePdfPath
File path to the (already created) PDF to use as the source for this component layer.
Contains details of a control point used during georeferencing Geospatial PDF outputs.
QMap< QString, QString > customLayerTreeGroups
Optional map of map layer ID to custom logical layer tree group in created PDF file.
QMap< QString, bool > initialLayerVisibility
Optional map of map layer ID to initial visibility state.
QList< QgsAbstractGeospatialPdfExporter::GeoReferencedSection > georeferencedSections
List of georeferenced sections.
QStringList layerOrder
Optional list of layer IDs, in the order desired to appear in the generated Geospatial PDF file.
QMap< QString, QString > layerIdToPdfLayerTreeNameMap
Optional map of map layer ID to custom layer tree name to show in the created PDF file.
QgsAbstractMetadataBase::KeywordMap keywords
Metadata keyword map.
QString creator
Metadata creator tag.
QSizeF pageSizeMm
Page size, in millimeters.
QString author
Metadata author tag.
QString subject
Metadata subject tag.
QString title
Metadata title tag.
QStringList layerTreeGroupOrder
Specifies the ordering of layer tree groups in the generated Geospatial PDF file.
QDateTime creationDateTime
Metadata creation datetime.
bool includeFeatures
true if feature vector information (such as attributes) should be exported.
QString producer
Metadata producer tag.
bool useIso32000ExtensionFormatGeoreferencing
true if ISO32000 extension format georeferencing should be used.
QSet< QString > mutuallyExclusiveGroups
Contains a list of group names which should be considered as mutually exclusive.
QgsCoordinateReferenceSystem crs
Coordinate reference system for georeferenced section.
QList< QgsAbstractGeospatialPdfExporter::ControlPoint > controlPoints
List of control points corresponding to this georeferenced section.
QgsPolygon pageBoundsPolygon
Bounds of the georeferenced section on the page, in millimeters, as a free-form polygon.
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...
int quality
Image quality, typically used for JPEG compression (whose quality ranges from 1 to 100) if quality is...
Qgis::LayoutRenderFlags 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 '.').
QString directory
Target folder.
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 Geospatial PDF 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.
Qgis::LayoutRenderFlags flags
Layout context flags, which control how the export will be created.
bool writeGeoPdf
true if geospatial PDF 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.
Qgis::LayoutRenderFlags flags
Layout context flags, which control how the export will be created.
double dpi
Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.
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::LayoutRenderFlags flags
Layout context flags, which control how the export will be created.
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.
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.