32 #include <QImageWriter>
34 #include <QSvgGenerator>
37 #include <QTextStream>
43 class LayoutContextPreviewSettingRestorer
47 LayoutContextPreviewSettingRestorer(
QgsLayout *layout )
49 , mPreviousSetting( layout->renderContext().mIsPreviewRender )
51 mLayout->renderContext().mIsPreviewRender =
false;
54 ~LayoutContextPreviewSettingRestorer()
56 mLayout->renderContext().mIsPreviewRender = mPreviousSetting;
59 LayoutContextPreviewSettingRestorer(
const LayoutContextPreviewSettingRestorer &other ) =
delete;
60 LayoutContextPreviewSettingRestorer &operator=(
const LayoutContextPreviewSettingRestorer &other ) =
delete;
64 bool mPreviousSetting =
false;
67 class LayoutGuideHider
74 const QList< QgsLayoutGuide * > guides = mLayout->guides().guides();
77 mPrevVisibility.insert( guide, guide->item()->isVisible() );
78 guide->item()->setVisible(
false );
84 for (
auto it = mPrevVisibility.constBegin(); it != mPrevVisibility.constEnd(); ++it )
86 it.key()->item()->setVisible( it.value() );
90 LayoutGuideHider(
const LayoutGuideHider &other ) =
delete;
91 LayoutGuideHider &operator=(
const LayoutGuideHider &other ) =
delete;
95 QHash< QgsLayoutGuide *, bool > mPrevVisibility;
101 explicit LayoutItemHider(
const QList<QGraphicsItem *> &items )
103 mItemsToIterate.reserve( items.count() );
104 for ( QGraphicsItem *item : items )
106 const bool isVisible = item->isVisible();
107 mPrevVisibility[item] = isVisible;
109 mItemsToIterate.append( item );
111 layoutItem->setProperty(
"wasVisible", isVisible );
119 for (
auto it = mPrevVisibility.constBegin(); it != mPrevVisibility.constEnd(); ++it )
127 for (
auto it = mPrevVisibility.constBegin(); it != mPrevVisibility.constEnd(); ++it )
129 it.key()->setVisible( it.value() );
131 layoutItem->setProperty(
"wasVisible", QVariant() );
135 QList< QGraphicsItem * > itemsToIterate()
const {
return mItemsToIterate; }
137 LayoutItemHider(
const LayoutItemHider &other ) =
delete;
138 LayoutItemHider &operator=(
const LayoutItemHider &other ) =
delete;
142 QList<QGraphicsItem * > mItemsToIterate;
143 QHash<QGraphicsItem *, bool> mPrevVisibility;
156 qDeleteAll( mLabelingResults );
169 if ( mLayout->pageCollection()->pageCount() <= page || page < 0 )
180 LayoutContextPreviewSettingRestorer restorer( mLayout );
183 QRectF paperRect = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() );
192 if ( mLayout->pageCollection()->pageCount() <= page || page < 0 )
203 LayoutContextPreviewSettingRestorer restorer( mLayout );
206 QRectF paperRect = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() );
208 const double imageAspectRatio =
static_cast< double >( imageSize.width() ) / imageSize.height();
209 const double paperAspectRatio = paperRect.width() / paperRect.height();
210 if ( imageSize.isValid() && ( !
qgsDoubleNear( imageAspectRatio, paperAspectRatio, 0.008 ) ) )
215 QgsMessageLog::logMessage( QObject::tr(
"Ignoring custom image size because aspect ratio %1 does not match paper ratio %2" ).arg( QString::number( imageAspectRatio,
'g', 3 ), QString::number( paperAspectRatio,
'g', 3 ) ), QStringLiteral(
"Layout" ), Qgis::MessageLevel::Warning );
223 class LayoutItemCacheSettingRestorer
227 LayoutItemCacheSettingRestorer(
QgsLayout *layout )
230 const QList< QGraphicsItem * > items = mLayout->items();
231 for ( QGraphicsItem *item : items )
233 mPrevCacheMode.insert( item, item->cacheMode() );
234 item->setCacheMode( QGraphicsItem::NoCache );
238 ~LayoutItemCacheSettingRestorer()
240 for (
auto it = mPrevCacheMode.constBegin(); it != mPrevCacheMode.constEnd(); ++it )
242 it.key()->setCacheMode( it.value() );
246 LayoutItemCacheSettingRestorer(
const LayoutItemCacheSettingRestorer &other ) =
delete;
247 LayoutItemCacheSettingRestorer &operator=(
const LayoutItemCacheSettingRestorer &other ) =
delete;
251 QHash< QGraphicsItem *, QGraphicsItem::CacheMode > mPrevCacheMode;
258 QPaintDevice *paintDevice = painter->device();
259 if ( !paintDevice || !mLayout )
264 LayoutItemCacheSettingRestorer cacheRestorer( mLayout );
265 ( void )cacheRestorer;
266 LayoutContextPreviewSettingRestorer restorer( mLayout );
268 LayoutGuideHider guideHider( mLayout );
273 mLayout->render( painter, QRectF( 0, 0, paintDevice->width(), paintDevice->height() ), region );
281 LayoutContextPreviewSettingRestorer restorer( mLayout );
284 double resolution = mLayout->renderContext().dpi();
286 if ( imageSize.isValid() )
290 resolution = ( imageSize.width() / region.width()
291 + imageSize.height() / region.height() ) / 2.0 * oneInchInLayoutUnits;
299 int width = imageSize.isValid() ? imageSize.width()
300 :
static_cast< int >( resolution * region.width() / oneInchInLayoutUnits );
301 int height = imageSize.isValid() ? imageSize.height()
302 :
static_cast< int >( resolution * region.height() / oneInchInLayoutUnits );
304 QImage image( QSize( width, height ), QImage::Format_ARGB32 );
305 if ( !image.isNull() )
308 if ( width > 32768 || height > 32768 )
309 QgsMessageLog::logMessage( QObject::tr(
"Error: output width or height is larger than 32768 pixel, result will be clipped" ) );
310 image.setDotsPerMeterX(
static_cast< int >( std::round( resolution / 25.4 * 1000 ) ) );
311 image.setDotsPerMeterY(
static_cast< int>( std::round( resolution / 25.4 * 1000 ) ) );
312 image.fill( Qt::transparent );
313 QPainter imagePainter( &image );
315 if ( !imagePainter.isActive() )
323 class LayoutContextSettingsRestorer
328 LayoutContextSettingsRestorer(
QgsLayout *layout )
330 , mPreviousDpi( layout->renderContext().dpi() )
331 , mPreviousFlags( layout->renderContext().flags() )
332 , mPreviousTextFormat( layout->renderContext().textRenderFormat() )
333 , mPreviousExportLayer( layout->renderContext().currentExportLayer() )
334 , mPreviousSimplifyMethod( layout->renderContext().simplifyMethod() )
335 , mExportThemes( layout->renderContext().exportThemes() )
336 , mPredefinedScales( layout->renderContext().predefinedScales() )
341 ~LayoutContextSettingsRestorer()
343 mLayout->renderContext().setDpi( mPreviousDpi );
344 mLayout->renderContext().setFlags( mPreviousFlags );
345 mLayout->renderContext().setTextRenderFormat( mPreviousTextFormat );
347 mLayout->renderContext().setCurrentExportLayer( mPreviousExportLayer );
349 mLayout->renderContext().setSimplifyMethod( mPreviousSimplifyMethod );
350 mLayout->renderContext().setExportThemes( mExportThemes );
351 mLayout->renderContext().setPredefinedScales( mPredefinedScales );
354 LayoutContextSettingsRestorer(
const LayoutContextSettingsRestorer &other ) =
delete;
355 LayoutContextSettingsRestorer &operator=(
const LayoutContextSettingsRestorer &other ) =
delete;
359 double mPreviousDpi = 0;
360 QgsLayoutRenderContext::Flags mPreviousFlags = QgsLayoutRenderContext::Flags();
362 int mPreviousExportLayer = 0;
364 QStringList mExportThemes;
365 QVector< double > mPredefinedScales;
376 if ( settings.
dpi <= 0 )
377 settings.
dpi = mLayout->renderContext().dpi();
379 mErrorFileName.clear();
381 int worldFilePageNo = -1;
384 worldFilePageNo = referenceMap->page();
387 QFileInfo fi( filePath );
389 if ( !dir.exists( fi.absolutePath() ) )
391 dir.mkpath( fi.absolutePath() );
396 pageDetails.
baseName = fi.completeBaseName();
399 LayoutContextPreviewSettingRestorer restorer( mLayout );
401 LayoutContextSettingsRestorer dpiRestorer( mLayout );
403 mLayout->renderContext().setDpi( settings.
dpi );
404 mLayout->renderContext().setFlags( settings.
flags );
408 if ( settings.
pages.empty() )
410 for (
int page = 0; page < mLayout->pageCollection()->pageCount(); ++page )
415 for (
int page : std::as_const( settings.
pages ) )
417 if ( page >= 0 && page < mLayout->pageCollection()->pageCount() )
422 for (
int page : std::as_const( pages ) )
424 if ( !mLayout->pageCollection()->shouldExportPage( page ) )
431 QImage image = createImage( settings, page, bounds, skip );
436 pageDetails.
page = page;
439 if ( image.isNull() )
441 mErrorFileName = outputFilePath;
445 if ( !saveImage( image, outputFilePath, pageDetails.
extension, settings.
exportMetadata ? mLayout->project() :
nullptr ) )
447 mErrorFileName = outputFilePath;
451 const bool shouldGeoreference = ( page == worldFilePageNo );
452 if ( shouldGeoreference )
454 georeferenceOutputPrivate( outputFilePath,
nullptr, bounds, settings.
dpi, shouldGeoreference );
459 double a, b,
c, d, e, f;
460 if ( bounds.isValid() )
465 QFileInfo fi( outputFilePath );
467 QString outputSuffix = fi.suffix();
468 QString worldFileName = fi.absolutePath() +
'/' + fi.completeBaseName() +
'.'
469 + outputSuffix.at( 0 ) + outputSuffix.at( fi.suffix().size() - 1 ) +
'w';
471 writeWorldFile( worldFileName, a, b,
c, d, e, f );
476 captureLabelingResults();
487 int total = iterator->
count();
488 double step = total > 0 ? 100.0 / total : 100.0;
490 while ( iterator->
next() )
495 feedback->setProperty(
"progress", QObject::tr(
"Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
497 feedback->setProperty(
"progress", QObject::tr(
"Exporting section %1" ).arg( i + 1 ).arg( total ) );
507 QString filePath = iterator->
filePath( baseFilePath, extension );
512 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 ) );
530 if ( !mLayout || mLayout->pageCollection()->pageCount() == 0 )
534 if ( settings.
dpi <= 0 )
535 settings.
dpi = mLayout->renderContext().dpi();
537 mErrorFileName.clear();
539 LayoutContextPreviewSettingRestorer restorer( mLayout );
541 LayoutContextSettingsRestorer contextRestorer( mLayout );
542 ( void )contextRestorer;
543 mLayout->renderContext().setDpi( settings.
dpi );
548 mLayout->renderContext().setSimplifyMethod( createExportSimplifyMethod() );
551 std::unique_ptr< QgsLayoutGeoPdfExporter > geoPdfExporter;
553 geoPdfExporter = std::make_unique< QgsLayoutGeoPdfExporter >( mLayout );
555 mLayout->renderContext().setFlags( settings.
flags );
563 mLayout->renderContext().setExportThemes( settings.
exportThemes );
575 const QList<QGraphicsItem *> items = mLayout->items( Qt::AscendingOrder );
577 QList< QgsLayoutGeoPdfExporter::ComponentLayerDetail > pdfComponents;
587 component.
name = layerDetail.name;
588 component.
mapLayerId = layerDetail.mapLayerId;
589 component.
opacity = layerDetail.opacity;
591 component.
group = layerDetail.mapTheme;
592 component.
sourcePdfPath = settings.writeGeoPdf ? geoPdfExporter->generateTemporaryFilepath( QStringLiteral(
"layer_%1.pdf" ).arg( layerId ) ) : baseDir.filePath( QStringLiteral(
"%1_%2.pdf" ).arg( baseFileName ).arg( layerId, 4, 10, QChar(
'0' ) ) );
593 pdfComponents << component;
594 preparePrintAsPdf( mLayout, printer, component.sourcePdfPath );
595 preparePrint( mLayout, printer,
false );
597 if ( !p.begin( &printer ) )
602 #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
607 return layerExportResult;
609 result = handleLayeredExport( items, exportFunc );
613 if ( settings.writeGeoPdf )
616 details.
dpi = settings.dpi;
618 QgsLayoutSize pageSize = mLayout->pageCollection()->page( 0 )->sizeWithUnits();
622 if ( settings.exportMetadata )
625 details.
author = mLayout->project()->metadata().author();
628 details.
creationDateTime = mLayout->project()->metadata().creationDateTime();
629 details.
subject = mLayout->project()->metadata().abstract();
630 details.
title = mLayout->project()->metadata().title();
631 details.
keywords = mLayout->project()->metadata().keywords();
634 const QList< QgsMapLayer * > layers = mLayout->project()->mapLayers().values();
640 if ( settings.appendGeoreference )
643 QList< QgsLayoutItemMap * > maps;
644 mLayout->layoutItems( maps );
648 georef.
crs = map->crs();
650 const QPointF topLeft = map->mapToScene( QPointF( 0, 0 ) );
651 const QPointF topRight = map->mapToScene( QPointF( map->rect().width(), 0 ) );
652 const QPointF bottomLeft = map->mapToScene( QPointF( 0, map->rect().height() ) );
653 const QPointF bottomRight = map->mapToScene( QPointF( map->rect().width(), map->rect().height() ) );
666 const QTransform t = map->layoutToMapCoordsTransform();
667 const QgsPointXY topLeftMap = t.map( topLeft );
668 const QgsPointXY topRightMap = t.map( topRight );
669 const QgsPointXY bottomLeftMap = t.map( bottomLeft );
670 const QgsPointXY bottomRightMap = t.map( bottomRight );
682 details.
layerOrder = geoPdfExporter->layerOrder();
687 if ( !geoPdfExporter->finalize( pdfComponents, filePath, details ) )
698 preparePrintAsPdf( mLayout, printer, filePath );
699 preparePrint( mLayout, printer,
false );
701 if ( !p.begin( &printer ) )
706 #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
712 bool shouldAppendGeoreference = settings.
appendGeoreference && mLayout && mLayout->referenceMap() && mLayout->referenceMap()->page() == 0;
715 georeferenceOutputPrivate( filePath,
nullptr, QRectF(), settings.
dpi, shouldAppendGeoreference, settings.
exportMetadata );
718 captureLabelingResults();
734 int total = iterator->
count();
735 double step = total > 0 ? 100.0 / total : 100.0;
738 while ( iterator->
next() )
743 feedback->setProperty(
"progress", QObject::tr(
"Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
745 feedback->setProperty(
"progress", QObject::tr(
"Exporting section %1" ).arg( i + 1 ) );
757 LayoutContextPreviewSettingRestorer restorer( iterator->
layout() );
759 LayoutContextSettingsRestorer contextRestorer( iterator->
layout() );
760 ( void )contextRestorer;
782 preparePrintAsPdf( iterator->
layout(), printer, fileName );
783 preparePrint( iterator->
layout(), printer,
false );
785 if ( !p.begin( &printer ) )
790 #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
801 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 ) );
825 int total = iterator->
count();
826 double step = total > 0 ? 100.0 / total : 100.0;
828 while ( iterator->
next() )
833 feedback->setProperty(
"progress", QObject::tr(
"Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
835 feedback->setProperty(
"progress", QObject::tr(
"Exporting section %1" ).arg( i + 1 ).arg( total ) );
844 QString filePath = iterator->
filePath( baseFilePath, QStringLiteral(
"pdf" ) );
851 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 ) );
873 if ( settings.
dpi <= 0 )
874 settings.
dpi = mLayout->renderContext().dpi();
876 mErrorFileName.clear();
878 LayoutContextPreviewSettingRestorer restorer( mLayout );
880 LayoutContextSettingsRestorer contextRestorer( mLayout );
881 ( void )contextRestorer;
882 mLayout->renderContext().setDpi( settings.
dpi );
884 mLayout->renderContext().setFlags( settings.
flags );
891 preparePrint( mLayout, printer,
true );
893 if ( !p.begin( &printer ) )
898 #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
904 captureLabelingResults();
919 int total = iterator->
count();
920 double step = total > 0 ? 100.0 / total : 100.0;
923 while ( iterator->
next() )
928 feedback->setProperty(
"progress", QObject::tr(
"Printing %1 of %2" ).arg( i + 1 ).arg( total ) );
930 feedback->setProperty(
"progress", QObject::tr(
"Printing section %1" ).arg( i + 1 ).arg( total ) );
942 LayoutContextPreviewSettingRestorer restorer( iterator->
layout() );
944 LayoutContextSettingsRestorer contextRestorer( iterator->
layout() );
945 ( void )contextRestorer;
958 preparePrint( iterator->
layout(), printer,
true );
960 if ( !p.begin( &printer ) )
965 #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
997 if ( settings.
dpi <= 0 )
998 settings.
dpi = mLayout->renderContext().dpi();
1000 mErrorFileName.clear();
1002 LayoutContextPreviewSettingRestorer restorer( mLayout );
1004 LayoutContextSettingsRestorer contextRestorer( mLayout );
1005 ( void )contextRestorer;
1006 mLayout->renderContext().setDpi( settings.
dpi );
1008 mLayout->renderContext().setFlags( settings.
flags );
1015 mLayout->renderContext().setSimplifyMethod( createExportSimplifyMethod() );
1018 QFileInfo fi( filePath );
1021 pageDetails.
baseName = fi.baseName();
1022 pageDetails.
extension = fi.completeSuffix();
1026 for (
int i = 0; i < mLayout->pageCollection()->pageCount(); ++i )
1028 if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1033 pageDetails.
page = i;
1040 if ( mLayout->pageCollection()->pageCount() == 1 )
1043 bounds = mLayout->layoutBounds(
true );
1048 bounds = mLayout->pageItemBounds( i,
true );
1057 bounds = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() );
1061 int width =
static_cast< int >( bounds.width() * settings.
dpi / inchesToLayoutUnits );
1063 int height =
static_cast< int >( bounds.height() * settings.
dpi / inchesToLayoutUnits );
1064 if ( width == 0 || height == 0 )
1073 const QRectF paperRect = QRectF( pageItem->pos().x(),
1074 pageItem->pos().y(),
1075 pageItem->rect().width(),
1076 pageItem->rect().height() );
1078 QDomNode svgDocRoot;
1079 const QList<QGraphicsItem *> items = mLayout->items( paperRect,
1080 Qt::IntersectsItemBoundingRect,
1081 Qt::AscendingOrder );
1085 return renderToLayeredSvg( settings, width, height, i, bounds, fileName, layerId, layerDetail.name, svg, svgDocRoot, settings.
exportMetadata );
1087 ExportResult res = handleLayeredExport( items, exportFunc );
1092 appendMetadataToSvg( svg );
1094 QFile out( fileName );
1095 bool openOk = out.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate );
1098 mErrorFileName = fileName;
1102 out.write( svg.toByteArray() );
1108 QSvgGenerator generator;
1111 generator.setTitle( mLayout->project()->metadata().title() );
1112 generator.setDescription( mLayout->project()->metadata().abstract() );
1114 generator.setOutputDevice( &svgBuffer );
1115 generator.setSize( QSize( width, height ) );
1116 generator.setViewBox( QRect( 0, 0, width, height ) );
1117 generator.setResolution(
static_cast< int >( std::round( settings.
dpi ) ) );
1120 bool createOk = p.begin( &generator );
1123 mErrorFileName = fileName;
1136 svgBuffer.open( QIODevice::ReadOnly );
1140 if ( ! svg.setContent( &svgBuffer,
false, &errorMsg, &errorLine ) )
1142 mErrorFileName = fileName;
1147 appendMetadataToSvg( svg );
1149 QFile out( fileName );
1150 bool openOk = out.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate );
1153 mErrorFileName = fileName;
1157 out.write( svg.toByteArray() );
1161 captureLabelingResults();
1172 int total = iterator->
count();
1173 double step = total > 0 ? 100.0 / total : 100.0;
1175 while ( iterator->
next() )
1180 feedback->setProperty(
"progress", QObject::tr(
"Exporting %1 of %2" ).arg( i + 1 ).arg( total ) );
1182 feedback->setProperty(
"progress", QObject::tr(
"Exporting section %1" ).arg( i + 1 ).arg( total ) );
1192 QString filePath = iterator->
filePath( baseFilePath, QStringLiteral(
"svg" ) );
1199 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 ) );
1218 return mLabelingResults;
1223 QMap<QString, QgsLabelingResults *> res;
1224 std::swap( mLabelingResults, res );
1228 void QgsLayoutExporter::preparePrintAsPdf(
QgsLayout *layout, QPrinter &printer,
const QString &filePath )
1230 QFileInfo fi( filePath );
1232 if ( !dir.exists( fi.absolutePath() ) )
1234 dir.mkpath( fi.absolutePath() );
1237 printer.setOutputFileName( filePath );
1238 printer.setOutputFormat( QPrinter::PdfFormat );
1240 updatePrinterPageSize(
layout, printer, firstPageToBeExported(
layout ) );
1246 #if defined(HAS_KDE_QT5_PDF_TRANSFORM_FIX) || QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
1253 void QgsLayoutExporter::preparePrint(
QgsLayout *layout, QPrinter &printer,
bool setFirstPageSize )
1255 printer.setFullPage(
true );
1256 printer.setColorMode( QPrinter::Color );
1261 if ( setFirstPageSize )
1263 updatePrinterPageSize(
layout, printer, firstPageToBeExported(
layout ) );
1269 if ( mLayout->pageCollection()->pageCount() == 0 )
1272 preparePrint( mLayout, printer,
true );
1274 if ( !p.begin( &printer ) )
1280 printPrivate( printer, p );
1285 QgsLayoutExporter::ExportResult QgsLayoutExporter::printPrivate( QPrinter &printer, QPainter &painter,
bool startNewPage,
double dpi,
bool rasterize )
1288 int fromPage = ( printer.fromPage() < 1 ) ? 0 : printer.fromPage() - 1;
1289 int toPage = ( printer.toPage() < 1 ) ? mLayout->pageCollection()->pageCount() - 1 : printer.toPage() - 1;
1291 bool pageExported =
false;
1294 for (
int i = fromPage; i <= toPage; ++i )
1296 if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1301 updatePrinterPageSize( mLayout, printer, i );
1302 if ( ( pageExported && i > fromPage ) || startNewPage )
1308 if ( !image.isNull() )
1310 QRectF targetArea( 0, 0, image.width(), image.height() );
1311 painter.drawImage( targetArea, image, targetArea );
1317 pageExported =
true;
1322 for (
int i = fromPage; i <= toPage; ++i )
1324 if ( !mLayout->pageCollection()->shouldExportPage( i ) )
1329 updatePrinterPageSize( mLayout, printer, i );
1331 if ( ( pageExported && i > fromPage ) || startNewPage )
1336 pageExported =
true;
1342 void QgsLayoutExporter::updatePrinterPageSize(
QgsLayout *layout, QPrinter &printer,
int page )
1347 QPageLayout pageLayout( QPageSize( pageSizeMM.
toQSizeF(), QPageSize::Millimeter ),
1348 QPageLayout::Portrait,
1349 QMarginsF( 0, 0, 0, 0 ) );
1350 pageLayout.setMode( QPageLayout::FullPageMode );
1351 printer.setPageLayout( pageLayout );
1352 printer.setFullPage(
true );
1353 printer.setPageMargins( QMarginsF( 0, 0, 0, 0 ) );
1356 QgsLayoutExporter::ExportResult QgsLayoutExporter::renderToLayeredSvg(
const SvgExportSettings &settings,
double width,
double height,
int page,
const QRectF &bounds,
const QString &filename,
unsigned int svgLayerId,
const QString &layerName, QDomDocument &svg, QDomNode &svgDocRoot,
bool includeMetadata )
const
1360 QSvgGenerator generator;
1361 if ( includeMetadata )
1364 generator.setTitle( l->name() );
1365 else if ( mLayout->project() )
1366 generator.setTitle( mLayout->project()->title() );
1369 generator.setOutputDevice( &svgBuffer );
1370 generator.setSize( QSize(
static_cast< int >( std::round( width ) ),
1371 static_cast< int >( std::round( height ) ) ) );
1372 generator.setViewBox( QRect( 0, 0,
1373 static_cast< int >( std::round( width ) ),
1374 static_cast< int >( std::round( height ) ) ) );
1375 generator.setResolution(
static_cast< int >( std::round( settings.dpi ) ) );
1377 QPainter svgPainter( &generator );
1378 if ( settings.cropToContents )
1389 svgBuffer.open( QIODevice::ReadOnly );
1393 if ( ! doc.setContent( &svgBuffer,
false, &errorMsg, &errorLine ) )
1395 mErrorFileName = filename;
1398 if ( 1 == svgLayerId )
1400 svg = QDomDocument( doc.doctype() );
1401 svg.appendChild( svg.importNode( doc.firstChild(),
false ) );
1402 svgDocRoot = svg.importNode( doc.elementsByTagName( QStringLiteral(
"svg" ) ).at( 0 ),
false );
1403 svgDocRoot.toElement().setAttribute( QStringLiteral(
"xmlns:inkscape" ), QStringLiteral(
"http://www.inkscape.org/namespaces/inkscape" ) );
1404 svg.appendChild( svgDocRoot );
1406 QDomNode mainGroup = svg.importNode( doc.elementsByTagName( QStringLiteral(
"g" ) ).at( 0 ),
true );
1407 mainGroup.toElement().setAttribute( QStringLiteral(
"id" ), layerName );
1408 mainGroup.toElement().setAttribute( QStringLiteral(
"inkscape:label" ), layerName );
1409 mainGroup.toElement().setAttribute( QStringLiteral(
"inkscape:groupmode" ), QStringLiteral(
"layer" ) );
1410 QDomNode defs = svg.importNode( doc.elementsByTagName( QStringLiteral(
"defs" ) ).at( 0 ),
true );
1411 svgDocRoot.appendChild( defs );
1412 svgDocRoot.appendChild( mainGroup );
1417 void QgsLayoutExporter::appendMetadataToSvg( QDomDocument &svg )
const
1420 QDomElement metadataElement = svg.createElement( QStringLiteral(
"metadata" ) );
1421 QDomElement rdfElement = svg.createElement( QStringLiteral(
"rdf:RDF" ) );
1422 rdfElement.setAttribute( QStringLiteral(
"xmlns:rdf" ), QStringLiteral(
"http://www.w3.org/1999/02/22-rdf-syntax-ns#" ) );
1423 rdfElement.setAttribute( QStringLiteral(
"xmlns:rdfs" ), QStringLiteral(
"http://www.w3.org/2000/01/rdf-schema#" ) );
1424 rdfElement.setAttribute( QStringLiteral(
"xmlns:dc" ), QStringLiteral(
"http://purl.org/dc/elements/1.1/" ) );
1425 QDomElement descriptionElement = svg.createElement( QStringLiteral(
"rdf:Description" ) );
1426 QDomElement workElement = svg.createElement( QStringLiteral(
"cc:Work" ) );
1427 workElement.setAttribute( QStringLiteral(
"rdf:about" ), QString() );
1429 auto addTextNode = [&workElement, &descriptionElement, &svg](
const QString & tag,
const QString & value )
1432 QDomElement element = svg.createElement( tag );
1433 QDomText t = svg.createTextNode( value );
1434 element.appendChild( t );
1435 workElement.appendChild( element );
1438 descriptionElement.setAttribute( tag, value );
1441 addTextNode( QStringLiteral(
"dc:format" ), QStringLiteral(
"image/svg+xml" ) );
1442 addTextNode( QStringLiteral(
"dc:title" ), metadata.
title() );
1443 addTextNode( QStringLiteral(
"dc:date" ), metadata.
creationDateTime().toString( Qt::ISODate ) );
1444 addTextNode( QStringLiteral(
"dc:identifier" ), metadata.
identifier() );
1445 addTextNode( QStringLiteral(
"dc:description" ), metadata.
abstract() );
1447 auto addAgentNode = [&workElement, &descriptionElement, &svg](
const QString & tag,
const QString & value )
1450 QDomElement inkscapeElement = svg.createElement( tag );
1451 QDomElement agentElement = svg.createElement( QStringLiteral(
"cc:Agent" ) );
1452 QDomElement titleElement = svg.createElement( QStringLiteral(
"dc:title" ) );
1453 QDomText t = svg.createTextNode( value );
1454 titleElement.appendChild( t );
1455 agentElement.appendChild( titleElement );
1456 inkscapeElement.appendChild( agentElement );
1457 workElement.appendChild( inkscapeElement );
1460 QDomElement bagElement = svg.createElement( QStringLiteral(
"rdf:Bag" ) );
1461 QDomElement liElement = svg.createElement( QStringLiteral(
"rdf:li" ) );
1462 t = svg.createTextNode( value );
1463 liElement.appendChild( t );
1464 bagElement.appendChild( liElement );
1466 QDomElement element = svg.createElement( tag );
1467 element.appendChild( bagElement );
1468 descriptionElement.appendChild( element );
1471 addAgentNode( QStringLiteral(
"dc:creator" ), metadata.
author() );
1472 addAgentNode( QStringLiteral(
"dc:publisher" ), QStringLiteral(
"QGIS %1" ).arg(
Qgis::version() ) );
1476 QDomElement element = svg.createElement( QStringLiteral(
"dc:subject" ) );
1477 QDomElement bagElement = svg.createElement( QStringLiteral(
"rdf:Bag" ) );
1479 for (
auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
1481 const QStringList words = it.value();
1482 for (
const QString &keyword : words )
1484 QDomElement liElement = svg.createElement( QStringLiteral(
"rdf:li" ) );
1485 QDomText t = svg.createTextNode( keyword );
1486 liElement.appendChild( t );
1487 bagElement.appendChild( liElement );
1490 element.appendChild( bagElement );
1491 workElement.appendChild( element );
1492 descriptionElement.appendChild( element );
1495 rdfElement.appendChild( descriptionElement );
1496 rdfElement.appendChild( workElement );
1497 metadataElement.appendChild( rdfElement );
1498 svg.documentElement().appendChild( metadataElement );
1499 svg.documentElement().setAttribute( QStringLiteral(
"xmlns:cc" ), QStringLiteral(
"http://creativecommons.org/ns#" ) );
1502 std::unique_ptr<double[]> QgsLayoutExporter::computeGeoTransform(
const QgsLayoutItemMap *map,
const QRectF ®ion,
double dpi )
const
1505 map = mLayout->referenceMap();
1511 dpi = mLayout->renderContext().dpi();
1514 QRectF exportRegion = region;
1515 if ( !exportRegion.isValid() )
1517 int pageNumber = map->
page();
1520 double pageY = page->pos().y();
1521 QSizeF pageSize = page->rect().size();
1522 exportRegion = QRectF( 0, pageY, pageSize.width(), pageSize.height() );
1526 QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
1529 double outputHeightMM = exportRegion.height();
1530 double outputWidthMM = exportRegion.width();
1534 double mapXCenter = mapExtent.
center().
x();
1535 double mapYCenter = mapExtent.
center().
y();
1537 double sinAlpha = std::sin( alpha );
1538 double cosAlpha = std::cos( alpha );
1541 QPointF mapItemPos = map->pos();
1543 mapItemPos.rx() -= exportRegion.left();
1544 mapItemPos.ry() -= exportRegion.top();
1547 double xRatio = mapExtent.
width() / mapItemSceneRect.width();
1548 double yRatio = mapExtent.
height() / mapItemSceneRect.height();
1549 double xmin = mapExtent.
xMinimum() - mapItemPos.x() * xRatio;
1550 double ymax = mapExtent.
yMaximum() + mapItemPos.y() * yRatio;
1551 QgsRectangle paperExtent( xmin, ymax - outputHeightMM * yRatio, xmin + outputWidthMM * xRatio, ymax );
1554 double X0 = paperExtent.xMinimum();
1555 double Y0 = paperExtent.yMaximum();
1560 double X1 = X0 - mapXCenter;
1561 double Y1 = Y0 - mapYCenter;
1562 double X2 = X1 * cosAlpha + Y1 * sinAlpha;
1563 double Y2 = -X1 * sinAlpha + Y1 * cosAlpha;
1564 X0 = X2 + mapXCenter;
1565 Y0 = Y2 + mapYCenter;
1569 int pageWidthPixels =
static_cast< int >( dpi * outputWidthMM / 25.4 );
1570 int pageHeightPixels =
static_cast< int >( dpi * outputHeightMM / 25.4 );
1571 double pixelWidthScale = paperExtent.width() / pageWidthPixels;
1572 double pixelHeightScale = paperExtent.height() / pageHeightPixels;
1575 std::unique_ptr<double[]> t(
new double[6] );
1577 t[1] = cosAlpha * pixelWidthScale;
1578 t[2] = -sinAlpha * pixelWidthScale;
1580 t[4] = -sinAlpha * pixelHeightScale;
1581 t[5] = -cosAlpha * pixelHeightScale;
1586 void QgsLayoutExporter::writeWorldFile(
const QString &worldFileName,
double a,
double b,
double c,
double d,
double e,
double f )
const
1588 QFile worldFile( worldFileName );
1589 if ( !worldFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
1593 QTextStream fout( &worldFile );
1597 fout << QString::number( a,
'f', 12 ) <<
"\r\n";
1598 fout << QString::number( d,
'f', 12 ) <<
"\r\n";
1599 fout << QString::number( b,
'f', 12 ) <<
"\r\n";
1600 fout << QString::number( e,
'f', 12 ) <<
"\r\n";
1601 fout << QString::number(
c,
'f', 12 ) <<
"\r\n";
1602 fout << QString::number( f,
'f', 12 ) <<
"\r\n";
1607 return georeferenceOutputPrivate( file, map, exportRegion, dpi,
false );
1610 bool QgsLayoutExporter::georeferenceOutputPrivate(
const QString &file,
QgsLayoutItemMap *map,
const QRectF &exportRegion,
double dpi,
bool includeGeoreference,
bool includeMetadata )
const
1615 if ( !map && includeGeoreference )
1616 map = mLayout->referenceMap();
1618 std::unique_ptr<double[]> t;
1620 if ( map && includeGeoreference )
1623 dpi = mLayout->renderContext().dpi();
1625 t = computeGeoTransform( map, exportRegion, dpi );
1630 CPLSetConfigOption(
"GDAL_PDF_DPI", QString::number( dpi ).toLocal8Bit().constData() );
1635 GDALSetGeoTransform( outputDS.get(), t.get() );
1637 if ( includeMetadata )
1639 QString creationDateString;
1640 const QDateTime creationDateTime = mLayout->project()->metadata().creationDateTime();
1641 if ( creationDateTime.isValid() )
1643 creationDateString = QStringLiteral(
"D:%1" ).arg( mLayout->project()->metadata().creationDateTime().toString( QStringLiteral(
"yyyyMMddHHmmss" ) ) );
1644 if ( creationDateTime.timeZone().isValid() )
1646 int offsetFromUtc = creationDateTime.timeZone().offsetFromUtc( creationDateTime );
1647 creationDateString += ( offsetFromUtc >= 0 ) ?
'+' :
'-';
1648 offsetFromUtc = std::abs( offsetFromUtc );
1649 int offsetHours = offsetFromUtc / 3600;
1650 int offsetMins = ( offsetFromUtc % 3600 ) / 60;
1651 creationDateString += QStringLiteral(
"%1'%2'" ).arg( offsetHours ).arg( offsetMins );
1654 GDALSetMetadataItem( outputDS.get(),
"CREATION_DATE", creationDateString.toUtf8().constData(),
nullptr );
1656 GDALSetMetadataItem( outputDS.get(),
"AUTHOR", mLayout->project()->metadata().author().toUtf8().constData(),
nullptr );
1657 const QString creator = QStringLiteral(
"QGIS %1" ).arg(
Qgis::version() );
1658 GDALSetMetadataItem( outputDS.get(),
"CREATOR", creator.toUtf8().constData(),
nullptr );
1659 GDALSetMetadataItem( outputDS.get(),
"PRODUCER", creator.toUtf8().constData(),
nullptr );
1660 GDALSetMetadataItem( outputDS.get(),
"SUBJECT", mLayout->project()->metadata().abstract().toUtf8().constData(),
nullptr );
1661 GDALSetMetadataItem( outputDS.get(),
"TITLE", mLayout->project()->metadata().title().toUtf8().constData(),
nullptr );
1664 QStringList allKeywords;
1665 for (
auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
1667 allKeywords.append( QStringLiteral(
"%1: %2" ).arg( it.key(), it.value().join(
',' ) ) );
1669 const QString keywordString = allKeywords.join(
';' );
1670 GDALSetMetadataItem( outputDS.get(),
"KEYWORDS", keywordString.toUtf8().constData(),
nullptr );
1676 CPLSetConfigOption(
"GDAL_PDF_DPI",
nullptr );
1683 if ( items.count() == 1 )
1687 QString name = layoutItem->displayName();
1689 if ( name.startsWith(
'<' ) && name.endsWith(
'>' ) )
1690 name = name.mid( 1, name.length() - 2 );
1694 else if ( items.count() > 1 )
1696 QStringList currentLayerItemTypes;
1697 for ( QGraphicsItem *item : items )
1703 if ( !currentLayerItemTypes.contains( itemType ) && !currentLayerItemTypes.contains( itemTypePlural ) )
1704 currentLayerItemTypes << itemType;
1705 else if ( currentLayerItemTypes.contains( itemType ) )
1707 currentLayerItemTypes.replace( currentLayerItemTypes.indexOf( itemType ), itemTypePlural );
1712 if ( !currentLayerItemTypes.contains( QObject::tr(
"Other" ) ) )
1713 currentLayerItemTypes.append( QObject::tr(
"Other" ) );
1716 return currentLayerItemTypes.join( QLatin1String(
", " ) );
1718 return QObject::tr(
"Layer %1" ).arg( layerId );
1724 LayoutItemHider itemHider( items );
1729 unsigned int layerId = 1;
1731 itemHider.hideAll();
1732 const QList< QGraphicsItem * > itemsToIterate = itemHider.itemsToIterate();
1733 QList< QGraphicsItem * > currentLayerItems;
1734 for ( QGraphicsItem *item : itemsToIterate )
1738 bool canPlaceInExistingLayer =
false;
1745 switch ( prevItemBehavior )
1748 canPlaceInExistingLayer =
true;
1752 canPlaceInExistingLayer = prevType == -1 || prevType == layoutItem->
type();
1757 canPlaceInExistingLayer =
false;
1765 switch ( prevItemBehavior )
1769 canPlaceInExistingLayer = prevType == -1 || prevType == layoutItem->
type();
1774 canPlaceInExistingLayer =
false;
1782 canPlaceInExistingLayer =
false;
1787 canPlaceInExistingLayer =
false;
1791 prevType = layoutItem->
type();
1798 if ( canPlaceInExistingLayer )
1800 currentLayerItems << item;
1805 if ( !currentLayerItems.isEmpty() )
1809 ExportResult result = exportFunc( layerId, layerDetails );
1813 currentLayerItems.clear();
1816 itemHider.hideAll();
1821 int layoutItemLayerIdx = 0;
1823 mLayout->renderContext().setCurrentExportLayer( layoutItemLayerIdx );
1829 mLayout->renderContext().setCurrentExportLayer( layoutItemLayerIdx );
1833 ExportResult result = exportFunc( layerId, layerDetails );
1838 layoutItemLayerIdx++;
1840 layerDetails.mapLayerId.clear();
1842 mLayout->renderContext().setCurrentExportLayer( -1 );
1845 currentLayerItems.clear();
1849 currentLayerItems << item;
1853 if ( !currentLayerItems.isEmpty() )
1856 ExportResult result = exportFunc( layerId, layerDetails );
1871 return simplifyMethod;
1885 int pageNumber = map->
page();
1887 double pageY = page->pos().y();
1888 QSizeF pageSize = page->rect().size();
1889 QRectF pageRect( 0, pageY, pageSize.width(), pageSize.height() );
1905 double destinationHeight = exportRegion.height();
1906 double destinationWidth = exportRegion.width();
1908 QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
1913 double xRatio = mapExtent.
width() / mapItemSceneRect.width();
1914 double yRatio = mapExtent.
height() / mapItemSceneRect.height();
1916 double xCenter = mapExtent.
center().
x();
1917 double yCenter = mapExtent.
center().
y();
1920 QPointF mapItemPos = map->pos();
1922 mapItemPos.rx() -= exportRegion.left();
1923 mapItemPos.ry() -= exportRegion.top();
1925 double xmin = mapExtent.
xMinimum() - mapItemPos.x() * xRatio;
1926 double ymax = mapExtent.
yMaximum() + mapItemPos.y() * yRatio;
1927 QgsRectangle paperExtent( xmin, ymax - destinationHeight * yRatio, xmin + destinationWidth * xRatio, ymax );
1929 double X0 = paperExtent.
xMinimum();
1930 double Y0 = paperExtent.
yMinimum();
1933 dpi = mLayout->renderContext().dpi();
1935 int widthPx =
static_cast< int >( dpi * destinationWidth / 25.4 );
1936 int heightPx =
static_cast< int >( dpi * destinationHeight / 25.4 );
1938 double Ww = paperExtent.
width() / widthPx;
1939 double Hh = paperExtent.
height() / heightPx;
1948 s[5] = Y0 + paperExtent.
height();
1952 r[0] = std::cos( alpha );
1953 r[1] = -std::sin( alpha );
1954 r[2] = xCenter * ( 1 - std::cos( alpha ) ) + yCenter * std::sin( alpha );
1955 r[3] = std::sin( alpha );
1956 r[4] = std::cos( alpha );
1957 r[5] = - xCenter * std::sin( alpha ) + yCenter * ( 1 - std::cos( alpha ) );
1960 a = r[0] * s[0] + r[1] * s[3];
1961 b = r[0] * s[1] + r[1] * s[4];
1962 c = r[0] * s[2] + r[1] * s[5] + r[2];
1963 d = r[3] * s[0] + r[4] * s[3];
1964 e = r[3] * s[1] + r[4] * s[4];
1965 f = r[3] * s[2] + r[4] * s[5] + r[5];
1973 QList< QgsLayoutItem *> items;
1979 if ( currentItem->isVisible() && currentItem->requiresRasterization() )
1990 QList< QgsLayoutItem *> items;
1996 if ( currentItem->isVisible() && currentItem->containsAdvancedEffects() )
2009 if ( mLayout->pageCollection()->pageCount() == 1 )
2012 bounds = mLayout->layoutBounds(
true );
2017 bounds = mLayout->pageItemBounds( page,
true );
2019 if ( bounds.width() <= 0 || bounds.height() <= 0 )
2027 bounds = bounds.adjusted( -settings.
cropMargins.
left() * pixelToLayoutUnits,
2039 int QgsLayoutExporter::firstPageToBeExported(
QgsLayout *layout )
2042 for (
int i = 0; i < pageCount; ++i )
2056 if ( details.
page == 0 )
2066 void QgsLayoutExporter::captureLabelingResults()
2068 qDeleteAll( mLabelingResults );
2069 mLabelingResults.clear();
2071 QList< QgsLayoutItemMap * > maps;
2072 mLayout->layoutItems( maps );
2076 mLabelingResults[ map->
uuid() ] = map->mExportLabelingResults.release();
2080 bool QgsLayoutExporter::saveImage(
const QImage &image,
const QString &imageFilename,
const QString &imageFormat,
QgsProject *projectForMetadata )
2082 QImageWriter w( imageFilename, imageFormat.toLocal8Bit().constData() );
2083 if ( imageFormat.compare( QLatin1String(
"tiff" ), Qt::CaseInsensitive ) == 0 || imageFormat.compare( QLatin1String(
"tif" ), Qt::CaseInsensitive ) == 0 )
2085 w.setCompression( 1 );
2087 if ( projectForMetadata )
2089 w.setText( QStringLiteral(
"Author" ), projectForMetadata->
metadata().
author() );
2090 const QString creator = QStringLiteral(
"QGIS %1" ).arg(
Qgis::version() );
2091 w.setText( QStringLiteral(
"Creator" ), creator );
2092 w.setText( QStringLiteral(
"Producer" ), creator );
2093 w.setText( QStringLiteral(
"Subject" ), projectForMetadata->
metadata().
abstract() );
2094 w.setText( QStringLiteral(
"Created" ), projectForMetadata->
metadata().
creationDateTime().toString( Qt::ISODate ) );
2095 w.setText( QStringLiteral(
"Title" ), projectForMetadata->
metadata().
title() );
2098 QStringList allKeywords;
2099 for (
auto it = keywords.constBegin(); it != keywords.constEnd(); ++it )
2101 allKeywords.append( QStringLiteral(
"%1: %2" ).arg( it.key(), it.value().join(
',' ) ) );
2103 const QString keywordString = allKeywords.join(
';' );
2104 w.setText( QStringLiteral(
"Keywords" ), keywordString );
2106 return w.write( image );
2109 #endif // ! QT_NO_PRINTER