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 );
602 mLayout->renderContext().setExportThemes( settings.
exportThemes );
614 const QList<QGraphicsItem *> items = mLayout->items( Qt::AscendingOrder );
616 QList< QgsLayoutGeospatialPdfExporter::ComponentLayerDetail > pdfComponents;
621 QSet<QString> mutuallyExclusiveGroups;
627 component.
name = layerDetail.name;
628 component.
mapLayerId = layerDetail.mapLayerId;
629 component.
opacity = layerDetail.opacity;
631 component.
group = layerDetail.groupName;
632 if ( !layerDetail.mapTheme.isEmpty() )
634 component.
group = layerDetail.mapTheme;
635 mutuallyExclusiveGroups.insert( layerDetail.mapTheme );
638 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' ) ) );
639 pdfComponents << component;
641 preparePrintAsPdf( mLayout, &printer, component.
sourcePdfPath );
642 preparePrint( mLayout, &printer,
false );
644 if ( !p.begin( &printer ) )
652 return layerExportResult;
654 auto getExportGroupNameFunc = [](
QgsLayoutItem * item )->QString
656 return item->customProperty( u
"pdfExportGroup"_s ).toString();
658 result = handleLayeredExport( items, exportFunc, getExportGroupNameFunc );
662 if ( settings.writeGeoPdf )
665 details.
dpi = settings.dpi;
667 QgsLayoutSize pageSize = mLayout->pageCollection()->page( 0 )->sizeWithUnits();
672 if ( settings.exportMetadata )
675 details.
author = mLayout->project()->metadata().author();
677 details.
creator = getCreator();
678 details.
creationDateTime = mLayout->project()->metadata().creationDateTime();
679 details.
subject = mLayout->project()->metadata().abstract();
680 details.
title = mLayout->project()->metadata().title();
681 details.
keywords = mLayout->project()->metadata().keywords();
684 const QList< QgsMapLayer * > layers = mLayout->project()->mapLayers().values();
690 if ( settings.appendGeoreference )
693 QList< QgsLayoutItemMap * > maps;
694 mLayout->layoutItems( maps );
698 georef.
crs = map->crs();
700 const QPointF topLeft = map->mapToScene( QPointF( 0, 0 ) );
701 const QPointF topRight = map->mapToScene( QPointF( map->rect().width(), 0 ) );
702 const QPointF bottomLeft = map->mapToScene( QPointF( 0, map->rect().height() ) );
703 const QPointF bottomRight = map->mapToScene( QPointF( map->rect().width(), map->rect().height() ) );
716 const QTransform t = map->layoutToMapCoordsTransform();
717 const QgsPointXY topLeftMap = t.map( topLeft );
718 const QgsPointXY topRightMap = t.map( topRight );
719 const QgsPointXY bottomLeftMap = t.map( bottomLeft );
720 const QgsPointXY bottomRightMap = t.map( bottomRight );
732 details.
layerOrder = geospatialPdfExporter->layerOrder();
737 if ( !geospatialPdfExporter->finalize( pdfComponents, filePath, details ) )
740 mErrorMessage = geospatialPdfExporter->errorMessage();
750 QPdfWriter printer = QPdfWriter( filePath );
751 preparePrintAsPdf( mLayout, &printer, filePath );
752 preparePrint( mLayout, &printer,
false );
754 if ( !p.begin( &printer ) )
763 bool shouldAppendGeoreference = settings.
appendGeoreference && mLayout && mLayout->referenceMap() && mLayout->referenceMap()->page() == 0;
766 georeferenceOutputPrivate( filePath,
nullptr, QRectF(), settings.
dpi, shouldAppendGeoreference, settings.
exportMetadata );
769 captureLabelingResults();
782 QPdfWriter printer = QPdfWriter( fileName );
785 int total = iterator->
count();
786 double step = total > 0 ? 100.0 / total : 100.0;
789 while ( iterator->
next() )
794 feedback->setProperty(
"progress", QObject::tr(
"Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
796 feedback->setProperty(
"progress", QObject::tr(
"Exporting section %1" ).arg( i + 1 ) );
808 LayoutContextPreviewSettingRestorer restorer( iterator->
layout() );
810 LayoutContextSettingsRestorer contextRestorer( iterator->
layout() );
811 ( void )contextRestorer;
838 preparePrintAsPdf( iterator->
layout(), &printer, fileName );
839 preparePrint( iterator->
layout(), &printer,
false );
841 if ( !p.begin( &printer ) )
855 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 ) );
882 int total = iterator->
count();
883 double step = total > 0 ? 100.0 / total : 100.0;
885 while ( iterator->
next() )
890 feedback->setProperty(
"progress", QObject::tr(
"Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
892 feedback->setProperty(
"progress", QObject::tr(
"Exporting section %1" ).arg( i + 1 ) );
901 QString filePath = iterator->
filePath( baseFilePath, u
"pdf"_s );
908 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 ) );
926#if defined( HAVE_QTPRINTER )
933 if ( settings.
dpi <= 0 )
934 settings.
dpi = mLayout->renderContext().dpi();
936 mErrorFileName.clear();
938 LayoutContextPreviewSettingRestorer restorer( mLayout );
940 LayoutContextSettingsRestorer contextRestorer( mLayout );
941 ( void )contextRestorer;
942 mLayout->renderContext().setDpi( settings.
dpi );
944 mLayout->renderContext().setFlags( settings.
flags );
951 preparePrint( mLayout, &printer,
true );
953 if ( !p.begin( &printer ) )
962 captureLabelingResults();
977 int total = iterator->
count();
978 double step = total > 0 ? 100.0 / total : 100.0;
981 while ( iterator->
next() )
986 feedback->setProperty(
"progress", QObject::tr(
"Printing %1 of %2" ).arg( i + 1 ).arg( total ) );
988 feedback->setProperty(
"progress", QObject::tr(
"Printing section %1" ).arg( i + 1 ) );
1000 LayoutContextPreviewSettingRestorer restorer( iterator->
layout() );
1002 LayoutContextSettingsRestorer contextRestorer( iterator->
layout() );
1003 ( void )contextRestorer;
1012 if ( !settings.rasterizeWholeImage )
1017 preparePrint( iterator->
layout(), &printer,
true );
1019 if ( !p.begin( &printer ) )
1029 ExportResult result = exporter.printPrivate( &printer, p, !first, settings.dpi, settings.rasterizeWholeImage );
1033 error = exporter.errorMessage();
1056 if ( settings.
dpi <= 0 )
1057 settings.
dpi = mLayout->renderContext().dpi();
1059 mErrorFileName.clear();
1061 LayoutContextPreviewSettingRestorer restorer( mLayout );
1063 LayoutContextSettingsRestorer contextRestorer( mLayout );
1064 ( void )contextRestorer;
1065 mLayout->renderContext().setDpi( settings.
dpi );
1067 mLayout->renderContext().setFlags( settings.
flags );
1079 mLayout->renderContext().setMaskSettings( createExportMaskSettings() );
1083 mLayout->renderContext().setSimplifyMethod( createExportSimplifyMethod() );
1086 QFileInfo fi( filePath );
1089 pageDetails.
baseName = fi.baseName();
1090 pageDetails.
extension = fi.completeSuffix();
1094 for (
int i = 0; i < mLayout->pageCollection()->pageCount(); ++i )
1096 if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1101 pageDetails.
page = i;
1108 if ( mLayout->pageCollection()->pageCount() == 1 )
1111 bounds = mLayout->layoutBounds(
true );
1116 bounds = mLayout->pageItemBounds( i,
true );
1125 bounds = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() );
1129 int width =
static_cast< int >( bounds.width() * settings.
dpi / inchesToLayoutUnits );
1131 int height =
static_cast< int >( bounds.height() * settings.
dpi / inchesToLayoutUnits );
1132 if ( width == 0 || height == 0 )
1141 const QRectF paperRect = QRectF( pageItem->pos().x(),
1142 pageItem->pos().y(),
1143 pageItem->rect().width(),
1144 pageItem->rect().height() );
1146 QDomNode svgDocRoot;
1147 const QList<QGraphicsItem *> items = mLayout->items( paperRect,
1148 Qt::IntersectsItemBoundingRect,
1149 Qt::AscendingOrder );
1153 return renderToLayeredSvg( settings, width, height, i, bounds, fileName, layerId, layerDetail.name, svg, svgDocRoot, settings.
exportMetadata );
1155 auto getExportGroupNameFunc = [](
QgsLayoutItem * )->QString
1159 ExportResult res = handleLayeredExport( items, exportFunc, getExportGroupNameFunc );
1164 appendMetadataToSvg( svg );
1166 QFile out( fileName );
1167 bool openOk = out.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate );
1170 mErrorFileName = fileName;
1174 out.write( svg.toByteArray() );
1180 QSvgGenerator generator;
1183 generator.setTitle( mLayout->project()->metadata().title() );
1184 generator.setDescription( mLayout->project()->metadata().abstract() );
1186 generator.setOutputDevice( &svgBuffer );
1187 generator.setSize( QSize( width, height ) );
1188 generator.setViewBox( QRect( 0, 0, width, height ) );
1189 generator.setResolution(
static_cast< int >( std::round( settings.
dpi ) ) );
1192 bool createOk = p.begin( &generator );
1195 mErrorFileName = fileName;
1208 svgBuffer.open( QIODevice::ReadOnly );
1212 if ( ! svg.setContent( &svgBuffer,
false, &errorMsg, &errorLine ) )
1214 mErrorFileName = fileName;
1219 appendMetadataToSvg( svg );
1221 QFile out( fileName );
1222 bool openOk = out.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate );
1225 mErrorFileName = fileName;
1229 out.write( svg.toByteArray() );
1233 captureLabelingResults();
1244 int total = iterator->
count();
1245 double step = total > 0 ? 100.0 / total : 100.0;
1247 while ( iterator->
next() )
1252 feedback->setProperty(
"progress", QObject::tr(
"Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
1254 feedback->setProperty(
"progress", QObject::tr(
"Exporting section %1" ).arg( i + 1 ) );
1264 QString filePath = iterator->
filePath( baseFilePath, u
"svg"_s );
1271 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 ) );
1292 return mLabelingResults;
1297 QMap<QString, QgsLabelingResults *> res;
1298 std::swap( mLabelingResults, res );
1302void QgsLayoutExporter::preparePrintAsPdf(
QgsLayout *layout, QPdfWriter *device,
const QString &filePath )
1304 QFileInfo fi( filePath );
1306 if ( !dir.exists( fi.absolutePath() ) )
1308 dir.mkpath( fi.absolutePath() );
1311 updatePrinterPageSize(
layout, device, firstPageToBeExported(
layout ) );
1314 const QString title = !
layout->project() ||
layout->project()->metadata().title().isEmpty() ?
1315 fi.baseName() :
layout->project()->metadata().title();
1317 device->setTitle( title );
1319 QPagedPaintDevice::PdfVersion pdfVersion = QPagedPaintDevice::PdfVersion_1_4;
1321#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
1323 if (
const QgsProjectStyleSettings *styleSettings = (
layout->project() ?
layout->project()->styleSettings() :
nullptr ) )
1327 switch ( styleSettings->colorModel() )
1330 device->setColorModel( QPdfWriter::ColorModel::CMYK );
1334 device->setColorModel( QPdfWriter::ColorModel::RGB );
1338 const QColorSpace colorSpace = styleSettings->colorSpace();
1339 if ( colorSpace.isValid() )
1341 QPdfOutputIntent outputIntent;
1342 outputIntent.setOutputProfile( colorSpace );
1343 outputIntent.setOutputCondition( colorSpace.description() );
1347 outputIntent.setOutputConditionIdentifier( u
"Unknown identifier"_s );
1348 outputIntent.setRegistryName( u
"Unknown registry"_s );
1349 device->setOutputIntent( outputIntent );
1352 pdfVersion = QPagedPaintDevice::PdfVersion_X4;
1358 device->setPdfVersion( pdfVersion );
1359 setXmpMetadata( device,
layout );
1365#if QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
1372void QgsLayoutExporter::preparePrint(
QgsLayout *layout, QPagedPaintDevice *device,
bool setFirstPageSize )
1374 if ( QPdfWriter *pdf =
dynamic_cast<QPdfWriter *
>( device ) )
1376 pdf->setResolution(
static_cast< int>( std::round(
layout->renderContext().dpi() ) ) );
1378#if defined( HAVE_QTPRINTER )
1379 else if ( QPrinter *printer =
dynamic_cast<QPrinter *
>( device ) )
1381 printer->setFullPage(
true );
1382 printer->setColorMode( QPrinter::Color );
1384 printer->setResolution(
static_cast< int>( std::round(
layout->renderContext().dpi() ) ) );
1388 if ( setFirstPageSize )
1390 updatePrinterPageSize(
layout, device, firstPageToBeExported(
layout ) );
1396 if ( mLayout->pageCollection()->pageCount() == 0 )
1399 preparePrint( mLayout, device,
true );
1401 if ( !p.begin( device ) )
1407 printPrivate( device, p );
1412QgsLayoutExporter::ExportResult QgsLayoutExporter::printPrivate( QPagedPaintDevice *device, QPainter &painter,
bool startNewPage,
double dpi,
bool rasterize )
1416 int toPage = mLayout->pageCollection()->pageCount() - 1;
1418#if defined( HAVE_QTPRINTER )
1419 if ( QPrinter *printer =
dynamic_cast<QPrinter *
>( device ) )
1421 if ( printer->fromPage() >= 1 )
1422 fromPage = printer->fromPage() - 1;
1423 if ( printer->toPage() >= 1 )
1424 toPage = printer->toPage() - 1;
1428 bool pageExported =
false;
1431 for (
int i = fromPage; i <= toPage; ++i )
1433 if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1438 updatePrinterPageSize( mLayout, device, i );
1439 if ( ( pageExported && i > fromPage ) || startNewPage )
1445 if ( !image.isNull() )
1447 QRectF targetArea( 0, 0, image.width(), image.height() );
1448 painter.drawImage( targetArea, image, targetArea );
1454 pageExported =
true;
1459 for (
int i = fromPage; i <= toPage; ++i )
1461 if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1466 updatePrinterPageSize( mLayout, device, i );
1468 if ( ( pageExported && i > fromPage ) || startNewPage )
1473 pageExported =
true;
1479void QgsLayoutExporter::updatePrinterPageSize(
QgsLayout *layout, QPagedPaintDevice *device,
int page )
1481 QgsLayoutSize pageSize =
layout->pageCollection()->page( page )->sizeWithUnits();
1484 QPageLayout pageLayout( QPageSize( pageSizeMM.
toQSizeF(), QPageSize::Millimeter ),
1485 QPageLayout::Portrait,
1486 QMarginsF( 0, 0, 0, 0 ) );
1487 pageLayout.setMode( QPageLayout::FullPageMode );
1488 device->setPageLayout( pageLayout );
1489 device->setPageMargins( QMarginsF( 0, 0, 0, 0 ) );
1491#if defined( HAVE_QTPRINTER )
1492 if ( QPrinter *printer =
dynamic_cast<QPrinter *
>( device ) )
1494 printer->setFullPage(
true );
1499QgsLayoutExporter::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
1503 QSvgGenerator generator;
1504 if ( includeMetadata )
1506 if (
const QgsMasterLayoutInterface *l =
dynamic_cast< const QgsMasterLayoutInterface *
>( mLayout.data() ) )
1507 generator.setTitle( l->name() );
1508 else if ( mLayout->project() )
1509 generator.setTitle( mLayout->project()->title() );
1512 generator.setOutputDevice( &svgBuffer );
1513 generator.setSize( QSize(
static_cast< int >( std::round( width ) ),
1514 static_cast< int >( std::round( height ) ) ) );
1515 generator.setViewBox( QRect( 0, 0,
1516 static_cast< int >( std::round( width ) ),
1517 static_cast< int >( std::round( height ) ) ) );
1518 generator.setResolution(
static_cast< int >( std::round( settings.dpi ) ) );
1520 QPainter svgPainter( &generator );
1521 if ( settings.cropToContents )
1532 svgBuffer.open( QIODevice::ReadOnly );
1536 if ( ! doc.setContent( &svgBuffer,
false, &errorMsg, &errorLine ) )
1538 mErrorFileName = filename;
1541 if ( 1 == svgLayerId )
1543 svg = QDomDocument( doc.doctype() );
1544 svg.appendChild( svg.importNode( doc.firstChild(),
false ) );
1545 svgDocRoot = svg.importNode( doc.elementsByTagName( u
"svg"_s ).at( 0 ),
false );
1546 svgDocRoot.toElement().setAttribute( u
"xmlns:inkscape"_s, u
"http://www.inkscape.org/namespaces/inkscape"_s );
1547 svg.appendChild( svgDocRoot );
1549 QDomNode mainGroup = svg.importNode( doc.elementsByTagName( u
"g"_s ).at( 0 ),
true );
1550 mainGroup.toElement().setAttribute( u
"id"_s, layerName );
1551 mainGroup.toElement().setAttribute( u
"inkscape:label"_s, layerName );
1552 mainGroup.toElement().setAttribute( u
"inkscape:groupmode"_s, u
"layer"_s );
1553 QDomNode defs = svg.importNode( doc.elementsByTagName( u
"defs"_s ).at( 0 ),
true );
1554 svgDocRoot.appendChild( defs );
1555 svgDocRoot.appendChild( mainGroup );
1560void QgsLayoutExporter::appendMetadataToSvg( QDomDocument &svg )
const
1562 const QgsProjectMetadata &metadata = mLayout->project()->metadata();
1563 QDomElement metadataElement = svg.createElement( u
"metadata"_s );
1564 QDomElement rdfElement = svg.createElement( u
"rdf:RDF"_s );
1565 rdfElement.setAttribute( u
"xmlns:rdf"_s, u
"http://www.w3.org/1999/02/22-rdf-syntax-ns#"_s );
1566 rdfElement.setAttribute( u
"xmlns:rdfs"_s, u
"http://www.w3.org/2000/01/rdf-schema#"_s );
1567 rdfElement.setAttribute( u
"xmlns:dc"_s, u
"http://purl.org/dc/elements/1.1/"_s );
1568 QDomElement descriptionElement = svg.createElement( u
"rdf:Description"_s );
1569 QDomElement workElement = svg.createElement( u
"cc:Work"_s );
1570 workElement.setAttribute( u
"rdf:about"_s, QString() );
1572 auto addTextNode = [&workElement, &descriptionElement, &svg](
const QString & tag,
const QString & value )
1575 QDomElement element = svg.createElement( tag );
1576 QDomText t = svg.createTextNode( value );
1577 element.appendChild( t );
1578 workElement.appendChild( element );
1581 descriptionElement.setAttribute( tag, value );
1584 addTextNode( u
"dc:format"_s, u
"image/svg+xml"_s );
1585 addTextNode( u
"dc:title"_s, metadata.
title() );
1586 addTextNode( u
"dc:date"_s, metadata.
creationDateTime().toString( Qt::ISODate ) );
1587 addTextNode( u
"dc:identifier"_s, metadata.
identifier() );
1588 addTextNode( u
"dc:description"_s, metadata.
abstract() );
1590 auto addAgentNode = [&workElement, &descriptionElement, &svg](
const QString & tag,
const QString & value )
1593 QDomElement inkscapeElement = svg.createElement( tag );
1594 QDomElement agentElement = svg.createElement( u
"cc:Agent"_s );
1595 QDomElement titleElement = svg.createElement( u
"dc:title"_s );
1596 QDomText t = svg.createTextNode( value );
1597 titleElement.appendChild( t );
1598 agentElement.appendChild( titleElement );
1599 inkscapeElement.appendChild( agentElement );
1600 workElement.appendChild( inkscapeElement );
1603 QDomElement bagElement = svg.createElement( u
"rdf:Bag"_s );
1604 QDomElement liElement = svg.createElement( u
"rdf:li"_s );
1605 t = svg.createTextNode( value );
1606 liElement.appendChild( t );
1607 bagElement.appendChild( liElement );
1609 QDomElement element = svg.createElement( tag );
1610 element.appendChild( bagElement );
1611 descriptionElement.appendChild( element );
1614 addAgentNode( u
"dc:creator"_s, metadata.
author() );
1615 addAgentNode( u
"dc:publisher"_s, getCreator() );
1619 QDomElement element = svg.createElement( u
"dc:subject"_s );
1620 QDomElement bagElement = svg.createElement( u
"rdf:Bag"_s );
1622 for (
auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
1624 const QStringList words = it.value();
1625 for (
const QString &keyword : words )
1627 QDomElement liElement = svg.createElement( u
"rdf:li"_s );
1628 QDomText t = svg.createTextNode( keyword );
1629 liElement.appendChild( t );
1630 bagElement.appendChild( liElement );
1633 element.appendChild( bagElement );
1634 workElement.appendChild( element );
1635 descriptionElement.appendChild( element );
1638 rdfElement.appendChild( descriptionElement );
1639 rdfElement.appendChild( workElement );
1640 metadataElement.appendChild( rdfElement );
1641 svg.documentElement().appendChild( metadataElement );
1642 svg.documentElement().setAttribute( u
"xmlns:cc"_s, u
"http://creativecommons.org/ns#"_s );
1645std::unique_ptr<double[]> QgsLayoutExporter::computeGeoTransform(
const QgsLayoutItemMap *map,
const QRectF ®ion,
double dpi )
const
1648 map = mLayout->referenceMap();
1654 dpi = mLayout->renderContext().dpi();
1657 QRectF exportRegion = region;
1658 if ( !exportRegion.isValid() )
1660 int pageNumber = map->
page();
1662 QgsLayoutItemPage *page = mLayout->pageCollection()->page( pageNumber );
1663 double pageY = page->pos().y();
1664 QSizeF pageSize = page->rect().size();
1665 exportRegion = QRectF( 0, pageY, pageSize.width(), pageSize.height() );
1669 QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
1672 double outputHeightMM = exportRegion.height();
1673 double outputWidthMM = exportRegion.width();
1676 QgsRectangle mapExtent = map->
extent();
1677 double mapXCenter = mapExtent.
center().
x();
1678 double mapYCenter = mapExtent.
center().
y();
1680 double sinAlpha = std::sin( alpha );
1681 double cosAlpha = std::cos( alpha );
1684 QPointF mapItemPos = map->pos();
1686 mapItemPos.rx() -= exportRegion.left();
1687 mapItemPos.ry() -= exportRegion.top();
1690 double xRatio = mapExtent.
width() / mapItemSceneRect.width();
1691 double yRatio = mapExtent.
height() / mapItemSceneRect.height();
1692 double xmin = mapExtent.
xMinimum() - mapItemPos.x() * xRatio;
1693 double ymax = mapExtent.
yMaximum() + mapItemPos.y() * yRatio;
1694 QgsRectangle paperExtent( xmin, ymax - outputHeightMM * yRatio, xmin + outputWidthMM * xRatio, ymax );
1697 double X0 = paperExtent.xMinimum();
1698 double Y0 = paperExtent.yMaximum();
1703 double X1 = X0 - mapXCenter;
1704 double Y1 = Y0 - mapYCenter;
1705 double X2 = X1 * cosAlpha + Y1 * sinAlpha;
1706 double Y2 = -X1 * sinAlpha + Y1 * cosAlpha;
1707 X0 = X2 + mapXCenter;
1708 Y0 = Y2 + mapYCenter;
1712 int pageWidthPixels =
static_cast< int >( dpi * outputWidthMM / 25.4 );
1713 int pageHeightPixels =
static_cast< int >( dpi * outputHeightMM / 25.4 );
1714 double pixelWidthScale = paperExtent.width() / pageWidthPixels;
1715 double pixelHeightScale = paperExtent.height() / pageHeightPixels;
1718 std::unique_ptr<double[]> t(
new double[6] );
1720 t[1] = cosAlpha * pixelWidthScale;
1721 t[2] = -sinAlpha * pixelWidthScale;
1723 t[4] = -sinAlpha * pixelHeightScale;
1724 t[5] = -cosAlpha * pixelHeightScale;
1729void QgsLayoutExporter::writeWorldFile(
const QString &worldFileName,
double a,
double b,
double c,
double d,
double e,
double f )
const
1731 QFile worldFile( worldFileName );
1732 if ( !worldFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
1736 QTextStream fout( &worldFile );
1740 fout << QString::number( a,
'f', 12 ) <<
"\r\n";
1741 fout << QString::number( d,
'f', 12 ) <<
"\r\n";
1742 fout << QString::number( b,
'f', 12 ) <<
"\r\n";
1743 fout << QString::number( e,
'f', 12 ) <<
"\r\n";
1744 fout << QString::number(
c,
'f', 12 ) <<
"\r\n";
1745 fout << QString::number( f,
'f', 12 ) <<
"\r\n";
1750 return georeferenceOutputPrivate( file, map, exportRegion, dpi,
false );
1753bool QgsLayoutExporter::georeferenceOutputPrivate(
const QString &file,
QgsLayoutItemMap *map,
const QRectF &exportRegion,
double dpi,
bool includeGeoreference,
bool includeMetadata )
const
1758 if ( !map && includeGeoreference )
1759 map = mLayout->referenceMap();
1761 std::unique_ptr<double[]> t;
1763 if ( map && includeGeoreference )
1766 dpi = mLayout->renderContext().dpi();
1768 t = computeGeoTransform( map, exportRegion, dpi );
1773 CPLSetConfigOption(
"GDAL_PDF_DPI", QString::number( dpi ).toUtf8().constData() );
1778 GDALSetGeoTransform( outputDS.get(), t.get() );
1780 if ( includeMetadata )
1782 QString creationDateString;
1783 const QDateTime creationDateTime = mLayout->project()->metadata().creationDateTime();
1784#if QT_FEATURE_timezone > 0
1785 if ( creationDateTime.isValid() )
1787 creationDateString = u
"D:%1"_s.arg( mLayout->project()->metadata().creationDateTime().toString( u
"yyyyMMddHHmmss"_s ) );
1788 if ( creationDateTime.timeZone().isValid() )
1790 int offsetFromUtc = creationDateTime.timeZone().offsetFromUtc( creationDateTime );
1791 creationDateString += ( offsetFromUtc >= 0 ) ?
'+' :
'-';
1792 offsetFromUtc = std::abs( offsetFromUtc );
1793 int offsetHours = offsetFromUtc / 3600;
1794 int offsetMins = ( offsetFromUtc % 3600 ) / 60;
1795 creationDateString += u
"%1'%2'"_s.arg( offsetHours ).arg( offsetMins );
1799 QgsDebugError( u
"Qt is built without timezone support, skipping timezone for pdf export"_s );
1801 GDALSetMetadataItem( outputDS.get(),
"CREATION_DATE", creationDateString.toUtf8().constData(),
nullptr );
1803 GDALSetMetadataItem( outputDS.get(),
"AUTHOR", mLayout->project()->metadata().author().toUtf8().constData(),
nullptr );
1804 const QString creator = getCreator();
1805 GDALSetMetadataItem( outputDS.get(),
"CREATOR", creator.toUtf8().constData(),
nullptr );
1806 GDALSetMetadataItem( outputDS.get(),
"PRODUCER", creator.toUtf8().constData(),
nullptr );
1807 GDALSetMetadataItem( outputDS.get(),
"SUBJECT", mLayout->project()->metadata().abstract().toUtf8().constData(),
nullptr );
1808 GDALSetMetadataItem( outputDS.get(),
"TITLE", mLayout->project()->metadata().title().toUtf8().constData(),
nullptr );
1811 QStringList allKeywords;
1812 for (
auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
1814 allKeywords.append( u
"%1: %2"_s.arg( it.key(), it.value().join(
',' ) ) );
1816 const QString keywordString = allKeywords.join(
';' );
1817 GDALSetMetadataItem( outputDS.get(),
"KEYWORDS", keywordString.toUtf8().constData(),
nullptr );
1823 CPLSetConfigOption(
"GDAL_PDF_DPI",
nullptr );
1830 if ( items.count() == 1 )
1834 QString name = layoutItem->displayName();
1836 if ( name.startsWith(
'<' ) && name.endsWith(
'>' ) )
1837 name = name.mid( 1, name.length() - 2 );
1841 else if ( items.count() > 1 )
1843 QStringList currentLayerItemTypes;
1844 for ( QGraphicsItem *item : items )
1850 if ( !currentLayerItemTypes.contains( itemType ) && !currentLayerItemTypes.contains( itemTypePlural ) )
1851 currentLayerItemTypes << itemType;
1852 else if ( currentLayerItemTypes.contains( itemType ) )
1854 currentLayerItemTypes.replace( currentLayerItemTypes.indexOf( itemType ), itemTypePlural );
1859 if ( !currentLayerItemTypes.contains( QObject::tr(
"Other" ) ) )
1860 currentLayerItemTypes.append( QObject::tr(
"Other" ) );
1863 return currentLayerItemTypes.join(
", "_L1 );
1865 return QObject::tr(
"Layer %1" ).arg( layerId );
1870 const std::function<QString(
QgsLayoutItem *item )> &getItemExportGroupFunc )
1872 LayoutItemHider itemHider( items );
1877 QString previousItemGroup;
1878 unsigned int layerId = 1;
1879 QgsLayoutItem::ExportLayerDetail layerDetails;
1880 itemHider.hideAll();
1881 const QList< QGraphicsItem * > itemsToIterate = itemHider.itemsToIterate();
1882 QList< QGraphicsItem * > currentLayerItems;
1883 for ( QGraphicsItem *item : itemsToIterate )
1885 QgsLayoutItem *layoutItem =
dynamic_cast<QgsLayoutItem *
>( item );
1887 bool canPlaceInExistingLayer =
false;
1888 QString thisItemExportGroupName;
1892 thisItemExportGroupName = getItemExportGroupFunc( layoutItem );
1893 if ( !thisItemExportGroupName.isEmpty() )
1895 if ( thisItemExportGroupName != previousItemGroup && !currentLayerItems.empty() )
1898 layerDetails.
groupName = thisItemExportGroupName;
1901 switch ( itemExportBehavior )
1905 switch ( prevItemBehavior )
1908 canPlaceInExistingLayer =
true;
1912 canPlaceInExistingLayer = prevType == -1 || prevType == layoutItem->
type();
1917 canPlaceInExistingLayer =
false;
1925 switch ( prevItemBehavior )
1929 canPlaceInExistingLayer = prevType == -1 || prevType == layoutItem->
type();
1934 canPlaceInExistingLayer =
false;
1942 canPlaceInExistingLayer =
false;
1947 canPlaceInExistingLayer =
false;
1950 prevItemBehavior = itemExportBehavior;
1951 prevType = layoutItem->
type();
1952 previousItemGroup = thisItemExportGroupName;
1957 previousItemGroup.clear();
1960 if ( canPlaceInExistingLayer )
1962 currentLayerItems << item;
1967 if ( !currentLayerItems.isEmpty() )
1971 ExportResult result = exportFunc( layerId, layerDetails );
1975 currentLayerItems.clear();
1978 itemHider.hideAll();
1983 int layoutItemLayerIdx = 0;
1985 mLayout->renderContext().setCurrentExportLayer( layoutItemLayerIdx );
1991 mLayout->renderContext().setCurrentExportLayer( layoutItemLayerIdx );
1995 ExportResult result = exportFunc( layerId, layerDetails );
2000 layoutItemLayerIdx++;
2002 layerDetails.mapLayerId.clear();
2004 mLayout->renderContext().setCurrentExportLayer( -1 );
2007 currentLayerItems.clear();
2011 currentLayerItems << item;
2013 layerDetails.groupName = thisItemExportGroupName;
2016 if ( !currentLayerItems.isEmpty() )
2019 ExportResult result = exportFunc( layerId, layerDetails );
2028 QgsVectorSimplifyMethod simplifyMethod;
2034 return simplifyMethod;
2039 QgsMaskRenderSettings settings;
2057 int pageNumber = map->
page();
2059 double pageY = page->pos().y();
2060 QSizeF pageSize = page->rect().size();
2061 QRectF pageRect( 0, pageY, pageSize.width(), pageSize.height() );
2077 double destinationHeight = exportRegion.height();
2078 double destinationWidth = exportRegion.width();
2080 QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
2085 double xRatio = mapExtent.
width() / mapItemSceneRect.width();
2086 double yRatio = mapExtent.
height() / mapItemSceneRect.height();
2088 double xCenter = mapExtent.
center().
x();
2089 double yCenter = mapExtent.
center().
y();
2092 QPointF mapItemPos = map->pos();
2094 mapItemPos.rx() -= exportRegion.left();
2095 mapItemPos.ry() -= exportRegion.top();
2097 double xmin = mapExtent.
xMinimum() - mapItemPos.x() * xRatio;
2098 double ymax = mapExtent.
yMaximum() + mapItemPos.y() * yRatio;
2099 QgsRectangle paperExtent( xmin, ymax - destinationHeight * yRatio, xmin + destinationWidth * xRatio, ymax );
2101 double X0 = paperExtent.
xMinimum();
2102 double Y0 = paperExtent.
yMinimum();
2105 dpi = mLayout->renderContext().dpi();
2107 int widthPx =
static_cast< int >( dpi * destinationWidth / 25.4 );
2108 int heightPx =
static_cast< int >( dpi * destinationHeight / 25.4 );
2110 double Ww = paperExtent.
width() / widthPx;
2111 double Hh = paperExtent.
height() / heightPx;
2120 s[5] = Y0 + paperExtent.
height();
2124 r[0] = std::cos( alpha );
2125 r[1] = -std::sin( alpha );
2126 r[2] = xCenter * ( 1 - std::cos( alpha ) ) + yCenter * std::sin( alpha );
2127 r[3] = std::sin( alpha );
2128 r[4] = std::cos( alpha );
2129 r[5] = - xCenter * std::sin( alpha ) + yCenter * ( 1 - std::cos( alpha ) );
2132 a = r[0] * s[0] + r[1] * s[3];
2133 b = r[0] * s[1] + r[1] * s[4];
2134 c = r[0] * s[2] + r[1] * s[5] + r[2];
2135 d = r[3] * s[0] + r[4] * s[3];
2136 e = r[3] * s[1] + r[4] * s[4];
2137 f = r[3] * s[2] + r[4] * s[5] + r[5];
2145 QList< QgsLayoutItem *> items;
2146 layout->layoutItems( items );
2151 if ( currentItem->isVisible() && currentItem->requiresRasterization() )
2162 QList< QgsLayoutItem *> items;
2163 layout->layoutItems( items );
2168 if ( currentItem->isVisible() && currentItem->containsAdvancedEffects() )
2181 if ( mLayout->pageCollection()->pageCount() == 1 )
2184 bounds = mLayout->layoutBounds(
true );
2189 bounds = mLayout->pageItemBounds( page,
true );
2191 if ( bounds.width() <= 0 || bounds.height() <= 0 )
2199 bounds = bounds.adjusted( -settings.
cropMargins.
left() * pixelToLayoutUnits,
2211int QgsLayoutExporter::firstPageToBeExported(
QgsLayout *layout )
2213 const int pageCount =
layout->pageCollection()->pageCount();
2214 for (
int i = 0; i < pageCount; ++i )
2216 if ( !
layout->pageCollection()->shouldExportPage( i ) )
2228 if ( details.
page == 0 )
2238void QgsLayoutExporter::captureLabelingResults()
2240 qDeleteAll( mLabelingResults );
2241 mLabelingResults.clear();
2243 QList< QgsLayoutItemMap * > maps;
2244 mLayout->layoutItems( maps );
2248 mLabelingResults[ map->
uuid() ] = map->mExportLabelingResults.release();
2252bool QgsLayoutExporter::saveImage(
const QImage &image,
const QString &imageFilename,
const QString &imageFormat,
QgsProject *projectForMetadata,
int quality )
2254 QImageWriter w( imageFilename, imageFormat.toLocal8Bit().constData() );
2255 if ( imageFormat.compare(
"tiff"_L1, Qt::CaseInsensitive ) == 0 || imageFormat.compare(
"tif"_L1, Qt::CaseInsensitive ) == 0 )
2257 w.setCompression( 1 );
2261 w.setQuality( quality );
2263 if ( projectForMetadata )
2265 w.setText( u
"Author"_s, projectForMetadata->
metadata().
author() );
2266 const QString creator = getCreator();
2267 w.setText( u
"Creator"_s, creator );
2268 w.setText( u
"Producer"_s, creator );
2271 w.setText( u
"Title"_s, projectForMetadata->
metadata().
title() );
2274 QStringList allKeywords;
2275 for (
auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
2277 allKeywords.append( u
"%1: %2"_s.arg( it.key(), it.value().join(
',' ) ) );
2279 const QString keywordString = allKeywords.join(
';' );
2280 w.setText( u
"Keywords"_s, keywordString );
2282 return w.write( image );
2285QString QgsLayoutExporter::getCreator()
2290void QgsLayoutExporter::setXmpMetadata( QPdfWriter *pdfWriter,
QgsLayout *layout )
2292#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
2293 QUuid documentId = pdfWriter->documentId();
2295 QUuid documentId = QUuid::createUuid();
2299 const QDateTime creationDateTime =
layout->project() ?
layout->project()->metadata().creationDateTime() : QDateTime();
2300 const QString metaDataDate = creationDateTime.isValid() ? creationDateTime.toOffsetFromUtc( creationDateTime.offsetFromUtc() ).toString( Qt::ISODate ) : QString();
2301 const QString title = pdfWriter->title();
2302 const QString creator = getCreator();
2303 const QString producer = creator;
2304 const QString author =
layout->project() ?
layout->project()->metadata().author() : QString();
2308 const QLatin1String xmlNS(
"http://www.w3.org/XML/1998/namespace" );
2309 const QLatin1String adobeNS(
"adobe:ns:meta/" );
2310 const QLatin1String rdfNS(
"http://www.w3.org/1999/02/22-rdf-syntax-ns#" );
2311 const QLatin1String dcNS(
"http://purl.org/dc/elements/1.1/" );
2312 const QLatin1String xmpNS(
"http://ns.adobe.com/xap/1.0/" );
2313 const QLatin1String xmpMMNS(
"http://ns.adobe.com/xap/1.0/mm/" );
2314 const QLatin1String pdfNS(
"http://ns.adobe.com/pdf/1.3/" );
2315 const QLatin1String pdfaidNS(
"http://www.aiim.org/pdfa/ns/id/" );
2317 QByteArray xmpMetadata;
2318 QBuffer output( &xmpMetadata );
2319 output.open( QIODevice::WriteOnly );
2320 output.write(
"<?xpacket begin='' ?>" );
2322 QXmlStreamWriter w( &output );
2323 w.setAutoFormatting(
true );
2324 w.writeNamespace( adobeNS,
"x" );
2325 w.writeNamespace( rdfNS,
"rdf" );
2326 w.writeNamespace( dcNS,
"dc" );
2327 w.writeNamespace( xmpNS,
"xmp" );
2328 w.writeNamespace( xmpMMNS,
"xmpMM" );
2329 w.writeNamespace( pdfNS,
"pdf" );
2330 w.writeNamespace( pdfaidNS,
"pdfaid" );
2332 w.writeStartElement( adobeNS,
"xmpmeta" );
2333 w.writeStartElement( rdfNS,
"RDF" );
2336 w.writeStartElement( rdfNS,
"Description" );
2337 w.writeAttribute( rdfNS,
"about",
"" );
2338 w.writeStartElement( dcNS,
"title" );
2339 w.writeStartElement( rdfNS,
"Alt" );
2340 w.writeStartElement( rdfNS,
"li" );
2341 w.writeAttribute( xmlNS,
"lang",
"x-default" );
2342 w.writeCharacters( title );
2343 w.writeEndElement();
2344 w.writeEndElement();
2345 w.writeEndElement();
2347 w.writeStartElement( dcNS,
"creator" );
2348 w.writeStartElement( rdfNS,
"Seq" );
2349 w.writeStartElement( rdfNS,
"li" );
2350 w.writeCharacters( author );
2351 w.writeEndElement();
2352 w.writeEndElement();
2353 w.writeEndElement();
2355 w.writeEndElement();
2358 w.writeStartElement( rdfNS,
"Description" );
2359 w.writeAttribute( rdfNS,
"about",
"" );
2360 w.writeAttribute( pdfNS,
"Producer", producer );
2361 w.writeAttribute( pdfNS,
"Trapped",
"False" );
2362 w.writeEndElement();
2365 w.writeStartElement( rdfNS,
"Description" );
2366 w.writeAttribute( rdfNS,
"about",
"" );
2367 w.writeAttribute( xmpNS,
"CreatorTool", creator );
2368 w.writeAttribute( xmpNS,
"CreateDate", metaDataDate );
2369 w.writeAttribute( xmpNS,
"ModifyDate", metaDataDate );
2370 w.writeAttribute( xmpNS,
"MetadataDate", metaDataDate );
2371 w.writeEndElement();
2374 w.writeStartElement( rdfNS,
"Description" );
2375 w.writeAttribute( rdfNS,
"about",
"" );
2376 w.writeAttribute( xmpMMNS,
"DocumentID",
"uuid:" + documentId.toString( QUuid::WithoutBraces ) );
2377 w.writeAttribute( xmpMMNS,
"VersionID",
"1" );
2378 w.writeAttribute( xmpMMNS,
"RenditionClass",
"default" );
2379 w.writeEndElement();
2381#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
2384 switch ( pdfWriter->pdfVersion() )
2386 case QPagedPaintDevice::PdfVersion_1_4:
2387 case QPagedPaintDevice::PdfVersion_A1b:
2388 case QPagedPaintDevice::PdfVersion_1_6:
2390 case QPagedPaintDevice::PdfVersion_X4:
2391 const QLatin1String pdfxidNS(
"http://www.npes.org/pdfx/ns/id/" );
2392 w.writeNamespace( pdfxidNS,
"pdfxid" );
2393 w.writeStartElement( rdfNS,
"Description" );
2394 w.writeAttribute( rdfNS,
"about",
"" );
2395 w.writeAttribute( pdfxidNS,
"GTS_PDFXVersion",
"PDF/X-4" );
2396 w.writeEndElement();
2402 w.writeEndElement();
2403 w.writeEndElement();
2405 w.writeEndDocument();
2406 output.write(
"<?xpacket end='w'?>" );
2408 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.