25#include "cpl_string.h"
28#include <QMutexLocker>
29#include <QDomDocument>
38 GDALDriverH hDriverMem = GDALGetDriverByName(
"PDF" );
44 const char *pHavePoppler = GDALGetMetadataItem( hDriverMem,
"HAVE_POPPLER",
nullptr );
45 if ( pHavePoppler && strstr( pHavePoppler,
"YES" ) )
48 const char *pHavePdfium = GDALGetMetadataItem( hDriverMem,
"HAVE_PDFIUM",
nullptr );
49 if ( pHavePdfium && strstr( pHavePdfium,
"YES" ) )
58 GDALDriverH hDriverMem = GDALGetDriverByName(
"PDF" );
61 return QObject::tr(
"No GDAL PDF driver available." );
64 const char *pHavePoppler = GDALGetMetadataItem( hDriverMem,
"HAVE_POPPLER",
nullptr );
65 if ( pHavePoppler && strstr( pHavePoppler,
"YES" ) )
68 const char *pHavePdfium = GDALGetMetadataItem( hDriverMem,
"HAVE_PDFIUM",
nullptr );
69 if ( pHavePdfium && strstr( pHavePdfium,
"YES" ) )
72 return QObject::tr(
"GDAL PDF driver was not built with PDF read support. A build with PDF read support is required for GeoPDF creation." );
77 QgsDebugError( QStringLiteral(
"GDAL PDF creation error: %1 " ).arg( msg ) );
78 if ( QStringList *errorList =
static_cast< QStringList *
>( CPLGetErrorHandlerUserData() ) )
80 errorList->append( QString( msg ) );
89 const QString composition = createCompositionXml( components, details );
91 if ( composition.isEmpty() )
95 GDALDriverH driver = GDALGetDriverByName(
"PDF" );
98 mErrorMessage = QObject::tr(
"Cannot load GDAL PDF driver" );
103 QFile file( xmlFilePath );
104 if ( file.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
106 QTextStream out( &file );
107#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
108 out.setCodec(
"UTF-8" );
114 mErrorMessage = QObject::tr(
"Could not create GeoPDF composition file" );
118 char **papszOptions = CSLSetNameValue(
nullptr,
"COMPOSITION_FILE", xmlFilePath.toUtf8().constData() );
120 QStringList creationErrors;
124 gdal::dataset_unique_ptr outputDataset( GDALCreate( driver, destinationFile.toUtf8().constData(), 0, 0, 0, GDT_Unknown, papszOptions ) );
126 CPLPopErrorHandler();
127 const bool res = outputDataset.get() !=
nullptr;
130 if ( creationErrors.size() == 1 )
132 mErrorMessage = QObject::tr(
"Could not create PDF file: %1" ).arg( creationErrors.at( 0 ) );
134 else if ( !creationErrors.empty() )
136 mErrorMessage = QObject::tr(
"Could not create PDF file. Received errors:\n" );
137 for (
const QString &error : std::as_const( creationErrors ) )
139 mErrorMessage += ( !mErrorMessage.isEmpty() ? QStringLiteral(
"\n" ) : QString() ) + error;
145 mErrorMessage = QObject::tr(
"Could not create PDF file, but no error details are available" );
148 outputDataset.reset();
150 CSLDestroy( papszOptions );
164 case QPainter::CompositionMode_SourceOver:
165 case QPainter::CompositionMode_Multiply:
166 case QPainter::CompositionMode_Screen:
167 case QPainter::CompositionMode_Overlay:
168 case QPainter::CompositionMode_Darken:
169 case QPainter::CompositionMode_Lighten:
170 case QPainter::CompositionMode_ColorDodge:
171 case QPainter::CompositionMode_ColorBurn:
172 case QPainter::CompositionMode_HardLight:
173 case QPainter::CompositionMode_SoftLight:
174 case QPainter::CompositionMode_Difference:
175 case QPainter::CompositionMode_Exclusion:
188 QMutexLocker locker( &mMutex );
193 mCollatedFeatures[ group ][ layerId ].append( f );
196bool QgsAbstractGeoPdfExporter::saveTemporaryLayers()
198 for (
auto groupIt = mCollatedFeatures.constBegin(); groupIt != mCollatedFeatures.constEnd(); ++groupIt )
200 for (
auto it = groupIt->constBegin(); it != groupIt->constEnd(); ++it )
204 VectorComponentDetail detail = componentDetailForLayerId( it.key() );
205 detail.sourceVectorPath = filePath;
206 detail.group = groupIt.key();
212 saveOptions.
driverName = QStringLiteral(
"GPKG" );
215 if ( writer->hasError() )
217 mErrorMessage = writer->errorMessage();
226 mErrorMessage = writer->errorMessage();
231 detail.sourceVectorLayer = layerName;
232 mVectorComponents << detail;
242 bool initiallyVisible =
false;
244 QString mutuallyExclusiveGroupId;
246 std::vector< std::unique_ptr< TreeNode > > children;
247 TreeNode *parent =
nullptr;
249 void addChild( std::unique_ptr< TreeNode > child )
251 child->parent =
this;
252 children.emplace_back( std::move( child ) );
255 QDomElement toElement( QDomDocument &doc )
const
257 QDomElement layerElement = doc.createElement( QStringLiteral(
"Layer" ) );
258 layerElement.setAttribute( QStringLiteral(
"id" ),
id );
259 layerElement.setAttribute( QStringLiteral(
"name" ), name );
260 layerElement.setAttribute( QStringLiteral(
"initiallyVisible" ), initiallyVisible ? QStringLiteral(
"true" ) : QStringLiteral(
"false" ) );
261 if ( !mutuallyExclusiveGroupId.isEmpty() )
262 layerElement.setAttribute( QStringLiteral(
"mutuallyExclusiveGroupId" ), mutuallyExclusiveGroupId );
264 for (
const auto &child : children )
266 layerElement.appendChild( child->toElement( doc ) );
272 QDomElement createIfLayerOnElement( QDomDocument &doc, QDomElement &contentElement )
const
274 QDomElement element = doc.createElement( QStringLiteral(
"IfLayerOn" ) );
275 element.setAttribute( QStringLiteral(
"layerId" ),
id );
276 contentElement.appendChild( element );
280 QDomElement createNestedIfLayerOnElements( QDomDocument &doc, QDomElement &contentElement )
const
282 TreeNode *currentParent = parent;
283 QDomElement finalElement = doc.createElement( QStringLiteral(
"IfLayerOn" ) );
284 finalElement.setAttribute( QStringLiteral(
"layerId" ),
id );
286 QDomElement currentElement = finalElement;
287 while ( currentParent )
289 QDomElement ifGroupOn = doc.createElement( QStringLiteral(
"IfLayerOn" ) );
290 ifGroupOn.setAttribute( QStringLiteral(
"layerId" ), currentParent->id );
291 ifGroupOn.appendChild( currentElement );
292 currentElement = ifGroupOn;
293 currentParent = currentParent->parent;
295 contentElement.appendChild( currentElement );
301QString QgsAbstractGeoPdfExporter::createCompositionXml(
const QList<ComponentLayerDetail> &components,
const ExportDetails &details )
305 QDomElement compositionElem = doc.createElement( QStringLiteral(
"PDFComposition" ) );
308 QDomElement metadata = doc.createElement( QStringLiteral(
"Metadata" ) );
309 if ( !details.author.isEmpty() )
311 QDomElement author = doc.createElement( QStringLiteral(
"Author" ) );
312 author.appendChild( doc.createTextNode( details.author ) );
313 metadata.appendChild( author );
315 if ( !details.producer.isEmpty() )
317 QDomElement producer = doc.createElement( QStringLiteral(
"Producer" ) );
318 producer.appendChild( doc.createTextNode( details.producer ) );
319 metadata.appendChild( producer );
321 if ( !details.creator.isEmpty() )
323 QDomElement creator = doc.createElement( QStringLiteral(
"Creator" ) );
324 creator.appendChild( doc.createTextNode( details.creator ) );
325 metadata.appendChild( creator );
327 if ( details.creationDateTime.isValid() )
329 QDomElement creationDate = doc.createElement( QStringLiteral(
"CreationDate" ) );
330 QString creationDateString = QStringLiteral(
"D:%1" ).arg( details.creationDateTime.toString( QStringLiteral(
"yyyyMMddHHmmss" ) ) );
331 if ( details.creationDateTime.timeZone().isValid() )
333 int offsetFromUtc = details.creationDateTime.timeZone().offsetFromUtc( details.creationDateTime );
334 creationDateString += ( offsetFromUtc >= 0 ) ?
'+' :
'-';
335 offsetFromUtc = std::abs( offsetFromUtc );
336 int offsetHours = offsetFromUtc / 3600;
337 int offsetMins = ( offsetFromUtc % 3600 ) / 60;
338 creationDateString += QStringLiteral(
"%1'%2'" ).arg( offsetHours ).arg( offsetMins );
340 creationDate.appendChild( doc.createTextNode( creationDateString ) );
341 metadata.appendChild( creationDate );
343 if ( !details.subject.isEmpty() )
345 QDomElement subject = doc.createElement( QStringLiteral(
"Subject" ) );
346 subject.appendChild( doc.createTextNode( details.subject ) );
347 metadata.appendChild( subject );
349 if ( !details.title.isEmpty() )
351 QDomElement title = doc.createElement( QStringLiteral(
"Title" ) );
352 title.appendChild( doc.createTextNode( details.title ) );
353 metadata.appendChild( title );
355 if ( !details.keywords.empty() )
357 QStringList allKeywords;
358 for (
auto it = details.keywords.constBegin(); it != details.keywords.constEnd(); ++it )
360 allKeywords.append( QStringLiteral(
"%1: %2" ).arg( it.key(), it.value().join(
',' ) ) );
362 QDomElement keywords = doc.createElement( QStringLiteral(
"Keywords" ) );
363 keywords.appendChild( doc.createTextNode( allKeywords.join(
';' ) ) );
364 metadata.appendChild( keywords );
366 compositionElem.appendChild( metadata );
368 QSet< QString > createdLayerIds;
369 std::vector< std::unique_ptr< TreeNode > > rootGroups;
370 std::vector< std::unique_ptr< TreeNode > > rootLayers;
371 QMap< QString, TreeNode * > groupNameMap;
373 QStringList layerTreeGroupOrder = details.layerTreeGroupOrder;
377 for (
auto it = details.customLayerTreeGroups.constBegin(); it != details.customLayerTreeGroups.constEnd(); ++it )
379 if ( layerTreeGroupOrder.contains( it.value() ) )
381 layerTreeGroupOrder.append( it.value() );
385 if ( details.includeFeatures )
387 for (
const VectorComponentDetail &component : std::as_const( mVectorComponents ) )
389 if ( !component.group.isEmpty() && !layerTreeGroupOrder.contains( component.group ) )
391 layerTreeGroupOrder.append( component.group );
397 for (
const ComponentLayerDetail &component : components )
399 if ( !component.group.isEmpty() && !layerTreeGroupOrder.contains( component.group ) )
401 layerTreeGroupOrder.append( component.group );
405 QMap< QString, TreeNode * > groupNameToTreeNode;
406 QMap< QString, TreeNode * > layerIdToTreeNode;
408 auto createGroup = [&details, &groupNameToTreeNode](
const QString & groupName ) -> std::unique_ptr< TreeNode >
410 std::unique_ptr< TreeNode > group = std::make_unique< TreeNode >();
411 const QString
id = QUuid::createUuid().toString();
413 groupNameToTreeNode[ groupName ] = group.get();
415 group->name = groupName;
416 group->initiallyVisible =
true;
417 if ( details.mutuallyExclusiveGroups.contains( groupName ) )
418 group->mutuallyExclusiveGroupId = QStringLiteral(
"__mutually_exclusive_groups__" );
422 if ( details.includeFeatures )
424 for (
const VectorComponentDetail &component : std::as_const( mVectorComponents ) )
426 const QString destinationGroup = details.customLayerTreeGroups.value( component.mapLayerId, component.group );
428 std::unique_ptr< TreeNode > layer = std::make_unique< TreeNode >();
429 layer->id = destinationGroup.isEmpty() ? component.mapLayerId : QStringLiteral(
"%1_%2" ).arg( destinationGroup, component.mapLayerId );
430 layer->name = details.layerIdToPdfLayerTreeNameMap.contains( component.mapLayerId ) ? details.layerIdToPdfLayerTreeNameMap.value( component.mapLayerId ) : component.name;
431 layer->initiallyVisible = details.initialLayerVisibility.value( component.mapLayerId,
true );
432 layer->mapLayerId = component.mapLayerId;
434 layerIdToTreeNode.insert( component.mapLayerId, layer.get() );
435 if ( !destinationGroup.isEmpty() )
437 if ( TreeNode *groupNode = groupNameMap.value( destinationGroup ) )
439 groupNode->addChild( std::move( layer ) );
443 std::unique_ptr< TreeNode > group = createGroup( destinationGroup );
444 group->addChild( std::move( layer ) );
445 groupNameMap.insert( destinationGroup, group.get() );
446 rootGroups.emplace_back( std::move( group ) );
451 rootLayers.emplace_back( std::move( layer ) );
454 createdLayerIds.insert( component.mapLayerId );
462 for (
const ComponentLayerDetail &component : components )
464 if ( !component.mapLayerId.isEmpty() && createdLayerIds.contains( component.mapLayerId ) )
467 const QString destinationGroup = details.customLayerTreeGroups.value( component.mapLayerId, component.group );
468 if ( destinationGroup.isEmpty() && component.mapLayerId.isEmpty() )
471 std::unique_ptr< TreeNode > mapLayerNode;
472 if ( !component.mapLayerId.isEmpty() )
474 mapLayerNode = std::make_unique< TreeNode >();
475 mapLayerNode->id = destinationGroup.isEmpty() ? component.mapLayerId : QStringLiteral(
"%1_%2" ).arg( destinationGroup, component.mapLayerId );
476 mapLayerNode->name = details.layerIdToPdfLayerTreeNameMap.value( component.mapLayerId, component.name );
477 mapLayerNode->initiallyVisible = details.initialLayerVisibility.value( component.mapLayerId,
true );
479 layerIdToTreeNode.insert( component.mapLayerId, mapLayerNode.get() );
482 if ( !destinationGroup.isEmpty() )
484 if ( TreeNode *groupNode = groupNameMap.value( destinationGroup ) )
487 groupNode->addChild( std::move( mapLayerNode ) );
491 std::unique_ptr< TreeNode > group = createGroup( destinationGroup );
493 group->addChild( std::move( mapLayerNode ) );
494 groupNameMap.insert( destinationGroup, group.get() );
495 rootGroups.emplace_back( std::move( group ) );
501 rootLayers.emplace_back( std::move( mapLayerNode ) );
504 if ( !component.mapLayerId.isEmpty() )
506 createdLayerIds.insert( component.mapLayerId );
511 QDomElement page = doc.createElement( QStringLiteral(
"Page" ) );
512 QDomElement dpi = doc.createElement( QStringLiteral(
"DPI" ) );
515 page.appendChild( dpi );
517 QDomElement width = doc.createElement( QStringLiteral(
"Width" ) );
518 const double pageWidthPdfUnits = std::ceil( details.pageSizeMm.width() / 25.4 * 72 );
519 width.appendChild( doc.createTextNode(
qgsDoubleToString( pageWidthPdfUnits ) ) );
520 page.appendChild( width );
521 QDomElement height = doc.createElement( QStringLiteral(
"Height" ) );
522 const double pageHeightPdfUnits = std::ceil( details.pageSizeMm.height() / 25.4 * 72 );
523 height.appendChild( doc.createTextNode(
qgsDoubleToString( pageHeightPdfUnits ) ) );
524 page.appendChild( height );
530 QDomElement georeferencing = doc.createElement( QStringLiteral(
"Georeferencing" ) );
531 georeferencing.setAttribute( QStringLiteral(
"id" ), QStringLiteral(
"georeferenced_%1" ).arg( i++ ) );
532 georeferencing.setAttribute( QStringLiteral(
"OGCBestPracticeFormat" ), details.useOgcBestPracticeFormatGeoreferencing ? QStringLiteral(
"true" ) : QStringLiteral(
"false" ) );
533 georeferencing.setAttribute( QStringLiteral(
"ISO32000ExtensionFormat" ), details.useIso32000ExtensionFormatGeoreferencing ? QStringLiteral(
"true" ) : QStringLiteral(
"false" ) );
535 if ( section.crs.isValid() )
537 QDomElement srs = doc.createElement( QStringLiteral(
"SRS" ) );
540 if ( !section.crs.authid().isEmpty() && !section.crs.authid().startsWith( QStringLiteral(
"user" ), Qt::CaseInsensitive ) )
542 srs.appendChild( doc.createTextNode( section.crs.authid() ) );
548 georeferencing.appendChild( srs );
551 if ( !section.pageBoundsPolygon.isEmpty() )
560 QDomElement boundingPolygon = doc.createElement( QStringLiteral(
"BoundingPolygon" ) );
563 QTransform t = QTransform::fromTranslate( 0, pageHeightPdfUnits ).scale( pageWidthPdfUnits / details.pageSizeMm.width(),
564 -pageHeightPdfUnits / details.pageSizeMm.height() );
568 boundingPolygon.appendChild( doc.createTextNode( p.
asWkt() ) );
570 georeferencing.appendChild( boundingPolygon );
579 QDomElement boundingBox = doc.createElement( QStringLiteral(
"BoundingBox" ) );
580 boundingBox.setAttribute( QStringLiteral(
"x1" ),
qgsDoubleToString( section.pageBoundsMm.xMinimum() / 25.4 * 72 ) );
581 boundingBox.setAttribute( QStringLiteral(
"y1" ),
qgsDoubleToString( section.pageBoundsMm.yMinimum() / 25.4 * 72 ) );
582 boundingBox.setAttribute( QStringLiteral(
"x2" ),
qgsDoubleToString( section.pageBoundsMm.xMaximum() / 25.4 * 72 ) );
583 boundingBox.setAttribute( QStringLiteral(
"y2" ),
qgsDoubleToString( section.pageBoundsMm.yMaximum() / 25.4 * 72 ) );
584 georeferencing.appendChild( boundingBox );
587 for (
const ControlPoint &point : section.controlPoints )
589 QDomElement cp1 = doc.createElement( QStringLiteral(
"ControlPoint" ) );
590 cp1.setAttribute( QStringLiteral(
"x" ),
qgsDoubleToString( point.pagePoint.x() / 25.4 * 72 ) );
591 cp1.setAttribute( QStringLiteral(
"y" ),
qgsDoubleToString( ( details.pageSizeMm.height() - point.pagePoint.y() ) / 25.4 * 72 ) );
592 cp1.setAttribute( QStringLiteral(
"GeoX" ),
qgsDoubleToString( point.geoPoint.x() ) );
593 cp1.setAttribute( QStringLiteral(
"GeoY" ),
qgsDoubleToString( point.geoPoint.y() ) );
594 georeferencing.appendChild( cp1 );
597 page.appendChild( georeferencing );
600 auto createPdfDatasetElement = [&doc](
const ComponentLayerDetail & component ) -> QDomElement
602 QDomElement pdfDataset = doc.createElement( QStringLiteral(
"PDF" ) );
603 pdfDataset.setAttribute( QStringLiteral(
"dataset" ), component.sourcePdfPath );
604 if ( component.opacity != 1.0 || component.compositionMode != QPainter::CompositionMode_SourceOver )
606 QDomElement blendingElement = doc.createElement( QStringLiteral(
"Blending" ) );
607 blendingElement.setAttribute( QStringLiteral(
"opacity" ), component.opacity );
608 blendingElement.setAttribute( QStringLiteral(
"function" ), compositionModeToString( component.compositionMode ) );
610 pdfDataset.appendChild( blendingElement );
616 QDomElement content = doc.createElement( QStringLiteral(
"Content" ) );
617 for (
const ComponentLayerDetail &component : components )
619 if ( component.mapLayerId.isEmpty() && component.group.isEmpty() )
621 content.appendChild( createPdfDatasetElement( component ) );
623 else if ( !component.mapLayerId.isEmpty() )
625 if ( TreeNode *treeNode = layerIdToTreeNode.value( component.mapLayerId ) )
627 QDomElement ifLayerOnElement = treeNode->createNestedIfLayerOnElements( doc, content );
628 ifLayerOnElement.appendChild( createPdfDatasetElement( component ) );
631 else if ( TreeNode *groupNode = groupNameToTreeNode.value( component.group ) )
633 QDomElement ifGroupOn = groupNode->createIfLayerOnElement( doc, content );
634 ifGroupOn.appendChild( createPdfDatasetElement( component ) );
639 if ( details.includeFeatures )
641 for (
const VectorComponentDetail &component : std::as_const( mVectorComponents ) )
643 if ( TreeNode *treeNode = layerIdToTreeNode.value( component.mapLayerId ) )
645 QDomElement ifLayerOnElement = treeNode->createNestedIfLayerOnElements( doc, content );
647 QDomElement vectorDataset = doc.createElement( QStringLiteral(
"Vector" ) );
648 vectorDataset.setAttribute( QStringLiteral(
"dataset" ), component.sourceVectorPath );
649 vectorDataset.setAttribute( QStringLiteral(
"layer" ), component.sourceVectorLayer );
650 vectorDataset.setAttribute( QStringLiteral(
"visible" ), QStringLiteral(
"false" ) );
651 QDomElement logicalStructure = doc.createElement( QStringLiteral(
"LogicalStructure" ) );
652 logicalStructure.setAttribute( QStringLiteral(
"displayLayerName" ), component.name );
653 if ( !component.displayAttribute.isEmpty() )
654 logicalStructure.setAttribute( QStringLiteral(
"fieldToDisplay" ), component.displayAttribute );
655 vectorDataset.appendChild( logicalStructure );
656 ifLayerOnElement.appendChild( vectorDataset );
661 page.appendChild( content );
664 QDomElement
layerTree = doc.createElement( QStringLiteral(
"LayerTree" ) );
670 std::sort( rootGroups.begin(), rootGroups.end(), [&layerTreeGroupOrder](
const std::unique_ptr< TreeNode > &a,
const std::unique_ptr< TreeNode > &b ) ->
bool
672 return layerTreeGroupOrder.indexOf( a->name ) < layerTreeGroupOrder.indexOf( b->name );
675 bool haveFoundMutuallyExclusiveGroup =
false;
676 for (
const auto &node : std::as_const( rootGroups ) )
678 if ( !node->mutuallyExclusiveGroupId.isEmpty() )
681 node->initiallyVisible = !haveFoundMutuallyExclusiveGroup;
682 haveFoundMutuallyExclusiveGroup =
true;
684 layerTree.appendChild( node->toElement( doc ) );
688 layerTreeGroupOrder.erase( std::remove_if( layerTreeGroupOrder.begin(), layerTreeGroupOrder.end(), [&details](
const QString & group )
690 return details.customLayerTreeGroups.key( group ).isEmpty();
691 } ), layerTreeGroupOrder.end() );
695 std::sort( rootLayers.begin(), rootLayers.end(), [&details](
const std::unique_ptr< TreeNode > &a,
const std::unique_ptr< TreeNode > &b ) ->
bool
697 const int indexA = details.layerOrder.indexOf( a->mapLayerId );
698 const int indexB = details.layerOrder.indexOf( b->mapLayerId );
700 if ( indexA >= 0 && indexB >= 0 )
701 return indexA < indexB;
702 else if ( indexA >= 0 )
704 else if ( indexB >= 0 )
707 return a->name.localeAwareCompare( b->name ) < 0;
710 for (
const auto &node : std::as_const( rootLayers ) )
712 layerTree.appendChild( node->toElement( doc ) );
715 compositionElem.appendChild( layerTree );
716 compositionElem.appendChild( page );
718 doc.appendChild( compositionElem );
721 QTextStream stream( &composition );
722 doc.save( stream, -1 );
727QString QgsAbstractGeoPdfExporter::compositionModeToString( QPainter::CompositionMode mode )
731 case QPainter::CompositionMode_SourceOver:
732 return QStringLiteral(
"Normal" );
734 case QPainter::CompositionMode_Multiply:
735 return QStringLiteral(
"Multiply" );
737 case QPainter::CompositionMode_Screen:
738 return QStringLiteral(
"Screen" );
740 case QPainter::CompositionMode_Overlay:
741 return QStringLiteral(
"Overlay" );
743 case QPainter::CompositionMode_Darken:
744 return QStringLiteral(
"Darken" );
746 case QPainter::CompositionMode_Lighten:
747 return QStringLiteral(
"Lighten" );
749 case QPainter::CompositionMode_ColorDodge:
750 return QStringLiteral(
"ColorDodge" );
752 case QPainter::CompositionMode_ColorBurn:
753 return QStringLiteral(
"ColorBurn" );
755 case QPainter::CompositionMode_HardLight:
756 return QStringLiteral(
"HardLight" );
758 case QPainter::CompositionMode_SoftLight:
759 return QStringLiteral(
"SoftLight" );
761 case QPainter::CompositionMode_Difference:
762 return QStringLiteral(
"Difference" );
764 case QPainter::CompositionMode_Exclusion:
765 return QStringLiteral(
"Exclusion" );
771 QgsDebugError( QStringLiteral(
"Unsupported PDF blend mode %1" ).arg( mode ) );
772 return QStringLiteral(
"Normal" );
@ PreferredGdal
Preferred format for conversion of CRS to WKT for use with the GDAL library.
@ NoSymbology
Export only data.
void pushRenderedFeature(const QString &layerId, const QgsAbstractGeoPdfExporter::RenderedFeature &feature, const QString &group=QString())
Called multiple times during the rendering operation, whenever a feature associated with the specifie...
QString generateTemporaryFilepath(const QString &filename) const
Returns a file path to use for temporary files required for GeoPDF creation.
static bool geoPDFCreationAvailable()
Returns true if the current QGIS build is capable of GeoPDF support.
static bool compositionModeSupported(QPainter::CompositionMode mode)
Returns true if the specified composition mode is supported for layers during GeoPDF exports.
static QString geoPDFAvailabilityExplanation()
Returns a user-friendly, translated string explaining why GeoPDF export support is not available on t...
bool finalize(const QList< QgsAbstractGeoPdfExporter::ComponentLayerDetail > &components, const QString &destinationFile, const ExportDetails &details)
To be called after the rendering operation is complete.
This class represents a coordinate reference system (CRS).
Contains information about the context in which a coordinate transform is executed.
void transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection d=Qgis::TransformDirection::Forward, bool transformZ=false) override
Transforms the geometry using a coordinate transform.
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
@ RegeneratePrimaryKey
This flag indicates, that a primary key field cannot be guaranteed to be unique and the sink should i...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
static QString stringToSafeFilename(const QString &string)
Converts a string to a safe filename, replacing characters which are not safe for filenames with an '...
QString asWkt(int precision=17) const override
Returns a WKT representation of the geometry.
Options to pass to writeAsVectorFormat()
QString driverName
OGR driver to use.
Qgis::FeatureSymbologyExport symbologyExport
Symbology to export.
static QgsVectorFileWriter * create(const QString &fileName, const QgsFields &fields, Qgis::WkbType geometryType, const QgsCoordinateReferenceSystem &srs, const QgsCoordinateTransformContext &transformContext, const QgsVectorFileWriter::SaveVectorOptions &options, QgsFeatureSink::SinkFlags sinkFlags=QgsFeatureSink::SinkFlags(), QString *newFilename=nullptr, QString *newLayer=nullptr)
Create a new vector file writer.
QgsLayerTree * layerTree(const QgsWmsRenderContext &context)
std::unique_ptr< std::remove_pointer< GDALDatasetH >::type, GDALDatasetCloser > dataset_unique_ptr
Scoped GDAL dataset.
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
void CPL_STDCALL collectErrors(CPLErr, int, const char *msg)
QList< QgsFeature > QgsFeatureList
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)
bool includeFeatures
true if feature vector information (such as attributes) should be exported.
Contains information about a feature rendered inside the PDF.
QgsFeature feature
Rendered feature.
QgsGeometry renderedBounds
Bounds, in PDF units, of rendered feature.