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 geospatial PDF 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 geospatial PDF 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();
128 const bool res = outputDataset.get() !=
nullptr;
131 if ( creationErrors.size() == 1 )
133 mErrorMessage = QObject::tr(
"Could not create PDF file: %1" ).arg( creationErrors.at( 0 ) );
135 else if ( !creationErrors.empty() )
137 mErrorMessage = QObject::tr(
"Could not create PDF file. Received errors:\n" );
138 for (
const QString &error : std::as_const( creationErrors ) )
140 mErrorMessage += ( !mErrorMessage.isEmpty() ? QStringLiteral(
"\n" ) : QString() ) + error;
146 mErrorMessage = QObject::tr(
"Could not create PDF file, but no error details are available" );
149 outputDataset.reset();
151 CSLDestroy( papszOptions );
165 case QPainter::CompositionMode_SourceOver:
166 case QPainter::CompositionMode_Multiply:
167 case QPainter::CompositionMode_Screen:
168 case QPainter::CompositionMode_Overlay:
169 case QPainter::CompositionMode_Darken:
170 case QPainter::CompositionMode_Lighten:
171 case QPainter::CompositionMode_ColorDodge:
172 case QPainter::CompositionMode_ColorBurn:
173 case QPainter::CompositionMode_HardLight:
174 case QPainter::CompositionMode_SoftLight:
175 case QPainter::CompositionMode_Difference:
176 case QPainter::CompositionMode_Exclusion:
189 QMutexLocker locker( &mMutex );
194 mCollatedFeatures[ group ][ layerId ].append( f );
197bool QgsAbstractGeospatialPdfExporter::saveTemporaryLayers()
199 for (
auto groupIt = mCollatedFeatures.constBegin(); groupIt != mCollatedFeatures.constEnd(); ++groupIt )
201 for (
auto it = groupIt->constBegin(); it != groupIt->constEnd(); ++it )
205 VectorComponentDetail detail = componentDetailForLayerId( it.key() );
206 detail.sourceVectorPath = filePath;
207 detail.group = groupIt.key();
213 saveOptions.
driverName = QStringLiteral(
"GPKG" );
216 if ( writer->hasError() )
218 mErrorMessage = writer->errorMessage();
227 mErrorMessage = writer->errorMessage();
232 detail.sourceVectorLayer = layerName;
233 mVectorComponents << detail;
243 bool initiallyVisible =
false;
245 QString mutuallyExclusiveGroupId;
247 std::vector< std::unique_ptr< TreeNode > > children;
248 TreeNode *parent =
nullptr;
250 void addChild( std::unique_ptr< TreeNode > child )
252 child->parent =
this;
253 children.emplace_back( std::move( child ) );
256 QDomElement toElement( QDomDocument &doc )
const
258 QDomElement layerElement = doc.createElement( QStringLiteral(
"Layer" ) );
259 layerElement.setAttribute( QStringLiteral(
"id" ),
id );
260 layerElement.setAttribute( QStringLiteral(
"name" ), name );
261 layerElement.setAttribute( QStringLiteral(
"initiallyVisible" ), initiallyVisible ? QStringLiteral(
"true" ) : QStringLiteral(
"false" ) );
262 if ( !mutuallyExclusiveGroupId.isEmpty() )
263 layerElement.setAttribute( QStringLiteral(
"mutuallyExclusiveGroupId" ), mutuallyExclusiveGroupId );
265 for (
const auto &child : children )
267 layerElement.appendChild( child->toElement( doc ) );
273 QDomElement createIfLayerOnElement( QDomDocument &doc, QDomElement &contentElement )
const
275 QDomElement element = doc.createElement( QStringLiteral(
"IfLayerOn" ) );
276 element.setAttribute( QStringLiteral(
"layerId" ),
id );
277 contentElement.appendChild( element );
281 QDomElement createNestedIfLayerOnElements( QDomDocument &doc, QDomElement &contentElement )
const
283 TreeNode *currentParent = parent;
284 QDomElement finalElement = doc.createElement( QStringLiteral(
"IfLayerOn" ) );
285 finalElement.setAttribute( QStringLiteral(
"layerId" ),
id );
287 QDomElement currentElement = finalElement;
288 while ( currentParent )
290 QDomElement ifGroupOn = doc.createElement( QStringLiteral(
"IfLayerOn" ) );
291 ifGroupOn.setAttribute( QStringLiteral(
"layerId" ), currentParent->id );
292 ifGroupOn.appendChild( currentElement );
293 currentElement = ifGroupOn;
294 currentParent = currentParent->parent;
296 contentElement.appendChild( currentElement );
302QString QgsAbstractGeospatialPdfExporter::createCompositionXml(
const QList<ComponentLayerDetail> &components,
const ExportDetails &details )
306 QDomElement compositionElem = doc.createElement( QStringLiteral(
"PDFComposition" ) );
309 QDomElement metadata = doc.createElement( QStringLiteral(
"Metadata" ) );
310 if ( !details.author.isEmpty() )
312 QDomElement author = doc.createElement( QStringLiteral(
"Author" ) );
313 author.appendChild( doc.createTextNode( details.author ) );
314 metadata.appendChild( author );
316 if ( !details.producer.isEmpty() )
318 QDomElement producer = doc.createElement( QStringLiteral(
"Producer" ) );
319 producer.appendChild( doc.createTextNode( details.producer ) );
320 metadata.appendChild( producer );
322 if ( !details.creator.isEmpty() )
324 QDomElement creator = doc.createElement( QStringLiteral(
"Creator" ) );
325 creator.appendChild( doc.createTextNode( details.creator ) );
326 metadata.appendChild( creator );
328 if ( details.creationDateTime.isValid() )
330 QDomElement creationDate = doc.createElement( QStringLiteral(
"CreationDate" ) );
331 QString creationDateString = QStringLiteral(
"D:%1" ).arg( details.creationDateTime.toString( QStringLiteral(
"yyyyMMddHHmmss" ) ) );
332 if ( details.creationDateTime.timeZone().isValid() )
334 int offsetFromUtc = details.creationDateTime.timeZone().offsetFromUtc( details.creationDateTime );
335 creationDateString += ( offsetFromUtc >= 0 ) ?
'+' :
'-';
336 offsetFromUtc = std::abs( offsetFromUtc );
337 int offsetHours = offsetFromUtc / 3600;
338 int offsetMins = ( offsetFromUtc % 3600 ) / 60;
339 creationDateString += QStringLiteral(
"%1'%2'" ).arg( offsetHours ).arg( offsetMins );
341 creationDate.appendChild( doc.createTextNode( creationDateString ) );
342 metadata.appendChild( creationDate );
344 if ( !details.subject.isEmpty() )
346 QDomElement subject = doc.createElement( QStringLiteral(
"Subject" ) );
347 subject.appendChild( doc.createTextNode( details.subject ) );
348 metadata.appendChild( subject );
350 if ( !details.title.isEmpty() )
352 QDomElement title = doc.createElement( QStringLiteral(
"Title" ) );
353 title.appendChild( doc.createTextNode( details.title ) );
354 metadata.appendChild( title );
356 if ( !details.keywords.empty() )
358 QStringList allKeywords;
359 for (
auto it = details.keywords.constBegin(); it != details.keywords.constEnd(); ++it )
361 allKeywords.append( QStringLiteral(
"%1: %2" ).arg( it.key(), it.value().join(
',' ) ) );
363 QDomElement keywords = doc.createElement( QStringLiteral(
"Keywords" ) );
364 keywords.appendChild( doc.createTextNode( allKeywords.join(
';' ) ) );
365 metadata.appendChild( keywords );
367 compositionElem.appendChild( metadata );
369 QSet< QString > createdLayerIds;
370 std::vector< std::unique_ptr< TreeNode > > rootGroups;
371 std::vector< std::unique_ptr< TreeNode > > rootLayers;
372 QMap< QString, TreeNode * > groupNameMap;
374 QStringList layerTreeGroupOrder = details.layerTreeGroupOrder;
378 for (
auto it = details.customLayerTreeGroups.constBegin(); it != details.customLayerTreeGroups.constEnd(); ++it )
380 if ( layerTreeGroupOrder.contains( it.value() ) )
382 layerTreeGroupOrder.append( it.value() );
386 if ( details.includeFeatures )
388 for (
const VectorComponentDetail &component : std::as_const( mVectorComponents ) )
390 if ( !component.group.isEmpty() && !layerTreeGroupOrder.contains( component.group ) )
392 layerTreeGroupOrder.append( component.group );
398 for (
const ComponentLayerDetail &component : components )
400 if ( !component.group.isEmpty() && !layerTreeGroupOrder.contains( component.group ) )
402 layerTreeGroupOrder.append( component.group );
406 QMap< QString, TreeNode * > groupNameToTreeNode;
407 QMap< QString, TreeNode * > layerIdToTreeNode;
409 auto createGroup = [&details, &groupNameToTreeNode](
const QString & groupName ) -> std::unique_ptr< TreeNode >
411 std::unique_ptr< TreeNode > group = std::make_unique< TreeNode >();
412 const QString
id = QUuid::createUuid().toString();
414 groupNameToTreeNode[ groupName ] = group.get();
416 group->name = groupName;
417 group->initiallyVisible =
true;
418 if ( details.mutuallyExclusiveGroups.contains( groupName ) )
419 group->mutuallyExclusiveGroupId = QStringLiteral(
"__mutually_exclusive_groups__" );
423 if ( details.includeFeatures )
425 for (
const VectorComponentDetail &component : std::as_const( mVectorComponents ) )
427 const QString destinationGroup = details.customLayerTreeGroups.value( component.mapLayerId, component.group );
429 std::unique_ptr< TreeNode > layer = std::make_unique< TreeNode >();
430 layer->id = destinationGroup.isEmpty() ? component.mapLayerId : QStringLiteral(
"%1_%2" ).arg( destinationGroup, component.mapLayerId );
431 layer->name = details.layerIdToPdfLayerTreeNameMap.contains( component.mapLayerId ) ? details.layerIdToPdfLayerTreeNameMap.value( component.mapLayerId ) : component.name;
432 layer->initiallyVisible = details.initialLayerVisibility.value( component.mapLayerId,
true );
433 layer->mapLayerId = component.mapLayerId;
435 layerIdToTreeNode.insert( component.mapLayerId, layer.get() );
436 if ( !destinationGroup.isEmpty() )
438 if ( TreeNode *groupNode = groupNameMap.value( destinationGroup ) )
440 groupNode->addChild( std::move( layer ) );
444 std::unique_ptr< TreeNode > group = createGroup( destinationGroup );
445 group->addChild( std::move( layer ) );
446 groupNameMap.insert( destinationGroup, group.get() );
447 rootGroups.emplace_back( std::move( group ) );
452 rootLayers.emplace_back( std::move( layer ) );
455 createdLayerIds.insert( component.mapLayerId );
463 for (
const ComponentLayerDetail &component : components )
465 if ( !component.mapLayerId.isEmpty() && createdLayerIds.contains( component.mapLayerId ) )
468 const QString destinationGroup = details.customLayerTreeGroups.value( component.mapLayerId, component.group );
469 if ( destinationGroup.isEmpty() && component.mapLayerId.isEmpty() )
472 std::unique_ptr< TreeNode > mapLayerNode;
473 if ( !component.mapLayerId.isEmpty() )
475 mapLayerNode = std::make_unique< TreeNode >();
476 mapLayerNode->id = destinationGroup.isEmpty() ? component.mapLayerId : QStringLiteral(
"%1_%2" ).arg( destinationGroup, component.mapLayerId );
477 mapLayerNode->name = details.layerIdToPdfLayerTreeNameMap.value( component.mapLayerId, component.name );
478 mapLayerNode->initiallyVisible = details.initialLayerVisibility.value( component.mapLayerId,
true );
480 layerIdToTreeNode.insert( component.mapLayerId, mapLayerNode.get() );
483 if ( !destinationGroup.isEmpty() )
485 if ( TreeNode *groupNode = groupNameMap.value( destinationGroup ) )
488 groupNode->addChild( std::move( mapLayerNode ) );
492 std::unique_ptr< TreeNode > group = createGroup( destinationGroup );
494 group->addChild( std::move( mapLayerNode ) );
495 groupNameMap.insert( destinationGroup, group.get() );
496 rootGroups.emplace_back( std::move( group ) );
502 rootLayers.emplace_back( std::move( mapLayerNode ) );
505 if ( !component.mapLayerId.isEmpty() )
507 createdLayerIds.insert( component.mapLayerId );
512 QDomElement page = doc.createElement( QStringLiteral(
"Page" ) );
513 QDomElement dpi = doc.createElement( QStringLiteral(
"DPI" ) );
516 page.appendChild( dpi );
518 QDomElement width = doc.createElement( QStringLiteral(
"Width" ) );
519 const double pageWidthPdfUnits = std::ceil( details.pageSizeMm.width() / 25.4 * 72 );
520 width.appendChild( doc.createTextNode(
qgsDoubleToString( pageWidthPdfUnits ) ) );
521 page.appendChild( width );
522 QDomElement height = doc.createElement( QStringLiteral(
"Height" ) );
523 const double pageHeightPdfUnits = std::ceil( details.pageSizeMm.height() / 25.4 * 72 );
524 height.appendChild( doc.createTextNode(
qgsDoubleToString( pageHeightPdfUnits ) ) );
525 page.appendChild( height );
531 QDomElement georeferencing = doc.createElement( QStringLiteral(
"Georeferencing" ) );
532 georeferencing.setAttribute( QStringLiteral(
"id" ), QStringLiteral(
"georeferenced_%1" ).arg( i++ ) );
533 georeferencing.setAttribute( QStringLiteral(
"OGCBestPracticeFormat" ), details.useOgcBestPracticeFormatGeoreferencing ? QStringLiteral(
"true" ) : QStringLiteral(
"false" ) );
534 georeferencing.setAttribute( QStringLiteral(
"ISO32000ExtensionFormat" ), details.useIso32000ExtensionFormatGeoreferencing ? QStringLiteral(
"true" ) : QStringLiteral(
"false" ) );
536 if ( section.crs.isValid() )
538 QDomElement srs = doc.createElement( QStringLiteral(
"SRS" ) );
541 if ( !section.crs.authid().isEmpty() && !section.crs.authid().startsWith( QStringLiteral(
"user" ), Qt::CaseInsensitive ) )
543 srs.appendChild( doc.createTextNode( section.crs.authid() ) );
549 georeferencing.appendChild( srs );
552 if ( !section.pageBoundsPolygon.isEmpty() )
561 QDomElement boundingPolygon = doc.createElement( QStringLiteral(
"BoundingPolygon" ) );
564 QTransform t = QTransform::fromTranslate( 0, pageHeightPdfUnits ).scale( pageWidthPdfUnits / details.pageSizeMm.width(),
565 -pageHeightPdfUnits / details.pageSizeMm.height() );
569 boundingPolygon.appendChild( doc.createTextNode( p.
asWkt() ) );
571 georeferencing.appendChild( boundingPolygon );
580 QDomElement boundingBox = doc.createElement( QStringLiteral(
"BoundingBox" ) );
581 boundingBox.setAttribute( QStringLiteral(
"x1" ),
qgsDoubleToString( section.pageBoundsMm.xMinimum() / 25.4 * 72 ) );
582 boundingBox.setAttribute( QStringLiteral(
"y1" ),
qgsDoubleToString( section.pageBoundsMm.yMinimum() / 25.4 * 72 ) );
583 boundingBox.setAttribute( QStringLiteral(
"x2" ),
qgsDoubleToString( section.pageBoundsMm.xMaximum() / 25.4 * 72 ) );
584 boundingBox.setAttribute( QStringLiteral(
"y2" ),
qgsDoubleToString( section.pageBoundsMm.yMaximum() / 25.4 * 72 ) );
585 georeferencing.appendChild( boundingBox );
588 for (
const ControlPoint &point : section.controlPoints )
590 QDomElement cp1 = doc.createElement( QStringLiteral(
"ControlPoint" ) );
591 cp1.setAttribute( QStringLiteral(
"x" ),
qgsDoubleToString( point.pagePoint.x() / 25.4 * 72 ) );
592 cp1.setAttribute( QStringLiteral(
"y" ),
qgsDoubleToString( ( details.pageSizeMm.height() - point.pagePoint.y() ) / 25.4 * 72 ) );
593 cp1.setAttribute( QStringLiteral(
"GeoX" ),
qgsDoubleToString( point.geoPoint.x() ) );
594 cp1.setAttribute( QStringLiteral(
"GeoY" ),
qgsDoubleToString( point.geoPoint.y() ) );
595 georeferencing.appendChild( cp1 );
598 page.appendChild( georeferencing );
601 auto createPdfDatasetElement = [&doc](
const ComponentLayerDetail & component ) -> QDomElement
603 QDomElement pdfDataset = doc.createElement( QStringLiteral(
"PDF" ) );
604 pdfDataset.setAttribute( QStringLiteral(
"dataset" ), component.sourcePdfPath );
605 if ( component.opacity != 1.0 || component.compositionMode != QPainter::CompositionMode_SourceOver )
607 QDomElement blendingElement = doc.createElement( QStringLiteral(
"Blending" ) );
608 blendingElement.setAttribute( QStringLiteral(
"opacity" ), component.opacity );
609 blendingElement.setAttribute( QStringLiteral(
"function" ), compositionModeToString( component.compositionMode ) );
611 pdfDataset.appendChild( blendingElement );
617 QDomElement content = doc.createElement( QStringLiteral(
"Content" ) );
618 for (
const ComponentLayerDetail &component : components )
620 if ( component.mapLayerId.isEmpty() && component.group.isEmpty() )
622 content.appendChild( createPdfDatasetElement( component ) );
624 else if ( !component.mapLayerId.isEmpty() )
626 if ( TreeNode *treeNode = layerIdToTreeNode.value( component.mapLayerId ) )
628 QDomElement ifLayerOnElement = treeNode->createNestedIfLayerOnElements( doc, content );
629 ifLayerOnElement.appendChild( createPdfDatasetElement( component ) );
632 else if ( TreeNode *groupNode = groupNameToTreeNode.value( component.group ) )
634 QDomElement ifGroupOn = groupNode->createIfLayerOnElement( doc, content );
635 ifGroupOn.appendChild( createPdfDatasetElement( component ) );
640 if ( details.includeFeatures )
642 for (
const VectorComponentDetail &component : std::as_const( mVectorComponents ) )
644 if ( TreeNode *treeNode = layerIdToTreeNode.value( component.mapLayerId ) )
646 QDomElement ifLayerOnElement = treeNode->createNestedIfLayerOnElements( doc, content );
648 QDomElement vectorDataset = doc.createElement( QStringLiteral(
"Vector" ) );
649 vectorDataset.setAttribute( QStringLiteral(
"dataset" ), component.sourceVectorPath );
650 vectorDataset.setAttribute( QStringLiteral(
"layer" ), component.sourceVectorLayer );
651 vectorDataset.setAttribute( QStringLiteral(
"visible" ), QStringLiteral(
"false" ) );
652 QDomElement logicalStructure = doc.createElement( QStringLiteral(
"LogicalStructure" ) );
653 logicalStructure.setAttribute( QStringLiteral(
"displayLayerName" ), component.name );
654 if ( !component.displayAttribute.isEmpty() )
655 logicalStructure.setAttribute( QStringLiteral(
"fieldToDisplay" ), component.displayAttribute );
656 vectorDataset.appendChild( logicalStructure );
657 ifLayerOnElement.appendChild( vectorDataset );
662 page.appendChild( content );
665 QDomElement
layerTree = doc.createElement( QStringLiteral(
"LayerTree" ) );
671 std::sort( rootGroups.begin(), rootGroups.end(), [&layerTreeGroupOrder](
const std::unique_ptr< TreeNode > &a,
const std::unique_ptr< TreeNode > &b ) ->
bool
673 return layerTreeGroupOrder.indexOf( a->name ) < layerTreeGroupOrder.indexOf( b->name );
676 bool haveFoundMutuallyExclusiveGroup =
false;
677 for (
const auto &node : std::as_const( rootGroups ) )
679 if ( !node->mutuallyExclusiveGroupId.isEmpty() )
682 node->initiallyVisible = !haveFoundMutuallyExclusiveGroup;
683 haveFoundMutuallyExclusiveGroup =
true;
685 layerTree.appendChild( node->toElement( doc ) );
689 layerTreeGroupOrder.erase( std::remove_if( layerTreeGroupOrder.begin(), layerTreeGroupOrder.end(), [&details](
const QString & group )
691 return details.customLayerTreeGroups.key( group ).isEmpty();
692 } ), layerTreeGroupOrder.end() );
696 std::sort( rootLayers.begin(), rootLayers.end(), [&details](
const std::unique_ptr< TreeNode > &a,
const std::unique_ptr< TreeNode > &b ) ->
bool
698 const int indexA = details.layerOrder.indexOf( a->mapLayerId );
699 const int indexB = details.layerOrder.indexOf( b->mapLayerId );
701 if ( indexA >= 0 && indexB >= 0 )
702 return indexA < indexB;
703 else if ( indexA >= 0 )
705 else if ( indexB >= 0 )
708 return a->name.localeAwareCompare( b->name ) < 0;
711 for (
const auto &node : std::as_const( rootLayers ) )
713 layerTree.appendChild( node->toElement( doc ) );
716 compositionElem.appendChild( layerTree );
717 compositionElem.appendChild( page );
719 doc.appendChild( compositionElem );
722 QTextStream stream( &composition );
723 doc.save( stream, -1 );
728QString QgsAbstractGeospatialPdfExporter::compositionModeToString( QPainter::CompositionMode mode )
732 case QPainter::CompositionMode_SourceOver:
733 return QStringLiteral(
"Normal" );
735 case QPainter::CompositionMode_Multiply:
736 return QStringLiteral(
"Multiply" );
738 case QPainter::CompositionMode_Screen:
739 return QStringLiteral(
"Screen" );
741 case QPainter::CompositionMode_Overlay:
742 return QStringLiteral(
"Overlay" );
744 case QPainter::CompositionMode_Darken:
745 return QStringLiteral(
"Darken" );
747 case QPainter::CompositionMode_Lighten:
748 return QStringLiteral(
"Lighten" );
750 case QPainter::CompositionMode_ColorDodge:
751 return QStringLiteral(
"ColorDodge" );
753 case QPainter::CompositionMode_ColorBurn:
754 return QStringLiteral(
"ColorBurn" );
756 case QPainter::CompositionMode_HardLight:
757 return QStringLiteral(
"HardLight" );
759 case QPainter::CompositionMode_SoftLight:
760 return QStringLiteral(
"SoftLight" );
762 case QPainter::CompositionMode_Difference:
763 return QStringLiteral(
"Difference" );
765 case QPainter::CompositionMode_Exclusion:
766 return QStringLiteral(
"Exclusion" );
772 QgsDebugError( QStringLiteral(
"Unsupported PDF blend mode %1" ).arg( mode ) );
773 return QStringLiteral(
"Normal" );
@ PreferredGdal
Preferred format for conversion of CRS to WKT for use with the GDAL library.
@ NoSymbology
Export only data.
static bool compositionModeSupported(QPainter::CompositionMode mode)
Returns true if the specified composition mode is supported for layers during Geospatial PDF exports.
static QString geospatialPDFAvailabilityExplanation()
Returns a user-friendly, translated string explaining why Geospatial PDF export support is not availa...
bool finalize(const QList< QgsAbstractGeospatialPdfExporter::ComponentLayerDetail > &components, const QString &destinationFile, const ExportDetails &details)
To be called after the rendering operation is complete.
void pushRenderedFeature(const QString &layerId, const QgsAbstractGeospatialPdfExporter::RenderedFeature &feature, const QString &group=QString())
Called multiple times during the rendering operation, whenever a feature associated with the specifie...
static bool geospatialPDFCreationAvailable()
Returns true if the current QGIS build is capable of Geospatial PDF support.
QString generateTemporaryFilepath(const QString &filename) const
Returns a file path to use for temporary files required for Geospatial PDF creation.
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.
QgsGeometry renderedBounds
Bounds, in PDF units, of rendered feature.
QgsFeature feature
Rendered feature.