LCOV - code coverage report
Current view: top level - frmts/pdf - pdfdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1700 2906 58.5 %
Date: 2025-12-05 02:43:06 Functions: 83 97 85.6 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  PDF driver
       4             :  * Purpose:  GDALDataset driver for PDF dataset.
       5             :  * Author:   Even Rouault, <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  *
       9             :  * Support for open-source PDFium library
      10             :  *
      11             :  * Copyright (C) 2015 Klokan Technologies GmbH (http://www.klokantech.com/)
      12             :  * Author: Martin Mikita <martin.mikita@klokantech.com>, xmikit00 @ FIT VUT Brno
      13             :  *
      14             :  ******************************************************************************
      15             :  * Copyright (c) 2010-2014, Even Rouault <even dot rouault at spatialys.com>
      16             :  *
      17             :  * SPDX-License-Identifier: MIT
      18             :  ****************************************************************************/
      19             : 
      20             : #include "gdal_pdf.h"
      21             : 
      22             : #include "cpl_json_streaming_writer.h"
      23             : #include "cpl_vsi_virtual.h"
      24             : #include "cpl_spawn.h"
      25             : #include "cpl_string.h"
      26             : #include "gdal_frmts.h"
      27             : #include "gdalalgorithm.h"
      28             : #include "ogr_spatialref.h"
      29             : #include "ogr_geometry.h"
      30             : 
      31             : #ifdef HAVE_POPPLER
      32             : #include "cpl_multiproc.h"
      33             : #include "pdfio.h"
      34             : #endif  // HAVE_POPPLER
      35             : 
      36             : #include "pdfcreatecopy.h"
      37             : 
      38             : #include "pdfdrivercore.h"
      39             : 
      40             : #include <algorithm>
      41             : #include <cassert>
      42             : #include <limits>
      43             : #include <set>
      44             : 
      45             : #ifdef HAVE_PDFIUM
      46             : // To be able to use
      47             : // https://github.com/rouault/pdfium_build_gdal_3_5/releases/download/v1_pdfium_5106/install-win10-vs2019-x64-rev5106.zip
      48             : // with newer Visual Studio versions.
      49             : // Trick from https://github.com/conan-io/conan-center-index/issues/4826
      50             : #if _MSC_VER >= 1932  // Visual Studio 2022 version 17.2+
      51             : #pragma comment(                                                               \
      52             :     linker,                                                                    \
      53             :     "/alternatename:__imp___std_init_once_complete=__imp_InitOnceComplete")
      54             : #pragma comment(                                                               \
      55             :     linker,                                                                    \
      56             :     "/alternatename:__imp___std_init_once_begin_initialize=__imp_InitOnceBeginInitialize")
      57             : #endif
      58             : #endif
      59             : 
      60             : /* g++ -fPIC -g -Wall frmts/pdf/pdfdataset.cpp -shared -o gdal_PDF.so -Iport
      61             :  * -Igcore -Iogr -L. -lgdal -lpoppler -I/usr/include/poppler */
      62             : 
      63             : #ifdef HAVE_PDF_READ_SUPPORT
      64             : 
      65             : static double Get(GDALPDFObject *poObj, int nIndice = -1);
      66             : 
      67             : #ifdef HAVE_POPPLER
      68             : 
      69             : static CPLMutex *hGlobalParamsMutex = nullptr;
      70             : 
      71             : /************************************************************************/
      72             : /*                         GDALPDFOutputDev                             */
      73             : /************************************************************************/
      74             : 
      75             : class GDALPDFOutputDev final : public SplashOutputDev
      76             : {
      77             :   private:
      78             :     int bEnableVector;
      79             :     int bEnableText;
      80             :     int bEnableBitmap;
      81             : 
      82           0 :     void skipBytes(Stream *str, int width, int height, int nComps, int nBits)
      83             :     {
      84           0 :         int nVals = width * nComps;
      85           0 :         int nLineSize = (nVals * nBits + 7) >> 3;
      86           0 :         int nBytes = nLineSize * height;
      87           0 :         for (int i = 0; i < nBytes; i++)
      88             :         {
      89           0 :             if (str->getChar() == EOF)
      90           0 :                 break;
      91             :         }
      92           0 :     }
      93             : 
      94             :   public:
      95          56 :     GDALPDFOutputDev(SplashColorMode colorModeA, int bitmapRowPadA,
      96             :                      bool reverseVideoA, SplashColorPtr paperColorA)
      97          56 :         : SplashOutputDev(colorModeA, bitmapRowPadA, reverseVideoA,
      98             :                           paperColorA),
      99          56 :           bEnableVector(TRUE), bEnableText(TRUE), bEnableBitmap(TRUE)
     100             :     {
     101          56 :     }
     102             : 
     103          11 :     void SetEnableVector(int bFlag)
     104             :     {
     105          11 :         bEnableVector = bFlag;
     106          11 :     }
     107             : 
     108          11 :     void SetEnableText(int bFlag)
     109             :     {
     110          11 :         bEnableText = bFlag;
     111          11 :     }
     112             : 
     113          11 :     void SetEnableBitmap(int bFlag)
     114             :     {
     115          11 :         bEnableBitmap = bFlag;
     116          11 :     }
     117             : 
     118             :     void startPage(int pageNum, GfxState *state, XRef *xrefIn) override;
     119             : 
     120        1673 :     void stroke(GfxState *state) override
     121             :     {
     122        1673 :         if (bEnableVector)
     123        1664 :             SplashOutputDev::stroke(state);
     124        1673 :     }
     125             : 
     126           8 :     void fill(GfxState *state) override
     127             :     {
     128           8 :         if (bEnableVector)
     129           8 :             SplashOutputDev::fill(state);
     130           8 :     }
     131             : 
     132          38 :     void eoFill(GfxState *state) override
     133             :     {
     134          38 :         if (bEnableVector)
     135          32 :             SplashOutputDev::eoFill(state);
     136          38 :     }
     137             : 
     138        4295 :     virtual void drawChar(GfxState *state, double x, double y, double dx,
     139             :                           double dy, double originX, double originY,
     140             :                           CharCode code, int nBytes, const Unicode *u,
     141             :                           int uLen) override
     142             :     {
     143        4295 :         if (bEnableText)
     144        4259 :             SplashOutputDev::drawChar(state, x, y, dx, dy, originX, originY,
     145             :                                       code, nBytes, u, uLen);
     146        4295 :     }
     147             : 
     148         681 :     void beginTextObject(GfxState *state) override
     149             :     {
     150         681 :         if (bEnableText)
     151         678 :             SplashOutputDev::beginTextObject(state);
     152         681 :     }
     153             : 
     154         681 :     void endTextObject(GfxState *state) override
     155             :     {
     156         681 :         if (bEnableText)
     157         678 :             SplashOutputDev::endTextObject(state);
     158         681 :     }
     159             : 
     160           0 :     virtual void drawImageMask(GfxState *state, Object *ref, Stream *str,
     161             :                                int width, int height, bool invert,
     162             :                                bool interpolate, bool inlineImg) override
     163             :     {
     164           0 :         if (bEnableBitmap)
     165           0 :             SplashOutputDev::drawImageMask(state, ref, str, width, height,
     166             :                                            invert, interpolate, inlineImg);
     167             :         else
     168             :         {
     169           0 :             VSIPDFFileStream::resetNoCheckReturnValue(str);
     170           0 :             if (inlineImg)
     171             :             {
     172           0 :                 skipBytes(str, width, height, 1, 1);
     173             :             }
     174           0 :             str->close();
     175             :         }
     176           0 :     }
     177             : 
     178           0 :     virtual void setSoftMaskFromImageMask(GfxState *state, Object *ref,
     179             :                                           Stream *str, int width, int height,
     180             :                                           bool invert, bool inlineImg,
     181             :                                           double *baseMatrix) override
     182             :     {
     183           0 :         if (bEnableBitmap)
     184           0 :             SplashOutputDev::setSoftMaskFromImageMask(
     185             :                 state, ref, str, width, height, invert, inlineImg, baseMatrix);
     186             :         else
     187           0 :             str->close();
     188           0 :     }
     189             : 
     190           0 :     virtual void unsetSoftMaskFromImageMask(GfxState *state,
     191             :                                             double *baseMatrix) override
     192             :     {
     193           0 :         if (bEnableBitmap)
     194           0 :             SplashOutputDev::unsetSoftMaskFromImageMask(state, baseMatrix);
     195           0 :     }
     196             : 
     197          43 :     virtual void drawImage(GfxState *state, Object *ref, Stream *str, int width,
     198             :                            int height, GfxImageColorMap *colorMap,
     199             :                            bool interpolate, const int *maskColors,
     200             :                            bool inlineImg) override
     201             :     {
     202          43 :         if (bEnableBitmap)
     203          40 :             SplashOutputDev::drawImage(state, ref, str, width, height, colorMap,
     204             :                                        interpolate, maskColors, inlineImg);
     205             :         else
     206             :         {
     207           3 :             VSIPDFFileStream::resetNoCheckReturnValue(str);
     208           3 :             if (inlineImg)
     209             :             {
     210           0 :                 skipBytes(str, width, height, colorMap->getNumPixelComps(),
     211             :                           colorMap->getBits());
     212             :             }
     213           3 :             str->close();
     214             :         }
     215          43 :     }
     216             : 
     217           0 :     virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str,
     218             :                                  int width, int height,
     219             :                                  GfxImageColorMap *colorMap, bool interpolate,
     220             :                                  Stream *maskStr, int maskWidth, int maskHeight,
     221             :                                  bool maskInvert, bool maskInterpolate) override
     222             :     {
     223           0 :         if (bEnableBitmap)
     224           0 :             SplashOutputDev::drawMaskedImage(
     225             :                 state, ref, str, width, height, colorMap, interpolate, maskStr,
     226             :                 maskWidth, maskHeight, maskInvert, maskInterpolate);
     227             :         else
     228           0 :             str->close();
     229           0 :     }
     230             : 
     231           2 :     virtual void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str,
     232             :                                      int width, int height,
     233             :                                      GfxImageColorMap *colorMap,
     234             :                                      bool interpolate, Stream *maskStr,
     235             :                                      int maskWidth, int maskHeight,
     236             :                                      GfxImageColorMap *maskColorMap,
     237             :                                      bool maskInterpolate) override
     238             :     {
     239           2 :         if (bEnableBitmap)
     240             :         {
     241           2 :             if (maskColorMap->getBits() <=
     242             :                 0) /* workaround poppler bug (robustness) */
     243             :             {
     244           0 :                 str->close();
     245           0 :                 return;
     246             :             }
     247           2 :             SplashOutputDev::drawSoftMaskedImage(
     248             :                 state, ref, str, width, height, colorMap, interpolate, maskStr,
     249             :                 maskWidth, maskHeight, maskColorMap, maskInterpolate);
     250             :         }
     251             :         else
     252           0 :             str->close();
     253             :     }
     254             : };
     255             : 
     256          56 : void GDALPDFOutputDev::startPage(int pageNum, GfxState *state, XRef *xrefIn)
     257             : {
     258          56 :     SplashOutputDev::startPage(pageNum, state, xrefIn);
     259          56 :     SplashBitmap *poBitmap = getBitmap();
     260         112 :     memset(poBitmap->getDataPtr(), 255,
     261          56 :            static_cast<size_t>(poBitmap->getRowSize()) * poBitmap->getHeight());
     262          56 : }
     263             : 
     264             : #endif  // ~ HAVE_POPPLER
     265             : 
     266             : /************************************************************************/
     267             : /*                         Dump routines                                */
     268             : /************************************************************************/
     269             : 
     270             : class GDALPDFDumper
     271             : {
     272             :   private:
     273             :     FILE *f = nullptr;
     274             :     const int nDepthLimit;
     275             :     std::set<int> aoSetObjectExplored{};
     276             :     const bool bDumpParent;
     277             : 
     278             :     void DumpSimplified(GDALPDFObject *poObj);
     279             : 
     280             :     CPL_DISALLOW_COPY_ASSIGN(GDALPDFDumper)
     281             : 
     282             :   public:
     283           1 :     GDALPDFDumper(const char *pszFilename, const char *pszDumpFile,
     284             :                   int nDepthLimitIn = -1)
     285           1 :         : nDepthLimit(nDepthLimitIn),
     286           1 :           bDumpParent(CPLGetConfigOption("PDF_DUMP_PARENT", "FALSE"))
     287             :     {
     288           1 :         if (strcmp(pszDumpFile, "stderr") == 0)
     289           0 :             f = stderr;
     290           1 :         else if (EQUAL(pszDumpFile, "YES"))
     291           0 :             f = fopen(CPLSPrintf("dump_%s.txt", CPLGetFilename(pszFilename)),
     292             :                       "wt");
     293             :         else
     294           1 :             f = fopen(pszDumpFile, "wt");
     295           1 :         if (f == nullptr)
     296           0 :             f = stderr;
     297           1 :     }
     298             : 
     299           1 :     ~GDALPDFDumper()
     300           1 :     {
     301           1 :         if (f != stderr)
     302           1 :             fclose(f);
     303           1 :     }
     304             : 
     305             :     void Dump(GDALPDFObject *poObj, int nDepth = 0);
     306             :     void Dump(GDALPDFDictionary *poDict, int nDepth = 0);
     307             :     void Dump(GDALPDFArray *poArray, int nDepth = 0);
     308             : };
     309             : 
     310           3 : void GDALPDFDumper::Dump(GDALPDFArray *poArray, int nDepth)
     311             : {
     312           3 :     if (nDepthLimit >= 0 && nDepth > nDepthLimit)
     313           0 :         return;
     314             : 
     315           3 :     int nLength = poArray->GetLength();
     316             :     int i;
     317           6 :     CPLString osIndent;
     318          14 :     for (i = 0; i < nDepth; i++)
     319          11 :         osIndent += " ";
     320           8 :     for (i = 0; i < nLength; i++)
     321             :     {
     322           5 :         fprintf(f, "%sItem[%d]:", osIndent.c_str(), i);
     323           5 :         GDALPDFObject *poObj = nullptr;
     324           5 :         if ((poObj = poArray->Get(i)) != nullptr)
     325             :         {
     326           5 :             if (poObj->GetType() == PDFObjectType_String ||
     327           5 :                 poObj->GetType() == PDFObjectType_Null ||
     328           5 :                 poObj->GetType() == PDFObjectType_Bool ||
     329           5 :                 poObj->GetType() == PDFObjectType_Int ||
     330          11 :                 poObj->GetType() == PDFObjectType_Real ||
     331           1 :                 poObj->GetType() == PDFObjectType_Name)
     332             :             {
     333           4 :                 fprintf(f, " ");
     334           4 :                 DumpSimplified(poObj);
     335           4 :                 fprintf(f, "\n");
     336             :             }
     337             :             else
     338             :             {
     339           1 :                 fprintf(f, "\n");
     340           1 :                 Dump(poObj, nDepth + 1);
     341             :             }
     342             :         }
     343             :     }
     344             : }
     345             : 
     346         493 : void GDALPDFDumper::DumpSimplified(GDALPDFObject *poObj)
     347             : {
     348         493 :     switch (poObj->GetType())
     349             :     {
     350           0 :         case PDFObjectType_String:
     351           0 :             fprintf(f, "%s (string)", poObj->GetString().c_str());
     352           0 :             break;
     353             : 
     354           0 :         case PDFObjectType_Null:
     355           0 :             fprintf(f, "null");
     356           0 :             break;
     357             : 
     358           0 :         case PDFObjectType_Bool:
     359           0 :             fprintf(f, "%s (bool)", poObj->GetBool() ? "true" : "false");
     360           0 :             break;
     361             : 
     362         247 :         case PDFObjectType_Int:
     363         247 :             fprintf(f, "%d (int)", poObj->GetInt());
     364         247 :             break;
     365             : 
     366           0 :         case PDFObjectType_Real:
     367           0 :             fprintf(f, "%f (real)", poObj->GetReal());
     368           0 :             break;
     369             : 
     370         246 :         case PDFObjectType_Name:
     371         246 :             fprintf(f, "%s (name)", poObj->GetName().c_str());
     372         246 :             break;
     373             : 
     374           0 :         default:
     375           0 :             fprintf(f, "unknown !");
     376           0 :             break;
     377             :     }
     378         493 : }
     379             : 
     380          70 : void GDALPDFDumper::Dump(GDALPDFObject *poObj, int nDepth)
     381             : {
     382          70 :     if (nDepthLimit >= 0 && nDepth > nDepthLimit)
     383           1 :         return;
     384             : 
     385             :     int i;
     386          70 :     CPLString osIndent;
     387         516 :     for (i = 0; i < nDepth; i++)
     388         446 :         osIndent += " ";
     389          70 :     fprintf(f, "%sType = %s", osIndent.c_str(), poObj->GetTypeName());
     390          70 :     int nRefNum = poObj->GetRefNum().toInt();
     391          70 :     if (nRefNum != 0)
     392          66 :         fprintf(f, ", Num = %d, Gen = %d", nRefNum, poObj->GetRefGen());
     393          70 :     fprintf(f, "\n");
     394             : 
     395          70 :     if (nRefNum != 0)
     396             :     {
     397          66 :         if (aoSetObjectExplored.find(nRefNum) != aoSetObjectExplored.end())
     398           1 :             return;
     399          65 :         aoSetObjectExplored.insert(nRefNum);
     400             :     }
     401             : 
     402          69 :     switch (poObj->GetType())
     403             :     {
     404           3 :         case PDFObjectType_Array:
     405           3 :             Dump(poObj->GetArray(), nDepth + 1);
     406           3 :             break;
     407             : 
     408          66 :         case PDFObjectType_Dictionary:
     409          66 :             Dump(poObj->GetDictionary(), nDepth + 1);
     410          66 :             break;
     411             : 
     412           0 :         case PDFObjectType_String:
     413             :         case PDFObjectType_Null:
     414             :         case PDFObjectType_Bool:
     415             :         case PDFObjectType_Int:
     416             :         case PDFObjectType_Real:
     417             :         case PDFObjectType_Name:
     418           0 :             fprintf(f, "%s", osIndent.c_str());
     419           0 :             DumpSimplified(poObj);
     420           0 :             fprintf(f, "\n");
     421           0 :             break;
     422             : 
     423           0 :         default:
     424           0 :             fprintf(f, "%s", osIndent.c_str());
     425           0 :             fprintf(f, "unknown !\n");
     426           0 :             break;
     427             :     }
     428             : 
     429          69 :     GDALPDFStream *poStream = poObj->GetStream();
     430          69 :     if (poStream != nullptr)
     431             :     {
     432          61 :         fprintf(f,
     433             :                 "%sHas stream (" CPL_FRMT_GIB
     434             :                 " uncompressed bytes, " CPL_FRMT_GIB " raw bytes)\n",
     435          61 :                 osIndent.c_str(), static_cast<GIntBig>(poStream->GetLength()),
     436          61 :                 static_cast<GIntBig>(poStream->GetRawLength()));
     437             :     }
     438             : }
     439             : 
     440          66 : void GDALPDFDumper::Dump(GDALPDFDictionary *poDict, int nDepth)
     441             : {
     442          66 :     if (nDepthLimit >= 0 && nDepth > nDepthLimit)
     443           0 :         return;
     444             : 
     445         132 :     CPLString osIndent;
     446         564 :     for (int i = 0; i < nDepth; i++)
     447         498 :         osIndent += " ";
     448          66 :     int i = 0;
     449          66 :     const auto &oMap = poDict->GetValues();
     450         623 :     for (const auto &[osKey, poObj] : oMap)
     451             :     {
     452         557 :         fprintf(f, "%sItem[%d] : %s", osIndent.c_str(), i, osKey.c_str());
     453         557 :         ++i;
     454         557 :         if (osKey == "Parent" && !bDumpParent)
     455             :         {
     456           0 :             if (poObj->GetRefNum().toBool())
     457           0 :                 fprintf(f, ", Num = %d, Gen = %d", poObj->GetRefNum().toInt(),
     458           0 :                         poObj->GetRefGen());
     459           0 :             fprintf(f, "\n");
     460           0 :             continue;
     461             :         }
     462         557 :         if (poObj != nullptr)
     463             :         {
     464         557 :             if (poObj->GetType() == PDFObjectType_String ||
     465         557 :                 poObj->GetType() == PDFObjectType_Null ||
     466         557 :                 poObj->GetType() == PDFObjectType_Bool ||
     467         557 :                 poObj->GetType() == PDFObjectType_Int ||
     468        1428 :                 poObj->GetType() == PDFObjectType_Real ||
     469         314 :                 poObj->GetType() == PDFObjectType_Name)
     470             :             {
     471         489 :                 fprintf(f, " = ");
     472         489 :                 DumpSimplified(poObj);
     473         489 :                 fprintf(f, "\n");
     474             :             }
     475             :             else
     476             :             {
     477          68 :                 fprintf(f, "\n");
     478          68 :                 Dump(poObj, nDepth + 1);
     479             :             }
     480             :         }
     481             :     }
     482             : }
     483             : 
     484             : /************************************************************************/
     485             : /*                         PDFRasterBand()                              */
     486             : /************************************************************************/
     487             : 
     488         649 : PDFRasterBand::PDFRasterBand(PDFDataset *poDSIn, int nBandIn,
     489         649 :                              int nResolutionLevelIn)
     490         649 :     : nResolutionLevel(nResolutionLevelIn)
     491             : {
     492         649 :     poDS = poDSIn;
     493         649 :     nBand = nBandIn;
     494             : 
     495         649 :     eDataType = GDT_UInt8;
     496             : 
     497         649 :     if (nResolutionLevel > 0)
     498             :     {
     499           0 :         nBlockXSize = 256;
     500           0 :         nBlockYSize = 256;
     501           0 :         poDSIn->SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
     502             :     }
     503         649 :     else if (poDSIn->m_nBlockXSize)
     504             :     {
     505          36 :         nBlockXSize = poDSIn->m_nBlockXSize;
     506          36 :         nBlockYSize = poDSIn->m_nBlockYSize;
     507          36 :         poDSIn->SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
     508             :     }
     509         613 :     else if (poDSIn->GetRasterXSize() <
     510         613 :              64 * 1024 * 1024 / poDSIn->GetRasterYSize())
     511             :     {
     512         610 :         nBlockXSize = poDSIn->GetRasterXSize();
     513         610 :         nBlockYSize = 1;
     514             :     }
     515             :     else
     516             :     {
     517           3 :         nBlockXSize = std::min(1024, poDSIn->GetRasterXSize());
     518           3 :         nBlockYSize = std::min(1024, poDSIn->GetRasterYSize());
     519           3 :         poDSIn->SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
     520             :     }
     521         649 : }
     522             : 
     523             : /************************************************************************/
     524             : /*                         InitOverviews()                              */
     525             : /************************************************************************/
     526             : 
     527           5 : void PDFDataset::InitOverviews()
     528             : {
     529             : #ifdef HAVE_PDFIUM
     530             :     // Only if used pdfium, make "arbitrary overviews"
     531             :     // Blocks are 256x256
     532             :     if (m_bUseLib.test(PDFLIB_PDFIUM) && m_apoOvrDS.empty() &&
     533             :         m_apoOvrDSBackup.empty())
     534             :     {
     535             :         int nXSize = nRasterXSize;
     536             :         int nYSize = nRasterYSize;
     537             :         constexpr int minSize = 256;
     538             :         int nDiscard = 1;
     539             :         while (nXSize > minSize || nYSize > minSize)
     540             :         {
     541             :             nXSize = (nXSize + 1) / 2;
     542             :             nYSize = (nYSize + 1) / 2;
     543             : 
     544             :             auto poOvrDS = std::make_unique<PDFDataset>(this, nXSize, nYSize);
     545             : 
     546             :             for (int i = 0; i < nBands; i++)
     547             :                 poOvrDS->SetBand(
     548             :                     i + 1, new PDFRasterBand(poOvrDS.get(), i + 1, nDiscard));
     549             : 
     550             :             m_apoOvrDS.emplace_back(std::move(poOvrDS));
     551             :             ++nDiscard;
     552             :         }
     553             :     }
     554             : #endif
     555             : #if defined(HAVE_POPPLER) || defined(HAVE_PODOFO)
     556          13 :     if (!m_bUseLib.test(PDFLIB_PDFIUM) && m_apoOvrDS.empty() &&
     557          13 :         m_apoOvrDSBackup.empty() && m_osUserPwd != "ASK_INTERACTIVE")
     558             :     {
     559           2 :         int nXSize = nRasterXSize;
     560           2 :         int nYSize = nRasterYSize;
     561           2 :         constexpr int minSize = 256;
     562           2 :         double dfDPI = m_dfDPI;
     563           4 :         while (nXSize > minSize || nYSize > minSize)
     564             :         {
     565           2 :             nXSize = (nXSize + 1) / 2;
     566           2 :             nYSize = (nYSize + 1) / 2;
     567           2 :             dfDPI /= 2;
     568             : 
     569           2 :             GDALOpenInfo oOpenInfo(GetDescription(), GA_ReadOnly);
     570           2 :             CPLStringList aosOpenOptions(CSLDuplicate(papszOpenOptions));
     571           2 :             aosOpenOptions.SetNameValue("DPI", CPLSPrintf("%g", dfDPI));
     572           2 :             aosOpenOptions.SetNameValue("BANDS", CPLSPrintf("%d", nBands));
     573           2 :             aosOpenOptions.SetNameValue("@OPEN_FOR_OVERVIEW", "YES");
     574           2 :             if (!m_osUserPwd.empty())
     575           0 :                 aosOpenOptions.SetNameValue("USER_PWD", m_osUserPwd.c_str());
     576           2 :             oOpenInfo.papszOpenOptions = aosOpenOptions.List();
     577           2 :             auto poOvrDS = std::unique_ptr<PDFDataset>(Open(&oOpenInfo));
     578           2 :             if (!poOvrDS || poOvrDS->nBands != nBands)
     579           0 :                 break;
     580           2 :             poOvrDS->m_bIsOvrDS = true;
     581           2 :             m_apoOvrDS.emplace_back(std::move(poOvrDS));
     582             :         }
     583             :     }
     584             : #endif
     585           5 : }
     586             : 
     587             : /************************************************************************/
     588             : /*                        GetColorInterpretation()                      */
     589             : /************************************************************************/
     590             : 
     591          16 : GDALColorInterp PDFRasterBand::GetColorInterpretation()
     592             : {
     593          16 :     PDFDataset *poGDS = cpl::down_cast<PDFDataset *>(poDS);
     594          16 :     if (poGDS->nBands == 1)
     595           0 :         return GCI_GrayIndex;
     596             :     else
     597          16 :         return static_cast<GDALColorInterp>(GCI_RedBand + (nBand - 1));
     598             : }
     599             : 
     600             : /************************************************************************/
     601             : /*                          GetOverviewCount()                          */
     602             : /************************************************************************/
     603             : 
     604           9 : int PDFRasterBand::GetOverviewCount()
     605             : {
     606           9 :     PDFDataset *poGDS = cpl::down_cast<PDFDataset *>(poDS);
     607           9 :     if (poGDS->m_bIsOvrDS)
     608           0 :         return 0;
     609           9 :     if (GDALPamRasterBand::GetOverviewCount() > 0)
     610           4 :         return GDALPamRasterBand::GetOverviewCount();
     611             :     else
     612             :     {
     613           5 :         poGDS->InitOverviews();
     614           5 :         return static_cast<int>(poGDS->m_apoOvrDS.size());
     615             :     }
     616             : }
     617             : 
     618             : /************************************************************************/
     619             : /*                            GetOverview()                             */
     620             : /************************************************************************/
     621             : 
     622           4 : GDALRasterBand *PDFRasterBand::GetOverview(int iOverviewIndex)
     623             : {
     624           4 :     if (GDALPamRasterBand::GetOverviewCount() > 0)
     625           1 :         return GDALPamRasterBand::GetOverview(iOverviewIndex);
     626             : 
     627           3 :     else if (iOverviewIndex < 0 || iOverviewIndex >= GetOverviewCount())
     628           2 :         return nullptr;
     629             :     else
     630             :     {
     631           1 :         PDFDataset *poGDS = cpl::down_cast<PDFDataset *>(poDS);
     632           1 :         return poGDS->m_apoOvrDS[iOverviewIndex]->GetRasterBand(nBand);
     633             :     }
     634             : }
     635             : 
     636             : /************************************************************************/
     637             : /*                           ~PDFRasterBand()                           */
     638             : /************************************************************************/
     639             : 
     640        1298 : PDFRasterBand::~PDFRasterBand()
     641             : {
     642        1298 : }
     643             : 
     644             : /************************************************************************/
     645             : /*                         IReadBlockFromTile()                         */
     646             : /************************************************************************/
     647             : 
     648         160 : CPLErr PDFRasterBand::IReadBlockFromTile(int nBlockXOff, int nBlockYOff,
     649             :                                          void *pImage)
     650             : 
     651             : {
     652         160 :     PDFDataset *poGDS = cpl::down_cast<PDFDataset *>(poDS);
     653             : 
     654         160 :     int nReqXSize = nBlockXSize;
     655         160 :     int nReqYSize = nBlockYSize;
     656         160 :     if ((nBlockXOff + 1) * nBlockXSize > nRasterXSize)
     657          20 :         nReqXSize = nRasterXSize - nBlockXOff * nBlockXSize;
     658         160 :     if ((nBlockYOff + 1) * nBlockYSize > nRasterYSize)
     659          24 :         nReqYSize = nRasterYSize - nBlockYOff * nBlockYSize;
     660             : 
     661         160 :     int nXBlocks = DIV_ROUND_UP(nRasterXSize, nBlockXSize);
     662         160 :     int iTile = poGDS->m_aiTiles[nBlockYOff * nXBlocks + nBlockXOff];
     663         160 :     if (iTile < 0)
     664             :     {
     665           0 :         memset(pImage, 0, static_cast<size_t>(nBlockXSize) * nBlockYSize);
     666           0 :         return CE_None;
     667             :     }
     668             : 
     669         160 :     GDALPDFTileDesc &sTile = poGDS->m_asTiles[iTile];
     670         160 :     GDALPDFObject *poImage = sTile.poImage;
     671             : 
     672         160 :     if (nBand == 4)
     673             :     {
     674          30 :         GDALPDFDictionary *poImageDict = poImage->GetDictionary();
     675          30 :         GDALPDFObject *poSMask = poImageDict->Get("SMask");
     676          60 :         if (poSMask != nullptr &&
     677          30 :             poSMask->GetType() == PDFObjectType_Dictionary)
     678             :         {
     679          30 :             GDALPDFDictionary *poSMaskDict = poSMask->GetDictionary();
     680          30 :             GDALPDFObject *poWidth = poSMaskDict->Get("Width");
     681          30 :             GDALPDFObject *poHeight = poSMaskDict->Get("Height");
     682          30 :             GDALPDFObject *poColorSpace = poSMaskDict->Get("ColorSpace");
     683             :             GDALPDFObject *poBitsPerComponent =
     684          30 :                 poSMaskDict->Get("BitsPerComponent");
     685          30 :             double dfBits = 0;
     686          30 :             if (poBitsPerComponent)
     687          30 :                 dfBits = Get(poBitsPerComponent);
     688          30 :             if (poWidth && Get(poWidth) == nReqXSize && poHeight &&
     689          30 :                 Get(poHeight) == nReqYSize && poColorSpace &&
     690          60 :                 poColorSpace->GetType() == PDFObjectType_Name &&
     691         112 :                 poColorSpace->GetName() == "DeviceGray" &&
     692          22 :                 (dfBits == 1 || dfBits == 8))
     693             :             {
     694          30 :                 GDALPDFStream *poStream = poSMask->GetStream();
     695          30 :                 GByte *pabyStream = nullptr;
     696             : 
     697          30 :                 if (poStream == nullptr)
     698           0 :                     return CE_Failure;
     699             : 
     700          30 :                 pabyStream = reinterpret_cast<GByte *>(poStream->GetBytes());
     701          30 :                 if (pabyStream == nullptr)
     702           0 :                     return CE_Failure;
     703             : 
     704          30 :                 const int nReqXSize1 = (nReqXSize + 7) / 8;
     705          52 :                 if ((dfBits == 8 &&
     706          22 :                      static_cast<size_t>(poStream->GetLength()) !=
     707          60 :                          static_cast<size_t>(nReqXSize) * nReqYSize) ||
     708           8 :                     (dfBits == 1 &&
     709           8 :                      static_cast<size_t>(poStream->GetLength()) !=
     710           8 :                          static_cast<size_t>(nReqXSize1) * nReqYSize))
     711             :                 {
     712           0 :                     VSIFree(pabyStream);
     713           0 :                     return CE_Failure;
     714             :                 }
     715             : 
     716          30 :                 GByte *pabyData = static_cast<GByte *>(pImage);
     717          30 :                 if (nReqXSize != nBlockXSize || nReqYSize != nBlockYSize)
     718             :                 {
     719          10 :                     memset(pabyData, 0,
     720          10 :                            static_cast<size_t>(nBlockXSize) * nBlockYSize);
     721             :                 }
     722             : 
     723          30 :                 if (dfBits == 8)
     724             :                 {
     725         686 :                     for (int j = 0; j < nReqYSize; j++)
     726             :                     {
     727       21912 :                         for (int i = 0; i < nReqXSize; i++)
     728             :                         {
     729       21248 :                             pabyData[j * nBlockXSize + i] =
     730       21248 :                                 pabyStream[j * nReqXSize + i];
     731             :                         }
     732             :                     }
     733             :                 }
     734             :                 else
     735             :                 {
     736         244 :                     for (int j = 0; j < nReqYSize; j++)
     737             :                     {
     738        3288 :                         for (int i = 0; i < nReqXSize; i++)
     739             :                         {
     740        3052 :                             if (pabyStream[j * nReqXSize1 + i / 8] &
     741        3052 :                                 (1 << (7 - (i % 8))))
     742         896 :                                 pabyData[j * nBlockXSize + i] = 255;
     743             :                             else
     744        2156 :                                 pabyData[j * nBlockXSize + i] = 0;
     745             :                         }
     746             :                     }
     747             :                 }
     748             : 
     749          30 :                 VSIFree(pabyStream);
     750          30 :                 return CE_None;
     751             :             }
     752             :         }
     753             : 
     754           0 :         memset(pImage, 255, static_cast<size_t>(nBlockXSize) * nBlockYSize);
     755           0 :         return CE_None;
     756             :     }
     757             : 
     758         130 :     if (poGDS->m_nLastBlockXOff == nBlockXOff &&
     759           0 :         poGDS->m_nLastBlockYOff == nBlockYOff &&
     760           0 :         poGDS->m_pabyCachedData != nullptr)
     761             :     {
     762             : #ifdef DEBUG
     763           0 :         CPLDebug("PDF", "Using cached block (%d, %d)", nBlockXOff, nBlockYOff);
     764             : #endif
     765             :         // do nothing
     766             :     }
     767             :     else
     768             :     {
     769         130 :         if (!poGDS->m_bTried)
     770             :         {
     771           5 :             poGDS->m_bTried = true;
     772           5 :             poGDS->m_pabyCachedData =
     773           5 :                 static_cast<GByte *>(VSIMalloc3(3, nBlockXSize, nBlockYSize));
     774             :         }
     775         130 :         if (poGDS->m_pabyCachedData == nullptr)
     776           0 :             return CE_Failure;
     777             : 
     778         130 :         GDALPDFStream *poStream = poImage->GetStream();
     779         130 :         GByte *pabyStream = nullptr;
     780             : 
     781         130 :         if (poStream == nullptr)
     782           0 :             return CE_Failure;
     783             : 
     784         130 :         pabyStream = reinterpret_cast<GByte *>(poStream->GetBytes());
     785         130 :         if (pabyStream == nullptr)
     786           0 :             return CE_Failure;
     787             : 
     788         130 :         if (static_cast<size_t>(poStream->GetLength()) !=
     789         130 :             static_cast<size_t>(sTile.nBands) * nReqXSize * nReqYSize)
     790             :         {
     791           0 :             VSIFree(pabyStream);
     792           0 :             return CE_Failure;
     793             :         }
     794             : 
     795         130 :         memcpy(poGDS->m_pabyCachedData, pabyStream,
     796         130 :                static_cast<size_t>(poStream->GetLength()));
     797         130 :         VSIFree(pabyStream);
     798         130 :         poGDS->m_nLastBlockXOff = nBlockXOff;
     799         130 :         poGDS->m_nLastBlockYOff = nBlockYOff;
     800             :     }
     801             : 
     802         130 :     GByte *pabyData = static_cast<GByte *>(pImage);
     803         130 :     if (nBand != 4 && (nReqXSize != nBlockXSize || nReqYSize != nBlockYSize))
     804             :     {
     805          30 :         memset(pabyData, 0, static_cast<size_t>(nBlockXSize) * nBlockYSize);
     806             :     }
     807             : 
     808         130 :     if (poGDS->nBands >= 3 && sTile.nBands == 3)
     809             :     {
     810        2790 :         for (int j = 0; j < nReqYSize; j++)
     811             :         {
     812       75600 :             for (int i = 0; i < nReqXSize; i++)
     813             :             {
     814       72900 :                 pabyData[j * nBlockXSize + i] =
     815             :                     poGDS
     816       72900 :                         ->m_pabyCachedData[3 * (j * nReqXSize + i) + nBand - 1];
     817             :             }
     818          90 :         }
     819             :     }
     820          40 :     else if (sTile.nBands == 1)
     821             :     {
     822        6184 :         for (int j = 0; j < nReqYSize; j++)
     823             :         {
     824     1054720 :             for (int i = 0; i < nReqXSize; i++)
     825             :             {
     826     1048580 :                 pabyData[j * nBlockXSize + i] =
     827     1048580 :                     poGDS->m_pabyCachedData[j * nReqXSize + i];
     828             :             }
     829             :         }
     830             :     }
     831             : 
     832         130 :     return CE_None;
     833             : }
     834             : 
     835             : /************************************************************************/
     836             : /*                     GetSuggestedBlockAccessPattern()                 */
     837             : /************************************************************************/
     838             : 
     839             : GDALSuggestedBlockAccessPattern
     840           1 : PDFRasterBand::GetSuggestedBlockAccessPattern() const
     841             : {
     842           1 :     PDFDataset *poGDS = cpl::down_cast<PDFDataset *>(poDS);
     843           1 :     if (!poGDS->m_aiTiles.empty())
     844           0 :         return GSBAP_RANDOM;
     845           1 :     return GSBAP_LARGEST_CHUNK_POSSIBLE;
     846             : }
     847             : 
     848             : /************************************************************************/
     849             : /*                             IReadBlock()                             */
     850             : /************************************************************************/
     851             : 
     852       25832 : CPLErr PDFRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage)
     853             : 
     854             : {
     855       25832 :     PDFDataset *poGDS = cpl::down_cast<PDFDataset *>(poDS);
     856             : 
     857       25832 :     if (!poGDS->m_aiTiles.empty())
     858             :     {
     859         160 :         if (IReadBlockFromTile(nBlockXOff, nBlockYOff, pImage) == CE_None)
     860             :         {
     861         160 :             return CE_None;
     862             :         }
     863             :         else
     864             :         {
     865           0 :             poGDS->m_aiTiles.resize(0);
     866           0 :             poGDS->m_bTried = false;
     867           0 :             CPLFree(poGDS->m_pabyCachedData);
     868           0 :             poGDS->m_pabyCachedData = nullptr;
     869           0 :             poGDS->m_nLastBlockXOff = -1;
     870           0 :             poGDS->m_nLastBlockYOff = -1;
     871             :         }
     872             :     }
     873             : 
     874       25672 :     int nReqXSize = nBlockXSize;
     875       25672 :     int nReqYSize = nBlockYSize;
     876       25672 :     if ((nBlockXOff + 1) * nBlockXSize > nRasterXSize)
     877           0 :         nReqXSize = nRasterXSize - nBlockXOff * nBlockXSize;
     878       25672 :     if (nBlockYSize == 1)
     879       25671 :         nReqYSize = nRasterYSize;
     880           1 :     else if ((nBlockYOff + 1) * nBlockYSize > nRasterYSize)
     881           0 :         nReqYSize = nRasterYSize - nBlockYOff * nBlockYSize;
     882             : 
     883       25672 :     if (!poGDS->m_bTried)
     884             :     {
     885          55 :         poGDS->m_bTried = true;
     886          55 :         if (nBlockYSize == 1)
     887         162 :             poGDS->m_pabyCachedData = static_cast<GByte *>(VSIMalloc3(
     888          54 :                 std::max(3, poGDS->nBands), nRasterXSize, nRasterYSize));
     889             :         else
     890           3 :             poGDS->m_pabyCachedData = static_cast<GByte *>(VSIMalloc3(
     891           1 :                 std::max(3, poGDS->nBands), nBlockXSize, nBlockYSize));
     892             :     }
     893       25672 :     if (poGDS->m_pabyCachedData == nullptr)
     894           0 :         return CE_Failure;
     895             : 
     896       25672 :     if (poGDS->m_nLastBlockXOff == nBlockXOff &&
     897       25617 :         (nBlockYSize == 1 || poGDS->m_nLastBlockYOff == nBlockYOff) &&
     898       25617 :         poGDS->m_pabyCachedData != nullptr)
     899             :     {
     900             :         /*CPLDebug("PDF", "Using cached block (%d, %d)",
     901             :                  nBlockXOff, nBlockYOff);*/
     902             :         // do nothing
     903             :     }
     904             :     else
     905             :     {
     906             : #ifdef HAVE_PODOFO
     907             :         if (poGDS->m_bUseLib.test(PDFLIB_PODOFO) && nBand == 4)
     908             :         {
     909             :             memset(pImage, 255, nBlockXSize * nBlockYSize);
     910             :             return CE_None;
     911             :         }
     912             : #endif
     913             : 
     914          55 :         const int nReqXOff = nBlockXOff * nBlockXSize;
     915          55 :         const int nReqYOff = (nBlockYSize == 1) ? 0 : nBlockYOff * nBlockYSize;
     916          55 :         const GSpacing nPixelSpace = 1;
     917          55 :         const GSpacing nLineSpace = nBlockXSize;
     918          55 :         const GSpacing nBandSpace =
     919          55 :             static_cast<GSpacing>(nBlockXSize) *
     920          55 :             ((nBlockYSize == 1) ? nRasterYSize : nBlockYSize);
     921             : 
     922          55 :         CPLErr eErr = poGDS->ReadPixels(nReqXOff, nReqYOff, nReqXSize,
     923             :                                         nReqYSize, nPixelSpace, nLineSpace,
     924             :                                         nBandSpace, poGDS->m_pabyCachedData);
     925             : 
     926          55 :         if (eErr == CE_None)
     927             :         {
     928          55 :             poGDS->m_nLastBlockXOff = nBlockXOff;
     929          55 :             poGDS->m_nLastBlockYOff = nBlockYOff;
     930             :         }
     931             :         else
     932             :         {
     933           0 :             CPLFree(poGDS->m_pabyCachedData);
     934           0 :             poGDS->m_pabyCachedData = nullptr;
     935             :         }
     936             :     }
     937       25672 :     if (poGDS->m_pabyCachedData == nullptr)
     938           0 :         return CE_Failure;
     939             : 
     940       25672 :     if (nBlockYSize == 1)
     941       25671 :         memcpy(pImage,
     942       25671 :                poGDS->m_pabyCachedData +
     943       25671 :                    (nBand - 1) * nBlockXSize * nRasterYSize +
     944       25671 :                    nBlockYOff * nBlockXSize,
     945       25671 :                nBlockXSize);
     946             :     else
     947             :     {
     948           1 :         memcpy(pImage,
     949           1 :                poGDS->m_pabyCachedData +
     950           1 :                    static_cast<size_t>(nBand - 1) * nBlockXSize * nBlockYSize,
     951           1 :                static_cast<size_t>(nBlockXSize) * nBlockYSize);
     952             : 
     953           1 :         if (poGDS->m_bCacheBlocksForOtherBands && nBand == 1)
     954             :         {
     955           3 :             for (int iBand = 2; iBand <= poGDS->nBands; ++iBand)
     956             :             {
     957           4 :                 auto poOtherBand = cpl::down_cast<PDFRasterBand *>(
     958           2 :                     poGDS->papoBands[iBand - 1]);
     959             :                 GDALRasterBlock *poBlock =
     960           2 :                     poOtherBand->TryGetLockedBlockRef(nBlockXOff, nBlockYOff);
     961           2 :                 if (poBlock)
     962             :                 {
     963           0 :                     poBlock->DropLock();
     964             :                 }
     965             :                 else
     966             :                 {
     967           4 :                     poBlock = poOtherBand->GetLockedBlockRef(nBlockXOff,
     968           2 :                                                              nBlockYOff, TRUE);
     969           2 :                     if (poBlock)
     970             :                     {
     971           4 :                         memcpy(poBlock->GetDataRef(),
     972           2 :                                poGDS->m_pabyCachedData +
     973           2 :                                    static_cast<size_t>(iBand - 1) *
     974           2 :                                        nBlockXSize * nBlockYSize,
     975           2 :                                static_cast<size_t>(nBlockXSize) * nBlockYSize);
     976           2 :                         poBlock->DropLock();
     977             :                     }
     978             :                 }
     979             :             }
     980             :         }
     981             :     }
     982             : 
     983       25672 :     return CE_None;
     984             : }
     985             : 
     986             : /************************************************************************/
     987             : /*                    PDFEnterPasswordFromConsoleIfNeeded()             */
     988             : /************************************************************************/
     989             : 
     990           2 : static const char *PDFEnterPasswordFromConsoleIfNeeded(const char *pszUserPwd)
     991             : {
     992           2 :     if (EQUAL(pszUserPwd, "ASK_INTERACTIVE"))
     993             :     {
     994             :         static char szPassword[81];
     995           2 :         printf("Enter password (will be echo'ed in the console): "); /*ok*/
     996           2 :         if (nullptr == fgets(szPassword, sizeof(szPassword), stdin))
     997             :         {
     998           0 :             fprintf(stderr, "WARNING: Error getting password.\n"); /*ok*/
     999             :         }
    1000           2 :         szPassword[sizeof(szPassword) - 1] = 0;
    1001           2 :         char *sz10 = strchr(szPassword, '\n');
    1002           2 :         if (sz10)
    1003           0 :             *sz10 = 0;
    1004           2 :         return szPassword;
    1005             :     }
    1006           0 :     return pszUserPwd;
    1007             : }
    1008             : 
    1009             : #ifdef HAVE_PDFIUM
    1010             : 
    1011             : /************************************************************************/
    1012             : /*                         Pdfium Load/Unload                           */
    1013             : /* Copyright (C) 2015 Klokan Technologies GmbH (http://www.klokantech.com/) */
    1014             : /* Author: Martin Mikita <martin.mikita@klokantech.com>                 */
    1015             : /************************************************************************/
    1016             : 
    1017             : // Flag for calling PDFium Init and Destroy methods
    1018             : bool PDFDataset::g_bPdfiumInit = false;
    1019             : 
    1020             : // Pdfium global read mutex - Pdfium is not multi-thread
    1021             : static CPLMutex *g_oPdfiumReadMutex = nullptr;
    1022             : static CPLMutex *g_oPdfiumLoadDocMutex = nullptr;
    1023             : 
    1024             : // Comparison of char* for std::map
    1025             : struct cmp_str
    1026             : {
    1027             :     bool operator()(char const *a, char const *b) const
    1028             :     {
    1029             :         return strcmp(a, b) < 0;
    1030             :     }
    1031             : };
    1032             : 
    1033             : static int GDALPdfiumGetBlock(void *param, unsigned long position,
    1034             :                               unsigned char *pBuf, unsigned long size)
    1035             : {
    1036             :     VSILFILE *fp = static_cast<VSILFILE *>(param);
    1037             :     VSIFSeekL(fp, position, SEEK_SET);
    1038             :     return VSIFReadL(pBuf, size, 1, fp) == 1;
    1039             : }
    1040             : 
    1041             : // List of all PDF datasets
    1042             : typedef std::map<const char *, TPdfiumDocumentStruct *, cmp_str>
    1043             :     TMapPdfiumDatasets;
    1044             : static TMapPdfiumDatasets g_mPdfiumDatasets;
    1045             : 
    1046             : /**
    1047             :  * Loading PDFIUM page
    1048             :  * - multithreading requires "mutex"
    1049             :  * - one page can require too much RAM
    1050             :  * - we will have one document per filename and one object per page
    1051             :  */
    1052             : 
    1053             : static int LoadPdfiumDocumentPage(const char *pszFilename,
    1054             :                                   const char *pszUserPwd, int pageNum,
    1055             :                                   TPdfiumDocumentStruct **doc,
    1056             :                                   TPdfiumPageStruct **page, int *pnPageCount)
    1057             : {
    1058             :     // Prepare nullptr for error returning
    1059             :     if (doc)
    1060             :         *doc = nullptr;
    1061             :     if (page)
    1062             :         *page = nullptr;
    1063             :     if (pnPageCount)
    1064             :         *pnPageCount = 0;
    1065             : 
    1066             :     // Loading document and page must be only in one thread!
    1067             :     CPLCreateOrAcquireMutex(&g_oPdfiumLoadDocMutex, PDFIUM_MUTEX_TIMEOUT);
    1068             : 
    1069             :     // Library can be destroyed if every PDF dataset was closed!
    1070             :     if (!PDFDataset::g_bPdfiumInit)
    1071             :     {
    1072             :         FPDF_InitLibrary();
    1073             :         PDFDataset::g_bPdfiumInit = TRUE;
    1074             :     }
    1075             : 
    1076             :     TMapPdfiumDatasets::iterator it;
    1077             :     it = g_mPdfiumDatasets.find(pszFilename);
    1078             :     TPdfiumDocumentStruct *poDoc = nullptr;
    1079             :     // Load new document if missing
    1080             :     if (it == g_mPdfiumDatasets.end())
    1081             :     {
    1082             :         // Try without password (if PDF not requires password it can fail)
    1083             : 
    1084             :         VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
    1085             :         if (fp == nullptr)
    1086             :         {
    1087             :             CPLReleaseMutex(g_oPdfiumLoadDocMutex);
    1088             :             return FALSE;
    1089             :         }
    1090             :         VSIFSeekL(fp, 0, SEEK_END);
    1091             :         const auto nFileLen64 = VSIFTellL(fp);
    1092             :         if constexpr (LONG_MAX < std::numeric_limits<vsi_l_offset>::max())
    1093             :         {
    1094             :             if (nFileLen64 > LONG_MAX)
    1095             :             {
    1096             :                 VSIFCloseL(fp);
    1097             :                 CPLReleaseMutex(g_oPdfiumLoadDocMutex);
    1098             :                 return FALSE;
    1099             :             }
    1100             :         }
    1101             : 
    1102             :         FPDF_FILEACCESS *psFileAccess = new FPDF_FILEACCESS;
    1103             :         psFileAccess->m_Param = fp;
    1104             :         psFileAccess->m_FileLen = static_cast<unsigned long>(nFileLen64);
    1105             :         psFileAccess->m_GetBlock = GDALPdfiumGetBlock;
    1106             :         CPDF_Document *docPdfium = CPDFDocumentFromFPDFDocument(
    1107             :             FPDF_LoadCustomDocument(psFileAccess, nullptr));
    1108             :         if (docPdfium == nullptr)
    1109             :         {
    1110             :             unsigned long err = FPDF_GetLastError();
    1111             :             if (err == FPDF_ERR_PASSWORD)
    1112             :             {
    1113             :                 if (pszUserPwd)
    1114             :                 {
    1115             :                     pszUserPwd =
    1116             :                         PDFEnterPasswordFromConsoleIfNeeded(pszUserPwd);
    1117             :                     docPdfium = CPDFDocumentFromFPDFDocument(
    1118             :                         FPDF_LoadCustomDocument(psFileAccess, pszUserPwd));
    1119             :                     if (docPdfium == nullptr)
    1120             :                         err = FPDF_GetLastError();
    1121             :                     else
    1122             :                         err = FPDF_ERR_SUCCESS;
    1123             :                 }
    1124             :                 else
    1125             :                 {
    1126             :                     CPLError(CE_Failure, CPLE_AppDefined,
    1127             :                              "A password is needed. You can specify it through "
    1128             :                              "the PDF_USER_PWD "
    1129             :                              "configuration option / USER_PWD open option "
    1130             :                              "(that can be set to ASK_INTERACTIVE)");
    1131             : 
    1132             :                     VSIFCloseL(fp);
    1133             :                     delete psFileAccess;
    1134             :                     CPLReleaseMutex(g_oPdfiumLoadDocMutex);
    1135             :                     return FALSE;
    1136             :                 }
    1137             :             }  // First Error Password [null password given]
    1138             :             if (err != FPDF_ERR_SUCCESS)
    1139             :             {
    1140             :                 if (err == FPDF_ERR_PASSWORD)
    1141             :                     CPLError(CE_Failure, CPLE_AppDefined,
    1142             :                              "PDFium Invalid password.");
    1143             :                 else if (err == FPDF_ERR_SECURITY)
    1144             :                     CPLError(CE_Failure, CPLE_AppDefined,
    1145             :                              "PDFium Unsupported security scheme.");
    1146             :                 else if (err == FPDF_ERR_FORMAT)
    1147             :                     CPLError(CE_Failure, CPLE_AppDefined,
    1148             :                              "PDFium File not in PDF format or corrupted.");
    1149             :                 else if (err == FPDF_ERR_FILE)
    1150             :                     CPLError(CE_Failure, CPLE_AppDefined,
    1151             :                              "PDFium File not found or could not be opened.");
    1152             :                 else
    1153             :                     CPLError(CE_Failure, CPLE_AppDefined,
    1154             :                              "PDFium Unknown PDF error or invalid PDF.");
    1155             : 
    1156             :                 VSIFCloseL(fp);
    1157             :                 delete psFileAccess;
    1158             :                 CPLReleaseMutex(g_oPdfiumLoadDocMutex);
    1159             :                 return FALSE;
    1160             :             }
    1161             :         }  // ~ wrong PDF or password required
    1162             : 
    1163             :         // Create new poDoc
    1164             :         poDoc = new TPdfiumDocumentStruct;
    1165             :         if (!poDoc)
    1166             :         {
    1167             :             CPLError(CE_Failure, CPLE_AppDefined,
    1168             :                      "Not enough memory for Pdfium Document object");
    1169             : 
    1170             :             VSIFCloseL(fp);
    1171             :             delete psFileAccess;
    1172             :             CPLReleaseMutex(g_oPdfiumLoadDocMutex);
    1173             :             return FALSE;
    1174             :         }
    1175             :         poDoc->filename = CPLStrdup(pszFilename);
    1176             :         poDoc->doc = docPdfium;
    1177             :         poDoc->psFileAccess = psFileAccess;
    1178             : 
    1179             :         g_mPdfiumDatasets[poDoc->filename] = poDoc;
    1180             :     }
    1181             :     // Document already loaded
    1182             :     else
    1183             :     {
    1184             :         poDoc = it->second;
    1185             :     }
    1186             : 
    1187             :     // Check page num in document
    1188             :     int nPages = poDoc->doc->GetPageCount();
    1189             :     if (pageNum < 1 || pageNum > nPages)
    1190             :     {
    1191             :         CPLError(CE_Failure, CPLE_AppDefined,
    1192             :                  "PDFium Invalid page number (%d/%d) for document %s", pageNum,
    1193             :                  nPages, pszFilename);
    1194             : 
    1195             :         CPLReleaseMutex(g_oPdfiumLoadDocMutex);
    1196             :         return FALSE;
    1197             :     }
    1198             : 
    1199             :     /* Sanity check to validate page count */
    1200             :     if (pageNum != nPages)
    1201             :     {
    1202             :         if (poDoc->doc->GetPageDictionary(nPages - 1) == nullptr)
    1203             :         {
    1204             :             CPLError(CE_Failure, CPLE_AppDefined,
    1205             :                      "Invalid PDF : invalid page count");
    1206             :             CPLReleaseMutex(g_oPdfiumLoadDocMutex);
    1207             :             return FALSE;
    1208             :         }
    1209             :     }
    1210             : 
    1211             :     TMapPdfiumPages::iterator itPage;
    1212             :     itPage = poDoc->pages.find(pageNum);
    1213             :     TPdfiumPageStruct *poPage = nullptr;
    1214             :     // Page not loaded
    1215             :     if (itPage == poDoc->pages.end())
    1216             :     {
    1217             :         auto pDict = poDoc->doc->GetMutablePageDictionary(pageNum - 1);
    1218             :         if (pDict == nullptr)
    1219             :         {
    1220             :             CPLError(CE_Failure, CPLE_AppDefined,
    1221             :                      "Invalid PDFium : invalid page");
    1222             : 
    1223             :             CPLReleaseMutex(g_oPdfiumLoadDocMutex);
    1224             :             return FALSE;
    1225             :         }
    1226             :         auto pPage = pdfium::MakeRetain<CPDF_Page>(poDoc->doc, pDict);
    1227             : 
    1228             :         poPage = new TPdfiumPageStruct;
    1229             :         if (!poPage)
    1230             :         {
    1231             :             CPLError(CE_Failure, CPLE_AppDefined,
    1232             :                      "Not enough memory for Pdfium Page object");
    1233             : 
    1234             :             CPLReleaseMutex(g_oPdfiumLoadDocMutex);
    1235             :             return FALSE;
    1236             :         }
    1237             :         poPage->pageNum = pageNum;
    1238             :         poPage->page = pPage.Leak();
    1239             :         poPage->readMutex = nullptr;
    1240             :         poPage->sharedNum = 0;
    1241             : 
    1242             :         poDoc->pages[pageNum] = poPage;
    1243             :     }
    1244             :     // Page already loaded
    1245             :     else
    1246             :     {
    1247             :         poPage = itPage->second;
    1248             :     }
    1249             : 
    1250             :     // Increase number of used
    1251             :     ++poPage->sharedNum;
    1252             : 
    1253             :     if (doc)
    1254             :         *doc = poDoc;
    1255             :     if (page)
    1256             :         *page = poPage;
    1257             :     if (pnPageCount)
    1258             :         *pnPageCount = nPages;
    1259             : 
    1260             :     CPLReleaseMutex(g_oPdfiumLoadDocMutex);
    1261             : 
    1262             :     return TRUE;
    1263             : }
    1264             : 
    1265             : // ~ static int LoadPdfiumDocumentPage()
    1266             : 
    1267             : static int UnloadPdfiumDocumentPage(TPdfiumDocumentStruct **doc,
    1268             :                                     TPdfiumPageStruct **page)
    1269             : {
    1270             :     if (!doc || !page)
    1271             :         return FALSE;
    1272             : 
    1273             :     TPdfiumPageStruct *pPage = *page;
    1274             :     TPdfiumDocumentStruct *pDoc = *doc;
    1275             : 
    1276             :     // Get mutex for loading pdfium
    1277             :     CPLCreateOrAcquireMutex(&g_oPdfiumLoadDocMutex, PDFIUM_MUTEX_TIMEOUT);
    1278             : 
    1279             :     // Decrease page use
    1280             :     --pPage->sharedNum;
    1281             : 
    1282             : #ifdef DEBUG
    1283             :     CPLDebug("PDF", "PDFDataset::UnloadPdfiumDocumentPage: page shared num %d",
    1284             :              pPage->sharedNum);
    1285             : #endif
    1286             :     // Page is used (also document)
    1287             :     if (pPage->sharedNum != 0)
    1288             :     {
    1289             :         CPLReleaseMutex(g_oPdfiumLoadDocMutex);
    1290             :         return TRUE;
    1291             :     }
    1292             : 
    1293             :     // Get mutex, release and destroy it
    1294             :     CPLCreateOrAcquireMutex(&(pPage->readMutex), PDFIUM_MUTEX_TIMEOUT);
    1295             :     CPLReleaseMutex(pPage->readMutex);
    1296             :     CPLDestroyMutex(pPage->readMutex);
    1297             :     // Close page and remove from map
    1298             :     FPDF_ClosePage(FPDFPageFromIPDFPage(pPage->page));
    1299             : 
    1300             :     pDoc->pages.erase(pPage->pageNum);
    1301             :     delete pPage;
    1302             :     pPage = nullptr;
    1303             : 
    1304             : #ifdef DEBUG
    1305             :     CPLDebug("PDF", "PDFDataset::UnloadPdfiumDocumentPage: pages %lu",
    1306             :              pDoc->pages.size());
    1307             : #endif
    1308             :     // Another page is used
    1309             :     if (!pDoc->pages.empty())
    1310             :     {
    1311             :         CPLReleaseMutex(g_oPdfiumLoadDocMutex);
    1312             :         return TRUE;
    1313             :     }
    1314             : 
    1315             :     // Close document and remove from map
    1316             :     FPDF_CloseDocument(FPDFDocumentFromCPDFDocument(pDoc->doc));
    1317             :     g_mPdfiumDatasets.erase(pDoc->filename);
    1318             :     CPLFree(pDoc->filename);
    1319             :     VSIFCloseL(static_cast<VSILFILE *>(pDoc->psFileAccess->m_Param));
    1320             :     delete pDoc->psFileAccess;
    1321             :     delete pDoc;
    1322             :     pDoc = nullptr;
    1323             : 
    1324             : #ifdef DEBUG
    1325             :     CPLDebug("PDF", "PDFDataset::UnloadPdfiumDocumentPage: documents %lu",
    1326             :              g_mPdfiumDatasets.size());
    1327             : #endif
    1328             :     // Another document is used
    1329             :     if (!g_mPdfiumDatasets.empty())
    1330             :     {
    1331             :         CPLReleaseMutex(g_oPdfiumLoadDocMutex);
    1332             :         return TRUE;
    1333             :     }
    1334             : 
    1335             : #ifdef DEBUG
    1336             :     CPLDebug("PDF", "PDFDataset::UnloadPdfiumDocumentPage: Nothing loaded, "
    1337             :                     "destroy Library");
    1338             : #endif
    1339             :     // No document loaded, destroy pdfium
    1340             :     FPDF_DestroyLibrary();
    1341             :     PDFDataset::g_bPdfiumInit = FALSE;
    1342             : 
    1343             :     CPLReleaseMutex(g_oPdfiumLoadDocMutex);
    1344             : 
    1345             :     return TRUE;
    1346             : }
    1347             : 
    1348             : // ~ static int UnloadPdfiumDocumentPage()
    1349             : 
    1350             : #endif  // ~ HAVE_PDFIUM
    1351             : 
    1352             : /************************************************************************/
    1353             : /*                             GetOption()                              */
    1354             : /************************************************************************/
    1355             : 
    1356        1151 : const char *PDFDataset::GetOption(char **papszOpenOptionsIn,
    1357             :                                   const char *pszOptionName,
    1358             :                                   const char *pszDefaultVal)
    1359             : {
    1360        1151 :     CPLErr eLastErrType = CPLGetLastErrorType();
    1361        1151 :     CPLErrorNum nLastErrno = CPLGetLastErrorNo();
    1362        2302 :     CPLString osLastErrorMsg(CPLGetLastErrorMsg());
    1363        1151 :     CPLXMLNode *psNode = CPLParseXMLString(PDFGetOpenOptionList());
    1364        1151 :     CPLErrorSetState(eLastErrType, nLastErrno, osLastErrorMsg);
    1365        1151 :     if (psNode == nullptr)
    1366           0 :         return pszDefaultVal;
    1367        1151 :     CPLXMLNode *psIter = psNode->psChild;
    1368        4736 :     while (psIter != nullptr)
    1369             :     {
    1370        4736 :         if (EQUAL(CPLGetXMLValue(psIter, "name", ""), pszOptionName))
    1371             :         {
    1372             :             const char *pszVal =
    1373        1151 :                 CSLFetchNameValue(papszOpenOptionsIn, pszOptionName);
    1374        1151 :             if (pszVal != nullptr)
    1375             :             {
    1376          20 :                 CPLDestroyXMLNode(psNode);
    1377          20 :                 return pszVal;
    1378             :             }
    1379             :             const char *pszAltConfigOption =
    1380        1131 :                 CPLGetXMLValue(psIter, "alt_config_option", nullptr);
    1381        1131 :             if (pszAltConfigOption != nullptr)
    1382             :             {
    1383        1131 :                 pszVal = CPLGetConfigOption(pszAltConfigOption, pszDefaultVal);
    1384        1131 :                 CPLDestroyXMLNode(psNode);
    1385        1131 :                 return pszVal;
    1386             :             }
    1387           0 :             CPLDestroyXMLNode(psNode);
    1388           0 :             return pszDefaultVal;
    1389             :         }
    1390        3585 :         psIter = psIter->psNext;
    1391             :     }
    1392           0 :     CPLError(CE_Failure, CPLE_AppDefined,
    1393             :              "Requesting an undocumented open option '%s'", pszOptionName);
    1394           0 :     CPLDestroyXMLNode(psNode);
    1395           0 :     return pszDefaultVal;
    1396             : }
    1397             : 
    1398             : #ifdef HAVE_PDFIUM
    1399             : 
    1400             : /************************************************************************/
    1401             : /*                         GDALPDFiumOCContext                          */
    1402             : /************************************************************************/
    1403             : 
    1404             : class GDALPDFiumOCContext final : public CPDF_OCContextInterface
    1405             : {
    1406             :     PDFDataset *m_poDS;
    1407             :     RetainPtr<CPDF_OCContext> m_DefaultOCContext;
    1408             : 
    1409             :     CPL_DISALLOW_COPY_ASSIGN(GDALPDFiumOCContext)
    1410             : 
    1411             :   public:
    1412             :     GDALPDFiumOCContext(PDFDataset *poDS, CPDF_Document *pDoc,
    1413             :                         CPDF_OCContext::UsageType usage)
    1414             :         : m_poDS(poDS),
    1415             :           m_DefaultOCContext(pdfium::MakeRetain<CPDF_OCContext>(pDoc, usage))
    1416             :     {
    1417             :     }
    1418             : 
    1419             :     virtual bool
    1420             :     CheckOCGDictVisible(const CPDF_Dictionary *pOCGDict) const override
    1421             :     {
    1422             :         // CPLDebug("PDF", "CheckOCGDictVisible(%d,%d)",
    1423             :         //          pOCGDict->GetObjNum(), pOCGDict->GetGenNum() );
    1424             :         PDFDataset::VisibilityState eVisibility =
    1425             :             m_poDS->GetVisibilityStateForOGCPdfium(pOCGDict->GetObjNum(),
    1426             :                                                    pOCGDict->GetGenNum());
    1427             :         if (eVisibility == PDFDataset::VISIBILITY_ON)
    1428             :             return true;
    1429             :         if (eVisibility == PDFDataset::VISIBILITY_OFF)
    1430             :             return false;
    1431             :         return m_DefaultOCContext->CheckOCGDictVisible(pOCGDict);
    1432             :     }
    1433             : };
    1434             : 
    1435             : /************************************************************************/
    1436             : /*                      GDALPDFiumRenderDeviceDriver                    */
    1437             : /************************************************************************/
    1438             : 
    1439             : class GDALPDFiumRenderDeviceDriver final : public RenderDeviceDriverIface
    1440             : {
    1441             :     std::unique_ptr<RenderDeviceDriverIface> m_poParent;
    1442             :     CFX_RenderDevice *device_;
    1443             : 
    1444             :     int bEnableVector;
    1445             :     int bEnableText;
    1446             :     int bEnableBitmap;
    1447             :     int bTemporaryEnableVectorForTextStroking;
    1448             : 
    1449             :     CPL_DISALLOW_COPY_ASSIGN(GDALPDFiumRenderDeviceDriver)
    1450             : 
    1451             :   public:
    1452             :     GDALPDFiumRenderDeviceDriver(
    1453             :         std::unique_ptr<RenderDeviceDriverIface> &&poParent,
    1454             :         CFX_RenderDevice *pDevice)
    1455             :         : m_poParent(std::move(poParent)), device_(pDevice),
    1456             :           bEnableVector(TRUE), bEnableText(TRUE), bEnableBitmap(TRUE),
    1457             :           bTemporaryEnableVectorForTextStroking(FALSE)
    1458             :     {
    1459             :     }
    1460             : 
    1461             :     virtual ~GDALPDFiumRenderDeviceDriver() = default;
    1462             : 
    1463             :     void SetEnableVector(int bFlag)
    1464             :     {
    1465             :         bEnableVector = bFlag;
    1466             :     }
    1467             : 
    1468             :     void SetEnableText(int bFlag)
    1469             :     {
    1470             :         bEnableText = bFlag;
    1471             :     }
    1472             : 
    1473             :     void SetEnableBitmap(int bFlag)
    1474             :     {
    1475             :         bEnableBitmap = bFlag;
    1476             :     }
    1477             : 
    1478             :     DeviceType GetDeviceType() const override
    1479             :     {
    1480             :         return m_poParent->GetDeviceType();
    1481             :     }
    1482             : 
    1483             :     int GetDeviceCaps(int caps_id) const override
    1484             :     {
    1485             :         return m_poParent->GetDeviceCaps(caps_id);
    1486             :     }
    1487             : 
    1488             :     void SaveState() override
    1489             :     {
    1490             :         m_poParent->SaveState();
    1491             :     }
    1492             : 
    1493             :     void RestoreState(bool bKeepSaved) override
    1494             :     {
    1495             :         m_poParent->RestoreState(bKeepSaved);
    1496             :     }
    1497             : 
    1498             :     void SetBaseClip(const FX_RECT &rect) override
    1499             :     {
    1500             :         m_poParent->SetBaseClip(rect);
    1501             :     }
    1502             : 
    1503             :     virtual bool
    1504             :     SetClip_PathFill(const CFX_Path &path, const CFX_Matrix *pObject2Device,
    1505             :                      const CFX_FillRenderOptions &fill_options) override
    1506             :     {
    1507             :         if (!bEnableVector && !bTemporaryEnableVectorForTextStroking)
    1508             :             return true;
    1509             :         return m_poParent->SetClip_PathFill(path, pObject2Device, fill_options);
    1510             :     }
    1511             : 
    1512             :     virtual bool
    1513             :     SetClip_PathStroke(const CFX_Path &path, const CFX_Matrix *pObject2Device,
    1514             :                        const CFX_GraphStateData *pGraphState) override
    1515             :     {
    1516             :         if (!bEnableVector && !bTemporaryEnableVectorForTextStroking)
    1517             :             return true;
    1518             :         return m_poParent->SetClip_PathStroke(path, pObject2Device,
    1519             :                                               pGraphState);
    1520             :     }
    1521             : 
    1522             :     virtual bool DrawPath(const CFX_Path &path,
    1523             :                           const CFX_Matrix *pObject2Device,
    1524             :                           const CFX_GraphStateData *pGraphState,
    1525             :                           uint32_t fill_color, uint32_t stroke_color,
    1526             :                           const CFX_FillRenderOptions &fill_options) override
    1527             :     {
    1528             :         if (!bEnableVector && !bTemporaryEnableVectorForTextStroking)
    1529             :             return true;
    1530             :         return m_poParent->DrawPath(path, pObject2Device, pGraphState,
    1531             :                                     fill_color, stroke_color, fill_options);
    1532             :     }
    1533             : 
    1534             :     bool FillRect(const FX_RECT &rect, uint32_t fill_color) override
    1535             :     {
    1536             :         return m_poParent->FillRect(rect, fill_color);
    1537             :     }
    1538             : 
    1539             :     virtual bool DrawCosmeticLine(const CFX_PointF &ptMoveTo,
    1540             :                                   const CFX_PointF &ptLineTo,
    1541             :                                   uint32_t color) override
    1542             :     {
    1543             :         if (!bEnableVector && !bTemporaryEnableVectorForTextStroking)
    1544             :             return TRUE;
    1545             :         return m_poParent->DrawCosmeticLine(ptMoveTo, ptLineTo, color);
    1546             :     }
    1547             : 
    1548             :     FX_RECT GetClipBox() const override
    1549             :     {
    1550             :         return m_poParent->GetClipBox();
    1551             :     }
    1552             : 
    1553             :     virtual bool GetDIBits(RetainPtr<CFX_DIBitmap> bitmap, int left,
    1554             :                            int top) const override
    1555             :     {
    1556             :         return m_poParent->GetDIBits(std::move(bitmap), left, top);
    1557             :     }
    1558             : 
    1559             :     RetainPtr<const CFX_DIBitmap> GetBackDrop() const override
    1560             :     {
    1561             :         return m_poParent->GetBackDrop();
    1562             :     }
    1563             : 
    1564             :     virtual bool SetDIBits(RetainPtr<const CFX_DIBBase> bitmap, uint32_t color,
    1565             :                            const FX_RECT &src_rect, int dest_left, int dest_top,
    1566             :                            BlendMode blend_type) override
    1567             :     {
    1568             :         if (!bEnableBitmap && !bTemporaryEnableVectorForTextStroking)
    1569             :             return true;
    1570             :         return m_poParent->SetDIBits(std::move(bitmap), color, src_rect,
    1571             :                                      dest_left, dest_top, blend_type);
    1572             :     }
    1573             : 
    1574             :     virtual bool StretchDIBits(RetainPtr<const CFX_DIBBase> bitmap,
    1575             :                                uint32_t color, int dest_left, int dest_top,
    1576             :                                int dest_width, int dest_height,
    1577             :                                const FX_RECT *pClipRect,
    1578             :                                const FXDIB_ResampleOptions &options,
    1579             :                                BlendMode blend_type) override
    1580             :     {
    1581             :         if (!bEnableBitmap && !bTemporaryEnableVectorForTextStroking)
    1582             :             return true;
    1583             :         return m_poParent->StretchDIBits(std::move(bitmap), color, dest_left,
    1584             :                                          dest_top, dest_width, dest_height,
    1585             :                                          pClipRect, options, blend_type);
    1586             :     }
    1587             : 
    1588             :     virtual StartResult StartDIBits(RetainPtr<const CFX_DIBBase> bitmap,
    1589             :                                     float alpha, uint32_t color,
    1590             :                                     const CFX_Matrix &matrix,
    1591             :                                     const FXDIB_ResampleOptions &options,
    1592             :                                     BlendMode blend_type) override
    1593             :     {
    1594             :         if (!bEnableBitmap && !bTemporaryEnableVectorForTextStroking)
    1595             :             return StartResult(Result::kSuccess, nullptr);
    1596             :         return m_poParent->StartDIBits(std::move(bitmap), alpha, color, matrix,
    1597             :                                        options, blend_type);
    1598             :     }
    1599             : 
    1600             :     virtual bool ContinueDIBits(CFX_AggImageRenderer *handle,
    1601             :                                 PauseIndicatorIface *pPause) override
    1602             :     {
    1603             :         return m_poParent->ContinueDIBits(handle, pPause);
    1604             :     }
    1605             : 
    1606             :     virtual bool DrawDeviceText(const pdfium::span<const TextCharPos> &pCharPos,
    1607             :                                 CFX_Font *pFont,
    1608             :                                 const CFX_Matrix &mtObject2Device,
    1609             :                                 float font_size, uint32_t color,
    1610             :                                 const CFX_TextRenderOptions &options) override
    1611             :     {
    1612             :         if (bEnableText)
    1613             :         {
    1614             :             // This is quite tricky. We call again the guy who called us
    1615             :             // (CFX_RenderDevice::DrawNormalText()) but we set a special flag to
    1616             :             // allow vector&raster operations so that the rendering will happen
    1617             :             // in the next phase
    1618             :             if (bTemporaryEnableVectorForTextStroking)
    1619             :                 return FALSE;  // this is the default behavior of the parent
    1620             :             bTemporaryEnableVectorForTextStroking = true;
    1621             :             bool bRet = device_->DrawNormalText(
    1622             :                 pCharPos, pFont, font_size, mtObject2Device, color, options);
    1623             :             bTemporaryEnableVectorForTextStroking = FALSE;
    1624             :             return bRet;
    1625             :         }
    1626             :         else
    1627             :             return true;  // pretend that we did the job
    1628             :     }
    1629             : 
    1630             :     int GetDriverType() const override
    1631             :     {
    1632             :         return m_poParent->GetDriverType();
    1633             :     }
    1634             : 
    1635             : #if defined(_SKIA_SUPPORT_)
    1636             :     virtual bool DrawShading(const CPDF_ShadingPattern &pattern,
    1637             :                              const CFX_Matrix &matrix, const FX_RECT &clip_rect,
    1638             :                              int alpha) override
    1639             :     {
    1640             :         if (!bEnableBitmap && !bTemporaryEnableVectorForTextStroking)
    1641             :             return true;
    1642             :         return m_poParent->DrawShading(pattern, matrix, clip_rect, alpha);
    1643             :     }
    1644             : #endif
    1645             : 
    1646             :     bool MultiplyAlpha(float alpha) override
    1647             :     {
    1648             :         return m_poParent->MultiplyAlpha(alpha);
    1649             :     }
    1650             : 
    1651             :     bool MultiplyAlphaMask(RetainPtr<const CFX_DIBitmap> mask) override
    1652             :     {
    1653             :         return m_poParent->MultiplyAlphaMask(std::move(mask));
    1654             :     }
    1655             : 
    1656             : #if defined(_SKIA_SUPPORT_)
    1657             :     virtual bool SetBitsWithMask(RetainPtr<const CFX_DIBBase> bitmap,
    1658             :                                  RetainPtr<const CFX_DIBBase> mask, int left,
    1659             :                                  int top, float alpha,
    1660             :                                  BlendMode blend_type) override
    1661             :     {
    1662             :         if (!bEnableBitmap && !bTemporaryEnableVectorForTextStroking)
    1663             :             return true;
    1664             :         return m_poParent->SetBitsWithMask(std::move(bitmap), std::move(mask),
    1665             :                                            left, top, alpha, blend_type);
    1666             :     }
    1667             : 
    1668             :     void SetGroupKnockout(bool group_knockout) override
    1669             :     {
    1670             :         m_poParent->SetGroupKnockout(group_knockout);
    1671             :     }
    1672             : #endif
    1673             : #if defined _SKIA_SUPPORT_ || defined _SKIA_SUPPORT_PATHS_
    1674             :     void Flush() override
    1675             :     {
    1676             :         return m_poParent->Flush();
    1677             :     }
    1678             : #endif
    1679             : };
    1680             : 
    1681             : /************************************************************************/
    1682             : /*                         PDFiumRenderPageBitmap()                     */
    1683             : /************************************************************************/
    1684             : 
    1685             : /* This method is a customization of RenderPageImpl()
    1686             :    from pdfium/fpdfsdk/cpdfsdk_renderpage.cpp to allow selection of which OGC/layer are
    1687             :    active. Thus it inherits the following license */
    1688             : // Copyright 2014-2020 PDFium Authors. All rights reserved.
    1689             : //
    1690             : // Redistribution and use in source and binary forms, with or without
    1691             : // modification, are permitted provided that the following conditions are
    1692             : // met:
    1693             : //
    1694             : //    * Redistributions of source code must retain the above copyright
    1695             : // notice, this list of conditions and the following disclaimer.
    1696             : //    * Redistributions in binary form must reproduce the above
    1697             : // copyright notice, this list of conditions and the following disclaimer
    1698             : // in the documentation and/or other materials provided with the
    1699             : // distribution.
    1700             : //    * Neither the name of Google Inc. nor the names of its
    1701             : // contributors may be used to endorse or promote products derived from
    1702             : // this software without specific prior written permission.
    1703             : //
    1704             : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    1705             : // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    1706             : // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    1707             : // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    1708             : // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    1709             : // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    1710             : // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    1711             : // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    1712             : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    1713             : // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    1714             : // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    1715             : 
    1716             : static void myRenderPageImpl(PDFDataset *poDS, CPDF_PageRenderContext *pContext,
    1717             :                              CPDF_Page *pPage, const CFX_Matrix &matrix,
    1718             :                              const FX_RECT &clipping_rect, int flags,
    1719             :                              const FPDF_COLORSCHEME *color_scheme,
    1720             :                              bool bNeedToRestore, CPDFSDK_PauseAdapter *pause)
    1721             : {
    1722             :     if (!pContext->options_)
    1723             :         pContext->options_ = std::make_unique<CPDF_RenderOptions>();
    1724             : 
    1725             :     auto &options = pContext->options_->GetOptions();
    1726             :     options.bClearType = !!(flags & FPDF_LCD_TEXT);
    1727             :     options.bNoNativeText = !!(flags & FPDF_NO_NATIVETEXT);
    1728             :     options.bLimitedImageCache = !!(flags & FPDF_RENDER_LIMITEDIMAGECACHE);
    1729             :     options.bForceHalftone = !!(flags & FPDF_RENDER_FORCEHALFTONE);
    1730             :     options.bNoTextSmooth = !!(flags & FPDF_RENDER_NO_SMOOTHTEXT);
    1731             :     options.bNoImageSmooth = !!(flags & FPDF_RENDER_NO_SMOOTHIMAGE);
    1732             :     options.bNoPathSmooth = !!(flags & FPDF_RENDER_NO_SMOOTHPATH);
    1733             : 
    1734             :     // Grayscale output
    1735             :     if (flags & FPDF_GRAYSCALE)
    1736             :         pContext->options_->SetColorMode(CPDF_RenderOptions::kGray);
    1737             : 
    1738             :     if (color_scheme)
    1739             :     {
    1740             :         pContext->options_->SetColorMode(CPDF_RenderOptions::kForcedColor);
    1741             :         SetColorFromScheme(color_scheme, pContext->options_.get());
    1742             :         options.bConvertFillToStroke = !!(flags & FPDF_CONVERT_FILL_TO_STROKE);
    1743             :     }
    1744             : 
    1745             :     const CPDF_OCContext::UsageType usage = (flags & FPDF_PRINTING)
    1746             :                                                 ? CPDF_OCContext::kPrint
    1747             :                                                 : CPDF_OCContext::kView;
    1748             :     pContext->options_->SetOCContext(pdfium::MakeRetain<GDALPDFiumOCContext>(
    1749             :         poDS, pPage->GetDocument(), usage));
    1750             : 
    1751             :     pContext->device_->SaveState();
    1752             :     pContext->device_->SetBaseClip(clipping_rect);
    1753             :     pContext->device_->SetClip_Rect(clipping_rect);
    1754             :     pContext->context_ = std::make_unique<CPDF_RenderContext>(
    1755             :         pPage->GetDocument(), pPage->GetMutablePageResources(),
    1756             :         pPage->GetPageImageCache());
    1757             : 
    1758             :     pContext->context_->AppendLayer(pPage, matrix);
    1759             : 
    1760             :     if (flags & FPDF_ANNOT)
    1761             :     {
    1762             :         auto pOwnedList = std::make_unique<CPDF_AnnotList>(pPage);
    1763             :         CPDF_AnnotList *pList = pOwnedList.get();
    1764             :         pContext->annots_ = std::move(pOwnedList);
    1765             :         bool bPrinting =
    1766             :             pContext->device_->GetDeviceType() != DeviceType::kDisplay;
    1767             : 
    1768             :         // TODO(https://crbug.com/pdfium/993) - maybe pass true here.
    1769             :         const bool bShowWidget = false;
    1770             :         pList->DisplayAnnots(pContext->context_.get(), bPrinting, matrix,
    1771             :                              bShowWidget);
    1772             :     }
    1773             : 
    1774             :     pContext->renderer_ = std::make_unique<CPDF_ProgressiveRenderer>(
    1775             :         pContext->context_.get(), pContext->device_.get(),
    1776             :         pContext->options_.get());
    1777             :     pContext->renderer_->Start(pause);
    1778             :     if (bNeedToRestore)
    1779             :         pContext->device_->RestoreState(false);
    1780             : }
    1781             : 
    1782             : static void
    1783             : myRenderPageWithContext(PDFDataset *poDS, CPDF_PageRenderContext *pContext,
    1784             :                         FPDF_PAGE page, int start_x, int start_y, int size_x,
    1785             :                         int size_y, int rotate, int flags,
    1786             :                         const FPDF_COLORSCHEME *color_scheme,
    1787             :                         bool bNeedToRestore, CPDFSDK_PauseAdapter *pause)
    1788             : {
    1789             :     CPDF_Page *pPage = CPDFPageFromFPDFPage(page);
    1790             :     if (!pPage)
    1791             :         return;
    1792             : 
    1793             :     const FX_RECT rect(start_x, start_y, start_x + size_x, start_y + size_y);
    1794             :     myRenderPageImpl(poDS, pContext, pPage,
    1795             :                      pPage->GetDisplayMatrixForRect(rect, rotate), rect, flags,
    1796             :                      color_scheme, bNeedToRestore, pause);
    1797             : }
    1798             : 
    1799             : class MyRenderDevice final : public CFX_RenderDevice
    1800             : {
    1801             : 
    1802             :   public:
    1803             :     // Substitution for CFX_DefaultRenderDevice::Attach
    1804             :     bool Attach(const RetainPtr<CFX_DIBitmap> &pBitmap, bool bRgbByteOrder,
    1805             :                 const RetainPtr<CFX_DIBitmap> &pBackdropBitmap,
    1806             :                 bool bGroupKnockout, const char *pszRenderingOptions);
    1807             : };
    1808             : 
    1809             : bool MyRenderDevice::Attach(const RetainPtr<CFX_DIBitmap> &pBitmap,
    1810             :                             bool bRgbByteOrder,
    1811             :                             const RetainPtr<CFX_DIBitmap> &pBackdropBitmap,
    1812             :                             bool bGroupKnockout,
    1813             :                             const char *pszRenderingOptions)
    1814             : {
    1815             :     SetBitmap(pBitmap);
    1816             : 
    1817             :     std::unique_ptr<RenderDeviceDriverIface> driver =
    1818             :         std::make_unique<pdfium::CFX_AggDeviceDriver>(
    1819             :             pBitmap, bRgbByteOrder, pBackdropBitmap, bGroupKnockout);
    1820             :     if (pszRenderingOptions != nullptr)
    1821             :     {
    1822             :         int bEnableVector = FALSE;
    1823             :         int bEnableText = FALSE;
    1824             :         int bEnableBitmap = FALSE;
    1825             : 
    1826             :         char **papszTokens = CSLTokenizeString2(pszRenderingOptions, " ,", 0);
    1827             :         for (int i = 0; papszTokens[i] != nullptr; i++)
    1828             :         {
    1829             :             if (EQUAL(papszTokens[i], "VECTOR"))
    1830             :                 bEnableVector = TRUE;
    1831             :             else if (EQUAL(papszTokens[i], "TEXT"))
    1832             :                 bEnableText = TRUE;
    1833             :             else if (EQUAL(papszTokens[i], "RASTER") ||
    1834             :                      EQUAL(papszTokens[i], "BITMAP"))
    1835             :                 bEnableBitmap = TRUE;
    1836             :             else
    1837             :             {
    1838             :                 CPLError(CE_Warning, CPLE_NotSupported,
    1839             :                          "Value %s is not a valid value for "
    1840             :                          "GDAL_PDF_RENDERING_OPTIONS",
    1841             :                          papszTokens[i]);
    1842             :             }
    1843             :         }
    1844             :         CSLDestroy(papszTokens);
    1845             : 
    1846             :         if (!bEnableVector || !bEnableText || !bEnableBitmap)
    1847             :         {
    1848             :             std::unique_ptr<GDALPDFiumRenderDeviceDriver> poGDALRDDriver =
    1849             :                 std::make_unique<GDALPDFiumRenderDeviceDriver>(
    1850             :                     std::move(driver), this);
    1851             :             poGDALRDDriver->SetEnableVector(bEnableVector);
    1852             :             poGDALRDDriver->SetEnableText(bEnableText);
    1853             :             poGDALRDDriver->SetEnableBitmap(bEnableBitmap);
    1854             :             driver = std::move(poGDALRDDriver);
    1855             :         }
    1856             :     }
    1857             : 
    1858             :     SetDeviceDriver(std::move(driver));
    1859             :     return true;
    1860             : }
    1861             : 
    1862             : void PDFDataset::PDFiumRenderPageBitmap(FPDF_BITMAP bitmap, FPDF_PAGE page,
    1863             :                                         int start_x, int start_y, int size_x,
    1864             :                                         int size_y,
    1865             :                                         const char *pszRenderingOptions)
    1866             : {
    1867             :     const int rotate = 0;
    1868             :     const int flags = 0;
    1869             : 
    1870             :     if (!bitmap)
    1871             :         return;
    1872             : 
    1873             :     CPDF_Page *pPage = CPDFPageFromFPDFPage(page);
    1874             :     if (!pPage)
    1875             :         return;
    1876             : 
    1877             :     auto pOwnedContext = std::make_unique<CPDF_PageRenderContext>();
    1878             :     CPDF_PageRenderContext *pContext = pOwnedContext.get();
    1879             :     CPDF_Page::RenderContextClearer clearer(pPage);
    1880             :     pPage->SetRenderContext(std::move(pOwnedContext));
    1881             : 
    1882             :     auto pOwnedDevice = std::make_unique<MyRenderDevice>();
    1883             :     auto pDevice = pOwnedDevice.get();
    1884             :     pContext->device_ = std::move(pOwnedDevice);
    1885             : 
    1886             :     RetainPtr<CFX_DIBitmap> pBitmap(CFXDIBitmapFromFPDFBitmap(bitmap));
    1887             : 
    1888             :     pDevice->Attach(pBitmap, !!(flags & FPDF_REVERSE_BYTE_ORDER), nullptr,
    1889             :                     false, pszRenderingOptions);
    1890             : 
    1891             :     myRenderPageWithContext(this, pContext, page, start_x, start_y, size_x,
    1892             :                             size_y, rotate, flags,
    1893             :                             /*color_scheme=*/nullptr,
    1894             :                             /*need_to_restore=*/true, /*pause=*/nullptr);
    1895             : 
    1896             : #ifdef _SKIA_SUPPORT_PATHS_
    1897             :     pDevice->Flush(true);
    1898             :     pBitmap->UnPreMultiply();
    1899             : #endif
    1900             : }
    1901             : 
    1902             : #endif /* HAVE_PDFIUM */
    1903             : 
    1904             : /************************************************************************/
    1905             : /*                             ReadPixels()                             */
    1906             : /************************************************************************/
    1907             : 
    1908          56 : CPLErr PDFDataset::ReadPixels(int nReqXOff, int nReqYOff, int nReqXSize,
    1909             :                               int nReqYSize, GSpacing nPixelSpace,
    1910             :                               GSpacing nLineSpace, GSpacing nBandSpace,
    1911             :                               GByte *pabyData)
    1912             : {
    1913          56 :     CPLErr eErr = CE_None;
    1914             :     const char *pszRenderingOptions =
    1915          56 :         GetOption(papszOpenOptions, "RENDERING_OPTIONS", nullptr);
    1916             : 
    1917             : #ifdef HAVE_POPPLER
    1918          56 :     if (m_bUseLib.test(PDFLIB_POPPLER))
    1919             :     {
    1920             :         SplashColor sColor;
    1921          56 :         sColor[0] = 255;
    1922          56 :         sColor[1] = 255;
    1923          56 :         sColor[2] = 255;
    1924             :         GDALPDFOutputDev *poSplashOut = new GDALPDFOutputDev(
    1925          56 :             (nBands < 4) ? splashModeRGB8 : splashModeXBGR8, 4, false,
    1926          56 :             (nBands < 4) ? sColor : nullptr);
    1927             : 
    1928          56 :         if (pszRenderingOptions != nullptr)
    1929             :         {
    1930           7 :             poSplashOut->SetEnableVector(FALSE);
    1931           7 :             poSplashOut->SetEnableText(FALSE);
    1932           7 :             poSplashOut->SetEnableBitmap(FALSE);
    1933             : 
    1934             :             char **papszTokens =
    1935           7 :                 CSLTokenizeString2(pszRenderingOptions, " ,", 0);
    1936          19 :             for (int i = 0; papszTokens[i] != nullptr; i++)
    1937             :             {
    1938          12 :                 if (EQUAL(papszTokens[i], "VECTOR"))
    1939           4 :                     poSplashOut->SetEnableVector(TRUE);
    1940           8 :                 else if (EQUAL(papszTokens[i], "TEXT"))
    1941           4 :                     poSplashOut->SetEnableText(TRUE);
    1942           4 :                 else if (EQUAL(papszTokens[i], "RASTER") ||
    1943           0 :                          EQUAL(papszTokens[i], "BITMAP"))
    1944           4 :                     poSplashOut->SetEnableBitmap(TRUE);
    1945             :                 else
    1946             :                 {
    1947           0 :                     CPLError(CE_Warning, CPLE_NotSupported,
    1948             :                              "Value %s is not a valid value for "
    1949             :                              "GDAL_PDF_RENDERING_OPTIONS",
    1950           0 :                              papszTokens[i]);
    1951             :                 }
    1952             :             }
    1953           7 :             CSLDestroy(papszTokens);
    1954             :         }
    1955             : 
    1956          56 :         PDFDoc *poDoc = m_poDocPoppler;
    1957          56 :         poSplashOut->startDoc(poDoc);
    1958             : 
    1959             :         // Note: Poppler 25.2 is certainly not the lowest version where we can
    1960             :         // avoid the hack.
    1961             : #if !(POPPLER_MAJOR_VERSION > 25 ||                                            \
    1962             :       (POPPLER_MAJOR_VERSION == 25 && POPPLER_MINOR_VERSION >= 2))
    1963             : #define USE_OPTCONTENT_HACK
    1964             : #endif
    1965             : 
    1966             : #ifdef USE_OPTCONTENT_HACK
    1967             :         /* EVIL: we modify a private member... */
    1968             :         /* poppler (at least 0.12 and 0.14 versions) don't render correctly */
    1969             :         /* some PDFs and display an error message 'Could not find a OCG with
    1970             :          * Ref' */
    1971             :         /* in those cases. This processing of optional content is an addition of
    1972             :          */
    1973             :         /* poppler in comparison to original xpdf, which hasn't the issue. All
    1974             :          * in */
    1975             :         /* all, nullifying optContent removes the error message and improves the
    1976             :          * rendering */
    1977          56 :         Catalog *poCatalog = poDoc->getCatalog();
    1978          56 :         OCGs *poOldOCGs = poCatalog->optContent;
    1979          56 :         if (!m_bUseOCG)
    1980          49 :             poCatalog->optContent = nullptr;
    1981             : #endif
    1982             :         try
    1983             :         {
    1984          56 :             poDoc->displayPageSlice(poSplashOut, m_iPage, m_dfDPI, m_dfDPI, 0,
    1985             :                                     TRUE, false, false, nReqXOff, nReqYOff,
    1986             :                                     nReqXSize, nReqYSize);
    1987             :         }
    1988           0 :         catch (const std::exception &e)
    1989             :         {
    1990           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1991           0 :                      "PDFDoc::displayPageSlice() failed with %s", e.what());
    1992             : 
    1993             : #ifdef USE_OPTCONTENT_HACK
    1994             :             /* Restore back */
    1995           0 :             poCatalog->optContent = poOldOCGs;
    1996             : #endif
    1997           0 :             delete poSplashOut;
    1998           0 :             return CE_Failure;
    1999             :         }
    2000             : 
    2001             : #ifdef USE_OPTCONTENT_HACK
    2002             :         /* Restore back */
    2003          56 :         poCatalog->optContent = poOldOCGs;
    2004             : #endif
    2005             : 
    2006          56 :         SplashBitmap *poBitmap = poSplashOut->getBitmap();
    2007         112 :         if (poBitmap->getWidth() != nReqXSize ||
    2008          56 :             poBitmap->getHeight() != nReqYSize)
    2009             :         {
    2010           0 :             CPLError(
    2011             :                 CE_Failure, CPLE_AppDefined,
    2012             :                 "Bitmap decoded size (%dx%d) doesn't match raster size (%dx%d)",
    2013             :                 poBitmap->getWidth(), poBitmap->getHeight(), nReqXSize,
    2014             :                 nReqYSize);
    2015           0 :             delete poSplashOut;
    2016           0 :             return CE_Failure;
    2017             :         }
    2018             : 
    2019          56 :         GByte *pabyDataR = pabyData;
    2020          56 :         GByte *pabyDataG = pabyData + nBandSpace;
    2021          56 :         GByte *pabyDataB = pabyData + 2 * nBandSpace;
    2022          56 :         GByte *pabyDataA = pabyData + 3 * nBandSpace;
    2023          56 :         GByte *pabySrc = poBitmap->getDataPtr();
    2024             :         GByte *pabyAlphaSrc =
    2025          56 :             reinterpret_cast<GByte *>(poBitmap->getAlphaPtr());
    2026             :         int i, j;
    2027       22919 :         for (j = 0; j < nReqYSize; j++)
    2028             :         {
    2029    20751600 :             for (i = 0; i < nReqXSize; i++)
    2030             :             {
    2031    20728800 :                 if (nBands < 4)
    2032             :                 {
    2033    20680200 :                     pabyDataR[i * nPixelSpace] = pabySrc[i * 3 + 0];
    2034    20680200 :                     pabyDataG[i * nPixelSpace] = pabySrc[i * 3 + 1];
    2035    20680200 :                     pabyDataB[i * nPixelSpace] = pabySrc[i * 3 + 2];
    2036             :                 }
    2037             :                 else
    2038             :                 {
    2039       48600 :                     pabyDataR[i * nPixelSpace] = pabySrc[i * 4 + 2];
    2040       48600 :                     pabyDataG[i * nPixelSpace] = pabySrc[i * 4 + 1];
    2041       48600 :                     pabyDataB[i * nPixelSpace] = pabySrc[i * 4 + 0];
    2042       48600 :                     pabyDataA[i * nPixelSpace] = pabyAlphaSrc[i];
    2043             :                 }
    2044             :             }
    2045       22863 :             pabyDataR += nLineSpace;
    2046       22863 :             pabyDataG += nLineSpace;
    2047       22863 :             pabyDataB += nLineSpace;
    2048       22863 :             pabyDataA += nLineSpace;
    2049       22863 :             pabyAlphaSrc += poBitmap->getAlphaRowSize();
    2050       22863 :             pabySrc += poBitmap->getRowSize();
    2051             :         }
    2052          56 :         delete poSplashOut;
    2053             :     }
    2054             : #endif  // HAVE_POPPLER
    2055             : 
    2056             : #ifdef HAVE_PODOFO
    2057             :     if (m_bUseLib.test(PDFLIB_PODOFO))
    2058             :     {
    2059             :         if (m_bPdfToPpmFailed)
    2060             :             return CE_Failure;
    2061             : 
    2062             :         if (pszRenderingOptions != nullptr &&
    2063             :             !EQUAL(pszRenderingOptions, "RASTER,VECTOR,TEXT"))
    2064             :         {
    2065             :             CPLError(CE_Warning, CPLE_NotSupported,
    2066             :                      "GDAL_PDF_RENDERING_OPTIONS only supported "
    2067             :                      "when PDF lib is Poppler.");
    2068             :         }
    2069             : 
    2070             :         CPLString osTmpFilename;
    2071             :         int nRet;
    2072             : 
    2073             : #ifdef notdef
    2074             :         int bUseSpawn =
    2075             :             CPLTestBool(CPLGetConfigOption("GDAL_PDF_USE_SPAWN", "YES"));
    2076             :         if (!bUseSpawn)
    2077             :         {
    2078             :             CPLString osCmd = CPLSPrintf(
    2079             :                 "pdftoppm -r %f -x %d -y %d -W %d -H %d -f %d -l %d \"%s\"",
    2080             :                 dfDPI, nReqXOff, nReqYOff, nReqXSize, nReqYSize, iPage, iPage,
    2081             :                 osFilename.c_str());
    2082             : 
    2083             :             if (!osUserPwd.empty())
    2084             :             {
    2085             :                 osCmd += " -upw \"";
    2086             :                 osCmd += osUserPwd;
    2087             :                 osCmd += "\"";
    2088             :             }
    2089             : 
    2090             :             CPLString osTmpFilenamePrefix = CPLGenerateTempFilenameSafe("pdf");
    2091             :             osTmpFilename =
    2092             :                 CPLSPrintf("%s-%d.ppm", osTmpFilenamePrefix.c_str(), iPage);
    2093             :             osCmd += CPLSPrintf(" \"%s\"", osTmpFilenamePrefix.c_str());
    2094             : 
    2095             :             CPLDebug("PDF", "Running '%s'", osCmd.c_str());
    2096             :             nRet = CPLSystem(nullptr, osCmd.c_str());
    2097             :         }
    2098             :         else
    2099             : #endif  // notdef
    2100             :         {
    2101             :             char **papszArgs = nullptr;
    2102             :             papszArgs = CSLAddString(papszArgs, "pdftoppm");
    2103             :             papszArgs = CSLAddString(papszArgs, "-r");
    2104             :             papszArgs = CSLAddString(papszArgs, CPLSPrintf("%f", m_dfDPI));
    2105             :             papszArgs = CSLAddString(papszArgs, "-x");
    2106             :             papszArgs = CSLAddString(papszArgs, CPLSPrintf("%d", nReqXOff));
    2107             :             papszArgs = CSLAddString(papszArgs, "-y");
    2108             :             papszArgs = CSLAddString(papszArgs, CPLSPrintf("%d", nReqYOff));
    2109             :             papszArgs = CSLAddString(papszArgs, "-W");
    2110             :             papszArgs = CSLAddString(papszArgs, CPLSPrintf("%d", nReqXSize));
    2111             :             papszArgs = CSLAddString(papszArgs, "-H");
    2112             :             papszArgs = CSLAddString(papszArgs, CPLSPrintf("%d", nReqYSize));
    2113             :             papszArgs = CSLAddString(papszArgs, "-f");
    2114             :             papszArgs = CSLAddString(papszArgs, CPLSPrintf("%d", m_iPage));
    2115             :             papszArgs = CSLAddString(papszArgs, "-l");
    2116             :             papszArgs = CSLAddString(papszArgs, CPLSPrintf("%d", m_iPage));
    2117             :             if (!m_osUserPwd.empty())
    2118             :             {
    2119             :                 papszArgs = CSLAddString(papszArgs, "-upw");
    2120             :                 papszArgs = CSLAddString(papszArgs, m_osUserPwd.c_str());
    2121             :             }
    2122             :             papszArgs = CSLAddString(papszArgs, m_osFilename.c_str());
    2123             : 
    2124             :             osTmpFilename = VSIMemGenerateHiddenFilename("pdf_temp.ppm");
    2125             :             VSILFILE *fpOut = VSIFOpenL(osTmpFilename, "wb");
    2126             :             if (fpOut != nullptr)
    2127             :             {
    2128             :                 nRet = CPLSpawn(papszArgs, nullptr, fpOut, FALSE);
    2129             :                 VSIFCloseL(fpOut);
    2130             :             }
    2131             :             else
    2132             :                 nRet = -1;
    2133             : 
    2134             :             CSLDestroy(papszArgs);
    2135             :         }
    2136             : 
    2137             :         if (nRet == 0)
    2138             :         {
    2139             :             auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
    2140             :                 osTmpFilename, GDAL_OF_RASTER, nullptr, nullptr, nullptr));
    2141             :             if (poDS)
    2142             :             {
    2143             :                 if (poDS->GetRasterCount() == 3)
    2144             :                 {
    2145             :                     eErr = poDS->RasterIO(GF_Read, 0, 0, nReqXSize, nReqYSize,
    2146             :                                           pabyData, nReqXSize, nReqYSize,
    2147             :                                           GDT_UInt8, 3, nullptr, nPixelSpace,
    2148             :                                           nLineSpace, nBandSpace, nullptr);
    2149             :                 }
    2150             :             }
    2151             :         }
    2152             :         else
    2153             :         {
    2154             :             CPLDebug("PDF", "Ret code = %d", nRet);
    2155             :             m_bPdfToPpmFailed = true;
    2156             :             eErr = CE_Failure;
    2157             :         }
    2158             :         VSIUnlink(osTmpFilename);
    2159             :     }
    2160             : #endif  // HAVE_PODOFO
    2161             : #ifdef HAVE_PDFIUM
    2162             :     if (m_bUseLib.test(PDFLIB_PDFIUM))
    2163             :     {
    2164             :         if (!m_poPagePdfium)
    2165             :         {
    2166             :             return CE_Failure;
    2167             :         }
    2168             : 
    2169             :         // Pdfium does not support multithreading
    2170             :         CPLCreateOrAcquireMutex(&g_oPdfiumReadMutex, PDFIUM_MUTEX_TIMEOUT);
    2171             : 
    2172             :         CPLCreateOrAcquireMutex(&(m_poPagePdfium->readMutex),
    2173             :                                 PDFIUM_MUTEX_TIMEOUT);
    2174             : 
    2175             :         // Parsing content required before rastering
    2176             :         // can takes too long for PDF with large number of objects/layers
    2177             :         m_poPagePdfium->page->ParseContent();
    2178             : 
    2179             :         FPDF_BITMAP bitmap =
    2180             :             FPDFBitmap_Create(nReqXSize, nReqYSize, nBands == 4 /*alpha*/);
    2181             :         // As coded now, FPDFBitmap_Create cannot allocate more than 1 GB
    2182             :         if (bitmap == nullptr)
    2183             :         {
    2184             :             // Release mutex - following code is thread-safe
    2185             :             CPLReleaseMutex(m_poPagePdfium->readMutex);
    2186             :             CPLReleaseMutex(g_oPdfiumReadMutex);
    2187             : 
    2188             : #ifdef notdef
    2189             :             // If the requested area is not too small, then try subdividing
    2190             :             if ((GIntBig)nReqXSize * nReqYSize * 4 > 1024 * 1024)
    2191             :             {
    2192             : #ifdef DEBUG
    2193             :                 CPLDebug(
    2194             :                     "PDF",
    2195             :                     "Subdividing PDFDataset::ReadPixels(%d, %d, %d, %d, "
    2196             :                     "scaleFactor=%d)",
    2197             :                     nReqXOff, nReqYOff, nReqXSize, nReqYSize,
    2198             :                     1 << ((PDFRasterBand *)GetRasterBand(1))->nResolutionLevel);
    2199             : #endif
    2200             :                 if (nReqXSize >= nReqYSize)
    2201             :                 {
    2202             :                     eErr = ReadPixels(nReqXOff, nReqYOff, nReqXSize / 2,
    2203             :                                       nReqYSize, nPixelSpace, nLineSpace,
    2204             :                                       nBandSpace, pabyData);
    2205             :                     if (eErr == CE_None)
    2206             :                     {
    2207             :                         eErr = ReadPixels(
    2208             :                             nReqXSize / 2, nReqYOff, nReqXSize - nReqXSize / 2,
    2209             :                             nReqYSize, nPixelSpace, nLineSpace, nBandSpace,
    2210             :                             pabyData + nPixelSpace * (nReqXSize / 2));
    2211             :                     }
    2212             :                 }
    2213             :                 else
    2214             :                 {
    2215             :                     eErr = ReadPixels(nReqXOff, nReqYOff, nReqXSize,
    2216             :                                       nReqYSize - nReqYSize / 2, nPixelSpace,
    2217             :                                       nLineSpace, nBandSpace, pabyData);
    2218             :                     if (eErr == CE_None)
    2219             :                     {
    2220             :                         eErr =
    2221             :                             ReadPixels(nReqXOff, nReqYSize / 2, nReqXSize,
    2222             :                                        nReqYSize - nReqYSize / 2, nPixelSpace,
    2223             :                                        nLineSpace, nBandSpace,
    2224             :                                        pabyData + nLineSpace * (nReqYSize / 2));
    2225             :                     }
    2226             :                 }
    2227             :                 return eErr;
    2228             :             }
    2229             : #endif
    2230             : 
    2231             :             CPLError(CE_Failure, CPLE_AppDefined,
    2232             :                      "FPDFBitmap_Create(%d,%d) failed", nReqXSize, nReqYSize);
    2233             : 
    2234             :             return CE_Failure;
    2235             :         }
    2236             :         // alpha is 0% which is transported to FF if not alpha
    2237             :         // Default background color is white
    2238             :         FPDF_DWORD color = 0x00FFFFFF;  // A,R,G,B
    2239             :         FPDFBitmap_FillRect(bitmap, 0, 0, nReqXSize, nReqYSize, color);
    2240             : 
    2241             : #ifdef DEBUG
    2242             :         // start_x, start_y, size_x, size_y, rotate, flags
    2243             :         CPLDebug("PDF",
    2244             :                  "PDFDataset::ReadPixels(%d, %d, %d, %d, scaleFactor=%d)",
    2245             :                  nReqXOff, nReqYOff, nReqXSize, nReqYSize,
    2246             :                  1 << cpl::down_cast<PDFRasterBand *>(GetRasterBand(1))
    2247             :                           ->nResolutionLevel);
    2248             : 
    2249             :         CPLDebug("PDF", "FPDF_RenderPageBitmap(%d, %d, %d, %d)", -nReqXOff,
    2250             :                  -nReqYOff, nRasterXSize, nRasterYSize);
    2251             : #endif
    2252             : 
    2253             :         // Part of PDF is render with -x, -y, page_width, page_height
    2254             :         // (not requested size!)
    2255             :         PDFiumRenderPageBitmap(
    2256             :             bitmap, FPDFPageFromIPDFPage(m_poPagePdfium->page), -nReqXOff,
    2257             :             -nReqYOff, nRasterXSize, nRasterYSize, pszRenderingOptions);
    2258             : 
    2259             :         int stride = FPDFBitmap_GetStride(bitmap);
    2260             :         const GByte *buffer =
    2261             :             reinterpret_cast<const GByte *>(FPDFBitmap_GetBuffer(bitmap));
    2262             : 
    2263             :         // Release mutex - following code is thread-safe
    2264             :         CPLReleaseMutex(m_poPagePdfium->readMutex);
    2265             :         CPLReleaseMutex(g_oPdfiumReadMutex);
    2266             : 
    2267             :         // Source data is B, G, R, unused.
    2268             :         // Destination data is R, G, B (,A if is alpha)
    2269             :         GByte *pabyDataR = pabyData;
    2270             :         GByte *pabyDataG = pabyData + 1 * nBandSpace;
    2271             :         GByte *pabyDataB = pabyData + 2 * nBandSpace;
    2272             :         GByte *pabyDataA = pabyData + 3 * nBandSpace;
    2273             :         // Copied from Poppler
    2274             :         int i, j;
    2275             :         for (j = 0; j < nReqYSize; j++)
    2276             :         {
    2277             :             for (i = 0; i < nReqXSize; i++)
    2278             :             {
    2279             :                 pabyDataR[i * nPixelSpace] = buffer[(i * 4) + 2];
    2280             :                 pabyDataG[i * nPixelSpace] = buffer[(i * 4) + 1];
    2281             :                 pabyDataB[i * nPixelSpace] = buffer[(i * 4) + 0];
    2282             :                 if (nBands == 4)
    2283             :                 {
    2284             :                     pabyDataA[i * nPixelSpace] = buffer[(i * 4) + 3];
    2285             :                 }
    2286             :             }
    2287             :             pabyDataR += nLineSpace;
    2288             :             pabyDataG += nLineSpace;
    2289             :             pabyDataB += nLineSpace;
    2290             :             pabyDataA += nLineSpace;
    2291             :             buffer += stride;
    2292             :         }
    2293             :         FPDFBitmap_Destroy(bitmap);
    2294             :     }
    2295             : #endif  // ~ HAVE_PDFIUM
    2296             : 
    2297          56 :     return eErr;
    2298             : }
    2299             : 
    2300             : /************************************************************************/
    2301             : /* ==================================================================== */
    2302             : /*                        PDFImageRasterBand                            */
    2303             : /* ==================================================================== */
    2304             : /************************************************************************/
    2305             : 
    2306             : class PDFImageRasterBand final : public PDFRasterBand
    2307             : {
    2308             :     friend class PDFDataset;
    2309             : 
    2310             :   public:
    2311             :     PDFImageRasterBand(PDFDataset *, int);
    2312             : 
    2313             :     CPLErr IReadBlock(int, int, void *) override;
    2314             : };
    2315             : 
    2316             : /************************************************************************/
    2317             : /*                        PDFImageRasterBand()                          */
    2318             : /************************************************************************/
    2319             : 
    2320           0 : PDFImageRasterBand::PDFImageRasterBand(PDFDataset *poDSIn, int nBandIn)
    2321           0 :     : PDFRasterBand(poDSIn, nBandIn, 0)
    2322             : {
    2323           0 : }
    2324             : 
    2325             : /************************************************************************/
    2326             : /*                             IReadBlock()                             */
    2327             : /************************************************************************/
    2328             : 
    2329           0 : CPLErr PDFImageRasterBand::IReadBlock(int /* nBlockXOff */, int nBlockYOff,
    2330             :                                       void *pImage)
    2331             : {
    2332           0 :     PDFDataset *poGDS = cpl::down_cast<PDFDataset *>(poDS);
    2333           0 :     CPLAssert(poGDS->m_poImageObj != nullptr);
    2334             : 
    2335           0 :     if (!poGDS->m_bTried)
    2336             :     {
    2337           0 :         int nBands = (poGDS->nBands == 1) ? 1 : 3;
    2338           0 :         poGDS->m_bTried = true;
    2339           0 :         if (nBands == 3)
    2340             :         {
    2341           0 :             poGDS->m_pabyCachedData = static_cast<GByte *>(
    2342           0 :                 VSIMalloc3(nBands, nRasterXSize, nRasterYSize));
    2343           0 :             if (poGDS->m_pabyCachedData == nullptr)
    2344           0 :                 return CE_Failure;
    2345             :         }
    2346             : 
    2347           0 :         GDALPDFStream *poStream = poGDS->m_poImageObj->GetStream();
    2348           0 :         GByte *pabyStream = nullptr;
    2349             : 
    2350           0 :         if (poStream == nullptr ||
    2351           0 :             static_cast<size_t>(poStream->GetLength()) !=
    2352           0 :                 static_cast<size_t>(nBands) * nRasterXSize * nRasterYSize ||
    2353           0 :             (pabyStream = reinterpret_cast<GByte *>(poStream->GetBytes())) ==
    2354             :                 nullptr)
    2355             :         {
    2356           0 :             VSIFree(poGDS->m_pabyCachedData);
    2357           0 :             poGDS->m_pabyCachedData = nullptr;
    2358           0 :             return CE_Failure;
    2359             :         }
    2360             : 
    2361           0 :         if (nBands == 3)
    2362             :         {
    2363             :             /* pixel interleaved to band interleaved */
    2364           0 :             for (size_t i = 0;
    2365           0 :                  i < static_cast<size_t>(nRasterXSize) * nRasterYSize; i++)
    2366             :             {
    2367           0 :                 poGDS->m_pabyCachedData[0 * static_cast<size_t>(nRasterXSize) *
    2368             :                                             nRasterYSize +
    2369           0 :                                         i] = pabyStream[3 * i + 0];
    2370           0 :                 poGDS->m_pabyCachedData[1 * static_cast<size_t>(nRasterXSize) *
    2371           0 :                                             nRasterYSize +
    2372           0 :                                         i] = pabyStream[3 * i + 1];
    2373           0 :                 poGDS->m_pabyCachedData[2 * static_cast<size_t>(nRasterXSize) *
    2374           0 :                                             nRasterYSize +
    2375           0 :                                         i] = pabyStream[3 * i + 2];
    2376             :             }
    2377           0 :             VSIFree(pabyStream);
    2378             :         }
    2379             :         else
    2380           0 :             poGDS->m_pabyCachedData = pabyStream;
    2381             :     }
    2382             : 
    2383           0 :     if (poGDS->m_pabyCachedData == nullptr)
    2384           0 :         return CE_Failure;
    2385             : 
    2386           0 :     if (nBand == 4)
    2387           0 :         memset(pImage, 255, nRasterXSize);
    2388             :     else
    2389           0 :         memcpy(pImage,
    2390           0 :                poGDS->m_pabyCachedData +
    2391           0 :                    static_cast<size_t>(nBand - 1) * nRasterXSize *
    2392           0 :                        nRasterYSize +
    2393           0 :                    static_cast<size_t>(nBlockYOff) * nRasterXSize,
    2394           0 :                nRasterXSize);
    2395             : 
    2396           0 :     return CE_None;
    2397             : }
    2398             : 
    2399             : /************************************************************************/
    2400             : /*                            PDFDataset()                              */
    2401             : /************************************************************************/
    2402             : 
    2403         214 : PDFDataset::PDFDataset(PDFDataset *poParentDSIn, int nXSize, int nYSize)
    2404         214 :     : m_bIsOvrDS(poParentDSIn != nullptr),
    2405             : #ifdef HAVE_PDFIUM
    2406             :       m_poDocPdfium(poParentDSIn ? poParentDSIn->m_poDocPdfium : nullptr),
    2407             :       m_poPagePdfium(poParentDSIn ? poParentDSIn->m_poPagePdfium : nullptr),
    2408             : #endif
    2409         214 :       m_bSetStyle(CPLTestBool(CPLGetConfigOption("OGR_PDF_SET_STYLE", "YES")))
    2410             : {
    2411         214 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2412         214 :     nRasterXSize = nXSize;
    2413         214 :     nRasterYSize = nYSize;
    2414         214 :     if (poParentDSIn)
    2415           0 :         m_bUseLib = poParentDSIn->m_bUseLib;
    2416             : 
    2417         214 :     InitMapOperators();
    2418         214 : }
    2419             : 
    2420             : /************************************************************************/
    2421             : /*                          IBuildOverviews()                           */
    2422             : /************************************************************************/
    2423             : 
    2424           1 : CPLErr PDFDataset::IBuildOverviews(const char *pszResampling, int nOverviews,
    2425             :                                    const int *panOverviewList, int nListBands,
    2426             :                                    const int *panBandList,
    2427             :                                    GDALProgressFunc pfnProgress,
    2428             :                                    void *pProgressData,
    2429             :                                    CSLConstList papszOptions)
    2430             : 
    2431             : {
    2432             :     /* -------------------------------------------------------------------- */
    2433             :     /*      In order for building external overviews to work properly we    */
    2434             :     /*      discard any concept of internal overviews when the user         */
    2435             :     /*      first requests to build external overviews.                     */
    2436             :     /* -------------------------------------------------------------------- */
    2437           1 :     if (!m_apoOvrDS.empty())
    2438             :     {
    2439           1 :         m_apoOvrDSBackup = std::move(m_apoOvrDS);
    2440           1 :         m_apoOvrDS.clear();
    2441             :     }
    2442             : 
    2443             :     // Prevents InitOverviews() to run
    2444           1 :     m_apoOvrDSBackup.emplace_back(nullptr);
    2445           1 :     const CPLErr eErr = GDALPamDataset::IBuildOverviews(
    2446             :         pszResampling, nOverviews, panOverviewList, nListBands, panBandList,
    2447             :         pfnProgress, pProgressData, papszOptions);
    2448           1 :     m_apoOvrDSBackup.pop_back();
    2449           1 :     return eErr;
    2450             : }
    2451             : 
    2452             : /************************************************************************/
    2453             : /*                           PDFFreeDoc()                               */
    2454             : /************************************************************************/
    2455             : 
    2456             : #ifdef HAVE_POPPLER
    2457         228 : static void PDFFreeDoc(PDFDoc *poDoc)
    2458             : {
    2459         228 :     if (poDoc)
    2460             :     {
    2461             :         /* hack to avoid potential cross heap issues on Win32 */
    2462             :         /* str is the VSIPDFFileStream object passed in the constructor of
    2463             :          * PDFDoc */
    2464             :         // NOTE: This is potentially very dangerous. See comment in
    2465             :         // VSIPDFFileStream::FillBuffer() */
    2466         228 :         delete poDoc->str;
    2467         228 :         poDoc->str = nullptr;
    2468             : 
    2469         228 :         delete poDoc;
    2470             :     }
    2471         228 : }
    2472             : #endif
    2473             : 
    2474             : /************************************************************************/
    2475             : /*                            GetCatalog()                              */
    2476             : /************************************************************************/
    2477             : 
    2478         454 : GDALPDFObject *PDFDataset::GetCatalog()
    2479             : {
    2480         454 :     if (m_poCatalogObject)
    2481         240 :         return m_poCatalogObject;
    2482             : 
    2483             : #ifdef HAVE_POPPLER
    2484         214 :     if (m_bUseLib.test(PDFLIB_POPPLER) && m_poDocPoppler)
    2485             :     {
    2486             :         m_poCatalogObjectPoppler =
    2487         214 :             std::make_unique<Object>(m_poDocPoppler->getXRef()->getCatalog());
    2488         214 :         if (!m_poCatalogObjectPoppler->isNull())
    2489         214 :             m_poCatalogObject =
    2490         214 :                 new GDALPDFObjectPoppler(m_poCatalogObjectPoppler.get(), FALSE);
    2491             :     }
    2492             : #endif
    2493             : 
    2494             : #ifdef HAVE_PODOFO
    2495             :     if (m_bUseLib.test(PDFLIB_PODOFO) && m_poDocPodofo)
    2496             :     {
    2497             :         int nCatalogNum = 0;
    2498             :         int nCatalogGen = 0;
    2499             :         VSILFILE *fp = VSIFOpenL(m_osFilename.c_str(), "rb");
    2500             :         if (fp != nullptr)
    2501             :         {
    2502             :             GDALPDFUpdateWriter oWriter(fp);
    2503             :             if (oWriter.ParseTrailerAndXRef())
    2504             :             {
    2505             :                 nCatalogNum = oWriter.GetCatalogNum().toInt();
    2506             :                 nCatalogGen = oWriter.GetCatalogGen();
    2507             :             }
    2508             :             oWriter.Close();
    2509             :         }
    2510             : 
    2511             :         PoDoFo::PdfObject *poCatalogPodofo =
    2512             :             m_poDocPodofo->GetObjects().GetObject(
    2513             :                 PoDoFo::PdfReference(nCatalogNum, nCatalogGen));
    2514             :         if (poCatalogPodofo)
    2515             :             m_poCatalogObject = new GDALPDFObjectPodofo(
    2516             :                 poCatalogPodofo, m_poDocPodofo->GetObjects());
    2517             :     }
    2518             : #endif
    2519             : 
    2520             : #ifdef HAVE_PDFIUM
    2521             :     if (m_bUseLib.test(PDFLIB_PDFIUM) && m_poDocPdfium)
    2522             :     {
    2523             :         RetainPtr<CPDF_Dictionary> catalog =
    2524             :             m_poDocPdfium->doc->GetMutableRoot();
    2525             :         if (catalog)
    2526             :             m_poCatalogObject = GDALPDFObjectPdfium::Build(catalog);
    2527             :     }
    2528             : #endif  // ~ HAVE_PDFIUM
    2529             : 
    2530         214 :     return m_poCatalogObject;
    2531             : }
    2532             : 
    2533             : /************************************************************************/
    2534             : /*                            ~PDFDataset()                            */
    2535             : /************************************************************************/
    2536             : 
    2537         428 : PDFDataset::~PDFDataset()
    2538             : {
    2539             : #ifdef HAVE_PDFIUM
    2540             :     m_apoOvrDS.clear();
    2541             :     m_apoOvrDSBackup.clear();
    2542             : #endif
    2543             : 
    2544         214 :     CPLFree(m_pabyCachedData);
    2545         214 :     m_pabyCachedData = nullptr;
    2546             : 
    2547         214 :     delete m_poNeatLine;
    2548         214 :     m_poNeatLine = nullptr;
    2549             : 
    2550             :     /* Collect data necessary to update */
    2551         214 :     int nNum = 0;
    2552         214 :     int nGen = 0;
    2553         214 :     GDALPDFDictionaryRW *poPageDictCopy = nullptr;
    2554         214 :     GDALPDFDictionaryRW *poCatalogDictCopy = nullptr;
    2555         214 :     if (m_poPageObj)
    2556             :     {
    2557         214 :         nNum = m_poPageObj->GetRefNum().toInt();
    2558         214 :         nGen = m_poPageObj->GetRefGen();
    2559         440 :         if (eAccess == GA_Update &&
    2560          12 :             (m_bProjDirty || m_bNeatLineDirty || m_bInfoDirty || m_bXMPDirty) &&
    2561         238 :             nNum != 0 && m_poPageObj != nullptr &&
    2562          12 :             m_poPageObj->GetType() == PDFObjectType_Dictionary)
    2563             :         {
    2564          12 :             poPageDictCopy = m_poPageObj->GetDictionary()->Clone();
    2565             : 
    2566          12 :             if (m_bXMPDirty)
    2567             :             {
    2568             :                 /* We need the catalog because it points to the XMP Metadata
    2569             :                  * object */
    2570           3 :                 GetCatalog();
    2571           6 :                 if (m_poCatalogObject &&
    2572           3 :                     m_poCatalogObject->GetType() == PDFObjectType_Dictionary)
    2573             :                     poCatalogDictCopy =
    2574           3 :                         m_poCatalogObject->GetDictionary()->Clone();
    2575             :             }
    2576             :         }
    2577             :     }
    2578             : 
    2579             :     /* Close document (and file descriptor) to be able to open it */
    2580             :     /* in read-write mode afterwards */
    2581         214 :     delete m_poPageObj;
    2582         214 :     m_poPageObj = nullptr;
    2583         214 :     delete m_poCatalogObject;
    2584         214 :     m_poCatalogObject = nullptr;
    2585             : #ifdef HAVE_POPPLER
    2586         214 :     if (m_bUseLib.test(PDFLIB_POPPLER))
    2587             :     {
    2588         214 :         m_poCatalogObjectPoppler.reset();
    2589         214 :         PDFFreeDoc(m_poDocPoppler);
    2590             :     }
    2591         214 :     m_poDocPoppler = nullptr;
    2592             : #endif
    2593             : #ifdef HAVE_PODOFO
    2594             :     if (m_bUseLib.test(PDFLIB_PODOFO))
    2595             :     {
    2596             :         delete m_poDocPodofo;
    2597             :     }
    2598             :     m_poDocPodofo = nullptr;
    2599             : #endif
    2600             : #ifdef HAVE_PDFIUM
    2601             :     if (!m_bIsOvrDS)
    2602             :     {
    2603             :         if (m_bUseLib.test(PDFLIB_PDFIUM))
    2604             :         {
    2605             :             UnloadPdfiumDocumentPage(&m_poDocPdfium, &m_poPagePdfium);
    2606             :         }
    2607             :     }
    2608             :     m_poDocPdfium = nullptr;
    2609             :     m_poPagePdfium = nullptr;
    2610             : #endif  // ~ HAVE_PDFIUM
    2611             : 
    2612         214 :     m_bHasLoadedLayers = true;
    2613         214 :     m_apoLayers.clear();
    2614             : 
    2615             :     /* Now do the update */
    2616         214 :     if (poPageDictCopy)
    2617             :     {
    2618          12 :         VSILFILE *fp = VSIFOpenL(m_osFilename, "rb+");
    2619          12 :         if (fp != nullptr)
    2620             :         {
    2621          24 :             GDALPDFUpdateWriter oWriter(fp);
    2622          12 :             if (oWriter.ParseTrailerAndXRef())
    2623             :             {
    2624          12 :                 if ((m_bProjDirty || m_bNeatLineDirty) &&
    2625             :                     poPageDictCopy != nullptr)
    2626           6 :                     oWriter.UpdateProj(this, m_dfDPI, poPageDictCopy,
    2627          12 :                                        GDALPDFObjectNum(nNum), nGen);
    2628             : 
    2629          12 :                 if (m_bInfoDirty)
    2630           3 :                     oWriter.UpdateInfo(this);
    2631             : 
    2632          12 :                 if (m_bXMPDirty && poCatalogDictCopy != nullptr)
    2633           3 :                     oWriter.UpdateXMP(this, poCatalogDictCopy);
    2634             :             }
    2635          12 :             oWriter.Close();
    2636             :         }
    2637             :         else
    2638             :         {
    2639           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2640             :                      "Cannot open %s in update mode", m_osFilename.c_str());
    2641             :         }
    2642             :     }
    2643         214 :     delete poPageDictCopy;
    2644         214 :     poPageDictCopy = nullptr;
    2645         214 :     delete poCatalogDictCopy;
    2646         214 :     poCatalogDictCopy = nullptr;
    2647             : 
    2648         214 :     if (m_nGCPCount > 0)
    2649             :     {
    2650           1 :         GDALDeinitGCPs(m_nGCPCount, m_pasGCPList);
    2651           1 :         CPLFree(m_pasGCPList);
    2652           1 :         m_pasGCPList = nullptr;
    2653           1 :         m_nGCPCount = 0;
    2654             :     }
    2655             : 
    2656         214 :     CleanupIntermediateResources();
    2657             : 
    2658             :     // Do that only after having destroyed Poppler objects
    2659         214 :     m_fp.reset();
    2660         428 : }
    2661             : 
    2662             : /************************************************************************/
    2663             : /*                            IRasterIO()                               */
    2664             : /************************************************************************/
    2665             : 
    2666        1671 : CPLErr PDFDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
    2667             :                              int nXSize, int nYSize, void *pData, int nBufXSize,
    2668             :                              int nBufYSize, GDALDataType eBufType,
    2669             :                              int nBandCount, BANDMAP_TYPE panBandMap,
    2670             :                              GSpacing nPixelSpace, GSpacing nLineSpace,
    2671             :                              GSpacing nBandSpace,
    2672             :                              GDALRasterIOExtraArg *psExtraArg)
    2673             : {
    2674             :     // Try to pass the request to the most appropriate overview dataset.
    2675        1671 :     if (nBufXSize < nXSize && nBufYSize < nYSize)
    2676             :     {
    2677           0 :         int bTried = FALSE;
    2678           0 :         const CPLErr eErr = TryOverviewRasterIO(
    2679             :             eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
    2680             :             eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace,
    2681             :             nBandSpace, psExtraArg, &bTried);
    2682           0 :         if (bTried)
    2683           0 :             return eErr;
    2684             :     }
    2685             : 
    2686             :     int nBandBlockXSize, nBandBlockYSize;
    2687        1671 :     int bReadPixels = FALSE;
    2688        1671 :     GetRasterBand(1)->GetBlockSize(&nBandBlockXSize, &nBandBlockYSize);
    2689        3342 :     if (m_aiTiles.empty() && eRWFlag == GF_Read && nXSize == nBufXSize &&
    2690        1671 :         nYSize == nBufYSize &&
    2691        1671 :         (nBufXSize > nBandBlockXSize || nBufYSize > nBandBlockYSize) &&
    2692        3343 :         eBufType == GDT_UInt8 && nBandCount == nBands &&
    2693           1 :         IsAllBands(nBandCount, panBandMap))
    2694             :     {
    2695           1 :         bReadPixels = TRUE;
    2696             : #ifdef HAVE_PODOFO
    2697             :         if (m_bUseLib.test(PDFLIB_PODOFO) && nBands == 4)
    2698             :         {
    2699             :             bReadPixels = FALSE;
    2700             :         }
    2701             : #endif
    2702             :     }
    2703             : 
    2704        1671 :     if (bReadPixels)
    2705           1 :         return ReadPixels(nXOff, nYOff, nXSize, nYSize, nPixelSpace, nLineSpace,
    2706           1 :                           nBandSpace, static_cast<GByte *>(pData));
    2707             : 
    2708        1670 :     if (nBufXSize != nXSize || nBufYSize != nYSize || eBufType != GDT_UInt8)
    2709             :     {
    2710           0 :         m_bCacheBlocksForOtherBands = true;
    2711             :     }
    2712        1670 :     CPLErr eErr = GDALPamDataset::IRasterIO(
    2713             :         eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
    2714             :         eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace, nBandSpace,
    2715             :         psExtraArg);
    2716        1670 :     m_bCacheBlocksForOtherBands = false;
    2717        1670 :     return eErr;
    2718             : }
    2719             : 
    2720             : /************************************************************************/
    2721             : /*                            IRasterIO()                               */
    2722             : /************************************************************************/
    2723             : 
    2724       25684 : CPLErr PDFRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
    2725             :                                 int nXSize, int nYSize, void *pData,
    2726             :                                 int nBufXSize, int nBufYSize,
    2727             :                                 GDALDataType eBufType, GSpacing nPixelSpace,
    2728             :                                 GSpacing nLineSpace,
    2729             :                                 GDALRasterIOExtraArg *psExtraArg)
    2730             : {
    2731       25684 :     PDFDataset *poGDS = cpl::down_cast<PDFDataset *>(poDS);
    2732             : 
    2733             :     // Try to pass the request to the most appropriate overview dataset.
    2734       25684 :     if (nBufXSize < nXSize && nBufYSize < nYSize)
    2735             :     {
    2736           0 :         int bTried = FALSE;
    2737           0 :         const CPLErr eErr = TryOverviewRasterIO(
    2738             :             eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
    2739             :             eBufType, nPixelSpace, nLineSpace, psExtraArg, &bTried);
    2740           0 :         if (bTried)
    2741           0 :             return eErr;
    2742             :     }
    2743             : 
    2744       25684 :     if (nBufXSize != nXSize || nBufYSize != nYSize || eBufType != GDT_UInt8)
    2745             :     {
    2746       20674 :         poGDS->m_bCacheBlocksForOtherBands = true;
    2747             :     }
    2748       25684 :     CPLErr eErr = GDALPamRasterBand::IRasterIO(
    2749             :         eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
    2750             :         eBufType, nPixelSpace, nLineSpace, psExtraArg);
    2751       25684 :     poGDS->m_bCacheBlocksForOtherBands = false;
    2752       25684 :     return eErr;
    2753             : }
    2754             : 
    2755             : /************************************************************************/
    2756             : /*                    PDFDatasetErrorFunction()                         */
    2757             : /************************************************************************/
    2758             : 
    2759             : #ifdef HAVE_POPPLER
    2760             : 
    2761          29 : static void PDFDatasetErrorFunctionCommon(const CPLString &osError)
    2762             : {
    2763          29 :     if (strcmp(osError.c_str(), "Incorrect password") == 0)
    2764           5 :         return;
    2765             :     /* Reported on newer USGS GeoPDF */
    2766          24 :     if (strcmp(osError.c_str(),
    2767          24 :                "Couldn't find group for reference to set OFF") == 0)
    2768             :     {
    2769           0 :         CPLDebug("PDF", "%s", osError.c_str());
    2770           0 :         return;
    2771             :     }
    2772             : 
    2773          24 :     CPLError(CE_Failure, CPLE_AppDefined, "%s", osError.c_str());
    2774             : }
    2775             : 
    2776             : static int g_nPopplerErrors = 0;
    2777             : constexpr int MAX_POPPLER_ERRORS = 1000;
    2778             : 
    2779          29 : static void PDFDatasetErrorFunction(ErrorCategory /* eErrCategory */,
    2780             :                                     Goffset nPos, const char *pszMsg)
    2781             : {
    2782          29 :     if (g_nPopplerErrors >= MAX_POPPLER_ERRORS)
    2783             :     {
    2784             :         // If there are too many errors, then unregister ourselves and turn
    2785             :         // quiet error mode, as the error() function in poppler can spend
    2786             :         // significant time formatting an error message we won't emit...
    2787           0 :         setErrorCallback(nullptr);
    2788           0 :         globalParams->setErrQuiet(true);
    2789           0 :         return;
    2790             :     }
    2791             : 
    2792          29 :     g_nPopplerErrors++;
    2793          58 :     CPLString osError;
    2794             : 
    2795          29 :     if (nPos >= 0)
    2796             :         osError.Printf("Pos = " CPL_FRMT_GUIB ", ",
    2797           0 :                        static_cast<GUIntBig>(nPos));
    2798          29 :     osError += pszMsg;
    2799          29 :     PDFDatasetErrorFunctionCommon(osError);
    2800             : }
    2801             : #endif
    2802             : 
    2803             : /************************************************************************/
    2804             : /*                GDALPDFParseStreamContentOnlyDrawForm()               */
    2805             : /************************************************************************/
    2806             : 
    2807         197 : static CPLString GDALPDFParseStreamContentOnlyDrawForm(const char *pszContent)
    2808             : {
    2809         394 :     CPLString osToken;
    2810             :     char ch;
    2811         197 :     int nCurIdx = 0;
    2812         394 :     CPLString osCurrentForm;
    2813             : 
    2814             :     // CPLDebug("PDF", "content = %s", pszContent);
    2815             : 
    2816         722 :     while ((ch = *pszContent) != '\0')
    2817             :     {
    2818         722 :         if (ch == '%')
    2819             :         {
    2820             :             /* Skip comments until end-of-line */
    2821           0 :             while ((ch = *pszContent) != '\0')
    2822             :             {
    2823           0 :                 if (ch == '\r' || ch == '\n')
    2824             :                     break;
    2825           0 :                 pszContent++;
    2826             :             }
    2827           0 :             if (ch == 0)
    2828           0 :                 break;
    2829             :         }
    2830         722 :         else if (ch == ' ' || ch == '\r' || ch == '\n')
    2831             :         {
    2832         234 :             if (!osToken.empty())
    2833             :             {
    2834         234 :                 if (nCurIdx == 0 && osToken[0] == '/')
    2835             :                 {
    2836          37 :                     osCurrentForm = osToken.substr(1);
    2837          37 :                     nCurIdx++;
    2838             :                 }
    2839         197 :                 else if (nCurIdx == 1 && osToken == "Do")
    2840             :                 {
    2841           0 :                     nCurIdx++;
    2842             :                 }
    2843             :                 else
    2844             :                 {
    2845         197 :                     return "";
    2846             :                 }
    2847             :             }
    2848          37 :             osToken = "";
    2849             :         }
    2850             :         else
    2851         488 :             osToken += ch;
    2852         525 :         pszContent++;
    2853             :     }
    2854             : 
    2855           0 :     return osCurrentForm;
    2856             : }
    2857             : 
    2858             : /************************************************************************/
    2859             : /*                    GDALPDFParseStreamContent()                       */
    2860             : /************************************************************************/
    2861             : 
    2862             : typedef enum
    2863             : {
    2864             :     STATE_INIT,
    2865             :     STATE_AFTER_q,
    2866             :     STATE_AFTER_cm,
    2867             :     STATE_AFTER_Do
    2868             : } PDFStreamState;
    2869             : 
    2870             : /* This parser is reduced to understanding sequences that draw rasters, such as
    2871             :    :
    2872             :    q
    2873             :    scaleX 0 0 scaleY translateX translateY cm
    2874             :    /ImXXX Do
    2875             :    Q
    2876             : 
    2877             :    All other sequences will abort the parsing.
    2878             : 
    2879             :    Returns TRUE if the stream only contains images.
    2880             : */
    2881             : 
    2882         197 : static int GDALPDFParseStreamContent(const char *pszContent,
    2883             :                                      GDALPDFDictionary *poXObjectDict,
    2884             :                                      double *pdfDPI, int *pbDPISet,
    2885             :                                      int *pnBands,
    2886             :                                      std::vector<GDALPDFTileDesc> &asTiles,
    2887             :                                      int bAcceptRotationTerms)
    2888             : {
    2889         394 :     CPLString osToken;
    2890             :     char ch;
    2891         197 :     PDFStreamState nState = STATE_INIT;
    2892         197 :     int nCurIdx = 0;
    2893             :     double adfVals[6];
    2894         394 :     CPLString osCurrentImage;
    2895             : 
    2896         197 :     double dfDPI = DEFAULT_DPI;
    2897         197 :     *pbDPISet = FALSE;
    2898             : 
    2899       11224 :     while ((ch = *pszContent) != '\0')
    2900             :     {
    2901       11080 :         if (ch == '%')
    2902             :         {
    2903             :             /* Skip comments until end-of-line */
    2904           0 :             while ((ch = *pszContent) != '\0')
    2905             :             {
    2906           0 :                 if (ch == '\r' || ch == '\n')
    2907             :                     break;
    2908           0 :                 pszContent++;
    2909             :             }
    2910           0 :             if (ch == 0)
    2911           0 :                 break;
    2912             :         }
    2913       11080 :         else if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n')
    2914             :         {
    2915        3342 :             if (!osToken.empty())
    2916             :             {
    2917        3342 :                 if (nState == STATE_INIT)
    2918             :                 {
    2919         352 :                     if (osToken == "q")
    2920             :                     {
    2921         299 :                         nState = STATE_AFTER_q;
    2922         299 :                         nCurIdx = 0;
    2923             :                     }
    2924          53 :                     else if (osToken != "Q")
    2925          53 :                         return FALSE;
    2926             :                 }
    2927        2990 :                 else if (nState == STATE_AFTER_q)
    2928             :                 {
    2929        2093 :                     if (osToken == "q")
    2930             :                     {
    2931             :                         // ignore
    2932             :                     }
    2933        2093 :                     else if (nCurIdx < 6)
    2934             :                     {
    2935        1794 :                         adfVals[nCurIdx++] = CPLAtof(osToken);
    2936             :                     }
    2937         299 :                     else if (nCurIdx == 6 && osToken == "cm")
    2938             :                     {
    2939         299 :                         nState = STATE_AFTER_cm;
    2940         299 :                         nCurIdx = 0;
    2941             :                     }
    2942             :                     else
    2943           0 :                         return FALSE;
    2944             :                 }
    2945         897 :                 else if (nState == STATE_AFTER_cm)
    2946             :                 {
    2947         598 :                     if (nCurIdx == 0 && osToken[0] == '/')
    2948             :                     {
    2949         299 :                         osCurrentImage = osToken.substr(1);
    2950             :                     }
    2951         299 :                     else if (osToken == "Do")
    2952             :                     {
    2953         299 :                         nState = STATE_AFTER_Do;
    2954             :                     }
    2955             :                     else
    2956           0 :                         return FALSE;
    2957             :                 }
    2958         299 :                 else if (nState == STATE_AFTER_Do)
    2959             :                 {
    2960         299 :                     if (osToken == "Q")
    2961             :                     {
    2962             :                         GDALPDFObject *poImage =
    2963         299 :                             poXObjectDict->Get(osCurrentImage);
    2964         598 :                         if (poImage != nullptr &&
    2965         299 :                             poImage->GetType() == PDFObjectType_Dictionary)
    2966             :                         {
    2967             :                             GDALPDFTileDesc sTile;
    2968             :                             GDALPDFDictionary *poImageDict =
    2969         299 :                                 poImage->GetDictionary();
    2970         299 :                             GDALPDFObject *poWidth = poImageDict->Get("Width");
    2971             :                             GDALPDFObject *poHeight =
    2972         299 :                                 poImageDict->Get("Height");
    2973             :                             GDALPDFObject *poColorSpace =
    2974         299 :                                 poImageDict->Get("ColorSpace");
    2975         299 :                             GDALPDFObject *poSMask = poImageDict->Get("SMask");
    2976         598 :                             if (poColorSpace &&
    2977         299 :                                 poColorSpace->GetType() == PDFObjectType_Name)
    2978             :                             {
    2979         296 :                                 if (poColorSpace->GetName() == "DeviceRGB")
    2980             :                                 {
    2981         113 :                                     sTile.nBands = 3;
    2982         113 :                                     if (*pnBands < 3)
    2983          26 :                                         *pnBands = 3;
    2984             :                                 }
    2985         183 :                                 else if (poColorSpace->GetName() ==
    2986             :                                          "DeviceGray")
    2987             :                                 {
    2988         183 :                                     sTile.nBands = 1;
    2989         183 :                                     if (*pnBands < 1)
    2990         129 :                                         *pnBands = 1;
    2991             :                                 }
    2992             :                                 else
    2993           0 :                                     sTile.nBands = 0;
    2994             :                             }
    2995         299 :                             if (poSMask != nullptr)
    2996          94 :                                 *pnBands = 4;
    2997             : 
    2998         299 :                             if (poWidth && poHeight &&
    2999           0 :                                 ((bAcceptRotationTerms &&
    3000         299 :                                   adfVals[1] == -adfVals[2]) ||
    3001         299 :                                  (!bAcceptRotationTerms && adfVals[1] == 0.0 &&
    3002         299 :                                   adfVals[2] == 0.0)))
    3003             :                             {
    3004         299 :                                 double dfWidth = Get(poWidth);
    3005         299 :                                 double dfHeight = Get(poHeight);
    3006         299 :                                 double dfScaleX = adfVals[0];
    3007         299 :                                 double dfScaleY = adfVals[3];
    3008         299 :                                 if (dfWidth > 0 && dfHeight > 0 &&
    3009         299 :                                     dfScaleX > 0 && dfScaleY > 0 &&
    3010         299 :                                     dfWidth / dfScaleX * DEFAULT_DPI <
    3011         299 :                                         INT_MAX &&
    3012         299 :                                     dfHeight / dfScaleY * DEFAULT_DPI < INT_MAX)
    3013             :                                 {
    3014         598 :                                     double dfDPI_X = ROUND_IF_CLOSE(
    3015         299 :                                         dfWidth / dfScaleX * DEFAULT_DPI, 1e-3);
    3016         598 :                                     double dfDPI_Y = ROUND_IF_CLOSE(
    3017         299 :                                         dfHeight / dfScaleY * DEFAULT_DPI,
    3018             :                                         1e-3);
    3019             :                                     // CPLDebug("PDF", "Image %s, width = %.16g,
    3020             :                                     // height = %.16g, scaleX = %.16g, scaleY =
    3021             :                                     // %.16g --> DPI_X = %.16g, DPI_Y = %.16g",
    3022             :                                     //                 osCurrentImage.c_str(),
    3023             :                                     //                 dfWidth, dfHeight,
    3024             :                                     //                 dfScaleX, dfScaleY,
    3025             :                                     //                 dfDPI_X, dfDPI_Y);
    3026         299 :                                     if (dfDPI_X > dfDPI)
    3027          20 :                                         dfDPI = dfDPI_X;
    3028         299 :                                     if (dfDPI_Y > dfDPI)
    3029           0 :                                         dfDPI = dfDPI_Y;
    3030             : 
    3031         299 :                                     memcpy(&(sTile.adfCM), adfVals,
    3032             :                                            6 * sizeof(double));
    3033         299 :                                     sTile.poImage = poImage;
    3034         299 :                                     sTile.dfWidth = dfWidth;
    3035         299 :                                     sTile.dfHeight = dfHeight;
    3036         299 :                                     asTiles.push_back(sTile);
    3037             : 
    3038         299 :                                     *pbDPISet = TRUE;
    3039         299 :                                     *pdfDPI = dfDPI;
    3040             :                                 }
    3041             :                             }
    3042             :                         }
    3043         299 :                         nState = STATE_INIT;
    3044             :                     }
    3045             :                     else
    3046           0 :                         return FALSE;
    3047             :                 }
    3048             :             }
    3049        3289 :             osToken = "";
    3050             :         }
    3051             :         else
    3052        7738 :             osToken += ch;
    3053       11027 :         pszContent++;
    3054             :     }
    3055             : 
    3056         144 :     return TRUE;
    3057             : }
    3058             : 
    3059             : /************************************************************************/
    3060             : /*                         CheckTiledRaster()                           */
    3061             : /************************************************************************/
    3062             : 
    3063         158 : int PDFDataset::CheckTiledRaster()
    3064             : {
    3065             :     size_t i;
    3066         158 :     int l_nBlockXSize = 0;
    3067         158 :     int l_nBlockYSize = 0;
    3068         158 :     const double dfUserUnit = m_dfDPI * USER_UNIT_IN_INCH;
    3069             : 
    3070             :     /* First pass : check that all tiles have same DPI, */
    3071             :     /* are contained entirely in the raster size, */
    3072             :     /* and determine the block size */
    3073         454 :     for (i = 0; i < m_asTiles.size(); i++)
    3074             :     {
    3075         299 :         double dfDrawWidth = m_asTiles[i].adfCM[0] * dfUserUnit;
    3076         299 :         double dfDrawHeight = m_asTiles[i].adfCM[3] * dfUserUnit;
    3077         299 :         double dfX = m_asTiles[i].adfCM[4] * dfUserUnit;
    3078         299 :         double dfY = m_asTiles[i].adfCM[5] * dfUserUnit;
    3079         299 :         int nX = static_cast<int>(dfX + 0.1);
    3080         299 :         int nY = static_cast<int>(dfY + 0.1);
    3081         299 :         int nWidth = static_cast<int>(m_asTiles[i].dfWidth + 1e-8);
    3082         299 :         int nHeight = static_cast<int>(m_asTiles[i].dfHeight + 1e-8);
    3083             : 
    3084         299 :         GDALPDFDictionary *poImageDict = m_asTiles[i].poImage->GetDictionary();
    3085             :         GDALPDFObject *poBitsPerComponent =
    3086         299 :             poImageDict->Get("BitsPerComponent");
    3087         299 :         GDALPDFObject *poColorSpace = poImageDict->Get("ColorSpace");
    3088         299 :         GDALPDFObject *poFilter = poImageDict->Get("Filter");
    3089             : 
    3090             :         /* Podofo cannot uncompress JPEG2000 streams */
    3091         299 :         if (m_bUseLib.test(PDFLIB_PODOFO) && poFilter != nullptr &&
    3092         299 :             poFilter->GetType() == PDFObjectType_Name &&
    3093           0 :             poFilter->GetName() == "JPXDecode")
    3094             :         {
    3095           0 :             CPLDebug("PDF", "Tile %d : Incompatible image for tiled reading",
    3096             :                      static_cast<int>(i));
    3097           0 :             return FALSE;
    3098             :         }
    3099             : 
    3100         299 :         if (poBitsPerComponent == nullptr || Get(poBitsPerComponent) != 8 ||
    3101         299 :             poColorSpace == nullptr ||
    3102        1077 :             poColorSpace->GetType() != PDFObjectType_Name ||
    3103         479 :             (poColorSpace->GetName() != "DeviceRGB" &&
    3104         183 :              poColorSpace->GetName() != "DeviceGray"))
    3105             :         {
    3106           3 :             CPLDebug("PDF", "Tile %d : Incompatible image for tiled reading",
    3107             :                      static_cast<int>(i));
    3108           3 :             return FALSE;
    3109             :         }
    3110             : 
    3111         296 :         if (fabs(dfDrawWidth - m_asTiles[i].dfWidth) > 1e-2 ||
    3112         296 :             fabs(dfDrawHeight - m_asTiles[i].dfHeight) > 1e-2 ||
    3113         296 :             fabs(nWidth - m_asTiles[i].dfWidth) > 1e-8 ||
    3114         296 :             fabs(nHeight - m_asTiles[i].dfHeight) > 1e-8 ||
    3115         296 :             fabs(nX - dfX) > 1e-1 || fabs(nY - dfY) > 1e-1 || nX < 0 ||
    3116         592 :             nY < 0 || nX + nWidth > nRasterXSize || nY >= nRasterYSize)
    3117             :         {
    3118           0 :             CPLDebug("PDF", "Tile %d : %f %f %f %f %f %f", static_cast<int>(i),
    3119           0 :                      dfX, dfY, dfDrawWidth, dfDrawHeight, m_asTiles[i].dfWidth,
    3120           0 :                      m_asTiles[i].dfHeight);
    3121           0 :             return FALSE;
    3122             :         }
    3123         296 :         if (l_nBlockXSize == 0 && l_nBlockYSize == 0 && nX == 0 && nY != 0)
    3124             :         {
    3125           9 :             l_nBlockXSize = nWidth;
    3126           9 :             l_nBlockYSize = nHeight;
    3127             :         }
    3128             :     }
    3129         155 :     if (l_nBlockXSize <= 0 || l_nBlockYSize <= 0 || l_nBlockXSize > 2048 ||
    3130             :         l_nBlockYSize > 2048)
    3131         146 :         return FALSE;
    3132             : 
    3133           9 :     int nXBlocks = DIV_ROUND_UP(nRasterXSize, l_nBlockXSize);
    3134           9 :     int nYBlocks = DIV_ROUND_UP(nRasterYSize, l_nBlockYSize);
    3135             : 
    3136             :     /* Second pass to determine that all tiles are properly aligned on block
    3137             :      * size */
    3138         159 :     for (i = 0; i < m_asTiles.size(); i++)
    3139             :     {
    3140         150 :         double dfX = m_asTiles[i].adfCM[4] * dfUserUnit;
    3141         150 :         double dfY = m_asTiles[i].adfCM[5] * dfUserUnit;
    3142         150 :         int nX = static_cast<int>(dfX + 0.1);
    3143         150 :         int nY = static_cast<int>(dfY + 0.1);
    3144         150 :         int nWidth = static_cast<int>(m_asTiles[i].dfWidth + 1e-8);
    3145         150 :         int nHeight = static_cast<int>(m_asTiles[i].dfHeight + 1e-8);
    3146         150 :         int bOK = TRUE;
    3147         150 :         int nBlockXOff = nX / l_nBlockXSize;
    3148         150 :         if ((nX % l_nBlockXSize) != 0)
    3149           0 :             bOK = FALSE;
    3150         150 :         if (nBlockXOff < nXBlocks - 1 && nWidth != l_nBlockXSize)
    3151           0 :             bOK = FALSE;
    3152         150 :         if (nBlockXOff == nXBlocks - 1 && nX + nWidth != nRasterXSize)
    3153           0 :             bOK = FALSE;
    3154             : 
    3155         150 :         if (nY > 0 && nHeight != l_nBlockYSize)
    3156           0 :             bOK = FALSE;
    3157         150 :         if (nY == 0 && nHeight != nRasterYSize - (nYBlocks - 1) * l_nBlockYSize)
    3158           0 :             bOK = FALSE;
    3159             : 
    3160         150 :         if (!bOK)
    3161             :         {
    3162           0 :             CPLDebug("PDF", "Tile %d : %d %d %d %d", static_cast<int>(i), nX,
    3163             :                      nY, nWidth, nHeight);
    3164           0 :             return FALSE;
    3165             :         }
    3166             :     }
    3167             : 
    3168             :     /* Third pass to set the aiTiles array */
    3169           9 :     m_aiTiles.resize(static_cast<size_t>(nXBlocks) * nYBlocks, -1);
    3170         159 :     for (i = 0; i < m_asTiles.size(); i++)
    3171             :     {
    3172         150 :         double dfX = m_asTiles[i].adfCM[4] * dfUserUnit;
    3173         150 :         double dfY = m_asTiles[i].adfCM[5] * dfUserUnit;
    3174         150 :         int nHeight = static_cast<int>(m_asTiles[i].dfHeight + 1e-8);
    3175         150 :         int nX = static_cast<int>(dfX + 0.1);
    3176         150 :         int nY = nRasterYSize - (static_cast<int>(dfY + 0.1) + nHeight);
    3177         150 :         int nBlockXOff = nX / l_nBlockXSize;
    3178         150 :         int nBlockYOff = nY / l_nBlockYSize;
    3179         150 :         m_aiTiles[nBlockYOff * nXBlocks + nBlockXOff] = static_cast<int>(i);
    3180             :     }
    3181             : 
    3182           9 :     this->m_nBlockXSize = l_nBlockXSize;
    3183           9 :     this->m_nBlockYSize = l_nBlockYSize;
    3184             : 
    3185           9 :     return TRUE;
    3186             : }
    3187             : 
    3188             : /************************************************************************/
    3189             : /*                              GuessDPI()                              */
    3190             : /************************************************************************/
    3191             : 
    3192         214 : void PDFDataset::GuessDPI(GDALPDFDictionary *poPageDict, int *pnBands)
    3193             : {
    3194         214 :     const char *pszDPI = GetOption(papszOpenOptions, "DPI", nullptr);
    3195         214 :     if (pszDPI != nullptr)
    3196             :     {
    3197             :         // coverity[tainted_data]
    3198           3 :         m_dfDPI = CPLAtof(pszDPI);
    3199             :     }
    3200             :     else
    3201             :     {
    3202             :         /* Try to get a better value from the images that are drawn */
    3203             :         /* Very simplistic logic. Will only work for raster only PDF */
    3204             : 
    3205         211 :         GDALPDFObject *poContents = poPageDict->Get("Contents");
    3206         421 :         if (poContents != nullptr &&
    3207         210 :             poContents->GetType() == PDFObjectType_Array)
    3208             :         {
    3209           1 :             GDALPDFArray *poContentsArray = poContents->GetArray();
    3210           1 :             if (poContentsArray->GetLength() == 1)
    3211             :             {
    3212           1 :                 poContents = poContentsArray->Get(0);
    3213             :             }
    3214             :         }
    3215             : 
    3216             :         GDALPDFObject *poXObject =
    3217         211 :             poPageDict->LookupObject("Resources.XObject");
    3218         421 :         if (poContents != nullptr &&
    3219         210 :             poContents->GetType() == PDFObjectType_Dictionary &&
    3220         421 :             poXObject != nullptr &&
    3221         198 :             poXObject->GetType() == PDFObjectType_Dictionary)
    3222             :         {
    3223         198 :             GDALPDFDictionary *poXObjectDict = poXObject->GetDictionary();
    3224         198 :             GDALPDFDictionary *poContentDict = poXObjectDict;
    3225         198 :             GDALPDFStream *poPageStream = poContents->GetStream();
    3226         198 :             if (poPageStream != nullptr)
    3227             :             {
    3228         197 :                 char *pszContent = nullptr;
    3229         197 :                 const int64_t MAX_LENGTH = 10 * 1000 * 1000;
    3230         197 :                 int64_t nLength = poPageStream->GetLength(MAX_LENGTH);
    3231         197 :                 int bResetTiles = FALSE;
    3232         197 :                 double dfScaleDPI = 1.0;
    3233             : 
    3234         197 :                 if (nLength < MAX_LENGTH)
    3235             :                 {
    3236         394 :                     CPLString osForm;
    3237         197 :                     pszContent = poPageStream->GetBytes();
    3238         197 :                     if (pszContent != nullptr)
    3239             :                     {
    3240             : #ifdef DEBUG
    3241             :                         const char *pszDumpStream =
    3242         197 :                             CPLGetConfigOption("PDF_DUMP_STREAM", nullptr);
    3243         197 :                         if (pszDumpStream != nullptr)
    3244             :                         {
    3245           0 :                             VSILFILE *fpDump = VSIFOpenL(pszDumpStream, "wb");
    3246           0 :                             if (fpDump)
    3247             :                             {
    3248           0 :                                 VSIFWriteL(pszContent, 1,
    3249             :                                            static_cast<int>(nLength), fpDump);
    3250           0 :                                 VSIFCloseL(fpDump);
    3251             :                             }
    3252             :                         }
    3253             : #endif  // DEBUG
    3254             :                         osForm =
    3255         197 :                             GDALPDFParseStreamContentOnlyDrawForm(pszContent);
    3256         197 :                         if (osForm.empty())
    3257             :                         {
    3258             :                             /* Special case for USGS Topo PDF, like
    3259             :                              * CA_Hollywood_20090811_OM_geo.pdf */
    3260             :                             const char *pszOGCDo =
    3261         197 :                                 strstr(pszContent, " /XO1 Do");
    3262         197 :                             if (pszOGCDo)
    3263             :                             {
    3264           0 :                                 const char *pszcm = strstr(pszContent, " cm ");
    3265           0 :                                 if (pszcm != nullptr && pszcm < pszOGCDo)
    3266             :                                 {
    3267             :                                     const char *pszNextcm =
    3268           0 :                                         strstr(pszcm + 2, "cm");
    3269           0 :                                     if (pszNextcm == nullptr ||
    3270             :                                         pszNextcm > pszOGCDo)
    3271             :                                     {
    3272           0 :                                         const char *pszIter = pszcm;
    3273           0 :                                         while (pszIter > pszContent)
    3274             :                                         {
    3275           0 :                                             if ((*pszIter >= '0' &&
    3276           0 :                                                  *pszIter <= '9') ||
    3277           0 :                                                 *pszIter == '-' ||
    3278           0 :                                                 *pszIter == '.' ||
    3279           0 :                                                 *pszIter == ' ')
    3280           0 :                                                 pszIter--;
    3281             :                                             else
    3282             :                                             {
    3283           0 :                                                 pszIter++;
    3284           0 :                                                 break;
    3285             :                                             }
    3286             :                                         }
    3287           0 :                                         CPLString oscm(pszIter);
    3288           0 :                                         oscm.resize(pszcm - pszIter);
    3289             :                                         char **papszTokens =
    3290           0 :                                             CSLTokenizeString(oscm);
    3291           0 :                                         double dfScaleX = -1.0;
    3292           0 :                                         double dfScaleY = -2.0;
    3293           0 :                                         if (CSLCount(papszTokens) == 6)
    3294             :                                         {
    3295           0 :                                             dfScaleX = CPLAtof(papszTokens[0]);
    3296           0 :                                             dfScaleY = CPLAtof(papszTokens[3]);
    3297             :                                         }
    3298           0 :                                         CSLDestroy(papszTokens);
    3299           0 :                                         if (dfScaleX == dfScaleY &&
    3300             :                                             dfScaleX > 0.0)
    3301             :                                         {
    3302           0 :                                             osForm = "XO1";
    3303           0 :                                             bResetTiles = TRUE;
    3304           0 :                                             dfScaleDPI = 1.0 / dfScaleX;
    3305             :                                         }
    3306           0 :                                     }
    3307             :                                 }
    3308             :                                 else
    3309             :                                 {
    3310           0 :                                     osForm = "XO1";
    3311           0 :                                     bResetTiles = TRUE;
    3312             :                                 }
    3313             :                             }
    3314             :                             /* Special case for USGS Topo PDF, like
    3315             :                              * CA_Sacramento_East_20120308_TM_geo.pdf */
    3316             :                             else
    3317             :                             {
    3318             :                                 CPLString osOCG =
    3319         394 :                                     FindLayerOCG(poPageDict, "Orthoimage");
    3320         197 :                                 if (!osOCG.empty())
    3321             :                                 {
    3322           0 :                                     const char *pszBDCLookup = CPLSPrintf(
    3323             :                                         "/OC /%s BDC", osOCG.c_str());
    3324             :                                     const char *pszBDC =
    3325           0 :                                         strstr(pszContent, pszBDCLookup);
    3326           0 :                                     if (pszBDC != nullptr)
    3327             :                                     {
    3328           0 :                                         const char *pszIter =
    3329           0 :                                             pszBDC + strlen(pszBDCLookup);
    3330           0 :                                         while (*pszIter != '\0')
    3331             :                                         {
    3332           0 :                                             if (*pszIter == 13 ||
    3333           0 :                                                 *pszIter == 10 ||
    3334           0 :                                                 *pszIter == ' ' ||
    3335           0 :                                                 *pszIter == 'q')
    3336           0 :                                                 pszIter++;
    3337             :                                             else
    3338             :                                                 break;
    3339             :                                         }
    3340           0 :                                         if (STARTS_WITH(pszIter,
    3341             :                                                         "1 0 0 1 0 0 cm\n"))
    3342           0 :                                             pszIter +=
    3343             :                                                 strlen("1 0 0 1 0 0 cm\n");
    3344           0 :                                         if (*pszIter == '/')
    3345             :                                         {
    3346           0 :                                             pszIter++;
    3347             :                                             const char *pszDo =
    3348           0 :                                                 strstr(pszIter, " Do");
    3349           0 :                                             if (pszDo != nullptr)
    3350             :                                             {
    3351           0 :                                                 osForm = pszIter;
    3352           0 :                                                 osForm.resize(pszDo - pszIter);
    3353           0 :                                                 bResetTiles = TRUE;
    3354             :                                             }
    3355             :                                         }
    3356             :                                     }
    3357             :                                 }
    3358             :                             }
    3359             :                         }
    3360             :                     }
    3361             : 
    3362         197 :                     if (!osForm.empty())
    3363             :                     {
    3364           0 :                         CPLFree(pszContent);
    3365           0 :                         pszContent = nullptr;
    3366             : 
    3367           0 :                         GDALPDFObject *poObjForm = poXObjectDict->Get(osForm);
    3368           0 :                         if (poObjForm != nullptr &&
    3369           0 :                             poObjForm->GetType() == PDFObjectType_Dictionary &&
    3370           0 :                             (poPageStream = poObjForm->GetStream()) != nullptr)
    3371             :                         {
    3372             :                             GDALPDFDictionary *poObjFormDict =
    3373           0 :                                 poObjForm->GetDictionary();
    3374             :                             GDALPDFObject *poSubtype =
    3375           0 :                                 poObjFormDict->Get("Subtype");
    3376           0 :                             if (poSubtype != nullptr &&
    3377           0 :                                 poSubtype->GetType() == PDFObjectType_Name &&
    3378           0 :                                 poSubtype->GetName() == "Form")
    3379             :                             {
    3380           0 :                                 nLength = poPageStream->GetLength(MAX_LENGTH);
    3381           0 :                                 if (nLength < MAX_LENGTH)
    3382             :                                 {
    3383           0 :                                     pszContent = poPageStream->GetBytes();
    3384             : 
    3385             :                                     GDALPDFObject *poXObject2 =
    3386           0 :                                         poObjFormDict->LookupObject(
    3387             :                                             "Resources.XObject");
    3388           0 :                                     if (poXObject2 != nullptr &&
    3389           0 :                                         poXObject2->GetType() ==
    3390             :                                             PDFObjectType_Dictionary)
    3391             :                                         poContentDict =
    3392           0 :                                             poXObject2->GetDictionary();
    3393             :                                 }
    3394             :                             }
    3395             :                         }
    3396             :                     }
    3397             :                 }
    3398             : 
    3399         197 :                 if (pszContent != nullptr)
    3400             :                 {
    3401         197 :                     int bDPISet = FALSE;
    3402             : 
    3403         197 :                     const char *pszContentToParse = pszContent;
    3404         197 :                     if (bResetTiles)
    3405             :                     {
    3406           0 :                         while (*pszContentToParse != '\0')
    3407             :                         {
    3408           0 :                             if (*pszContentToParse == 13 ||
    3409           0 :                                 *pszContentToParse == 10 ||
    3410           0 :                                 *pszContentToParse == ' ' ||
    3411           0 :                                 (*pszContentToParse >= '0' &&
    3412           0 :                                  *pszContentToParse <= '9') ||
    3413           0 :                                 *pszContentToParse == '.' ||
    3414           0 :                                 *pszContentToParse == '-' ||
    3415           0 :                                 *pszContentToParse == 'l' ||
    3416           0 :                                 *pszContentToParse == 'm' ||
    3417           0 :                                 *pszContentToParse == 'n' ||
    3418           0 :                                 *pszContentToParse == 'W')
    3419           0 :                                 pszContentToParse++;
    3420             :                             else
    3421             :                                 break;
    3422             :                         }
    3423             :                     }
    3424             : 
    3425         197 :                     GDALPDFParseStreamContent(pszContentToParse, poContentDict,
    3426             :                                               &(m_dfDPI), &bDPISet, pnBands,
    3427         197 :                                               m_asTiles, bResetTiles);
    3428         197 :                     CPLFree(pszContent);
    3429         197 :                     if (bDPISet)
    3430             :                     {
    3431         158 :                         m_dfDPI *= dfScaleDPI;
    3432             : 
    3433         158 :                         CPLDebug("PDF",
    3434             :                                  "DPI guessed from contents stream = %.16g",
    3435             :                                  m_dfDPI);
    3436         158 :                         SetMetadataItem("DPI", CPLSPrintf("%.16g", m_dfDPI));
    3437         158 :                         if (bResetTiles)
    3438           0 :                             m_asTiles.resize(0);
    3439             :                     }
    3440             :                     else
    3441          39 :                         m_asTiles.resize(0);
    3442             :                 }
    3443             :             }
    3444             :         }
    3445             : 
    3446         211 :         GDALPDFObject *poUserUnit = nullptr;
    3447         387 :         if ((poUserUnit = poPageDict->Get("UserUnit")) != nullptr &&
    3448         176 :             (poUserUnit->GetType() == PDFObjectType_Int ||
    3449          12 :              poUserUnit->GetType() == PDFObjectType_Real))
    3450             :         {
    3451         176 :             m_dfDPI = ROUND_IF_CLOSE(Get(poUserUnit) * DEFAULT_DPI, 1e-5);
    3452         176 :             CPLDebug("PDF", "Found UserUnit in Page --> DPI = %.16g", m_dfDPI);
    3453         176 :             SetMetadataItem("DPI", CPLSPrintf("%.16g", m_dfDPI));
    3454             :         }
    3455             :     }
    3456             : 
    3457         214 :     if (m_dfDPI < 1e-2 || m_dfDPI > 7200)
    3458             :     {
    3459           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    3460             :                  "Invalid value for GDAL_PDF_DPI. Using default value instead");
    3461           0 :         m_dfDPI = GDAL_DEFAULT_DPI;
    3462             :     }
    3463         214 : }
    3464             : 
    3465             : /************************************************************************/
    3466             : /*                              FindXMP()                               */
    3467             : /************************************************************************/
    3468             : 
    3469           0 : void PDFDataset::FindXMP(GDALPDFObject *poObj)
    3470             : {
    3471           0 :     if (poObj->GetType() != PDFObjectType_Dictionary)
    3472           0 :         return;
    3473             : 
    3474           0 :     GDALPDFDictionary *poDict = poObj->GetDictionary();
    3475           0 :     GDALPDFObject *poType = poDict->Get("Type");
    3476           0 :     GDALPDFObject *poSubtype = poDict->Get("Subtype");
    3477           0 :     if (poType == nullptr || poType->GetType() != PDFObjectType_Name ||
    3478           0 :         poType->GetName() != "Metadata" || poSubtype == nullptr ||
    3479           0 :         poSubtype->GetType() != PDFObjectType_Name ||
    3480           0 :         poSubtype->GetName() != "XML")
    3481             :     {
    3482           0 :         return;
    3483             :     }
    3484             : 
    3485           0 :     GDALPDFStream *poStream = poObj->GetStream();
    3486           0 :     if (poStream == nullptr)
    3487           0 :         return;
    3488             : 
    3489           0 :     char *pszContent = poStream->GetBytes();
    3490           0 :     const auto nLength = poStream->GetLength();
    3491           0 :     if (pszContent != nullptr && nLength > 15 &&
    3492           0 :         STARTS_WITH(pszContent, "<?xpacket begin="))
    3493             :     {
    3494             :         char *apszMDList[2];
    3495           0 :         apszMDList[0] = pszContent;
    3496           0 :         apszMDList[1] = nullptr;
    3497           0 :         SetMetadata(apszMDList, "xml:XMP");
    3498             :     }
    3499           0 :     CPLFree(pszContent);
    3500             : }
    3501             : 
    3502             : /************************************************************************/
    3503             : /*                             ParseInfo()                              */
    3504             : /************************************************************************/
    3505             : 
    3506         214 : void PDFDataset::ParseInfo(GDALPDFObject *poInfoObj)
    3507             : {
    3508         214 :     if (poInfoObj->GetType() != PDFObjectType_Dictionary)
    3509         175 :         return;
    3510             : 
    3511          39 :     GDALPDFDictionary *poInfoObjDict = poInfoObj->GetDictionary();
    3512          39 :     GDALPDFObject *poItem = nullptr;
    3513          39 :     int bOneMDISet = FALSE;
    3514          48 :     if ((poItem = poInfoObjDict->Get("Author")) != nullptr &&
    3515           9 :         poItem->GetType() == PDFObjectType_String)
    3516             :     {
    3517           9 :         SetMetadataItem("AUTHOR", poItem->GetString().c_str());
    3518           9 :         bOneMDISet = TRUE;
    3519             :     }
    3520          66 :     if ((poItem = poInfoObjDict->Get("Creator")) != nullptr &&
    3521          27 :         poItem->GetType() == PDFObjectType_String)
    3522             :     {
    3523          27 :         SetMetadataItem("CREATOR", poItem->GetString().c_str());
    3524          27 :         bOneMDISet = TRUE;
    3525             :     }
    3526          43 :     if ((poItem = poInfoObjDict->Get("Keywords")) != nullptr &&
    3527           4 :         poItem->GetType() == PDFObjectType_String)
    3528             :     {
    3529           4 :         SetMetadataItem("KEYWORDS", poItem->GetString().c_str());
    3530           4 :         bOneMDISet = TRUE;
    3531             :     }
    3532          45 :     if ((poItem = poInfoObjDict->Get("Subject")) != nullptr &&
    3533           6 :         poItem->GetType() == PDFObjectType_String)
    3534             :     {
    3535           6 :         SetMetadataItem("SUBJECT", poItem->GetString().c_str());
    3536           6 :         bOneMDISet = TRUE;
    3537             :     }
    3538          46 :     if ((poItem = poInfoObjDict->Get("Title")) != nullptr &&
    3539           7 :         poItem->GetType() == PDFObjectType_String)
    3540             :     {
    3541           7 :         SetMetadataItem("TITLE", poItem->GetString().c_str());
    3542           7 :         bOneMDISet = TRUE;
    3543             :     }
    3544          52 :     if ((poItem = poInfoObjDict->Get("Producer")) != nullptr &&
    3545          13 :         poItem->GetType() == PDFObjectType_String)
    3546             :     {
    3547          19 :         if (bOneMDISet ||
    3548           6 :             poItem->GetString() != "PoDoFo - http://podofo.sf.net")
    3549             :         {
    3550           7 :             SetMetadataItem("PRODUCER", poItem->GetString().c_str());
    3551           7 :             bOneMDISet = TRUE;
    3552             :         }
    3553             :     }
    3554          68 :     if ((poItem = poInfoObjDict->Get("CreationDate")) != nullptr &&
    3555          29 :         poItem->GetType() == PDFObjectType_String)
    3556             :     {
    3557          29 :         if (bOneMDISet)
    3558          23 :             SetMetadataItem("CREATION_DATE", poItem->GetString().c_str());
    3559             :     }
    3560             : }
    3561             : 
    3562             : #if defined(HAVE_POPPLER) || defined(HAVE_PDFIUM)
    3563             : 
    3564             : /************************************************************************/
    3565             : /*                             AddLayer()                               */
    3566             : /************************************************************************/
    3567             : 
    3568         368 : void PDFDataset::AddLayer(const std::string &osName, int iPage)
    3569             : {
    3570         736 :     LayerStruct layerStruct;
    3571         368 :     layerStruct.osName = osName;
    3572         368 :     layerStruct.nInsertIdx = static_cast<int>(m_oLayerNameSet.size());
    3573         368 :     layerStruct.iPage = iPage;
    3574         368 :     m_oLayerNameSet.emplace_back(std::move(layerStruct));
    3575         368 : }
    3576             : 
    3577             : /************************************************************************/
    3578             : /*                            SortLayerList()                           */
    3579             : /************************************************************************/
    3580             : 
    3581             : // recent libc++ std::sort() involve unsigned integer overflow in some
    3582             : // situation
    3583             : CPL_NOSANITIZE_UNSIGNED_INT_OVERFLOW
    3584          63 : void PDFDataset::SortLayerList()
    3585             : {
    3586             :     // Sort layers by prioritizing page number and then insertion index
    3587          63 :     std::sort(m_oLayerNameSet.begin(), m_oLayerNameSet.end(),
    3588        1777 :               [](const LayerStruct &a, const LayerStruct &b)
    3589             :               {
    3590        1777 :                   if (a.iPage < b.iPage)
    3591          39 :                       return true;
    3592        1738 :                   if (a.iPage > b.iPage)
    3593           0 :                       return false;
    3594        1738 :                   return a.nInsertIdx < b.nInsertIdx;
    3595             :               });
    3596          63 : }
    3597             : 
    3598             : /************************************************************************/
    3599             : /*                           CreateLayerList()                          */
    3600             : /************************************************************************/
    3601             : 
    3602          63 : void PDFDataset::CreateLayerList()
    3603             : {
    3604          63 :     SortLayerList();
    3605             : 
    3606          63 :     if (m_oLayerNameSet.size() >= 100)
    3607             :     {
    3608         199 :         for (const auto &oLayerStruct : m_oLayerNameSet)
    3609             :         {
    3610             :             m_aosLayerNames.AddNameValue(
    3611             :                 CPLSPrintf("LAYER_%03d_NAME", m_aosLayerNames.size()),
    3612         198 :                 oLayerStruct.osName.c_str());
    3613             :         }
    3614             :     }
    3615             :     else
    3616             :     {
    3617         232 :         for (const auto &oLayerStruct : m_oLayerNameSet)
    3618             :         {
    3619             :             m_aosLayerNames.AddNameValue(
    3620             :                 CPLSPrintf("LAYER_%02d_NAME", m_aosLayerNames.size()),
    3621         170 :                 oLayerStruct.osName.c_str());
    3622             :         }
    3623             :     }
    3624          63 : }
    3625             : 
    3626             : /************************************************************************/
    3627             : /*                  BuildPostfixedLayerNameAndAddLayer()                */
    3628             : /************************************************************************/
    3629             : 
    3630             : /** Append a suffix with the page number(s) to the provided layer name, if
    3631             :  * it makes sense (that is if it is a multiple page PDF and we haven't selected
    3632             :  * a specific name). And also call AddLayer() on it if successful.
    3633             :  * If may return an empty string if the layer isn't used by the page of interest
    3634             :  */
    3635         440 : std::string PDFDataset::BuildPostfixedLayerNameAndAddLayer(
    3636             :     const std::string &osName, const std::pair<int, int> &oOCGRef,
    3637             :     int iPageOfInterest, int nPageCount)
    3638             : {
    3639         880 :     std::string osPostfixedName = osName;
    3640         440 :     int iLayerPage = 0;
    3641         440 :     if (nPageCount > 1 && !m_oMapOCGNumGenToPages.empty())
    3642             :     {
    3643         108 :         const auto oIterToPages = m_oMapOCGNumGenToPages.find(oOCGRef);
    3644         108 :         if (oIterToPages != m_oMapOCGNumGenToPages.end())
    3645             :         {
    3646         108 :             const auto &anPages = oIterToPages->second;
    3647         108 :             if (iPageOfInterest > 0)
    3648             :             {
    3649          96 :                 if (std::find(anPages.begin(), anPages.end(),
    3650          96 :                               iPageOfInterest) == anPages.end())
    3651             :                 {
    3652          72 :                     return std::string();
    3653             :                 }
    3654             :             }
    3655          12 :             else if (anPages.size() == 1)
    3656             :             {
    3657          12 :                 iLayerPage = anPages.front();
    3658          12 :                 osPostfixedName += CPLSPrintf(" (page %d)", anPages.front());
    3659             :             }
    3660             :             else
    3661             :             {
    3662           0 :                 osPostfixedName += " (pages ";
    3663           0 :                 for (size_t j = 0; j < anPages.size(); ++j)
    3664             :                 {
    3665           0 :                     if (j > 0)
    3666           0 :                         osPostfixedName += ", ";
    3667           0 :                     osPostfixedName += CPLSPrintf("%d", anPages[j]);
    3668             :                 }
    3669           0 :                 osPostfixedName += ')';
    3670             :             }
    3671             :         }
    3672             :     }
    3673             : 
    3674         368 :     AddLayer(osPostfixedName, iLayerPage);
    3675             : 
    3676         368 :     return osPostfixedName;
    3677             : }
    3678             : 
    3679             : #endif  //  defined(HAVE_POPPLER) || defined(HAVE_PDFIUM)
    3680             : 
    3681             : #ifdef HAVE_POPPLER
    3682             : 
    3683             : /************************************************************************/
    3684             : /*                       ExploreLayersPoppler()                         */
    3685             : /************************************************************************/
    3686             : 
    3687         215 : void PDFDataset::ExploreLayersPoppler(GDALPDFArray *poArray,
    3688             :                                       int iPageOfInterest, int nPageCount,
    3689             :                                       CPLString osTopLayer, int nRecLevel,
    3690             :                                       int &nVisited, bool &bStop)
    3691             : {
    3692         215 :     if (nRecLevel == 16 || nVisited == 1000)
    3693             :     {
    3694           0 :         CPLError(
    3695             :             CE_Failure, CPLE_AppDefined,
    3696             :             "ExploreLayersPoppler(): too deep exploration or too many items");
    3697           0 :         bStop = true;
    3698           0 :         return;
    3699             :     }
    3700         215 :     if (bStop)
    3701           0 :         return;
    3702             : 
    3703         215 :     int nLength = poArray->GetLength();
    3704         215 :     CPLString osCurLayer;
    3705         807 :     for (int i = 0; i < nLength; i++)
    3706             :     {
    3707         592 :         nVisited++;
    3708         592 :         GDALPDFObject *poObj = poArray->Get(i);
    3709         592 :         if (poObj == nullptr)
    3710           0 :             continue;
    3711         592 :         if (i == 0 && poObj->GetType() == PDFObjectType_String)
    3712             :         {
    3713             :             std::string osName =
    3714           0 :                 PDFSanitizeLayerName(poObj->GetString().c_str());
    3715           0 :             if (!osTopLayer.empty())
    3716             :             {
    3717           0 :                 osTopLayer += '.';
    3718           0 :                 osTopLayer += osName;
    3719             :             }
    3720             :             else
    3721           0 :                 osTopLayer = std::move(osName);
    3722           0 :             AddLayer(osTopLayer, 0);
    3723           0 :             m_oLayerOCGListPoppler.push_back(std::pair(osTopLayer, nullptr));
    3724             :         }
    3725         592 :         else if (poObj->GetType() == PDFObjectType_Array)
    3726             :         {
    3727         152 :             ExploreLayersPoppler(poObj->GetArray(), iPageOfInterest, nPageCount,
    3728             :                                  osCurLayer, nRecLevel + 1, nVisited, bStop);
    3729         152 :             if (bStop)
    3730           0 :                 return;
    3731         152 :             osCurLayer = "";
    3732             :         }
    3733         440 :         else if (poObj->GetType() == PDFObjectType_Dictionary)
    3734             :         {
    3735         440 :             GDALPDFDictionary *poDict = poObj->GetDictionary();
    3736         440 :             GDALPDFObject *poName = poDict->Get("Name");
    3737         440 :             if (poName != nullptr && poName->GetType() == PDFObjectType_String)
    3738             :             {
    3739             :                 std::string osName =
    3740         440 :                     PDFSanitizeLayerName(poName->GetString().c_str());
    3741             :                 /* coverity[copy_paste_error] */
    3742         440 :                 if (!osTopLayer.empty())
    3743             :                 {
    3744         310 :                     osCurLayer = osTopLayer;
    3745         310 :                     osCurLayer += '.';
    3746         310 :                     osCurLayer += osName;
    3747             :                 }
    3748             :                 else
    3749         130 :                     osCurLayer = std::move(osName);
    3750             :                     // CPLDebug("PDF", "Layer %s", osCurLayer.c_str());
    3751             : 
    3752             : #if POPPLER_MAJOR_VERSION > 25 ||                                              \
    3753             :     (POPPLER_MAJOR_VERSION == 25 && POPPLER_MINOR_VERSION >= 2)
    3754             :                 const
    3755             : #endif
    3756             :                     OCGs *optContentConfig =
    3757         440 :                         m_poDocPoppler->getOptContentConfig();
    3758             :                 struct Ref r;
    3759         440 :                 r.num = poObj->GetRefNum().toInt();
    3760         440 :                 r.gen = poObj->GetRefGen();
    3761         440 :                 OptionalContentGroup *ocg = optContentConfig->findOcgByRef(r);
    3762         440 :                 if (ocg)
    3763             :                 {
    3764         440 :                     const auto oRefPair = std::pair(poObj->GetRefNum().toInt(),
    3765         880 :                                                     poObj->GetRefGen());
    3766             :                     const std::string osPostfixedName =
    3767             :                         BuildPostfixedLayerNameAndAddLayer(
    3768         440 :                             osCurLayer, oRefPair, iPageOfInterest, nPageCount);
    3769         440 :                     if (osPostfixedName.empty())
    3770          72 :                         continue;
    3771             : 
    3772         368 :                     m_oLayerOCGListPoppler.push_back(
    3773         736 :                         std::make_pair(osPostfixedName, ocg));
    3774         368 :                     m_aoLayerWithRef.emplace_back(osPostfixedName.c_str(),
    3775         736 :                                                   poObj->GetRefNum(), r.gen);
    3776             :                 }
    3777             :             }
    3778             :         }
    3779             :     }
    3780             : }
    3781             : 
    3782             : /************************************************************************/
    3783             : /*                         FindLayersPoppler()                          */
    3784             : /************************************************************************/
    3785             : 
    3786         214 : void PDFDataset::FindLayersPoppler(int iPageOfInterest)
    3787             : {
    3788         214 :     int nPageCount = 0;
    3789         214 :     const auto poPages = GetPagesKids();
    3790         214 :     if (poPages)
    3791         214 :         nPageCount = poPages->GetLength();
    3792             : 
    3793             : #if POPPLER_MAJOR_VERSION > 25 ||                                              \
    3794             :     (POPPLER_MAJOR_VERSION == 25 && POPPLER_MINOR_VERSION >= 2)
    3795             :     const
    3796             : #endif
    3797         214 :         OCGs *optContentConfig = m_poDocPoppler->getOptContentConfig();
    3798         214 :     if (optContentConfig == nullptr || !optContentConfig->isOk())
    3799         151 :         return;
    3800             : 
    3801             : #if POPPLER_MAJOR_VERSION > 25 ||                                              \
    3802             :     (POPPLER_MAJOR_VERSION == 25 && POPPLER_MINOR_VERSION >= 2)
    3803             :     const
    3804             : #endif
    3805          63 :         Array *array = optContentConfig->getOrderArray();
    3806          63 :     if (array)
    3807             :     {
    3808          63 :         GDALPDFArray *poArray = GDALPDFCreateArray(array);
    3809          63 :         int nVisited = 0;
    3810          63 :         bool bStop = false;
    3811          63 :         ExploreLayersPoppler(poArray, iPageOfInterest, nPageCount, CPLString(),
    3812             :                              0, nVisited, bStop);
    3813          63 :         delete poArray;
    3814             :     }
    3815             :     else
    3816             :     {
    3817           0 :         for (const auto &refOCGPair : optContentConfig->getOCGs())
    3818             :         {
    3819           0 :             auto ocg = refOCGPair.second.get();
    3820           0 :             if (ocg != nullptr && ocg->getName() != nullptr)
    3821             :             {
    3822             :                 const char *pszLayerName =
    3823           0 :                     reinterpret_cast<const char *>(ocg->getName()->c_str());
    3824           0 :                 AddLayer(pszLayerName, 0);
    3825           0 :                 m_oLayerOCGListPoppler.push_back(
    3826           0 :                     std::make_pair(CPLString(pszLayerName), ocg));
    3827             :             }
    3828             :         }
    3829             :     }
    3830             : 
    3831          63 :     CreateLayerList();
    3832          63 :     m_oMDMD_PDF.SetMetadata(m_aosLayerNames.List(), "LAYERS");
    3833             : }
    3834             : 
    3835             : /************************************************************************/
    3836             : /*                       TurnLayersOnOffPoppler()                       */
    3837             : /************************************************************************/
    3838             : 
    3839         214 : void PDFDataset::TurnLayersOnOffPoppler()
    3840             : {
    3841             : #if POPPLER_MAJOR_VERSION > 25 ||                                              \
    3842             :     (POPPLER_MAJOR_VERSION == 25 && POPPLER_MINOR_VERSION >= 2)
    3843             :     const
    3844             : #endif
    3845         214 :         OCGs *optContentConfig = m_poDocPoppler->getOptContentConfig();
    3846         214 :     if (optContentConfig == nullptr || !optContentConfig->isOk())
    3847         151 :         return;
    3848             : 
    3849             :     // Which layers to turn ON ?
    3850          63 :     const char *pszLayers = GetOption(papszOpenOptions, "LAYERS", nullptr);
    3851          63 :     if (pszLayers)
    3852             :     {
    3853             :         int i;
    3854           2 :         int bAll = EQUAL(pszLayers, "ALL");
    3855          12 :         for (const auto &refOCGPair : optContentConfig->getOCGs())
    3856             :         {
    3857          10 :             auto ocg = refOCGPair.second.get();
    3858          10 :             ocg->setState((bAll) ? OptionalContentGroup::On
    3859             :                                  : OptionalContentGroup::Off);
    3860             :         }
    3861             : 
    3862           2 :         char **papszLayers = CSLTokenizeString2(pszLayers, ",", 0);
    3863           4 :         for (i = 0; !bAll && papszLayers[i] != nullptr; i++)
    3864             :         {
    3865           2 :             bool isFound = false;
    3866          12 :             for (auto oIter2 = m_oLayerOCGListPoppler.begin();
    3867          22 :                  oIter2 != m_oLayerOCGListPoppler.end(); ++oIter2)
    3868             :             {
    3869          10 :                 if (oIter2->first != papszLayers[i])
    3870           8 :                     continue;
    3871             : 
    3872           2 :                 isFound = true;
    3873           2 :                 auto oIter = oIter2;
    3874           2 :                 if (oIter->second)
    3875             :                 {
    3876             :                     // CPLDebug("PDF", "Turn '%s' on", papszLayers[i]);
    3877           2 :                     oIter->second->setState(OptionalContentGroup::On);
    3878             :                 }
    3879             : 
    3880             :                 // Turn child layers on, unless there's one of them explicitly
    3881             :                 // listed in the list.
    3882           2 :                 size_t nLen = strlen(papszLayers[i]);
    3883           2 :                 int bFoundChildLayer = FALSE;
    3884           2 :                 oIter = m_oLayerOCGListPoppler.begin();
    3885          10 :                 for (;
    3886          12 :                      oIter != m_oLayerOCGListPoppler.end() && !bFoundChildLayer;
    3887          10 :                      ++oIter)
    3888             :                 {
    3889          10 :                     if (oIter->first.size() > nLen &&
    3890           5 :                         strncmp(oIter->first.c_str(), papszLayers[i], nLen) ==
    3891          15 :                             0 &&
    3892           2 :                         oIter->first[nLen] == '.')
    3893             :                     {
    3894           4 :                         for (int j = 0; papszLayers[j] != nullptr; j++)
    3895             :                         {
    3896           2 :                             if (strcmp(papszLayers[j], oIter->first.c_str()) ==
    3897             :                                 0)
    3898             :                             {
    3899           0 :                                 bFoundChildLayer = TRUE;
    3900           0 :                                 break;
    3901             :                             }
    3902             :                         }
    3903             :                     }
    3904             :                 }
    3905             : 
    3906           2 :                 if (!bFoundChildLayer)
    3907             :                 {
    3908           2 :                     oIter = m_oLayerOCGListPoppler.begin();
    3909          12 :                     for (; oIter != m_oLayerOCGListPoppler.end() &&
    3910             :                            !bFoundChildLayer;
    3911          10 :                          ++oIter)
    3912             :                     {
    3913          10 :                         if (oIter->first.size() > nLen &&
    3914           5 :                             strncmp(oIter->first.c_str(), papszLayers[i],
    3915          15 :                                     nLen) == 0 &&
    3916           2 :                             oIter->first[nLen] == '.')
    3917             :                         {
    3918           2 :                             if (oIter->second)
    3919             :                             {
    3920             :                                 // CPLDebug("PDF", "Turn '%s' on too",
    3921             :                                 // oIter->first.c_str());
    3922           2 :                                 oIter->second->setState(
    3923             :                                     OptionalContentGroup::On);
    3924             :                             }
    3925             :                         }
    3926             :                     }
    3927             :                 }
    3928             : 
    3929             :                 // Turn parent layers on too
    3930           6 :                 std::string layer(papszLayers[i]);
    3931             :                 std::string::size_type j;
    3932           3 :                 while ((j = layer.find_last_of('.')) != std::string::npos)
    3933             :                 {
    3934           1 :                     layer.resize(j);
    3935           1 :                     oIter = m_oLayerOCGListPoppler.begin();
    3936           6 :                     for (; oIter != m_oLayerOCGListPoppler.end(); ++oIter)
    3937             :                     {
    3938           5 :                         if (oIter->first == layer && oIter->second)
    3939             :                         {
    3940             :                             // CPLDebug("PDF", "Turn '%s' on too",
    3941             :                             // layer.c_str());
    3942           1 :                             oIter->second->setState(OptionalContentGroup::On);
    3943             :                         }
    3944             :                     }
    3945             :                 }
    3946             :             }
    3947           2 :             if (!isFound)
    3948             :             {
    3949           0 :                 CPLError(CE_Warning, CPLE_AppDefined, "Unknown layer '%s'",
    3950           0 :                          papszLayers[i]);
    3951             :             }
    3952             :         }
    3953           2 :         CSLDestroy(papszLayers);
    3954             : 
    3955           2 :         m_bUseOCG = true;
    3956             :     }
    3957             : 
    3958             :     // Which layers to turn OFF ?
    3959             :     const char *pszLayersOFF =
    3960          63 :         GetOption(papszOpenOptions, "LAYERS_OFF", nullptr);
    3961          63 :     if (pszLayersOFF)
    3962             :     {
    3963           5 :         char **papszLayersOFF = CSLTokenizeString2(pszLayersOFF, ",", 0);
    3964          10 :         for (int i = 0; papszLayersOFF[i] != nullptr; i++)
    3965             :         {
    3966           5 :             bool isFound = false;
    3967          22 :             for (auto oIter2 = m_oLayerOCGListPoppler.begin();
    3968          39 :                  oIter2 != m_oLayerOCGListPoppler.end(); ++oIter2)
    3969             :             {
    3970          17 :                 if (oIter2->first != papszLayersOFF[i])
    3971          12 :                     continue;
    3972             : 
    3973           5 :                 isFound = true;
    3974           5 :                 auto oIter = oIter2;
    3975           5 :                 if (oIter->second)
    3976             :                 {
    3977             :                     // CPLDebug("PDF", "Turn '%s' off", papszLayersOFF[i]);
    3978           5 :                     oIter->second->setState(OptionalContentGroup::Off);
    3979             :                 }
    3980             : 
    3981             :                 // Turn child layers off too
    3982           5 :                 size_t nLen = strlen(papszLayersOFF[i]);
    3983           5 :                 oIter = m_oLayerOCGListPoppler.begin();
    3984          22 :                 for (; oIter != m_oLayerOCGListPoppler.end(); ++oIter)
    3985             :                 {
    3986          17 :                     if (oIter->first.size() > nLen &&
    3987           3 :                         strncmp(oIter->first.c_str(), papszLayersOFF[i],
    3988          20 :                                 nLen) == 0 &&
    3989           1 :                         oIter->first[nLen] == '.')
    3990             :                     {
    3991           1 :                         if (oIter->second)
    3992             :                         {
    3993             :                             // CPLDebug("PDF", "Turn '%s' off too",
    3994             :                             // oIter->first.c_str());
    3995           1 :                             oIter->second->setState(OptionalContentGroup::Off);
    3996             :                         }
    3997             :                     }
    3998             :                 }
    3999             :             }
    4000           5 :             if (!isFound)
    4001             :             {
    4002           0 :                 CPLError(CE_Warning, CPLE_AppDefined, "Unknown layer '%s'",
    4003           0 :                          papszLayersOFF[i]);
    4004             :             }
    4005             :         }
    4006           5 :         CSLDestroy(papszLayersOFF);
    4007             : 
    4008           5 :         m_bUseOCG = true;
    4009             :     }
    4010             : }
    4011             : 
    4012             : #endif
    4013             : 
    4014             : #ifdef HAVE_PDFIUM
    4015             : 
    4016             : /************************************************************************/
    4017             : /*                       ExploreLayersPdfium()                          */
    4018             : /************************************************************************/
    4019             : 
    4020             : void PDFDataset::ExploreLayersPdfium(GDALPDFArray *poArray, int iPageOfInterest,
    4021             :                                      int nPageCount, int nRecLevel,
    4022             :                                      CPLString osTopLayer)
    4023             : {
    4024             :     if (nRecLevel == 16)
    4025             :         return;
    4026             : 
    4027             :     const int nLength = poArray->GetLength();
    4028             :     std::string osCurLayer;
    4029             :     for (int i = 0; i < nLength; i++)
    4030             :     {
    4031             :         GDALPDFObject *poObj = poArray->Get(i);
    4032             :         if (poObj == nullptr)
    4033             :             continue;
    4034             :         if (i == 0 && poObj->GetType() == PDFObjectType_String)
    4035             :         {
    4036             :             const std::string osName =
    4037             :                 PDFSanitizeLayerName(poObj->GetString().c_str());
    4038             :             if (!osTopLayer.empty())
    4039             :                 osTopLayer = std::string(osTopLayer).append(".").append(osName);
    4040             :             else
    4041             :                 osTopLayer = osName;
    4042             :             AddLayer(osTopLayer, 0);
    4043             :             m_oMapLayerNameToOCGNumGenPdfium[osTopLayer] = std::pair(-1, -1);
    4044             :         }
    4045             :         else if (poObj->GetType() == PDFObjectType_Array)
    4046             :         {
    4047             :             ExploreLayersPdfium(poObj->GetArray(), iPageOfInterest, nPageCount,
    4048             :                                 nRecLevel + 1, osCurLayer);
    4049             :             osCurLayer.clear();
    4050             :         }
    4051             :         else if (poObj->GetType() == PDFObjectType_Dictionary)
    4052             :         {
    4053             :             GDALPDFDictionary *poDict = poObj->GetDictionary();
    4054             :             GDALPDFObject *poName = poDict->Get("Name");
    4055             :             if (poName != nullptr && poName->GetType() == PDFObjectType_String)
    4056             :             {
    4057             :                 std::string osName =
    4058             :                     PDFSanitizeLayerName(poName->GetString().c_str());
    4059             :                 // coverity[copy_paste_error]
    4060             :                 if (!osTopLayer.empty())
    4061             :                 {
    4062             :                     osCurLayer =
    4063             :                         std::string(osTopLayer).append(".").append(osName);
    4064             :                 }
    4065             :                 else
    4066             :                     osCurLayer = std::move(osName);
    4067             :                 // CPLDebug("PDF", "Layer %s", osCurLayer.c_str());
    4068             : 
    4069             :                 const auto oRefPair =
    4070             :                     std::pair(poObj->GetRefNum().toInt(), poObj->GetRefGen());
    4071             :                 const std::string osPostfixedName =
    4072             :                     BuildPostfixedLayerNameAndAddLayer(
    4073             :                         osCurLayer, oRefPair, iPageOfInterest, nPageCount);
    4074             :                 if (osPostfixedName.empty())
    4075             :                     continue;
    4076             : 
    4077             :                 m_aoLayerWithRef.emplace_back(
    4078             :                     osPostfixedName, poObj->GetRefNum(), poObj->GetRefGen());
    4079             :                 m_oMapLayerNameToOCGNumGenPdfium[osPostfixedName] = oRefPair;
    4080             :             }
    4081             :         }
    4082             :     }
    4083             : }
    4084             : 
    4085             : /************************************************************************/
    4086             : /*                         FindLayersPdfium()                          */
    4087             : /************************************************************************/
    4088             : 
    4089             : void PDFDataset::FindLayersPdfium(int iPageOfInterest)
    4090             : {
    4091             :     int nPageCount = 0;
    4092             :     const auto poPages = GetPagesKids();
    4093             :     if (poPages)
    4094             :         nPageCount = poPages->GetLength();
    4095             : 
    4096             :     GDALPDFObject *poCatalog = GetCatalog();
    4097             :     if (poCatalog == nullptr ||
    4098             :         poCatalog->GetType() != PDFObjectType_Dictionary)
    4099             :         return;
    4100             :     GDALPDFObject *poOrder = poCatalog->LookupObject("OCProperties.D.Order");
    4101             :     if (poOrder != nullptr && poOrder->GetType() == PDFObjectType_Array)
    4102             :     {
    4103             :         ExploreLayersPdfium(poOrder->GetArray(), iPageOfInterest, nPageCount,
    4104             :                             0);
    4105             :     }
    4106             : #if 0
    4107             :     else
    4108             :     {
    4109             :         GDALPDFObject* poOCGs = poD->GetDictionary()->Get("OCGs");
    4110             :         if( poOCGs != nullptr && poOCGs->GetType() == PDFObjectType_Array )
    4111             :         {
    4112             :             GDALPDFArray* poArray = poOCGs->GetArray();
    4113             :             int nLength = poArray->GetLength();
    4114             :             for(int i=0;i<nLength;i++)
    4115             :             {
    4116             :                 GDALPDFObject* poObj = poArray->Get(i);
    4117             :                 if( poObj != nullptr )
    4118             :                 {
    4119             :                     // TODO ?
    4120             :                 }
    4121             :             }
    4122             :         }
    4123             :     }
    4124             : #endif
    4125             : 
    4126             :     CreateLayerList();
    4127             :     m_oMDMD_PDF.SetMetadata(m_aosLayerNames.List(), "LAYERS");
    4128             : }
    4129             : 
    4130             : /************************************************************************/
    4131             : /*                       TurnLayersOnOffPdfium()                       */
    4132             : /************************************************************************/
    4133             : 
    4134             : void PDFDataset::TurnLayersOnOffPdfium()
    4135             : {
    4136             :     GDALPDFObject *poCatalog = GetCatalog();
    4137             :     if (poCatalog == nullptr ||
    4138             :         poCatalog->GetType() != PDFObjectType_Dictionary)
    4139             :         return;
    4140             :     GDALPDFObject *poOCGs = poCatalog->LookupObject("OCProperties.OCGs");
    4141             :     if (poOCGs == nullptr || poOCGs->GetType() != PDFObjectType_Array)
    4142             :         return;
    4143             : 
    4144             :     // Which layers to turn ON ?
    4145             :     const char *pszLayers = GetOption(papszOpenOptions, "LAYERS", nullptr);
    4146             :     if (pszLayers)
    4147             :     {
    4148             :         int i;
    4149             :         int bAll = EQUAL(pszLayers, "ALL");
    4150             : 
    4151             :         GDALPDFArray *poOCGsArray = poOCGs->GetArray();
    4152             :         int nLength = poOCGsArray->GetLength();
    4153             :         for (i = 0; i < nLength; i++)
    4154             :         {
    4155             :             GDALPDFObject *poOCG = poOCGsArray->Get(i);
    4156             :             m_oMapOCGNumGenToVisibilityStatePdfium[std::pair(
    4157             :                 poOCG->GetRefNum().toInt(), poOCG->GetRefGen())] =
    4158             :                 (bAll) ? VISIBILITY_ON : VISIBILITY_OFF;
    4159             :         }
    4160             : 
    4161             :         char **papszLayers = CSLTokenizeString2(pszLayers, ",", 0);
    4162             :         for (i = 0; !bAll && papszLayers[i] != nullptr; i++)
    4163             :         {
    4164             :             auto oIter = m_oMapLayerNameToOCGNumGenPdfium.find(papszLayers[i]);
    4165             :             if (oIter != m_oMapLayerNameToOCGNumGenPdfium.end())
    4166             :             {
    4167             :                 if (oIter->second.first >= 0)
    4168             :                 {
    4169             :                     // CPLDebug("PDF", "Turn '%s' on", papszLayers[i]);
    4170             :                     m_oMapOCGNumGenToVisibilityStatePdfium[oIter->second] =
    4171             :                         VISIBILITY_ON;
    4172             :                 }
    4173             : 
    4174             :                 // Turn child layers on, unless there's one of them explicitly
    4175             :                 // listed in the list.
    4176             :                 size_t nLen = strlen(papszLayers[i]);
    4177             :                 int bFoundChildLayer = FALSE;
    4178             :                 oIter = m_oMapLayerNameToOCGNumGenPdfium.begin();
    4179             :                 for (; oIter != m_oMapLayerNameToOCGNumGenPdfium.end() &&
    4180             :                        !bFoundChildLayer;
    4181             :                      oIter++)
    4182             :                 {
    4183             :                     if (oIter->first.size() > nLen &&
    4184             :                         strncmp(oIter->first.c_str(), papszLayers[i], nLen) ==
    4185             :                             0 &&
    4186             :                         oIter->first[nLen] == '.')
    4187             :                     {
    4188             :                         for (int j = 0; papszLayers[j] != nullptr; j++)
    4189             :                         {
    4190             :                             if (strcmp(papszLayers[j], oIter->first.c_str()) ==
    4191             :                                 0)
    4192             :                                 bFoundChildLayer = TRUE;
    4193             :                         }
    4194             :                     }
    4195             :                 }
    4196             : 
    4197             :                 if (!bFoundChildLayer)
    4198             :                 {
    4199             :                     oIter = m_oMapLayerNameToOCGNumGenPdfium.begin();
    4200             :                     for (; oIter != m_oMapLayerNameToOCGNumGenPdfium.end() &&
    4201             :                            !bFoundChildLayer;
    4202             :                          oIter++)
    4203             :                     {
    4204             :                         if (oIter->first.size() > nLen &&
    4205             :                             strncmp(oIter->first.c_str(), papszLayers[i],
    4206             :                                     nLen) == 0 &&
    4207             :                             oIter->first[nLen] == '.')
    4208             :                         {
    4209             :                             if (oIter->second.first >= 0)
    4210             :                             {
    4211             :                                 // CPLDebug("PDF", "Turn '%s' on too",
    4212             :                                 // oIter->first.c_str());
    4213             :                                 m_oMapOCGNumGenToVisibilityStatePdfium
    4214             :                                     [oIter->second] = VISIBILITY_ON;
    4215             :                             }
    4216             :                         }
    4217             :                     }
    4218             :                 }
    4219             : 
    4220             :                 // Turn parent layers on too
    4221             :                 char *pszLastDot = nullptr;
    4222             :                 while ((pszLastDot = strrchr(papszLayers[i], '.')) != nullptr)
    4223             :                 {
    4224             :                     *pszLastDot = '\0';
    4225             :                     oIter =
    4226             :                         m_oMapLayerNameToOCGNumGenPdfium.find(papszLayers[i]);
    4227             :                     if (oIter != m_oMapLayerNameToOCGNumGenPdfium.end())
    4228             :                     {
    4229             :                         if (oIter->second.first >= 0)
    4230             :                         {
    4231             :                             // CPLDebug("PDF", "Turn '%s' on too",
    4232             :                             // papszLayers[i]);
    4233             :                             m_oMapOCGNumGenToVisibilityStatePdfium
    4234             :                                 [oIter->second] = VISIBILITY_ON;
    4235             :                         }
    4236             :                     }
    4237             :                 }
    4238             :             }
    4239             :             else
    4240             :             {
    4241             :                 CPLError(CE_Warning, CPLE_AppDefined, "Unknown layer '%s'",
    4242             :                          papszLayers[i]);
    4243             :             }
    4244             :         }
    4245             :         CSLDestroy(papszLayers);
    4246             : 
    4247             :         m_bUseOCG = true;
    4248             :     }
    4249             : 
    4250             :     // Which layers to turn OFF ?
    4251             :     const char *pszLayersOFF =
    4252             :         GetOption(papszOpenOptions, "LAYERS_OFF", nullptr);
    4253             :     if (pszLayersOFF)
    4254             :     {
    4255             :         char **papszLayersOFF = CSLTokenizeString2(pszLayersOFF, ",", 0);
    4256             :         for (int i = 0; papszLayersOFF[i] != nullptr; i++)
    4257             :         {
    4258             :             auto oIter =
    4259             :                 m_oMapLayerNameToOCGNumGenPdfium.find(papszLayersOFF[i]);
    4260             :             if (oIter != m_oMapLayerNameToOCGNumGenPdfium.end())
    4261             :             {
    4262             :                 if (oIter->second.first >= 0)
    4263             :                 {
    4264             :                     // CPLDebug("PDF", "Turn '%s' (%d,%d) off",
    4265             :                     // papszLayersOFF[i], oIter->second.first,
    4266             :                     // oIter->second.second);
    4267             :                     m_oMapOCGNumGenToVisibilityStatePdfium[oIter->second] =
    4268             :                         VISIBILITY_OFF;
    4269             :                 }
    4270             : 
    4271             :                 // Turn child layers off too
    4272             :                 size_t nLen = strlen(papszLayersOFF[i]);
    4273             :                 oIter = m_oMapLayerNameToOCGNumGenPdfium.begin();
    4274             :                 for (; oIter != m_oMapLayerNameToOCGNumGenPdfium.end(); oIter++)
    4275             :                 {
    4276             :                     if (oIter->first.size() > nLen &&
    4277             :                         strncmp(oIter->first.c_str(), papszLayersOFF[i],
    4278             :                                 nLen) == 0 &&
    4279             :                         oIter->first[nLen] == '.')
    4280             :                     {
    4281             :                         if (oIter->second.first >= 0)
    4282             :                         {
    4283             :                             // CPLDebug("PDF", "Turn '%s' off too",
    4284             :                             // oIter->first.c_str());
    4285             :                             m_oMapOCGNumGenToVisibilityStatePdfium
    4286             :                                 [oIter->second] = VISIBILITY_OFF;
    4287             :                         }
    4288             :                     }
    4289             :                 }
    4290             :             }
    4291             :             else
    4292             :             {
    4293             :                 CPLError(CE_Warning, CPLE_AppDefined, "Unknown layer '%s'",
    4294             :                          papszLayersOFF[i]);
    4295             :             }
    4296             :         }
    4297             :         CSLDestroy(papszLayersOFF);
    4298             : 
    4299             :         m_bUseOCG = true;
    4300             :     }
    4301             : }
    4302             : 
    4303             : /************************************************************************/
    4304             : /*                    GetVisibilityStateForOGCPdfium()                  */
    4305             : /************************************************************************/
    4306             : 
    4307             : PDFDataset::VisibilityState PDFDataset::GetVisibilityStateForOGCPdfium(int nNum,
    4308             :                                                                        int nGen)
    4309             : {
    4310             :     auto oIter =
    4311             :         m_oMapOCGNumGenToVisibilityStatePdfium.find(std::pair(nNum, nGen));
    4312             :     if (oIter == m_oMapOCGNumGenToVisibilityStatePdfium.end())
    4313             :         return VISIBILITY_DEFAULT;
    4314             :     return oIter->second;
    4315             : }
    4316             : 
    4317             : #endif /* HAVE_PDFIUM */
    4318             : 
    4319             : /************************************************************************/
    4320             : /*                            GetPagesKids()                            */
    4321             : /************************************************************************/
    4322             : 
    4323         428 : GDALPDFArray *PDFDataset::GetPagesKids()
    4324             : {
    4325         428 :     const auto poCatalog = GetCatalog();
    4326         428 :     if (!poCatalog || poCatalog->GetType() != PDFObjectType_Dictionary)
    4327             :     {
    4328           0 :         return nullptr;
    4329             :     }
    4330         428 :     const auto poKids = poCatalog->LookupObject("Pages.Kids");
    4331         428 :     if (!poKids || poKids->GetType() != PDFObjectType_Array)
    4332             :     {
    4333           0 :         return nullptr;
    4334             :     }
    4335         428 :     return poKids->GetArray();
    4336             : }
    4337             : 
    4338             : /************************************************************************/
    4339             : /*                           MapOCGsToPages()                           */
    4340             : /************************************************************************/
    4341             : 
    4342         214 : void PDFDataset::MapOCGsToPages()
    4343             : {
    4344         214 :     const auto poKidsArray = GetPagesKids();
    4345         214 :     if (!poKidsArray)
    4346             :     {
    4347           0 :         return;
    4348             :     }
    4349         214 :     const int nKidsArrayLength = poKidsArray->GetLength();
    4350         460 :     for (int iPage = 0; iPage < nKidsArrayLength; ++iPage)
    4351             :     {
    4352         246 :         const auto poPage = poKidsArray->Get(iPage);
    4353         246 :         if (poPage && poPage->GetType() == PDFObjectType_Dictionary)
    4354             :         {
    4355         246 :             const auto poXObject = poPage->LookupObject("Resources.XObject");
    4356         246 :             if (poXObject && poXObject->GetType() == PDFObjectType_Dictionary)
    4357             :             {
    4358         530 :                 for (const auto &oNameObjectPair :
    4359        1293 :                      poXObject->GetDictionary()->GetValues())
    4360             :                 {
    4361             :                     const auto poProperties =
    4362         530 :                         oNameObjectPair.second->LookupObject(
    4363             :                             "Resources.Properties");
    4364         567 :                     if (poProperties &&
    4365          37 :                         poProperties->GetType() == PDFObjectType_Dictionary)
    4366             :                     {
    4367             :                         const auto &oMap =
    4368          37 :                             poProperties->GetDictionary()->GetValues();
    4369         282 :                         for (const auto &[osKey, poObj] : oMap)
    4370             :                         {
    4371         490 :                             if (poObj->GetRefNum().toBool() &&
    4372         245 :                                 poObj->GetType() == PDFObjectType_Dictionary)
    4373             :                             {
    4374             :                                 GDALPDFObject *poType =
    4375         245 :                                     poObj->GetDictionary()->Get("Type");
    4376             :                                 GDALPDFObject *poName =
    4377         245 :                                     poObj->GetDictionary()->Get("Name");
    4378         490 :                                 if (poType &&
    4379         490 :                                     poType->GetType() == PDFObjectType_Name &&
    4380         980 :                                     poType->GetName() == "OCG" && poName &&
    4381         245 :                                     poName->GetType() == PDFObjectType_String)
    4382             :                                 {
    4383             :                                     m_oMapOCGNumGenToPages
    4384         245 :                                         [std::pair(poObj->GetRefNum().toInt(),
    4385         490 :                                                    poObj->GetRefGen())]
    4386         245 :                                             .push_back(iPage + 1);
    4387             :                                 }
    4388             :                             }
    4389             :                         }
    4390             :                     }
    4391             :                 }
    4392             :             }
    4393             :         }
    4394             :     }
    4395             : }
    4396             : 
    4397             : /************************************************************************/
    4398             : /*                           FindLayerOCG()                             */
    4399             : /************************************************************************/
    4400             : 
    4401         197 : CPLString PDFDataset::FindLayerOCG(GDALPDFDictionary *poPageDict,
    4402             :                                    const char *pszLayerName)
    4403             : {
    4404             :     GDALPDFObject *poProperties =
    4405         197 :         poPageDict->LookupObject("Resources.Properties");
    4406         240 :     if (poProperties != nullptr &&
    4407          43 :         poProperties->GetType() == PDFObjectType_Dictionary)
    4408             :     {
    4409          43 :         const auto &oMap = poProperties->GetDictionary()->GetValues();
    4410         122 :         for (const auto &[osKey, poObj] : oMap)
    4411             :         {
    4412         157 :             if (poObj->GetRefNum().toBool() &&
    4413          78 :                 poObj->GetType() == PDFObjectType_Dictionary)
    4414             :             {
    4415          78 :                 GDALPDFObject *poType = poObj->GetDictionary()->Get("Type");
    4416          78 :                 GDALPDFObject *poName = poObj->GetDictionary()->Get("Name");
    4417         156 :                 if (poType != nullptr &&
    4418         156 :                     poType->GetType() == PDFObjectType_Name &&
    4419         312 :                     poType->GetName() == "OCG" && poName != nullptr &&
    4420          78 :                     poName->GetType() == PDFObjectType_String)
    4421             :                 {
    4422          78 :                     if (poName->GetString() == pszLayerName)
    4423           0 :                         return osKey;
    4424             :                 }
    4425             :             }
    4426             :         }
    4427             :     }
    4428         197 :     return "";
    4429             : }
    4430             : 
    4431             : /************************************************************************/
    4432             : /*                         FindLayersGeneric()                          */
    4433             : /************************************************************************/
    4434             : 
    4435           0 : void PDFDataset::FindLayersGeneric(GDALPDFDictionary *poPageDict)
    4436             : {
    4437             :     GDALPDFObject *poProperties =
    4438           0 :         poPageDict->LookupObject("Resources.Properties");
    4439           0 :     if (poProperties != nullptr &&
    4440           0 :         poProperties->GetType() == PDFObjectType_Dictionary)
    4441             :     {
    4442           0 :         const auto &oMap = poProperties->GetDictionary()->GetValues();
    4443           0 :         for (const auto &[osKey, poObj] : oMap)
    4444             :         {
    4445           0 :             if (poObj->GetRefNum().toBool() &&
    4446           0 :                 poObj->GetType() == PDFObjectType_Dictionary)
    4447             :             {
    4448           0 :                 GDALPDFObject *poType = poObj->GetDictionary()->Get("Type");
    4449           0 :                 GDALPDFObject *poName = poObj->GetDictionary()->Get("Name");
    4450           0 :                 if (poType != nullptr &&
    4451           0 :                     poType->GetType() == PDFObjectType_Name &&
    4452           0 :                     poType->GetName() == "OCG" && poName != nullptr &&
    4453           0 :                     poName->GetType() == PDFObjectType_String)
    4454             :                 {
    4455             :                     m_aoLayerWithRef.emplace_back(
    4456           0 :                         PDFSanitizeLayerName(poName->GetString().c_str())
    4457           0 :                             .c_str(),
    4458           0 :                         poObj->GetRefNum(), poObj->GetRefGen());
    4459             :                 }
    4460             :             }
    4461             :         }
    4462             :     }
    4463           0 : }
    4464             : 
    4465             : /************************************************************************/
    4466             : /*                                Open()                                */
    4467             : /************************************************************************/
    4468             : 
    4469         229 : PDFDataset *PDFDataset::Open(GDALOpenInfo *poOpenInfo)
    4470             : 
    4471             : {
    4472         229 :     if (!PDFDatasetIdentify(poOpenInfo))
    4473           1 :         return nullptr;
    4474             : 
    4475             :     const char *pszUserPwd =
    4476         228 :         GetOption(poOpenInfo->papszOpenOptions, "USER_PWD", nullptr);
    4477             : 
    4478         228 :     const bool bOpenSubdataset = STARTS_WITH(poOpenInfo->pszFilename, "PDF:");
    4479         228 :     const bool bOpenSubdatasetImage =
    4480         228 :         STARTS_WITH(poOpenInfo->pszFilename, "PDF_IMAGE:");
    4481         228 :     int iPage = -1;
    4482         228 :     int nImageNum = -1;
    4483         456 :     std::string osSubdatasetName;
    4484         228 :     const char *pszFilename = poOpenInfo->pszFilename;
    4485             : 
    4486         228 :     if (bOpenSubdataset)
    4487             :     {
    4488          15 :         iPage = atoi(pszFilename + 4);
    4489          15 :         if (iPage <= 0)
    4490           1 :             return nullptr;
    4491          14 :         pszFilename = strchr(pszFilename + 4, ':');
    4492          14 :         if (pszFilename == nullptr)
    4493           0 :             return nullptr;
    4494          14 :         pszFilename++;
    4495          14 :         osSubdatasetName = CPLSPrintf("Page %d", iPage);
    4496             :     }
    4497         213 :     else if (bOpenSubdatasetImage)
    4498             :     {
    4499           0 :         iPage = atoi(pszFilename + 10);
    4500           0 :         if (iPage <= 0)
    4501           0 :             return nullptr;
    4502           0 :         const char *pszNext = strchr(pszFilename + 10, ':');
    4503           0 :         if (pszNext == nullptr)
    4504           0 :             return nullptr;
    4505           0 :         nImageNum = atoi(pszNext + 1);
    4506           0 :         if (nImageNum <= 0)
    4507           0 :             return nullptr;
    4508           0 :         pszFilename = strchr(pszNext + 1, ':');
    4509           0 :         if (pszFilename == nullptr)
    4510           0 :             return nullptr;
    4511           0 :         pszFilename++;
    4512           0 :         osSubdatasetName = CPLSPrintf("Image %d", nImageNum);
    4513             :     }
    4514             :     else
    4515         213 :         iPage = 1;
    4516             : 
    4517         227 :     std::bitset<PDFLIB_COUNT> bHasLib;
    4518         227 :     bHasLib.reset();
    4519             :     // Each library set their flag
    4520             : #if defined(HAVE_POPPLER)
    4521         227 :     bHasLib.set(PDFLIB_POPPLER);
    4522             : #endif  // HAVE_POPPLER
    4523             : #if defined(HAVE_PODOFO)
    4524             :     bHasLib.set(PDFLIB_PODOFO);
    4525             : #endif  // HAVE_PODOFO
    4526             : #if defined(HAVE_PDFIUM)
    4527             :     bHasLib.set(PDFLIB_PDFIUM);
    4528             : #endif  // HAVE_PDFIUM
    4529             : 
    4530         227 :     std::bitset<PDFLIB_COUNT> bUseLib;
    4531             : 
    4532             :     // More than one library available
    4533             :     // Detect which one
    4534         227 :     if (bHasLib.count() != 1)
    4535             :     {
    4536           0 :         const char *pszDefaultLib = bHasLib.test(PDFLIB_PDFIUM)    ? "PDFIUM"
    4537           0 :                                     : bHasLib.test(PDFLIB_POPPLER) ? "POPPLER"
    4538           0 :                                                                    : "PODOFO";
    4539             :         const char *pszPDFLib =
    4540           0 :             GetOption(poOpenInfo->papszOpenOptions, "PDF_LIB", pszDefaultLib);
    4541             :         while (true)
    4542             :         {
    4543           0 :             if (EQUAL(pszPDFLib, "POPPLER"))
    4544           0 :                 bUseLib.set(PDFLIB_POPPLER);
    4545           0 :             else if (EQUAL(pszPDFLib, "PODOFO"))
    4546           0 :                 bUseLib.set(PDFLIB_PODOFO);
    4547           0 :             else if (EQUAL(pszPDFLib, "PDFIUM"))
    4548           0 :                 bUseLib.set(PDFLIB_PDFIUM);
    4549             : 
    4550           0 :             if (bUseLib.count() != 1 || (bHasLib & bUseLib) == 0)
    4551             :             {
    4552           0 :                 CPLDebug("PDF",
    4553             :                          "Invalid value for GDAL_PDF_LIB config option: %s. "
    4554             :                          "Fallback to %s",
    4555             :                          pszPDFLib, pszDefaultLib);
    4556           0 :                 pszPDFLib = pszDefaultLib;
    4557           0 :                 bUseLib.reset();
    4558             :             }
    4559             :             else
    4560           0 :                 break;
    4561             :         }
    4562             :     }
    4563             :     else
    4564         227 :         bUseLib = bHasLib;
    4565             : 
    4566         227 :     GDALPDFObject *poPageObj = nullptr;
    4567             : #ifdef HAVE_POPPLER
    4568         227 :     PDFDoc *poDocPoppler = nullptr;
    4569         227 :     Page *poPagePoppler = nullptr;
    4570         227 :     Catalog *poCatalogPoppler = nullptr;
    4571             : #endif
    4572             : #ifdef HAVE_PODOFO
    4573             :     std::unique_ptr<PoDoFo::PdfMemDocument> poDocPodofo;
    4574             :     PoDoFo::PdfPage *poPagePodofo = nullptr;
    4575             : #endif
    4576             : #ifdef HAVE_PDFIUM
    4577             :     TPdfiumDocumentStruct *poDocPdfium = nullptr;
    4578             :     TPdfiumPageStruct *poPagePdfium = nullptr;
    4579             : #endif
    4580         227 :     int nPages = 0;
    4581         227 :     VSIVirtualHandleUniquePtr fp;
    4582             : 
    4583             : #ifdef HAVE_POPPLER
    4584         227 :     if (bUseLib.test(PDFLIB_POPPLER))
    4585             :     {
    4586             :         static bool globalParamsCreatedByGDAL = false;
    4587             :         {
    4588         454 :             CPLMutexHolderD(&hGlobalParamsMutex);
    4589             :             /* poppler global variable */
    4590         227 :             if (globalParams == nullptr)
    4591             :             {
    4592           6 :                 globalParamsCreatedByGDAL = true;
    4593           6 :                 globalParams.reset(new GlobalParams());
    4594             :             }
    4595             : 
    4596         227 :             globalParams->setPrintCommands(CPLTestBool(
    4597             :                 CPLGetConfigOption("GDAL_PDF_PRINT_COMMANDS", "FALSE")));
    4598             :         }
    4599             : 
    4600         456 :         const auto registerErrorCallback = []()
    4601             :         {
    4602             :             /* Set custom error handler for poppler errors */
    4603         456 :             setErrorCallback(PDFDatasetErrorFunction);
    4604         456 :             assert(globalParams);  // avoid CSA false positive
    4605         456 :             globalParams->setErrQuiet(false);
    4606         456 :         };
    4607             : 
    4608         227 :         fp.reset(VSIFOpenL(pszFilename, "rb"));
    4609         227 :         if (!fp)
    4610          13 :             return nullptr;
    4611             : 
    4612             : #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    4613             :         {
    4614             :             // Workaround for ossfuzz only due to
    4615             :             // https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=37584
    4616             :             // https://gitlab.freedesktop.org/poppler/poppler/-/issues/1137
    4617             :             GByte *pabyRet = nullptr;
    4618             :             vsi_l_offset nSize = 0;
    4619             :             if (VSIIngestFile(fp.get(), pszFilename, &pabyRet, &nSize,
    4620             :                               10 * 1024 * 1024))
    4621             :             {
    4622             :                 // Replace nul byte by something else so that strstr() works
    4623             :                 for (size_t i = 0; i < nSize; i++)
    4624             :                 {
    4625             :                     if (pabyRet[i] == 0)
    4626             :                         pabyRet[i] = ' ';
    4627             :                 }
    4628             :                 if (strstr(reinterpret_cast<const char *>(pabyRet),
    4629             :                            "/JBIG2Decode"))
    4630             :                 {
    4631             :                     CPLError(CE_Failure, CPLE_AppDefined,
    4632             :                              "/JBIG2Decode found. Giving up due to potential "
    4633             :                              "very long processing time.");
    4634             :                     CPLFree(pabyRet);
    4635             :                     return nullptr;
    4636             :                 }
    4637             :             }
    4638             :             CPLFree(pabyRet);
    4639             :         }
    4640             : #endif
    4641             : 
    4642         226 :         fp.reset(VSICreateBufferedReaderHandle(fp.release()));
    4643             :         while (true)
    4644             :         {
    4645         228 :             fp->Seek(0, SEEK_SET);
    4646         228 :             g_nPopplerErrors = 0;
    4647         228 :             if (globalParamsCreatedByGDAL)
    4648         228 :                 registerErrorCallback();
    4649         228 :             Object oObj;
    4650             :             auto poStream =
    4651         228 :                 new VSIPDFFileStream(fp.get(), pszFilename, std::move(oObj));
    4652             : #if POPPLER_MAJOR_VERSION > 22 ||                                              \
    4653             :     (POPPLER_MAJOR_VERSION == 22 && POPPLER_MINOR_VERSION > 2)
    4654             :             std::optional<GooString> osUserPwd;
    4655             :             if (pszUserPwd)
    4656             :                 osUserPwd = std::optional<GooString>(pszUserPwd);
    4657             :             try
    4658             :             {
    4659             :                 poDocPoppler =
    4660             :                     new PDFDoc(poStream, std::optional<GooString>(), osUserPwd);
    4661             :             }
    4662             :             catch (const std::exception &e)
    4663             :             {
    4664             :                 CPLError(CE_Failure, CPLE_AppDefined,
    4665             :                          "PDFDoc::PDFDoc() failed with %s", e.what());
    4666             :                 return nullptr;
    4667             :             }
    4668             : #else
    4669         228 :             GooString *poUserPwd = nullptr;
    4670         228 :             if (pszUserPwd)
    4671           6 :                 poUserPwd = new GooString(pszUserPwd);
    4672         228 :             poDocPoppler = new PDFDoc(poStream, nullptr, poUserPwd);
    4673         228 :             delete poUserPwd;
    4674             : #endif
    4675         228 :             if (globalParamsCreatedByGDAL)
    4676         228 :                 registerErrorCallback();
    4677         228 :             if (g_nPopplerErrors >= MAX_POPPLER_ERRORS)
    4678             :             {
    4679           0 :                 PDFFreeDoc(poDocPoppler);
    4680           0 :                 return nullptr;
    4681             :             }
    4682             : 
    4683         228 :             if (!poDocPoppler->isOk() || poDocPoppler->getNumPages() == 0)
    4684             :             {
    4685          13 :                 if (poDocPoppler->getErrorCode() == errEncrypted)
    4686             :                 {
    4687           5 :                     if (pszUserPwd && EQUAL(pszUserPwd, "ASK_INTERACTIVE"))
    4688             :                     {
    4689             :                         pszUserPwd =
    4690           2 :                             PDFEnterPasswordFromConsoleIfNeeded(pszUserPwd);
    4691           2 :                         PDFFreeDoc(poDocPoppler);
    4692             : 
    4693             :                         /* Reset errors that could have been issued during
    4694             :                          * opening and that */
    4695             :                         /* did not result in an invalid document */
    4696           2 :                         CPLErrorReset();
    4697             : 
    4698           2 :                         continue;
    4699             :                     }
    4700           3 :                     else if (pszUserPwd == nullptr)
    4701             :                     {
    4702           1 :                         CPLError(CE_Failure, CPLE_AppDefined,
    4703             :                                  "A password is needed. You can specify it "
    4704             :                                  "through the PDF_USER_PWD "
    4705             :                                  "configuration option / USER_PWD open option "
    4706             :                                  "(that can be set to ASK_INTERACTIVE)");
    4707             :                     }
    4708             :                     else
    4709             :                     {
    4710           2 :                         CPLError(CE_Failure, CPLE_AppDefined,
    4711             :                                  "Invalid password");
    4712             :                     }
    4713             :                 }
    4714             :                 else
    4715             :                 {
    4716           8 :                     CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF");
    4717             :                 }
    4718             : 
    4719          11 :                 PDFFreeDoc(poDocPoppler);
    4720          11 :                 return nullptr;
    4721             :             }
    4722         215 :             else if (poDocPoppler->isLinearized() &&
    4723           0 :                      !poStream->FoundLinearizedHint())
    4724             :             {
    4725             :                 // This is a likely defect of poppler Linearization.cc file that
    4726             :                 // recognizes a file as linearized if the /Linearized hint is
    4727             :                 // missing, but the content of this dictionary are present. But
    4728             :                 // given the hacks of PDFFreeDoc() and
    4729             :                 // VSIPDFFileStream::FillBuffer() opening such a file will
    4730             :                 // result in a null-ptr deref at closing if we try to access a
    4731             :                 // page and build the page cache, so just exit now
    4732           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF");
    4733             : 
    4734           0 :                 PDFFreeDoc(poDocPoppler);
    4735           0 :                 return nullptr;
    4736             :             }
    4737             :             else
    4738             :             {
    4739         215 :                 break;
    4740             :             }
    4741           2 :         }
    4742             : 
    4743         215 :         poCatalogPoppler = poDocPoppler->getCatalog();
    4744         215 :         if (poCatalogPoppler == nullptr || !poCatalogPoppler->isOk())
    4745             :         {
    4746           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    4747             :                      "Invalid PDF : invalid catalog");
    4748           0 :             PDFFreeDoc(poDocPoppler);
    4749           0 :             return nullptr;
    4750             :         }
    4751             : 
    4752         215 :         nPages = poDocPoppler->getNumPages();
    4753             : 
    4754         215 :         if (iPage == 1 && nPages > 10000 &&
    4755           0 :             CPLTestBool(CPLGetConfigOption("GDAL_PDF_LIMIT_PAGE_COUNT", "YES")))
    4756             :         {
    4757           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    4758             :                      "This PDF document reports %d pages. "
    4759             :                      "Limiting count to 10000 for performance reasons. "
    4760             :                      "You may remove this limit by setting the "
    4761             :                      "GDAL_PDF_LIMIT_PAGE_COUNT configuration option to NO",
    4762             :                      nPages);
    4763           0 :             nPages = 10000;
    4764             :         }
    4765             : 
    4766         215 :         if (iPage < 1 || iPage > nPages)
    4767             :         {
    4768           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid page number (%d/%d)",
    4769             :                      iPage, nPages);
    4770           1 :             PDFFreeDoc(poDocPoppler);
    4771           1 :             return nullptr;
    4772             :         }
    4773             : 
    4774             :         /* Sanity check to validate page count */
    4775         214 :         if (iPage > 1 && nPages <= 10000 && iPage != nPages)
    4776             :         {
    4777           4 :             poPagePoppler = poCatalogPoppler->getPage(nPages);
    4778           4 :             if (poPagePoppler == nullptr || !poPagePoppler->isOk())
    4779             :             {
    4780           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    4781             :                          "Invalid PDF : invalid page count");
    4782           0 :                 PDFFreeDoc(poDocPoppler);
    4783           0 :                 return nullptr;
    4784             :             }
    4785             :         }
    4786             : 
    4787         214 :         poPagePoppler = poCatalogPoppler->getPage(iPage);
    4788         214 :         if (poPagePoppler == nullptr || !poPagePoppler->isOk())
    4789             :         {
    4790           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF : invalid page");
    4791           0 :             PDFFreeDoc(poDocPoppler);
    4792           0 :             return nullptr;
    4793             :         }
    4794             : 
    4795             : #if POPPLER_MAJOR_VERSION > 25 ||                                              \
    4796             :     (POPPLER_MAJOR_VERSION == 25 && POPPLER_MINOR_VERSION >= 3)
    4797             :         const Object &oPageObj = poPagePoppler->getPageObj();
    4798             : #else
    4799             :         /* Here's the dirty part: this is a private member */
    4800             :         /* so we had to #define private public to get it ! */
    4801         214 :         const Object &oPageObj = poPagePoppler->pageObj;
    4802             : #endif
    4803         214 :         if (!oPageObj.isDict())
    4804             :         {
    4805           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    4806             :                      "Invalid PDF : !oPageObj.isDict()");
    4807           0 :             PDFFreeDoc(poDocPoppler);
    4808           0 :             return nullptr;
    4809             :         }
    4810             : 
    4811         214 :         poPageObj = new GDALPDFObjectPoppler(&oPageObj);
    4812         214 :         Ref *poPageRef = poCatalogPoppler->getPageRef(iPage);
    4813         214 :         if (poPageRef != nullptr)
    4814             :         {
    4815         428 :             cpl::down_cast<GDALPDFObjectPoppler *>(poPageObj)->SetRefNumAndGen(
    4816         428 :                 GDALPDFObjectNum(poPageRef->num), poPageRef->gen);
    4817             :         }
    4818             :     }
    4819             : #endif  // ~ HAVE_POPPLER
    4820             : 
    4821             : #ifdef HAVE_PODOFO
    4822             :     if (bUseLib.test(PDFLIB_PODOFO) && poPageObj == nullptr)
    4823             :     {
    4824             : #if !(PODOFO_VERSION_MAJOR > 0 ||                                              \
    4825             :       (PODOFO_VERSION_MAJOR == 0 && PODOFO_VERSION_MINOR >= 10))
    4826             :         PoDoFo::PdfError::EnableDebug(false);
    4827             :         PoDoFo::PdfError::EnableLogging(false);
    4828             : #endif
    4829             : 
    4830             :         poDocPodofo = std::make_unique<PoDoFo::PdfMemDocument>();
    4831             :         try
    4832             :         {
    4833             :             poDocPodofo->Load(pszFilename);
    4834             :         }
    4835             :         catch (PoDoFo::PdfError &oError)
    4836             :         {
    4837             : #if PODOFO_VERSION_MAJOR > 0 ||                                                \
    4838             :     (PODOFO_VERSION_MAJOR == 0 && PODOFO_VERSION_MINOR >= 10)
    4839             :             if (oError.GetCode() == PoDoFo::PdfErrorCode::InvalidPassword)
    4840             : #else
    4841             :             if (oError.GetError() == PoDoFo::ePdfError_InvalidPassword)
    4842             : #endif
    4843             :             {
    4844             :                 if (pszUserPwd)
    4845             :                 {
    4846             :                     pszUserPwd =
    4847             :                         PDFEnterPasswordFromConsoleIfNeeded(pszUserPwd);
    4848             : 
    4849             :                     try
    4850             :                     {
    4851             : #if PODOFO_VERSION_MAJOR > 0 ||                                                \
    4852             :     (PODOFO_VERSION_MAJOR == 0 && PODOFO_VERSION_MINOR >= 10)
    4853             :                         poDocPodofo =
    4854             :                             std::make_unique<PoDoFo::PdfMemDocument>();
    4855             :                         poDocPodofo->Load(pszFilename, pszUserPwd);
    4856             : #else
    4857             :                         poDocPodofo->SetPassword(pszUserPwd);
    4858             : #endif
    4859             :                     }
    4860             :                     catch (PoDoFo::PdfError &oError2)
    4861             :                     {
    4862             : #if PODOFO_VERSION_MAJOR > 0 ||                                                \
    4863             :     (PODOFO_VERSION_MAJOR == 0 && PODOFO_VERSION_MINOR >= 10)
    4864             :                         if (oError2.GetCode() ==
    4865             :                             PoDoFo::PdfErrorCode::InvalidPassword)
    4866             : #else
    4867             :                         if (oError2.GetError() ==
    4868             :                             PoDoFo::ePdfError_InvalidPassword)
    4869             : #endif
    4870             :                         {
    4871             :                             CPLError(CE_Failure, CPLE_AppDefined,
    4872             :                                      "Invalid password");
    4873             :                         }
    4874             :                         else
    4875             :                         {
    4876             :                             CPLError(CE_Failure, CPLE_AppDefined,
    4877             :                                      "Invalid PDF : %s", oError2.what());
    4878             :                         }
    4879             :                         return nullptr;
    4880             :                     }
    4881             :                     catch (...)
    4882             :                     {
    4883             :                         CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF");
    4884             :                         return nullptr;
    4885             :                     }
    4886             :                 }
    4887             :                 else
    4888             :                 {
    4889             :                     CPLError(CE_Failure, CPLE_AppDefined,
    4890             :                              "A password is needed. You can specify it through "
    4891             :                              "the PDF_USER_PWD "
    4892             :                              "configuration option / USER_PWD open option "
    4893             :                              "(that can be set to ASK_INTERACTIVE)");
    4894             :                     return nullptr;
    4895             :                 }
    4896             :             }
    4897             :             else
    4898             :             {
    4899             :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF : %s",
    4900             :                          oError.what());
    4901             :                 return nullptr;
    4902             :             }
    4903             :         }
    4904             :         catch (...)
    4905             :         {
    4906             :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF");
    4907             :             return nullptr;
    4908             :         }
    4909             : 
    4910             : #if PODOFO_VERSION_MAJOR > 0 ||                                                \
    4911             :     (PODOFO_VERSION_MAJOR == 0 && PODOFO_VERSION_MINOR >= 10)
    4912             :         auto &oPageCollections = poDocPodofo->GetPages();
    4913             :         nPages = static_cast<int>(oPageCollections.GetCount());
    4914             : #else
    4915             :         nPages = poDocPodofo->GetPageCount();
    4916             : #endif
    4917             :         if (iPage < 1 || iPage > nPages)
    4918             :         {
    4919             :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid page number (%d/%d)",
    4920             :                      iPage, nPages);
    4921             :             return nullptr;
    4922             :         }
    4923             : 
    4924             :         try
    4925             :         {
    4926             : #if PODOFO_VERSION_MAJOR > 0 ||                                                \
    4927             :     (PODOFO_VERSION_MAJOR == 0 && PODOFO_VERSION_MINOR >= 10)
    4928             :             /* Sanity check to validate page count */
    4929             :             if (iPage != nPages)
    4930             :                 CPL_IGNORE_RET_VAL(oPageCollections.GetPageAt(nPages - 1));
    4931             : 
    4932             :             poPagePodofo = &oPageCollections.GetPageAt(iPage - 1);
    4933             : #else
    4934             :             /* Sanity check to validate page count */
    4935             :             if (iPage != nPages)
    4936             :                 CPL_IGNORE_RET_VAL(poDocPodofo->GetPage(nPages - 1));
    4937             : 
    4938             :             poPagePodofo = poDocPodofo->GetPage(iPage - 1);
    4939             : #endif
    4940             :         }
    4941             :         catch (PoDoFo::PdfError &oError)
    4942             :         {
    4943             :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF : %s",
    4944             :                      oError.what());
    4945             :             return nullptr;
    4946             :         }
    4947             :         catch (...)
    4948             :         {
    4949             :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF");
    4950             :             return nullptr;
    4951             :         }
    4952             : 
    4953             :         if (poPagePodofo == nullptr)
    4954             :         {
    4955             :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF : invalid page");
    4956             :             return nullptr;
    4957             :         }
    4958             : 
    4959             : #if PODOFO_VERSION_MAJOR > 0 ||                                                \
    4960             :     (PODOFO_VERSION_MAJOR == 0 && PODOFO_VERSION_MINOR >= 10)
    4961             :         const PoDoFo::PdfObject *pObj = &poPagePodofo->GetObject();
    4962             : #else
    4963             :         const PoDoFo::PdfObject *pObj = poPagePodofo->GetObject();
    4964             : #endif
    4965             :         poPageObj = new GDALPDFObjectPodofo(pObj, poDocPodofo->GetObjects());
    4966             :     }
    4967             : #endif  // ~ HAVE_PODOFO
    4968             : 
    4969             : #ifdef HAVE_PDFIUM
    4970             :     if (bUseLib.test(PDFLIB_PDFIUM) && poPageObj == nullptr)
    4971             :     {
    4972             :         if (!LoadPdfiumDocumentPage(pszFilename, pszUserPwd, iPage,
    4973             :                                     &poDocPdfium, &poPagePdfium, &nPages))
    4974             :         {
    4975             :             // CPLError is called inside function
    4976             :             return nullptr;
    4977             :         }
    4978             : 
    4979             :         const auto pageObj = poPagePdfium->page->GetDict();
    4980             :         if (pageObj == nullptr)
    4981             :         {
    4982             :             CPLError(CE_Failure, CPLE_AppDefined,
    4983             :                      "Invalid PDF : invalid page object");
    4984             :             UnloadPdfiumDocumentPage(&poDocPdfium, &poPagePdfium);
    4985             :             return nullptr;
    4986             :         }
    4987             :         poPageObj = GDALPDFObjectPdfium::Build(pageObj);
    4988             :     }
    4989             : #endif  // ~ HAVE_PDFIUM
    4990             : 
    4991         214 :     if (poPageObj == nullptr)
    4992           0 :         return nullptr;
    4993         214 :     GDALPDFDictionary *poPageDict = poPageObj->GetDictionary();
    4994         214 :     if (poPageDict == nullptr)
    4995             :     {
    4996           0 :         delete poPageObj;
    4997             : 
    4998           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    4999             :                  "Invalid PDF : poPageDict == nullptr");
    5000             : #ifdef HAVE_POPPLER
    5001           0 :         if (bUseLib.test(PDFLIB_POPPLER))
    5002           0 :             PDFFreeDoc(poDocPoppler);
    5003             : #endif
    5004             : #ifdef HAVE_PDFIUM
    5005             :         if (bUseLib.test(PDFLIB_PDFIUM))
    5006             :         {
    5007             :             UnloadPdfiumDocumentPage(&poDocPdfium, &poPagePdfium);
    5008             :         }
    5009             : #endif
    5010           0 :         return nullptr;
    5011             :     }
    5012             : 
    5013         214 :     const char *pszDumpObject = CPLGetConfigOption("PDF_DUMP_OBJECT", nullptr);
    5014         214 :     if (pszDumpObject != nullptr)
    5015             :     {
    5016           2 :         GDALPDFDumper oDumper(pszFilename, pszDumpObject);
    5017           1 :         oDumper.Dump(poPageObj);
    5018             :     }
    5019             : 
    5020         214 :     PDFDataset *poDS = new PDFDataset();
    5021         214 :     poDS->m_fp = std::move(fp);
    5022         214 :     poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
    5023         214 :     poDS->m_bUseLib = bUseLib;
    5024         214 :     poDS->m_osFilename = pszFilename;
    5025         214 :     poDS->eAccess = poOpenInfo->eAccess;
    5026             : 
    5027         214 :     if (nPages > 1 && !bOpenSubdataset)
    5028             :     {
    5029             :         int i;
    5030           4 :         CPLStringList aosList;
    5031           8 :         for (i = 0; i < nPages; i++)
    5032             :         {
    5033             :             char szKey[32];
    5034           6 :             snprintf(szKey, sizeof(szKey), "SUBDATASET_%d_NAME", i + 1);
    5035             :             aosList.AddNameValue(
    5036           6 :                 szKey, CPLSPrintf("PDF:%d:%s", i + 1, poOpenInfo->pszFilename));
    5037           6 :             snprintf(szKey, sizeof(szKey), "SUBDATASET_%d_DESC", i + 1);
    5038             :             aosList.AddNameValue(szKey, CPLSPrintf("Page %d of %s", i + 1,
    5039           6 :                                                    poOpenInfo->pszFilename));
    5040             :         }
    5041           2 :         poDS->SetMetadata(aosList.List(), "SUBDATASETS");
    5042             :     }
    5043             : 
    5044             : #ifdef HAVE_POPPLER
    5045         214 :     poDS->m_poDocPoppler = poDocPoppler;
    5046             : #endif
    5047             : #ifdef HAVE_PODOFO
    5048             :     poDS->m_poDocPodofo = poDocPodofo.release();
    5049             : #endif
    5050             : #ifdef HAVE_PDFIUM
    5051             :     poDS->m_poDocPdfium = poDocPdfium;
    5052             :     poDS->m_poPagePdfium = poPagePdfium;
    5053             : #endif
    5054         214 :     poDS->m_poPageObj = poPageObj;
    5055         214 :     poDS->m_osUserPwd = pszUserPwd ? pszUserPwd : "";
    5056         214 :     poDS->m_iPage = iPage;
    5057             : 
    5058             :     const char *pszDumpCatalog =
    5059         214 :         CPLGetConfigOption("PDF_DUMP_CATALOG", nullptr);
    5060         214 :     if (pszDumpCatalog != nullptr)
    5061             :     {
    5062           0 :         GDALPDFDumper oDumper(pszFilename, pszDumpCatalog);
    5063           0 :         auto poCatalog = poDS->GetCatalog();
    5064           0 :         if (poCatalog)
    5065           0 :             oDumper.Dump(poCatalog);
    5066             :     }
    5067             : 
    5068         214 :     int nBandsGuessed = 0;
    5069         214 :     if (nImageNum < 0)
    5070             :     {
    5071         214 :         poDS->GuessDPI(poPageDict, &nBandsGuessed);
    5072         214 :         if (nBandsGuessed < 4)
    5073         207 :             nBandsGuessed = 0;
    5074             :     }
    5075             :     else
    5076             :     {
    5077             :         const char *pszDPI =
    5078           0 :             GetOption(poOpenInfo->papszOpenOptions, "DPI", nullptr);
    5079           0 :         if (pszDPI != nullptr)
    5080             :         {
    5081             :             // coverity[tainted_data]
    5082           0 :             poDS->m_dfDPI = CPLAtof(pszDPI);
    5083             :         }
    5084             :     }
    5085             : 
    5086         214 :     double dfX1 = 0.0;
    5087         214 :     double dfY1 = 0.0;
    5088         214 :     double dfX2 = 0.0;
    5089         214 :     double dfY2 = 0.0;
    5090             : 
    5091             : #ifdef HAVE_POPPLER
    5092         214 :     if (bUseLib.test(PDFLIB_POPPLER))
    5093             :     {
    5094         214 :         const auto *psMediaBox = poPagePoppler->getMediaBox();
    5095         214 :         dfX1 = psMediaBox->x1;
    5096         214 :         dfY1 = psMediaBox->y1;
    5097         214 :         dfX2 = psMediaBox->x2;
    5098         214 :         dfY2 = psMediaBox->y2;
    5099             :     }
    5100             : #endif
    5101             : 
    5102             : #ifdef HAVE_PODOFO
    5103             :     if (bUseLib.test(PDFLIB_PODOFO))
    5104             :     {
    5105             :         CPLAssert(poPagePodofo);
    5106             :         auto oMediaBox = poPagePodofo->GetMediaBox();
    5107             :         dfX1 = oMediaBox.GetLeft();
    5108             :         dfY1 = oMediaBox.GetBottom();
    5109             : #if PODOFO_VERSION_MAJOR > 0 ||                                                \
    5110             :     (PODOFO_VERSION_MAJOR == 0 && PODOFO_VERSION_MINOR >= 10)
    5111             :         dfX2 = dfX1 + oMediaBox.Width;
    5112             :         dfY2 = dfY1 + oMediaBox.Height;
    5113             : #else
    5114             :         dfX2 = dfX1 + oMediaBox.GetWidth();
    5115             :         dfY2 = dfY1 + oMediaBox.GetHeight();
    5116             : #endif
    5117             :     }
    5118             : #endif
    5119             : 
    5120             : #ifdef HAVE_PDFIUM
    5121             :     if (bUseLib.test(PDFLIB_PDFIUM))
    5122             :     {
    5123             :         CPLAssert(poPagePdfium);
    5124             :         CFX_FloatRect rect = poPagePdfium->page->GetBBox();
    5125             :         dfX1 = rect.left;
    5126             :         dfX2 = rect.right;
    5127             :         dfY1 = rect.bottom;
    5128             :         dfY2 = rect.top;
    5129             :     }
    5130             : #endif  // ~ HAVE_PDFIUM
    5131             : 
    5132         214 :     double dfUserUnit = poDS->m_dfDPI * USER_UNIT_IN_INCH;
    5133         214 :     poDS->m_dfPageWidth = dfX2 - dfX1;
    5134         214 :     poDS->m_dfPageHeight = dfY2 - dfY1;
    5135             :     // CPLDebug("PDF", "left=%f right=%f bottom=%f top=%f", dfX1, dfX2, dfY1,
    5136             :     // dfY2);
    5137         214 :     const double dfXSize = floor((dfX2 - dfX1) * dfUserUnit + 0.5);
    5138         214 :     const double dfYSize = floor((dfY2 - dfY1) * dfUserUnit + 0.5);
    5139         214 :     if (!(dfXSize >= 0 && dfXSize <= INT_MAX && dfYSize >= 0 &&
    5140         214 :           dfYSize <= INT_MAX))
    5141             :     {
    5142           0 :         delete poDS;
    5143           0 :         return nullptr;
    5144             :     }
    5145         214 :     poDS->nRasterXSize = static_cast<int>(dfXSize);
    5146         214 :     poDS->nRasterYSize = static_cast<int>(dfYSize);
    5147             : 
    5148         214 :     if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize))
    5149             :     {
    5150           0 :         delete poDS;
    5151           0 :         return nullptr;
    5152             :     }
    5153             : 
    5154         214 :     double dfRotation = 0;
    5155             : #ifdef HAVE_POPPLER
    5156         214 :     if (bUseLib.test(PDFLIB_POPPLER))
    5157         214 :         dfRotation = poDocPoppler->getPageRotate(iPage);
    5158             : #endif
    5159             : 
    5160             : #ifdef HAVE_PODOFO
    5161             :     if (bUseLib.test(PDFLIB_PODOFO))
    5162             :     {
    5163             :         CPLAssert(poPagePodofo);
    5164             : #if PODOFO_VERSION_MAJOR >= 1
    5165             :         poPagePodofo->TryGetRotationRaw(dfRotation);
    5166             : #elif (PODOFO_VERSION_MAJOR == 0 && PODOFO_VERSION_MINOR >= 10)
    5167             :         dfRotation = poPagePodofo->GetRotationRaw();
    5168             : #else
    5169             :         dfRotation = poPagePodofo->GetRotation();
    5170             : #endif
    5171             :     }
    5172             : #endif
    5173             : 
    5174             : #ifdef HAVE_PDFIUM
    5175             :     if (bUseLib.test(PDFLIB_PDFIUM))
    5176             :     {
    5177             :         CPLAssert(poPagePdfium);
    5178             :         dfRotation = poPagePdfium->page->GetPageRotation() * 90;
    5179             :     }
    5180             : #endif
    5181             : 
    5182         214 :     if (dfRotation == 90 || dfRotation == -90 || dfRotation == 270)
    5183             :     {
    5184             : /* FIXME: the podofo case should be implemented. This needs to rotate */
    5185             : /* the output of pdftoppm */
    5186             : #if defined(HAVE_POPPLER) || defined(HAVE_PDFIUM)
    5187           0 :         if (bUseLib.test(PDFLIB_POPPLER) || bUseLib.test(PDFLIB_PDFIUM))
    5188             :         {
    5189           0 :             int nTmp = poDS->nRasterXSize;
    5190           0 :             poDS->nRasterXSize = poDS->nRasterYSize;
    5191           0 :             poDS->nRasterYSize = nTmp;
    5192             :         }
    5193             : #endif
    5194             :     }
    5195             : 
    5196         214 :     if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "@OPEN_FOR_OVERVIEW"))
    5197             :     {
    5198           2 :         poDS->m_nBlockXSize = 512;
    5199           2 :         poDS->m_nBlockYSize = 512;
    5200             :     }
    5201             :     /* Check if the PDF is only made of regularly tiled images */
    5202             :     /* (like some USGS GeoPDF production) */
    5203         370 :     else if (dfRotation == 0.0 && !poDS->m_asTiles.empty() &&
    5204         158 :              EQUAL(GetOption(poOpenInfo->papszOpenOptions, "LAYERS", "ALL"),
    5205             :                    "ALL"))
    5206             :     {
    5207         158 :         poDS->CheckTiledRaster();
    5208         158 :         if (!poDS->m_aiTiles.empty())
    5209           9 :             poDS->SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
    5210             :     }
    5211             : 
    5212         214 :     GDALPDFObject *poLGIDict = nullptr;
    5213         214 :     GDALPDFObject *poVP = nullptr;
    5214         214 :     int bIsOGCBP = FALSE;
    5215         214 :     if ((poLGIDict = poPageDict->Get("LGIDict")) != nullptr && nImageNum < 0)
    5216             :     {
    5217             :         /* Cf 08-139r3_GeoPDF_Encoding_Best_Practice_Version_2.2.pdf */
    5218           0 :         CPLDebug("PDF", "OGC Encoding Best Practice style detected");
    5219           0 :         if (poDS->ParseLGIDictObject(poLGIDict))
    5220             :         {
    5221           0 :             if (poDS->m_bHasCTM)
    5222             :             {
    5223           0 :                 if (dfRotation == 90)
    5224             :                 {
    5225           0 :                     poDS->m_gt[0] = poDS->m_adfCTM[4];
    5226           0 :                     poDS->m_gt[1] = poDS->m_adfCTM[2] / dfUserUnit;
    5227           0 :                     poDS->m_gt[2] = poDS->m_adfCTM[0] / dfUserUnit;
    5228           0 :                     poDS->m_gt[3] = poDS->m_adfCTM[5];
    5229           0 :                     poDS->m_gt[4] = poDS->m_adfCTM[3] / dfUserUnit;
    5230           0 :                     poDS->m_gt[5] = poDS->m_adfCTM[1] / dfUserUnit;
    5231             :                 }
    5232           0 :                 else if (dfRotation == -90 || dfRotation == 270)
    5233             :                 {
    5234           0 :                     poDS->m_gt[0] = poDS->m_adfCTM[4] +
    5235           0 :                                     poDS->m_adfCTM[2] * poDS->m_dfPageHeight +
    5236           0 :                                     poDS->m_adfCTM[0] * poDS->m_dfPageWidth;
    5237           0 :                     poDS->m_gt[1] = -poDS->m_adfCTM[2] / dfUserUnit;
    5238           0 :                     poDS->m_gt[2] = -poDS->m_adfCTM[0] / dfUserUnit;
    5239           0 :                     poDS->m_gt[3] = poDS->m_adfCTM[5] +
    5240           0 :                                     poDS->m_adfCTM[3] * poDS->m_dfPageHeight +
    5241           0 :                                     poDS->m_adfCTM[1] * poDS->m_dfPageWidth;
    5242           0 :                     poDS->m_gt[4] = -poDS->m_adfCTM[3] / dfUserUnit;
    5243           0 :                     poDS->m_gt[5] = -poDS->m_adfCTM[1] / dfUserUnit;
    5244             :                 }
    5245             :                 else
    5246             :                 {
    5247           0 :                     poDS->m_gt[0] = poDS->m_adfCTM[4] +
    5248           0 :                                     poDS->m_adfCTM[2] * dfY2 +
    5249           0 :                                     poDS->m_adfCTM[0] * dfX1;
    5250           0 :                     poDS->m_gt[1] = poDS->m_adfCTM[0] / dfUserUnit;
    5251           0 :                     poDS->m_gt[2] = -poDS->m_adfCTM[2] / dfUserUnit;
    5252           0 :                     poDS->m_gt[3] = poDS->m_adfCTM[5] +
    5253           0 :                                     poDS->m_adfCTM[3] * dfY2 +
    5254           0 :                                     poDS->m_adfCTM[1] * dfX1;
    5255           0 :                     poDS->m_gt[4] = poDS->m_adfCTM[1] / dfUserUnit;
    5256           0 :                     poDS->m_gt[5] = -poDS->m_adfCTM[3] / dfUserUnit;
    5257             :                 }
    5258             : 
    5259           0 :                 poDS->m_bGeoTransformValid = true;
    5260             :             }
    5261             : 
    5262           0 :             bIsOGCBP = TRUE;
    5263             : 
    5264             :             int i;
    5265           0 :             for (i = 0; i < poDS->m_nGCPCount; i++)
    5266             :             {
    5267           0 :                 if (dfRotation == 90)
    5268             :                 {
    5269           0 :                     double dfPixel =
    5270           0 :                         poDS->m_pasGCPList[i].dfGCPPixel * dfUserUnit;
    5271           0 :                     double dfLine =
    5272           0 :                         poDS->m_pasGCPList[i].dfGCPLine * dfUserUnit;
    5273           0 :                     poDS->m_pasGCPList[i].dfGCPPixel = dfLine;
    5274           0 :                     poDS->m_pasGCPList[i].dfGCPLine = dfPixel;
    5275             :                 }
    5276           0 :                 else if (dfRotation == -90 || dfRotation == 270)
    5277             :                 {
    5278           0 :                     double dfPixel =
    5279           0 :                         poDS->m_pasGCPList[i].dfGCPPixel * dfUserUnit;
    5280           0 :                     double dfLine =
    5281           0 :                         poDS->m_pasGCPList[i].dfGCPLine * dfUserUnit;
    5282           0 :                     poDS->m_pasGCPList[i].dfGCPPixel =
    5283           0 :                         poDS->nRasterXSize - dfLine;
    5284           0 :                     poDS->m_pasGCPList[i].dfGCPLine =
    5285           0 :                         poDS->nRasterYSize - dfPixel;
    5286             :                 }
    5287             :                 else
    5288             :                 {
    5289           0 :                     poDS->m_pasGCPList[i].dfGCPPixel =
    5290           0 :                         (-dfX1 + poDS->m_pasGCPList[i].dfGCPPixel) * dfUserUnit;
    5291           0 :                     poDS->m_pasGCPList[i].dfGCPLine =
    5292           0 :                         (dfY2 - poDS->m_pasGCPList[i].dfGCPLine) * dfUserUnit;
    5293             :                 }
    5294             :             }
    5295             :         }
    5296             :     }
    5297         214 :     else if ((poVP = poPageDict->Get("VP")) != nullptr && nImageNum < 0)
    5298             :     {
    5299             :         /* Cf adobe_supplement_iso32000.pdf */
    5300         155 :         CPLDebug("PDF", "Adobe ISO32000 style Geospatial PDF perhaps ?");
    5301         155 :         if (dfX1 != 0 || dfY1 != 0)
    5302             :         {
    5303           0 :             CPLDebug("PDF", "non null dfX1 or dfY1 values. untested case...");
    5304             :         }
    5305         155 :         poDS->ParseVP(poVP, dfX2 - dfX1, dfY2 - dfY1);
    5306             :     }
    5307             :     else
    5308             :     {
    5309             :         GDALPDFObject *poXObject =
    5310          59 :             poPageDict->LookupObject("Resources.XObject");
    5311             : 
    5312         116 :         if (poXObject != nullptr &&
    5313          57 :             poXObject->GetType() == PDFObjectType_Dictionary)
    5314             :         {
    5315          57 :             GDALPDFDictionary *poXObjectDict = poXObject->GetDictionary();
    5316          57 :             const auto &oMap = poXObjectDict->GetValues();
    5317          57 :             int nSubDataset = 0;
    5318         231 :             for (const auto &[osKey, poObj] : oMap)
    5319             :             {
    5320         174 :                 if (poObj->GetType() == PDFObjectType_Dictionary)
    5321             :                 {
    5322         174 :                     GDALPDFDictionary *poDict = poObj->GetDictionary();
    5323         174 :                     GDALPDFObject *poSubtype = nullptr;
    5324         174 :                     GDALPDFObject *poMeasure = nullptr;
    5325         174 :                     GDALPDFObject *poWidth = nullptr;
    5326         174 :                     GDALPDFObject *poHeight = nullptr;
    5327         174 :                     int nW = 0;
    5328         174 :                     int nH = 0;
    5329         174 :                     if ((poSubtype = poDict->Get("Subtype")) != nullptr &&
    5330         348 :                         poSubtype->GetType() == PDFObjectType_Name &&
    5331         174 :                         poSubtype->GetName() == "Image" &&
    5332         129 :                         (poMeasure = poDict->Get("Measure")) != nullptr &&
    5333           0 :                         poMeasure->GetType() == PDFObjectType_Dictionary &&
    5334           0 :                         (poWidth = poDict->Get("Width")) != nullptr &&
    5335           0 :                         poWidth->GetType() == PDFObjectType_Int &&
    5336           0 :                         (nW = poWidth->GetInt()) > 0 &&
    5337           0 :                         (poHeight = poDict->Get("Height")) != nullptr &&
    5338         348 :                         poHeight->GetType() == PDFObjectType_Int &&
    5339           0 :                         (nH = poHeight->GetInt()) > 0)
    5340             :                     {
    5341           0 :                         if (nImageNum < 0)
    5342           0 :                             CPLDebug("PDF",
    5343             :                                      "Measure found on Image object (%d)",
    5344           0 :                                      poObj->GetRefNum().toInt());
    5345             : 
    5346           0 :                         GDALPDFObject *poColorSpace = poDict->Get("ColorSpace");
    5347             :                         GDALPDFObject *poBitsPerComponent =
    5348           0 :                             poDict->Get("BitsPerComponent");
    5349           0 :                         if (poObj->GetRefNum().toBool() &&
    5350           0 :                             poObj->GetRefGen() == 0 &&
    5351           0 :                             poColorSpace != nullptr &&
    5352           0 :                             poColorSpace->GetType() == PDFObjectType_Name &&
    5353           0 :                             (poColorSpace->GetName() == "DeviceGray" ||
    5354           0 :                              poColorSpace->GetName() == "DeviceRGB") &&
    5355           0 :                             (poBitsPerComponent == nullptr ||
    5356           0 :                              (poBitsPerComponent->GetType() ==
    5357           0 :                                   PDFObjectType_Int &&
    5358           0 :                               poBitsPerComponent->GetInt() == 8)))
    5359             :                         {
    5360           0 :                             if (nImageNum < 0)
    5361             :                             {
    5362           0 :                                 nSubDataset++;
    5363           0 :                                 poDS->SetMetadataItem(
    5364             :                                     CPLSPrintf("SUBDATASET_%d_NAME",
    5365             :                                                nSubDataset),
    5366             :                                     CPLSPrintf("PDF_IMAGE:%d:%d:%s", iPage,
    5367           0 :                                                poObj->GetRefNum().toInt(),
    5368             :                                                pszFilename),
    5369             :                                     "SUBDATASETS");
    5370           0 :                                 poDS->SetMetadataItem(
    5371             :                                     CPLSPrintf("SUBDATASET_%d_DESC",
    5372             :                                                nSubDataset),
    5373             :                                     CPLSPrintf("Georeferenced image of size "
    5374             :                                                "%dx%d of page %d of %s",
    5375             :                                                nW, nH, iPage, pszFilename),
    5376             :                                     "SUBDATASETS");
    5377             :                             }
    5378           0 :                             else if (poObj->GetRefNum().toInt() == nImageNum)
    5379             :                             {
    5380           0 :                                 poDS->nRasterXSize = nW;
    5381           0 :                                 poDS->nRasterYSize = nH;
    5382           0 :                                 poDS->ParseMeasure(poMeasure, nW, nH, 0, nH, nW,
    5383             :                                                    0);
    5384           0 :                                 poDS->m_poImageObj = poObj;
    5385           0 :                                 if (poColorSpace->GetName() == "DeviceGray")
    5386           0 :                                     nBandsGuessed = 1;
    5387           0 :                                 break;
    5388             :                             }
    5389             :                         }
    5390             :                     }
    5391             :                 }
    5392             :             }
    5393             :         }
    5394             : 
    5395          59 :         if (nImageNum >= 0 && poDS->m_poImageObj == nullptr)
    5396             :         {
    5397           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot find image %d",
    5398             :                      nImageNum);
    5399           0 :             delete poDS;
    5400           0 :             return nullptr;
    5401             :         }
    5402             : 
    5403             :         /* Not a geospatial PDF doc */
    5404             :     }
    5405             : 
    5406             :     /* If pixel size or top left coordinates are very close to an int, round
    5407             :      * them to the int */
    5408             :     double dfEps =
    5409         214 :         (fabs(poDS->m_gt[0]) > 1e5 && fabs(poDS->m_gt[3]) > 1e5) ? 1e-5 : 1e-8;
    5410         214 :     poDS->m_gt[0] = ROUND_IF_CLOSE(poDS->m_gt[0], dfEps);
    5411         214 :     poDS->m_gt[1] = ROUND_IF_CLOSE(poDS->m_gt[1]);
    5412         214 :     poDS->m_gt[3] = ROUND_IF_CLOSE(poDS->m_gt[3], dfEps);
    5413         214 :     poDS->m_gt[5] = ROUND_IF_CLOSE(poDS->m_gt[5]);
    5414             : 
    5415         214 :     if (bUseLib.test(PDFLIB_PDFIUM))
    5416             :     {
    5417             :         // Attempt to "fix" the loss of precision due to the use of float32 for
    5418             :         // numbers by pdfium
    5419           0 :         if ((fabs(poDS->m_gt[0]) > 1e5 || fabs(poDS->m_gt[3]) > 1e5) &&
    5420           0 :             fabs(poDS->m_gt[0] - std::round(poDS->m_gt[0])) <
    5421           0 :                 1e-6 * fabs(poDS->m_gt[0]) &&
    5422           0 :             fabs(poDS->m_gt[1] - std::round(poDS->m_gt[1])) <
    5423           0 :                 1e-3 * fabs(poDS->m_gt[1]) &&
    5424           0 :             fabs(poDS->m_gt[3] - std::round(poDS->m_gt[3])) <
    5425           0 :                 1e-6 * fabs(poDS->m_gt[3]) &&
    5426           0 :             fabs(poDS->m_gt[5] - std::round(poDS->m_gt[5])) <
    5427           0 :                 1e-3 * fabs(poDS->m_gt[5]))
    5428             :         {
    5429           0 :             for (int i = 0; i < 6; i++)
    5430             :             {
    5431           0 :                 poDS->m_gt[i] = std::round(poDS->m_gt[i]);
    5432             :             }
    5433             :         }
    5434             :     }
    5435             : 
    5436         214 :     if (poDS->m_poNeatLine)
    5437             :     {
    5438         154 :         char *pszNeatLineWkt = nullptr;
    5439         154 :         OGRLinearRing *poRing = poDS->m_poNeatLine->getExteriorRing();
    5440             :         /* Adobe style is already in target SRS units */
    5441         154 :         if (bIsOGCBP)
    5442             :         {
    5443           0 :             int nPoints = poRing->getNumPoints();
    5444             :             int i;
    5445             : 
    5446           0 :             for (i = 0; i < nPoints; i++)
    5447             :             {
    5448             :                 double x, y;
    5449           0 :                 if (dfRotation == 90.0)
    5450             :                 {
    5451           0 :                     x = poRing->getY(i) * dfUserUnit;
    5452           0 :                     y = poRing->getX(i) * dfUserUnit;
    5453             :                 }
    5454           0 :                 else if (dfRotation == -90.0 || dfRotation == 270.0)
    5455             :                 {
    5456           0 :                     x = poDS->nRasterXSize - poRing->getY(i) * dfUserUnit;
    5457           0 :                     y = poDS->nRasterYSize - poRing->getX(i) * dfUserUnit;
    5458             :                 }
    5459             :                 else
    5460             :                 {
    5461           0 :                     x = (-dfX1 + poRing->getX(i)) * dfUserUnit;
    5462           0 :                     y = (dfY2 - poRing->getY(i)) * dfUserUnit;
    5463             :                 }
    5464             :                 double X =
    5465           0 :                     poDS->m_gt[0] + x * poDS->m_gt[1] + y * poDS->m_gt[2];
    5466             :                 double Y =
    5467           0 :                     poDS->m_gt[3] + x * poDS->m_gt[4] + y * poDS->m_gt[5];
    5468           0 :                 poRing->setPoint(i, X, Y);
    5469             :             }
    5470             :         }
    5471         154 :         poRing->closeRings();
    5472             : 
    5473         154 :         poDS->m_poNeatLine->exportToWkt(&pszNeatLineWkt);
    5474         154 :         if (nImageNum < 0)
    5475         154 :             poDS->SetMetadataItem("NEATLINE", pszNeatLineWkt);
    5476         154 :         CPLFree(pszNeatLineWkt);
    5477             :     }
    5478             : 
    5479         214 :     poDS->MapOCGsToPages();
    5480             : 
    5481             : #ifdef HAVE_POPPLER
    5482         214 :     if (bUseLib.test(PDFLIB_POPPLER))
    5483             :     {
    5484         214 :         auto poMetadata = poCatalogPoppler->readMetadata();
    5485         214 :         if (poMetadata)
    5486             :         {
    5487          19 :             const char *pszContent = poMetadata->c_str();
    5488          19 :             if (pszContent != nullptr &&
    5489          19 :                 STARTS_WITH(pszContent, "<?xpacket begin="))
    5490             :             {
    5491          19 :                 const char *const apszMDList[2] = {pszContent, nullptr};
    5492          19 :                 poDS->SetMetadata(const_cast<char **>(apszMDList), "xml:XMP");
    5493             :             }
    5494             : #if (POPPLER_MAJOR_VERSION < 21 ||                                             \
    5495             :      (POPPLER_MAJOR_VERSION == 21 && POPPLER_MINOR_VERSION < 10))
    5496          19 :             delete poMetadata;
    5497             : #endif
    5498             :         }
    5499             : 
    5500             :         /* Read Info object */
    5501             :         /* The test is necessary since with some corrupted PDFs
    5502             :          * poDocPoppler->getDocInfo() */
    5503             :         /* might abort() */
    5504         214 :         if (poDocPoppler->getXRef()->isOk())
    5505             :         {
    5506         428 :             Object oInfo = poDocPoppler->getDocInfo();
    5507         428 :             GDALPDFObjectPoppler oInfoObjPoppler(&oInfo, FALSE);
    5508         214 :             poDS->ParseInfo(&oInfoObjPoppler);
    5509             :         }
    5510             : 
    5511             :         /* Find layers */
    5512         416 :         poDS->FindLayersPoppler(
    5513         202 :             (bOpenSubdataset || bOpenSubdatasetImage) ? iPage : 0);
    5514             : 
    5515             :         /* Turn user specified layers on or off */
    5516         214 :         poDS->TurnLayersOnOffPoppler();
    5517             :     }
    5518             : #endif
    5519             : 
    5520             : #ifdef HAVE_PODOFO
    5521             :     if (bUseLib.test(PDFLIB_PODOFO))
    5522             :     {
    5523             :         for (const auto &obj : poDS->m_poDocPodofo->GetObjects())
    5524             :         {
    5525             :             GDALPDFObjectPodofo oObjPodofo(obj,
    5526             :                                            poDS->m_poDocPodofo->GetObjects());
    5527             :             poDS->FindXMP(&oObjPodofo);
    5528             :         }
    5529             : 
    5530             :         /* Find layers */
    5531             :         poDS->FindLayersGeneric(poPageDict);
    5532             : 
    5533             :         /* Read Info object */
    5534             :         const PoDoFo::PdfInfo *poInfo = poDS->m_poDocPodofo->GetInfo();
    5535             :         if (poInfo != nullptr)
    5536             :         {
    5537             :             GDALPDFObjectPodofo oInfoObjPodofo(
    5538             : #if PODOFO_VERSION_MAJOR > 0 ||                                                \
    5539             :     (PODOFO_VERSION_MAJOR == 0 && PODOFO_VERSION_MINOR >= 10)
    5540             :                 &(poInfo->GetObject()),
    5541             : #else
    5542             :                 poInfo->GetObject(),
    5543             : #endif
    5544             :                 poDS->m_poDocPodofo->GetObjects());
    5545             :             poDS->ParseInfo(&oInfoObjPodofo);
    5546             :         }
    5547             :     }
    5548             : #endif
    5549             : #ifdef HAVE_PDFIUM
    5550             :     if (bUseLib.test(PDFLIB_PDFIUM))
    5551             :     {
    5552             :         // coverity is confused by WrapRetain(), believing that multiple
    5553             :         // smart pointers manage the same raw pointer. Which is actually
    5554             :         // true, but a RetainPtr holds a reference counted object. It is
    5555             :         // thus safe to have several RetainPtr holding it.
    5556             :         // coverity[multiple_init_smart_ptr]
    5557             :         GDALPDFObjectPdfium *poRoot = GDALPDFObjectPdfium::Build(
    5558             :             pdfium::WrapRetain(poDocPdfium->doc->GetRoot()));
    5559             :         if (poRoot->GetType() == PDFObjectType_Dictionary)
    5560             :         {
    5561             :             GDALPDFDictionary *poDict = poRoot->GetDictionary();
    5562             :             GDALPDFObject *poMetadata(poDict->Get("Metadata"));
    5563             :             if (poMetadata != nullptr)
    5564             :             {
    5565             :                 GDALPDFStream *poStream = poMetadata->GetStream();
    5566             :                 if (poStream != nullptr)
    5567             :                 {
    5568             :                     char *pszContent = poStream->GetBytes();
    5569             :                     const auto nLength = poStream->GetLength();
    5570             :                     if (pszContent != nullptr && nLength > 15 &&
    5571             :                         STARTS_WITH(pszContent, "<?xpacket begin="))
    5572             :                     {
    5573             :                         char *apszMDList[2];
    5574             :                         apszMDList[0] = pszContent;
    5575             :                         apszMDList[1] = nullptr;
    5576             :                         poDS->SetMetadata(apszMDList, "xml:XMP");
    5577             :                     }
    5578             :                     CPLFree(pszContent);
    5579             :                 }
    5580             :             }
    5581             :         }
    5582             :         delete poRoot;
    5583             : 
    5584             :         /* Find layers */
    5585             :         poDS->FindLayersPdfium((bOpenSubdataset || bOpenSubdatasetImage) ? iPage
    5586             :                                                                          : 0);
    5587             : 
    5588             :         /* Turn user specified layers on or off */
    5589             :         poDS->TurnLayersOnOffPdfium();
    5590             : 
    5591             :         GDALPDFObjectPdfium *poInfo =
    5592             :             GDALPDFObjectPdfium::Build(poDocPdfium->doc->GetInfo());
    5593             :         if (poInfo)
    5594             :         {
    5595             :             /* Read Info object */
    5596             :             poDS->ParseInfo(poInfo);
    5597             :             delete poInfo;
    5598             :         }
    5599             :     }
    5600             : #endif  // ~ HAVE_PDFIUM
    5601             : 
    5602         214 :     int nBands = 3;
    5603             : #ifdef HAVE_PDFIUM
    5604             :     // Use Alpha channel for PDFIUM as default format RGBA
    5605             :     if (bUseLib.test(PDFLIB_PDFIUM))
    5606             :         nBands = 4;
    5607             : #endif
    5608         214 :     if (nBandsGuessed)
    5609           7 :         nBands = nBandsGuessed;
    5610             :     const char *pszPDFBands =
    5611         214 :         GetOption(poOpenInfo->papszOpenOptions, "BANDS", nullptr);
    5612         214 :     if (pszPDFBands)
    5613             :     {
    5614           2 :         nBands = atoi(pszPDFBands);
    5615           2 :         if (nBands != 3 && nBands != 4)
    5616             :         {
    5617           0 :             CPLError(CE_Warning, CPLE_NotSupported,
    5618             :                      "Invalid value for GDAL_PDF_BANDS. Using 3 as a fallback");
    5619           0 :             nBands = 3;
    5620             :         }
    5621             :     }
    5622             : #ifdef HAVE_PODOFO
    5623             :     if (bUseLib.test(PDFLIB_PODOFO) && nBands == 4 && poDS->m_aiTiles.empty())
    5624             :     {
    5625             :         CPLError(CE_Warning, CPLE_NotSupported,
    5626             :                  "GDAL_PDF_BANDS=4 not supported when PDF driver is compiled "
    5627             :                  "against Podofo. "
    5628             :                  "Using 3 as a fallback");
    5629             :         nBands = 3;
    5630             :     }
    5631             : #endif
    5632             : 
    5633             :     int iBand;
    5634         863 :     for (iBand = 1; iBand <= nBands; iBand++)
    5635             :     {
    5636         649 :         if (poDS->m_poImageObj != nullptr)
    5637           0 :             poDS->SetBand(iBand, new PDFImageRasterBand(poDS, iBand));
    5638             :         else
    5639         649 :             poDS->SetBand(iBand, new PDFRasterBand(poDS, iBand, 0));
    5640             :     }
    5641             : 
    5642             :     /* Check if this is a raster-only PDF file and that we are */
    5643             :     /* opened in vector-only mode */
    5644         492 :     if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) == 0 &&
    5645         227 :         (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
    5646          13 :         !poDS->OpenVectorLayers(poPageDict))
    5647             :     {
    5648           0 :         CPLDebug("PDF", "This is a raster-only PDF dataset, "
    5649             :                         "but it has been opened in vector-only mode");
    5650             :         /* Clear dirty flag */
    5651           0 :         poDS->m_bProjDirty = false;
    5652           0 :         poDS->m_bNeatLineDirty = false;
    5653           0 :         poDS->m_bInfoDirty = false;
    5654           0 :         poDS->m_bXMPDirty = false;
    5655           0 :         delete poDS;
    5656           0 :         return nullptr;
    5657             :     }
    5658             : 
    5659             :     /* -------------------------------------------------------------------- */
    5660             :     /*      Initialize any PAM information.                                 */
    5661             :     /* -------------------------------------------------------------------- */
    5662         214 :     if (bOpenSubdataset || bOpenSubdatasetImage)
    5663             :     {
    5664          12 :         poDS->SetPhysicalFilename(pszFilename);
    5665          12 :         poDS->SetSubdatasetName(osSubdatasetName.c_str());
    5666             :     }
    5667             :     else
    5668             :     {
    5669         202 :         poDS->SetDescription(poOpenInfo->pszFilename);
    5670             :     }
    5671             : 
    5672         214 :     poDS->TryLoadXML();
    5673             : 
    5674             :     /* -------------------------------------------------------------------- */
    5675             :     /*      Support overviews.                                              */
    5676             :     /* -------------------------------------------------------------------- */
    5677         214 :     if (!CSLFetchNameValue(poOpenInfo->papszOpenOptions, "@OPEN_FOR_OVERVIEW"))
    5678             :     {
    5679         212 :         poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);
    5680             :     }
    5681             : 
    5682             :     /* Clear dirty flag */
    5683         214 :     poDS->m_bProjDirty = false;
    5684         214 :     poDS->m_bNeatLineDirty = false;
    5685         214 :     poDS->m_bInfoDirty = false;
    5686         214 :     poDS->m_bXMPDirty = false;
    5687             : 
    5688         214 :     return (poDS);
    5689             : }
    5690             : 
    5691             : /************************************************************************/
    5692             : /*                       ParseLGIDictObject()                           */
    5693             : /************************************************************************/
    5694             : 
    5695           0 : int PDFDataset::ParseLGIDictObject(GDALPDFObject *poLGIDict)
    5696             : {
    5697           0 :     bool bOK = false;
    5698           0 :     if (poLGIDict->GetType() == PDFObjectType_Array)
    5699             :     {
    5700           0 :         GDALPDFArray *poArray = poLGIDict->GetArray();
    5701           0 :         int nArrayLength = poArray->GetLength();
    5702           0 :         int iMax = -1;
    5703           0 :         GDALPDFObject *poArrayElt = nullptr;
    5704           0 :         for (int i = 0; i < nArrayLength; i++)
    5705             :         {
    5706           0 :             if ((poArrayElt = poArray->Get(i)) == nullptr ||
    5707           0 :                 poArrayElt->GetType() != PDFObjectType_Dictionary)
    5708             :             {
    5709           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    5710             :                          "LGIDict[%d] is not a dictionary", i);
    5711           0 :                 return FALSE;
    5712             :             }
    5713             : 
    5714           0 :             int bIsBestCandidate = FALSE;
    5715           0 :             if (ParseLGIDictDictFirstPass(poArrayElt->GetDictionary(),
    5716           0 :                                           &bIsBestCandidate))
    5717             :             {
    5718           0 :                 if (bIsBestCandidate || iMax < 0)
    5719           0 :                     iMax = i;
    5720             :             }
    5721             :         }
    5722             : 
    5723           0 :         if (iMax < 0)
    5724           0 :             return FALSE;
    5725             : 
    5726           0 :         poArrayElt = poArray->Get(iMax);
    5727           0 :         bOK = CPL_TO_BOOL(
    5728           0 :             ParseLGIDictDictSecondPass(poArrayElt->GetDictionary()));
    5729             :     }
    5730           0 :     else if (poLGIDict->GetType() == PDFObjectType_Dictionary)
    5731             :     {
    5732           0 :         bOK = ParseLGIDictDictFirstPass(poLGIDict->GetDictionary()) &&
    5733           0 :               ParseLGIDictDictSecondPass(poLGIDict->GetDictionary());
    5734             :     }
    5735             :     else
    5736             :     {
    5737           0 :         CPLError(CE_Failure, CPLE_AppDefined, "LGIDict is of type %s",
    5738           0 :                  poLGIDict->GetTypeName());
    5739             :     }
    5740             : 
    5741           0 :     return bOK;
    5742             : }
    5743             : 
    5744             : /************************************************************************/
    5745             : /*                            Get()                                     */
    5746             : /************************************************************************/
    5747             : 
    5748       10995 : static double Get(GDALPDFObject *poObj, int nIndice)
    5749             : {
    5750       10995 :     if (poObj->GetType() == PDFObjectType_Array && nIndice >= 0)
    5751             :     {
    5752        4916 :         poObj = poObj->GetArray()->Get(nIndice);
    5753        4916 :         if (poObj == nullptr)
    5754           0 :             return 0;
    5755        4916 :         return Get(poObj);
    5756             :     }
    5757        6079 :     else if (poObj->GetType() == PDFObjectType_Int)
    5758        4871 :         return poObj->GetInt();
    5759        1208 :     else if (poObj->GetType() == PDFObjectType_Real)
    5760        1208 :         return poObj->GetReal();
    5761           0 :     else if (poObj->GetType() == PDFObjectType_String)
    5762             :     {
    5763           0 :         const char *pszStr = poObj->GetString().c_str();
    5764           0 :         size_t nLen = strlen(pszStr);
    5765           0 :         if (nLen == 0)
    5766           0 :             return 0;
    5767             :         /* cf Military_Installations_2008.pdf that has values like "96 0 0.0W"
    5768             :          */
    5769           0 :         char chLast = pszStr[nLen - 1];
    5770           0 :         if (chLast == 'W' || chLast == 'E' || chLast == 'N' || chLast == 'S')
    5771             :         {
    5772           0 :             double dfDeg = CPLAtof(pszStr);
    5773           0 :             double dfMin = 0.0;
    5774           0 :             double dfSec = 0.0;
    5775           0 :             const char *pszNext = strchr(pszStr, ' ');
    5776           0 :             if (pszNext)
    5777           0 :                 pszNext++;
    5778           0 :             if (pszNext)
    5779           0 :                 dfMin = CPLAtof(pszNext);
    5780           0 :             if (pszNext)
    5781           0 :                 pszNext = strchr(pszNext, ' ');
    5782           0 :             if (pszNext)
    5783           0 :                 pszNext++;
    5784           0 :             if (pszNext)
    5785           0 :                 dfSec = CPLAtof(pszNext);
    5786           0 :             double dfVal = dfDeg + dfMin / 60 + dfSec / 3600;
    5787           0 :             if (chLast == 'W' || chLast == 'S')
    5788           0 :                 return -dfVal;
    5789             :             else
    5790           0 :                 return dfVal;
    5791             :         }
    5792           0 :         return CPLAtof(pszStr);
    5793             :     }
    5794             :     else
    5795             :     {
    5796           0 :         CPLError(CE_Warning, CPLE_AppDefined, "Unexpected type : %s",
    5797           0 :                  poObj->GetTypeName());
    5798           0 :         return 0;
    5799             :     }
    5800             : }
    5801             : 
    5802             : /************************************************************************/
    5803             : /*                            Get()                                */
    5804             : /************************************************************************/
    5805             : 
    5806           0 : static double Get(GDALPDFDictionary *poDict, const char *pszName)
    5807             : {
    5808           0 :     GDALPDFObject *poObj = poDict->Get(pszName);
    5809           0 :     if (poObj != nullptr)
    5810           0 :         return Get(poObj);
    5811           0 :     CPLError(CE_Failure, CPLE_AppDefined, "Cannot find parameter %s", pszName);
    5812           0 :     return 0;
    5813             : }
    5814             : 
    5815             : /************************************************************************/
    5816             : /*                   ParseLGIDictDictFirstPass()                        */
    5817             : /************************************************************************/
    5818             : 
    5819           0 : int PDFDataset::ParseLGIDictDictFirstPass(GDALPDFDictionary *poLGIDict,
    5820             :                                           int *pbIsBestCandidate)
    5821             : {
    5822           0 :     if (pbIsBestCandidate)
    5823           0 :         *pbIsBestCandidate = FALSE;
    5824             : 
    5825           0 :     if (poLGIDict == nullptr)
    5826           0 :         return FALSE;
    5827             : 
    5828             :     /* -------------------------------------------------------------------- */
    5829             :     /*      Extract Type attribute                                          */
    5830             :     /* -------------------------------------------------------------------- */
    5831           0 :     GDALPDFObject *poType = poLGIDict->Get("Type");
    5832           0 :     if (poType == nullptr)
    5833             :     {
    5834           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    5835             :                  "Cannot find Type of LGIDict object");
    5836           0 :         return FALSE;
    5837             :     }
    5838             : 
    5839           0 :     if (poType->GetType() != PDFObjectType_Name)
    5840             :     {
    5841           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    5842             :                  "Invalid type for Type of LGIDict object");
    5843           0 :         return FALSE;
    5844             :     }
    5845             : 
    5846           0 :     if (strcmp(poType->GetName().c_str(), "LGIDict") != 0)
    5847             :     {
    5848           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    5849             :                  "Invalid value for Type of LGIDict object : %s",
    5850           0 :                  poType->GetName().c_str());
    5851           0 :         return FALSE;
    5852             :     }
    5853             : 
    5854             :     /* -------------------------------------------------------------------- */
    5855             :     /*      Extract Version attribute                                       */
    5856             :     /* -------------------------------------------------------------------- */
    5857           0 :     GDALPDFObject *poVersion = poLGIDict->Get("Version");
    5858           0 :     if (poVersion == nullptr)
    5859             :     {
    5860           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    5861             :                  "Cannot find Version of LGIDict object");
    5862           0 :         return FALSE;
    5863             :     }
    5864             : 
    5865           0 :     if (poVersion->GetType() == PDFObjectType_String)
    5866             :     {
    5867             :         /* OGC best practice is 2.1 */
    5868           0 :         CPLDebug("PDF", "LGIDict Version : %s", poVersion->GetString().c_str());
    5869             :     }
    5870           0 :     else if (poVersion->GetType() == PDFObjectType_Int)
    5871             :     {
    5872             :         /* Old TerraGo is 2 */
    5873           0 :         CPLDebug("PDF", "LGIDict Version : %d", poVersion->GetInt());
    5874             :     }
    5875             : 
    5876             :     /* USGS PDF maps have several LGIDict. Keep the one whose description */
    5877             :     /* is "Map Layers" by default */
    5878             :     const char *pszNeatlineToSelect =
    5879           0 :         GetOption(papszOpenOptions, "NEATLINE", "Map Layers");
    5880             : 
    5881             :     /* -------------------------------------------------------------------- */
    5882             :     /*      Extract Neatline attribute                                      */
    5883             :     /* -------------------------------------------------------------------- */
    5884           0 :     GDALPDFObject *poNeatline = poLGIDict->Get("Neatline");
    5885           0 :     if (poNeatline != nullptr && poNeatline->GetType() == PDFObjectType_Array)
    5886             :     {
    5887           0 :         int nLength = poNeatline->GetArray()->GetLength();
    5888           0 :         if ((nLength % 2) != 0 || nLength < 4)
    5889             :         {
    5890           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5891             :                      "Invalid length for Neatline");
    5892           0 :             return FALSE;
    5893             :         }
    5894             : 
    5895           0 :         GDALPDFObject *poDescription = poLGIDict->Get("Description");
    5896           0 :         bool bIsAskedNeatline = false;
    5897           0 :         if (poDescription != nullptr &&
    5898           0 :             poDescription->GetType() == PDFObjectType_String)
    5899             :         {
    5900           0 :             CPLDebug("PDF", "Description = %s",
    5901           0 :                      poDescription->GetString().c_str());
    5902             : 
    5903           0 :             if (EQUAL(poDescription->GetString().c_str(), pszNeatlineToSelect))
    5904             :             {
    5905           0 :                 m_dfMaxArea = 1e300;
    5906           0 :                 bIsAskedNeatline = true;
    5907             :             }
    5908             :         }
    5909             : 
    5910           0 :         if (!bIsAskedNeatline)
    5911             :         {
    5912           0 :             double dfMinX = 0.0;
    5913           0 :             double dfMinY = 0.0;
    5914           0 :             double dfMaxX = 0.0;
    5915           0 :             double dfMaxY = 0.0;
    5916           0 :             for (int i = 0; i < nLength; i += 2)
    5917             :             {
    5918           0 :                 double dfX = Get(poNeatline, i);
    5919           0 :                 double dfY = Get(poNeatline, i + 1);
    5920           0 :                 if (i == 0 || dfX < dfMinX)
    5921           0 :                     dfMinX = dfX;
    5922           0 :                 if (i == 0 || dfY < dfMinY)
    5923           0 :                     dfMinY = dfY;
    5924           0 :                 if (i == 0 || dfX > dfMaxX)
    5925           0 :                     dfMaxX = dfX;
    5926           0 :                 if (i == 0 || dfY > dfMaxY)
    5927           0 :                     dfMaxY = dfY;
    5928             :             }
    5929           0 :             double dfArea = (dfMaxX - dfMinX) * (dfMaxY - dfMinY);
    5930           0 :             if (dfArea < m_dfMaxArea)
    5931             :             {
    5932           0 :                 CPLDebug("PDF", "Not the largest neatline. Skipping it");
    5933           0 :                 return TRUE;
    5934             :             }
    5935             : 
    5936           0 :             CPLDebug("PDF", "This is the largest neatline for now");
    5937           0 :             m_dfMaxArea = dfArea;
    5938             :         }
    5939             :         else
    5940           0 :             CPLDebug("PDF", "The \"%s\" registration will be selected",
    5941             :                      pszNeatlineToSelect);
    5942             : 
    5943           0 :         if (pbIsBestCandidate)
    5944           0 :             *pbIsBestCandidate = TRUE;
    5945             : 
    5946           0 :         delete m_poNeatLine;
    5947           0 :         m_poNeatLine = new OGRPolygon();
    5948           0 :         OGRLinearRing *poRing = new OGRLinearRing();
    5949           0 :         if (nLength == 4)
    5950             :         {
    5951             :             /* 2 points only ? They are the bounding box */
    5952           0 :             double dfX1 = Get(poNeatline, 0);
    5953           0 :             double dfY1 = Get(poNeatline, 1);
    5954           0 :             double dfX2 = Get(poNeatline, 2);
    5955           0 :             double dfY2 = Get(poNeatline, 3);
    5956           0 :             poRing->addPoint(dfX1, dfY1);
    5957           0 :             poRing->addPoint(dfX2, dfY1);
    5958           0 :             poRing->addPoint(dfX2, dfY2);
    5959           0 :             poRing->addPoint(dfX1, dfY2);
    5960             :         }
    5961             :         else
    5962             :         {
    5963           0 :             for (int i = 0; i < nLength; i += 2)
    5964             :             {
    5965           0 :                 double dfX = Get(poNeatline, i);
    5966           0 :                 double dfY = Get(poNeatline, i + 1);
    5967           0 :                 poRing->addPoint(dfX, dfY);
    5968             :             }
    5969             :         }
    5970           0 :         poRing->closeRings();
    5971           0 :         m_poNeatLine->addRingDirectly(poRing);
    5972             :     }
    5973             : 
    5974           0 :     return TRUE;
    5975             : }
    5976             : 
    5977             : /************************************************************************/
    5978             : /*                  ParseLGIDictDictSecondPass()                        */
    5979             : /************************************************************************/
    5980             : 
    5981           0 : int PDFDataset::ParseLGIDictDictSecondPass(GDALPDFDictionary *poLGIDict)
    5982             : {
    5983             :     int i;
    5984             : 
    5985             :     /* -------------------------------------------------------------------- */
    5986             :     /*      Extract Description attribute                                   */
    5987             :     /* -------------------------------------------------------------------- */
    5988           0 :     GDALPDFObject *poDescription = poLGIDict->Get("Description");
    5989           0 :     if (poDescription != nullptr &&
    5990           0 :         poDescription->GetType() == PDFObjectType_String)
    5991             :     {
    5992           0 :         CPLDebug("PDF", "Description = %s", poDescription->GetString().c_str());
    5993             :     }
    5994             : 
    5995             :     /* -------------------------------------------------------------------- */
    5996             :     /*      Extract CTM attribute                                           */
    5997             :     /* -------------------------------------------------------------------- */
    5998           0 :     GDALPDFObject *poCTM = poLGIDict->Get("CTM");
    5999           0 :     m_bHasCTM = false;
    6000           0 :     if (poCTM != nullptr && poCTM->GetType() == PDFObjectType_Array &&
    6001           0 :         CPLTestBool(CPLGetConfigOption("PDF_USE_CTM", "YES")))
    6002             :     {
    6003           0 :         int nLength = poCTM->GetArray()->GetLength();
    6004           0 :         if (nLength != 6)
    6005             :         {
    6006           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid length for CTM");
    6007           0 :             return FALSE;
    6008             :         }
    6009             : 
    6010           0 :         m_bHasCTM = true;
    6011           0 :         for (i = 0; i < nLength; i++)
    6012             :         {
    6013           0 :             m_adfCTM[i] = Get(poCTM, i);
    6014             :             /* Nullify rotation terms that are significantly smaller than */
    6015             :             /* scaling terms. */
    6016           0 :             if ((i == 1 || i == 2) &&
    6017           0 :                 fabs(m_adfCTM[i]) < fabs(m_adfCTM[0]) * 1e-10)
    6018           0 :                 m_adfCTM[i] = 0;
    6019           0 :             CPLDebug("PDF", "CTM[%d] = %.16g", i, m_adfCTM[i]);
    6020             :         }
    6021             :     }
    6022             : 
    6023             :     /* -------------------------------------------------------------------- */
    6024             :     /*      Extract Registration attribute                                  */
    6025             :     /* -------------------------------------------------------------------- */
    6026           0 :     GDALPDFObject *poRegistration = poLGIDict->Get("Registration");
    6027           0 :     if (poRegistration != nullptr &&
    6028           0 :         poRegistration->GetType() == PDFObjectType_Array)
    6029             :     {
    6030           0 :         GDALPDFArray *poRegistrationArray = poRegistration->GetArray();
    6031           0 :         int nLength = poRegistrationArray->GetLength();
    6032           0 :         if (nLength > 4 || (!m_bHasCTM && nLength >= 2) ||
    6033           0 :             CPLTestBool(CPLGetConfigOption("PDF_REPORT_GCPS", "NO")))
    6034             :         {
    6035           0 :             m_nGCPCount = 0;
    6036           0 :             m_pasGCPList =
    6037           0 :                 static_cast<GDAL_GCP *>(CPLCalloc(sizeof(GDAL_GCP), nLength));
    6038             : 
    6039           0 :             for (i = 0; i < nLength; i++)
    6040             :             {
    6041           0 :                 GDALPDFObject *poGCP = poRegistrationArray->Get(i);
    6042           0 :                 if (poGCP != nullptr &&
    6043           0 :                     poGCP->GetType() == PDFObjectType_Array &&
    6044           0 :                     poGCP->GetArray()->GetLength() == 4)
    6045             :                 {
    6046           0 :                     double dfUserX = Get(poGCP, 0);
    6047           0 :                     double dfUserY = Get(poGCP, 1);
    6048           0 :                     double dfX = Get(poGCP, 2);
    6049           0 :                     double dfY = Get(poGCP, 3);
    6050           0 :                     CPLDebug("PDF", "GCP[%d].userX = %.16g", i, dfUserX);
    6051           0 :                     CPLDebug("PDF", "GCP[%d].userY = %.16g", i, dfUserY);
    6052           0 :                     CPLDebug("PDF", "GCP[%d].x = %.16g", i, dfX);
    6053           0 :                     CPLDebug("PDF", "GCP[%d].y = %.16g", i, dfY);
    6054             : 
    6055             :                     char szID[32];
    6056           0 :                     snprintf(szID, sizeof(szID), "%d", m_nGCPCount + 1);
    6057           0 :                     m_pasGCPList[m_nGCPCount].pszId = CPLStrdup(szID);
    6058           0 :                     m_pasGCPList[m_nGCPCount].pszInfo = CPLStrdup("");
    6059           0 :                     m_pasGCPList[m_nGCPCount].dfGCPPixel = dfUserX;
    6060           0 :                     m_pasGCPList[m_nGCPCount].dfGCPLine = dfUserY;
    6061           0 :                     m_pasGCPList[m_nGCPCount].dfGCPX = dfX;
    6062           0 :                     m_pasGCPList[m_nGCPCount].dfGCPY = dfY;
    6063           0 :                     m_nGCPCount++;
    6064             :                 }
    6065             :             }
    6066             : 
    6067           0 :             if (m_nGCPCount == 0)
    6068             :             {
    6069           0 :                 CPLFree(m_pasGCPList);
    6070           0 :                 m_pasGCPList = nullptr;
    6071             :             }
    6072             :         }
    6073             :     }
    6074             : 
    6075           0 :     if (!m_bHasCTM && m_nGCPCount == 0)
    6076             :     {
    6077           0 :         CPLDebug("PDF", "Neither CTM nor Registration found");
    6078           0 :         return FALSE;
    6079             :     }
    6080             : 
    6081             :     /* -------------------------------------------------------------------- */
    6082             :     /*      Extract Projection attribute                                    */
    6083             :     /* -------------------------------------------------------------------- */
    6084           0 :     GDALPDFObject *poProjection = poLGIDict->Get("Projection");
    6085           0 :     if (poProjection == nullptr ||
    6086           0 :         poProjection->GetType() != PDFObjectType_Dictionary)
    6087             :     {
    6088           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Could not find Projection");
    6089           0 :         return FALSE;
    6090             :     }
    6091             : 
    6092           0 :     return ParseProjDict(poProjection->GetDictionary());
    6093             : }
    6094             : 
    6095             : /************************************************************************/
    6096             : /*                         ParseProjDict()                               */
    6097             : /************************************************************************/
    6098             : 
    6099           0 : int PDFDataset::ParseProjDict(GDALPDFDictionary *poProjDict)
    6100             : {
    6101           0 :     if (poProjDict == nullptr)
    6102           0 :         return FALSE;
    6103           0 :     OGRSpatialReference oSRS;
    6104           0 :     oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    6105             : 
    6106             :     /* -------------------------------------------------------------------- */
    6107             :     /*      Extract WKT attribute (GDAL extension)                          */
    6108             :     /* -------------------------------------------------------------------- */
    6109           0 :     GDALPDFObject *poWKT = poProjDict->Get("WKT");
    6110           0 :     if (poWKT != nullptr && poWKT->GetType() == PDFObjectType_String &&
    6111           0 :         CPLTestBool(CPLGetConfigOption("GDAL_PDF_OGC_BP_READ_WKT", "TRUE")))
    6112             :     {
    6113           0 :         CPLDebug("PDF", "Found WKT attribute (GDAL extension). Using it");
    6114           0 :         const char *pszWKTRead = poWKT->GetString().c_str();
    6115           0 :         if (pszWKTRead[0] != 0)
    6116           0 :             m_oSRS.importFromWkt(pszWKTRead);
    6117           0 :         return TRUE;
    6118             :     }
    6119             : 
    6120             :     /* -------------------------------------------------------------------- */
    6121             :     /*      Extract Type attribute                                          */
    6122             :     /* -------------------------------------------------------------------- */
    6123           0 :     GDALPDFObject *poType = poProjDict->Get("Type");
    6124           0 :     if (poType == nullptr)
    6125             :     {
    6126           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    6127             :                  "Cannot find Type of Projection object");
    6128           0 :         return FALSE;
    6129             :     }
    6130             : 
    6131           0 :     if (poType->GetType() != PDFObjectType_Name)
    6132             :     {
    6133           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    6134             :                  "Invalid type for Type of Projection object");
    6135           0 :         return FALSE;
    6136             :     }
    6137             : 
    6138           0 :     if (strcmp(poType->GetName().c_str(), "Projection") != 0)
    6139             :     {
    6140           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    6141             :                  "Invalid value for Type of Projection object : %s",
    6142           0 :                  poType->GetName().c_str());
    6143           0 :         return FALSE;
    6144             :     }
    6145             : 
    6146             :     /* -------------------------------------------------------------------- */
    6147             :     /*      Extract Datum attribute                                         */
    6148             :     /* -------------------------------------------------------------------- */
    6149           0 :     int bIsWGS84 = FALSE;
    6150           0 :     int bIsNAD83 = FALSE;
    6151             :     /* int bIsNAD27 = FALSE; */
    6152             : 
    6153           0 :     GDALPDFObject *poDatum = poProjDict->Get("Datum");
    6154           0 :     if (poDatum != nullptr)
    6155             :     {
    6156           0 :         if (poDatum->GetType() == PDFObjectType_String)
    6157             :         {
    6158             :             /* Using Annex A of
    6159             :              * http://portal.opengeospatial.org/files/?artifact_id=40537 */
    6160           0 :             const char *pszDatum = poDatum->GetString().c_str();
    6161           0 :             CPLDebug("PDF", "Datum = %s", pszDatum);
    6162           0 :             if (EQUAL(pszDatum, "WE") || EQUAL(pszDatum, "WGE"))
    6163             :             {
    6164           0 :                 bIsWGS84 = TRUE;
    6165           0 :                 oSRS.SetWellKnownGeogCS("WGS84");
    6166             :             }
    6167           0 :             else if (EQUAL(pszDatum, "NAR") || STARTS_WITH_CI(pszDatum, "NAR-"))
    6168             :             {
    6169           0 :                 bIsNAD83 = TRUE;
    6170           0 :                 oSRS.SetWellKnownGeogCS("NAD83");
    6171             :             }
    6172           0 :             else if (EQUAL(pszDatum, "NAS") || STARTS_WITH_CI(pszDatum, "NAS-"))
    6173             :             {
    6174             :                 /* bIsNAD27 = TRUE; */
    6175           0 :                 oSRS.SetWellKnownGeogCS("NAD27");
    6176             :             }
    6177           0 :             else if (EQUAL(pszDatum, "HEN")) /* HERAT North, Afghanistan */
    6178             :             {
    6179           0 :                 oSRS.SetGeogCS("unknown" /*const char * pszGeogName*/,
    6180             :                                "unknown" /*const char * pszDatumName */,
    6181             :                                "International 1924", 6378388, 297);
    6182           0 :                 oSRS.SetTOWGS84(-333, -222, 114);
    6183             :             }
    6184           0 :             else if (EQUAL(pszDatum, "ING-A")) /* INDIAN 1960, Vietnam 16N */
    6185             :             {
    6186           0 :                 oSRS.importFromEPSG(4131);
    6187             :             }
    6188           0 :             else if (EQUAL(pszDatum, "GDS")) /* Geocentric Datum of Australia */
    6189             :             {
    6190           0 :                 oSRS.importFromEPSG(4283);
    6191             :             }
    6192           0 :             else if (STARTS_WITH_CI(pszDatum, "OHA-")) /* Old Hawaiian */
    6193             :             {
    6194           0 :                 oSRS.importFromEPSG(4135); /* matches OHA-M (Mean) */
    6195           0 :                 if (!EQUAL(pszDatum, "OHA-M"))
    6196             :                 {
    6197           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    6198             :                              "Using OHA-M (Old Hawaiian Mean) definition for "
    6199             :                              "%s. Potential issue with datum shift parameters",
    6200             :                              pszDatum);
    6201           0 :                     OGR_SRSNode *poNode = oSRS.GetRoot();
    6202           0 :                     int iChild = poNode->FindChild("AUTHORITY");
    6203           0 :                     if (iChild != -1)
    6204           0 :                         poNode->DestroyChild(iChild);
    6205           0 :                     iChild = poNode->FindChild("DATUM");
    6206           0 :                     if (iChild != -1)
    6207             :                     {
    6208           0 :                         poNode = poNode->GetChild(iChild);
    6209           0 :                         iChild = poNode->FindChild("AUTHORITY");
    6210           0 :                         if (iChild != -1)
    6211           0 :                             poNode->DestroyChild(iChild);
    6212             :                     }
    6213             :                 }
    6214             :             }
    6215             :             else
    6216             :             {
    6217           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    6218             :                          "Unhandled (yet) value for Datum : %s. Defaulting to "
    6219             :                          "WGS84...",
    6220             :                          pszDatum);
    6221           0 :                 oSRS.SetGeogCS("unknown" /*const char * pszGeogName*/,
    6222             :                                "unknown" /*const char * pszDatumName */,
    6223             :                                "unknown", 6378137, 298.257223563);
    6224             :             }
    6225             :         }
    6226           0 :         else if (poDatum->GetType() == PDFObjectType_Dictionary)
    6227             :         {
    6228           0 :             GDALPDFDictionary *poDatumDict = poDatum->GetDictionary();
    6229             : 
    6230           0 :             GDALPDFObject *poDatumDescription = poDatumDict->Get("Description");
    6231           0 :             const char *pszDatumDescription = "unknown";
    6232           0 :             if (poDatumDescription != nullptr &&
    6233           0 :                 poDatumDescription->GetType() == PDFObjectType_String)
    6234           0 :                 pszDatumDescription = poDatumDescription->GetString().c_str();
    6235           0 :             CPLDebug("PDF", "Datum.Description = %s", pszDatumDescription);
    6236             : 
    6237           0 :             GDALPDFObject *poEllipsoid = poDatumDict->Get("Ellipsoid");
    6238           0 :             if (poEllipsoid == nullptr ||
    6239           0 :                 !(poEllipsoid->GetType() == PDFObjectType_String ||
    6240           0 :                   poEllipsoid->GetType() == PDFObjectType_Dictionary))
    6241             :             {
    6242           0 :                 CPLError(
    6243             :                     CE_Warning, CPLE_AppDefined,
    6244             :                     "Cannot find Ellipsoid in Datum. Defaulting to WGS84...");
    6245           0 :                 oSRS.SetGeogCS("unknown", pszDatumDescription, "unknown",
    6246             :                                6378137, 298.257223563);
    6247             :             }
    6248           0 :             else if (poEllipsoid->GetType() == PDFObjectType_String)
    6249             :             {
    6250           0 :                 const char *pszEllipsoid = poEllipsoid->GetString().c_str();
    6251           0 :                 CPLDebug("PDF", "Datum.Ellipsoid = %s", pszEllipsoid);
    6252           0 :                 if (EQUAL(pszEllipsoid, "WE"))
    6253             :                 {
    6254           0 :                     oSRS.SetGeogCS("unknown", pszDatumDescription, "WGS 84",
    6255             :                                    6378137, 298.257223563);
    6256             :                 }
    6257             :                 else
    6258             :                 {
    6259           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    6260             :                              "Unhandled (yet) value for Ellipsoid : %s. "
    6261             :                              "Defaulting to WGS84...",
    6262             :                              pszEllipsoid);
    6263           0 :                     oSRS.SetGeogCS("unknown", pszDatumDescription, pszEllipsoid,
    6264             :                                    6378137, 298.257223563);
    6265             :                 }
    6266             :             }
    6267             :             else  // if (poEllipsoid->GetType() == PDFObjectType_Dictionary)
    6268             :             {
    6269             :                 GDALPDFDictionary *poEllipsoidDict =
    6270           0 :                     poEllipsoid->GetDictionary();
    6271             : 
    6272             :                 GDALPDFObject *poEllipsoidDescription =
    6273           0 :                     poEllipsoidDict->Get("Description");
    6274           0 :                 const char *pszEllipsoidDescription = "unknown";
    6275           0 :                 if (poEllipsoidDescription != nullptr &&
    6276           0 :                     poEllipsoidDescription->GetType() == PDFObjectType_String)
    6277             :                     pszEllipsoidDescription =
    6278           0 :                         poEllipsoidDescription->GetString().c_str();
    6279           0 :                 CPLDebug("PDF", "Datum.Ellipsoid.Description = %s",
    6280             :                          pszEllipsoidDescription);
    6281             : 
    6282           0 :                 double dfSemiMajor = Get(poEllipsoidDict, "SemiMajorAxis");
    6283           0 :                 CPLDebug("PDF", "Datum.Ellipsoid.SemiMajorAxis = %.16g",
    6284             :                          dfSemiMajor);
    6285           0 :                 double dfInvFlattening = -1.0;
    6286             : 
    6287           0 :                 if (poEllipsoidDict->Get("InvFlattening"))
    6288             :                 {
    6289           0 :                     dfInvFlattening = Get(poEllipsoidDict, "InvFlattening");
    6290           0 :                     CPLDebug("PDF", "Datum.Ellipsoid.InvFlattening = %.16g",
    6291             :                              dfInvFlattening);
    6292             :                 }
    6293           0 :                 else if (poEllipsoidDict->Get("SemiMinorAxis"))
    6294             :                 {
    6295           0 :                     double dfSemiMinor = Get(poEllipsoidDict, "SemiMinorAxis");
    6296           0 :                     CPLDebug("PDF", "Datum.Ellipsoid.SemiMinorAxis = %.16g",
    6297             :                              dfSemiMinor);
    6298             :                     dfInvFlattening =
    6299           0 :                         OSRCalcInvFlattening(dfSemiMajor, dfSemiMinor);
    6300             :                 }
    6301             : 
    6302           0 :                 if (dfSemiMajor != 0.0 && dfInvFlattening != -1.0)
    6303             :                 {
    6304           0 :                     oSRS.SetGeogCS("unknown", pszDatumDescription,
    6305             :                                    pszEllipsoidDescription, dfSemiMajor,
    6306             :                                    dfInvFlattening);
    6307             :                 }
    6308             :                 else
    6309             :                 {
    6310           0 :                     CPLError(
    6311             :                         CE_Warning, CPLE_AppDefined,
    6312             :                         "Invalid Ellipsoid object. Defaulting to WGS84...");
    6313           0 :                     oSRS.SetGeogCS("unknown", pszDatumDescription,
    6314             :                                    pszEllipsoidDescription, 6378137,
    6315             :                                    298.257223563);
    6316             :                 }
    6317             :             }
    6318             : 
    6319           0 :             GDALPDFObject *poTOWGS84 = poDatumDict->Get("ToWGS84");
    6320           0 :             if (poTOWGS84 != nullptr &&
    6321           0 :                 poTOWGS84->GetType() == PDFObjectType_Dictionary)
    6322             :             {
    6323           0 :                 GDALPDFDictionary *poTOWGS84Dict = poTOWGS84->GetDictionary();
    6324           0 :                 double dx = Get(poTOWGS84Dict, "dx");
    6325           0 :                 double dy = Get(poTOWGS84Dict, "dy");
    6326           0 :                 double dz = Get(poTOWGS84Dict, "dz");
    6327           0 :                 if (poTOWGS84Dict->Get("rx") && poTOWGS84Dict->Get("ry") &&
    6328           0 :                     poTOWGS84Dict->Get("rz") && poTOWGS84Dict->Get("sf"))
    6329             :                 {
    6330           0 :                     double rx = Get(poTOWGS84Dict, "rx");
    6331           0 :                     double ry = Get(poTOWGS84Dict, "ry");
    6332           0 :                     double rz = Get(poTOWGS84Dict, "rz");
    6333           0 :                     double sf = Get(poTOWGS84Dict, "sf");
    6334           0 :                     oSRS.SetTOWGS84(dx, dy, dz, rx, ry, rz, sf);
    6335             :                 }
    6336             :                 else
    6337             :                 {
    6338           0 :                     oSRS.SetTOWGS84(dx, dy, dz);
    6339             :                 }
    6340             :             }
    6341             :         }
    6342             :     }
    6343             : 
    6344             :     /* -------------------------------------------------------------------- */
    6345             :     /*      Extract Hemisphere attribute                                    */
    6346             :     /* -------------------------------------------------------------------- */
    6347           0 :     CPLString osHemisphere;
    6348           0 :     GDALPDFObject *poHemisphere = poProjDict->Get("Hemisphere");
    6349           0 :     if (poHemisphere != nullptr &&
    6350           0 :         poHemisphere->GetType() == PDFObjectType_String)
    6351             :     {
    6352           0 :         osHemisphere = poHemisphere->GetString();
    6353             :     }
    6354             : 
    6355             :     /* -------------------------------------------------------------------- */
    6356             :     /*      Extract ProjectionType attribute                                */
    6357             :     /* -------------------------------------------------------------------- */
    6358           0 :     GDALPDFObject *poProjectionType = poProjDict->Get("ProjectionType");
    6359           0 :     if (poProjectionType == nullptr ||
    6360           0 :         poProjectionType->GetType() != PDFObjectType_String)
    6361             :     {
    6362           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    6363             :                  "Cannot find ProjectionType of Projection object");
    6364           0 :         return FALSE;
    6365             :     }
    6366           0 :     CPLString osProjectionType(poProjectionType->GetString());
    6367           0 :     CPLDebug("PDF", "Projection.ProjectionType = %s", osProjectionType.c_str());
    6368             : 
    6369             :     /* Unhandled: NONE, GEODETIC */
    6370             : 
    6371           0 :     if (EQUAL(osProjectionType, "GEOGRAPHIC"))
    6372             :     {
    6373             :         /* Nothing to do */
    6374             :     }
    6375             : 
    6376             :     /* Unhandled: LOCAL CARTESIAN, MG (MGRS) */
    6377             : 
    6378           0 :     else if (EQUAL(osProjectionType, "UT")) /* UTM */
    6379             :     {
    6380           0 :         const double dfZone = Get(poProjDict, "Zone");
    6381           0 :         if (dfZone >= 1 && dfZone <= 60)
    6382             :         {
    6383           0 :             int nZone = static_cast<int>(dfZone);
    6384           0 :             int bNorth = EQUAL(osHemisphere, "N");
    6385           0 :             if (bIsWGS84)
    6386           0 :                 oSRS.importFromEPSG(((bNorth) ? 32600 : 32700) + nZone);
    6387             :             else
    6388           0 :                 oSRS.SetUTM(nZone, bNorth);
    6389             :         }
    6390             :     }
    6391             : 
    6392           0 :     else if (EQUAL(osProjectionType,
    6393             :                    "UP")) /* Universal Polar Stereographic (UPS) */
    6394             :     {
    6395           0 :         int bNorth = EQUAL(osHemisphere, "N");
    6396           0 :         if (bIsWGS84)
    6397           0 :             oSRS.importFromEPSG((bNorth) ? 32661 : 32761);
    6398             :         else
    6399           0 :             oSRS.SetPS((bNorth) ? 90 : -90, 0, 0.994, 200000, 200000);
    6400             :     }
    6401             : 
    6402           0 :     else if (EQUAL(osProjectionType, "SPCS")) /* State Plane */
    6403             :     {
    6404           0 :         const double dfZone = Get(poProjDict, "Zone");
    6405           0 :         if (dfZone >= 0 && dfZone <= INT_MAX)
    6406             :         {
    6407           0 :             int nZone = static_cast<int>(dfZone);
    6408           0 :             oSRS.SetStatePlane(nZone, bIsNAD83);
    6409             :         }
    6410             :     }
    6411             : 
    6412           0 :     else if (EQUAL(osProjectionType, "AC")) /* Albers Equal Area Conic */
    6413             :     {
    6414           0 :         double dfStdP1 = Get(poProjDict, "StandardParallelOne");
    6415           0 :         double dfStdP2 = Get(poProjDict, "StandardParallelTwo");
    6416           0 :         double dfCenterLat = Get(poProjDict, "OriginLatitude");
    6417           0 :         double dfCenterLong = Get(poProjDict, "CentralMeridian");
    6418           0 :         double dfFalseEasting = Get(poProjDict, "FalseEasting");
    6419           0 :         double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
    6420           0 :         oSRS.SetACEA(dfStdP1, dfStdP2, dfCenterLat, dfCenterLong,
    6421             :                      dfFalseEasting, dfFalseNorthing);
    6422             :     }
    6423             : 
    6424           0 :     else if (EQUAL(osProjectionType, "AL")) /* Azimuthal Equidistant */
    6425             :     {
    6426           0 :         double dfCenterLat = Get(poProjDict, "OriginLatitude");
    6427           0 :         double dfCenterLong = Get(poProjDict, "CentralMeridian");
    6428           0 :         double dfFalseEasting = Get(poProjDict, "FalseEasting");
    6429           0 :         double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
    6430           0 :         oSRS.SetAE(dfCenterLat, dfCenterLong, dfFalseEasting, dfFalseNorthing);
    6431             :     }
    6432             : 
    6433           0 :     else if (EQUAL(osProjectionType, "BF")) /* Bonne */
    6434             :     {
    6435           0 :         double dfStdP1 = Get(poProjDict, "OriginLatitude");
    6436           0 :         double dfCentralMeridian = Get(poProjDict, "CentralMeridian");
    6437           0 :         double dfFalseEasting = Get(poProjDict, "FalseEasting");
    6438           0 :         double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
    6439           0 :         oSRS.SetBonne(dfStdP1, dfCentralMeridian, dfFalseEasting,
    6440             :                       dfFalseNorthing);
    6441             :     }
    6442             : 
    6443           0 :     else if (EQUAL(osProjectionType, "CS")) /* Cassini */
    6444             :     {
    6445           0 :         double dfCenterLat = Get(poProjDict, "OriginLatitude");
    6446           0 :         double dfCenterLong = Get(poProjDict, "CentralMeridian");
    6447           0 :         double dfFalseEasting = Get(poProjDict, "FalseEasting");
    6448           0 :         double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
    6449           0 :         oSRS.SetCS(dfCenterLat, dfCenterLong, dfFalseEasting, dfFalseNorthing);
    6450             :     }
    6451             : 
    6452           0 :     else if (EQUAL(osProjectionType, "LI")) /* Cylindrical Equal Area */
    6453             :     {
    6454           0 :         double dfStdP1 = Get(poProjDict, "OriginLatitude");
    6455           0 :         double dfCentralMeridian = Get(poProjDict, "CentralMeridian");
    6456           0 :         double dfFalseEasting = Get(poProjDict, "FalseEasting");
    6457           0 :         double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
    6458           0 :         oSRS.SetCEA(dfStdP1, dfCentralMeridian, dfFalseEasting,
    6459             :                     dfFalseNorthing);
    6460             :     }
    6461             : 
    6462           0 :     else if (EQUAL(osProjectionType, "EF")) /* Eckert IV */
    6463             :     {
    6464           0 :         double dfCentralMeridian = Get(poProjDict, "CentralMeridian");
    6465           0 :         double dfFalseEasting = Get(poProjDict, "FalseEasting");
    6466           0 :         double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
    6467           0 :         oSRS.SetEckertIV(dfCentralMeridian, dfFalseEasting, dfFalseNorthing);
    6468             :     }
    6469             : 
    6470           0 :     else if (EQUAL(osProjectionType, "ED")) /* Eckert VI */
    6471             :     {
    6472           0 :         double dfCentralMeridian = Get(poProjDict, "CentralMeridian");
    6473           0 :         double dfFalseEasting = Get(poProjDict, "FalseEasting");
    6474           0 :         double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
    6475           0 :         oSRS.SetEckertVI(dfCentralMeridian, dfFalseEasting, dfFalseNorthing);
    6476             :     }
    6477             : 
    6478           0 :     else if (EQUAL(osProjectionType, "CP")) /* Equidistant Cylindrical */
    6479             :     {
    6480           0 :         double dfCenterLat = Get(poProjDict, "StandardParallel");
    6481           0 :         double dfCenterLong = Get(poProjDict, "CentralMeridian");
    6482           0 :         double dfFalseEasting = Get(poProjDict, "FalseEasting");
    6483           0 :         double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
    6484           0 :         oSRS.SetEquirectangular(dfCenterLat, dfCenterLong, dfFalseEasting,
    6485             :                                 dfFalseNorthing);
    6486             :     }
    6487             : 
    6488           0 :     else if (EQUAL(osProjectionType, "GN")) /* Gnomonic */
    6489             :     {
    6490           0 :         double dfCenterLat = Get(poProjDict, "OriginLatitude");
    6491           0 :         double dfCenterLong = Get(poProjDict, "CentralMeridian");
    6492           0 :         double dfFalseEasting = Get(poProjDict, "FalseEasting");
    6493           0 :         double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
    6494           0 :         oSRS.SetGnomonic(dfCenterLat, dfCenterLong, dfFalseEasting,
    6495             :                          dfFalseNorthing);
    6496             :     }
    6497             : 
    6498           0 :     else if (EQUAL(osProjectionType, "LE")) /* Lambert Conformal Conic */
    6499             :     {
    6500           0 :         double dfStdP1 = Get(poProjDict, "StandardParallelOne");
    6501           0 :         double dfStdP2 = Get(poProjDict, "StandardParallelTwo");
    6502           0 :         double dfCenterLat = Get(poProjDict, "OriginLatitude");
    6503           0 :         double dfCenterLong = Get(poProjDict, "CentralMeridian");
    6504           0 :         double dfFalseEasting = Get(poProjDict, "FalseEasting");
    6505           0 :         double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
    6506           0 :         oSRS.SetLCC(dfStdP1, dfStdP2, dfCenterLat, dfCenterLong, dfFalseEasting,
    6507             :                     dfFalseNorthing);
    6508             :     }
    6509             : 
    6510           0 :     else if (EQUAL(osProjectionType, "MC")) /* Mercator */
    6511             :     {
    6512             : #ifdef not_supported
    6513             :         if (poProjDict->Get("StandardParallelOne") == nullptr)
    6514             : #endif
    6515             :         {
    6516           0 :             double dfCenterLat = Get(poProjDict, "OriginLatitude");
    6517           0 :             double dfCenterLong = Get(poProjDict, "CentralMeridian");
    6518           0 :             double dfScale = Get(poProjDict, "ScaleFactor");
    6519           0 :             double dfFalseEasting = Get(poProjDict, "FalseEasting");
    6520           0 :             double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
    6521           0 :             oSRS.SetMercator(dfCenterLat, dfCenterLong, dfScale, dfFalseEasting,
    6522             :                              dfFalseNorthing);
    6523             :         }
    6524             : #ifdef not_supported
    6525             :         else
    6526             :         {
    6527             :             double dfStdP1 = Get(poProjDict, "StandardParallelOne");
    6528             :             double dfCenterLat = poProjDict->Get("OriginLatitude")
    6529             :                                      ? Get(poProjDict, "OriginLatitude")
    6530             :                                      : 0;
    6531             :             double dfCenterLong = Get(poProjDict, "CentralMeridian");
    6532             :             double dfFalseEasting = Get(poProjDict, "FalseEasting");
    6533             :             double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
    6534             :             oSRS.SetMercator2SP(dfStdP1, dfCenterLat, dfCenterLong,
    6535             :                                 dfFalseEasting, dfFalseNorthing);
    6536             :         }
    6537             : #endif
    6538             :     }
    6539             : 
    6540           0 :     else if (EQUAL(osProjectionType, "MH")) /* Miller Cylindrical */
    6541             :     {
    6542           0 :         double dfCenterLat = 0 /* ? */;
    6543           0 :         double dfCenterLong = Get(poProjDict, "CentralMeridian");
    6544           0 :         double dfFalseEasting = Get(poProjDict, "FalseEasting");
    6545           0 :         double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
    6546           0 :         oSRS.SetMC(dfCenterLat, dfCenterLong, dfFalseEasting, dfFalseNorthing);
    6547             :     }
    6548             : 
    6549           0 :     else if (EQUAL(osProjectionType, "MP")) /* Mollweide */
    6550             :     {
    6551           0 :         double dfCentralMeridian = Get(poProjDict, "CentralMeridian");
    6552           0 :         double dfFalseEasting = Get(poProjDict, "FalseEasting");
    6553           0 :         double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
    6554           0 :         oSRS.SetMollweide(dfCentralMeridian, dfFalseEasting, dfFalseNorthing);
    6555             :     }
    6556             : 
    6557             :     /* Unhandled:  "NY" : Ney's (Modified Lambert Conformal Conic) */
    6558             : 
    6559           0 :     else if (EQUAL(osProjectionType, "NT")) /* New Zealand Map Grid */
    6560             :     {
    6561             :         /* No parameter specified in the PDF, so let's take the ones of
    6562             :          * EPSG:27200 */
    6563           0 :         double dfCenterLat = -41;
    6564           0 :         double dfCenterLong = 173;
    6565           0 :         double dfFalseEasting = 2510000;
    6566           0 :         double dfFalseNorthing = 6023150;
    6567           0 :         oSRS.SetNZMG(dfCenterLat, dfCenterLong, dfFalseEasting,
    6568             :                      dfFalseNorthing);
    6569             :     }
    6570             : 
    6571           0 :     else if (EQUAL(osProjectionType, "OC")) /* Oblique Mercator */
    6572             :     {
    6573           0 :         double dfCenterLat = Get(poProjDict, "OriginLatitude");
    6574           0 :         double dfLat1 = Get(poProjDict, "LatitudeOne");
    6575           0 :         double dfLong1 = Get(poProjDict, "LongitudeOne");
    6576           0 :         double dfLat2 = Get(poProjDict, "LatitudeTwo");
    6577           0 :         double dfLong2 = Get(poProjDict, "LongitudeTwo");
    6578           0 :         double dfScale = Get(poProjDict, "ScaleFactor");
    6579           0 :         double dfFalseEasting = Get(poProjDict, "FalseEasting");
    6580           0 :         double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
    6581           0 :         oSRS.SetHOM2PNO(dfCenterLat, dfLat1, dfLong1, dfLat2, dfLong2, dfScale,
    6582             :                         dfFalseEasting, dfFalseNorthing);
    6583             :     }
    6584             : 
    6585           0 :     else if (EQUAL(osProjectionType, "OD")) /* Orthographic */
    6586             :     {
    6587           0 :         double dfCenterLat = Get(poProjDict, "OriginLatitude");
    6588           0 :         double dfCenterLong = Get(poProjDict, "CentralMeridian");
    6589           0 :         double dfFalseEasting = Get(poProjDict, "FalseEasting");
    6590           0 :         double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
    6591           0 :         oSRS.SetOrthographic(dfCenterLat, dfCenterLong, dfFalseEasting,
    6592             :                              dfFalseNorthing);
    6593             :     }
    6594             : 
    6595           0 :     else if (EQUAL(osProjectionType, "PG")) /* Polar Stereographic */
    6596             :     {
    6597           0 :         double dfCenterLat = Get(poProjDict, "LatitudeTrueScale");
    6598           0 :         double dfCenterLong = Get(poProjDict, "LongitudeDownFromPole");
    6599           0 :         double dfScale = 1.0;
    6600           0 :         double dfFalseEasting = Get(poProjDict, "FalseEasting");
    6601           0 :         double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
    6602           0 :         oSRS.SetPS(dfCenterLat, dfCenterLong, dfScale, dfFalseEasting,
    6603             :                    dfFalseNorthing);
    6604             :     }
    6605             : 
    6606           0 :     else if (EQUAL(osProjectionType, "PH")) /* Polyconic */
    6607             :     {
    6608           0 :         double dfCenterLat = Get(poProjDict, "OriginLatitude");
    6609           0 :         double dfCenterLong = Get(poProjDict, "CentralMeridian");
    6610           0 :         double dfFalseEasting = Get(poProjDict, "FalseEasting");
    6611           0 :         double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
    6612           0 :         oSRS.SetPolyconic(dfCenterLat, dfCenterLong, dfFalseEasting,
    6613             :                           dfFalseNorthing);
    6614             :     }
    6615             : 
    6616           0 :     else if (EQUAL(osProjectionType, "SA")) /* Sinusoidal */
    6617             :     {
    6618           0 :         double dfCenterLong = Get(poProjDict, "CentralMeridian");
    6619           0 :         double dfFalseEasting = Get(poProjDict, "FalseEasting");
    6620           0 :         double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
    6621           0 :         oSRS.SetSinusoidal(dfCenterLong, dfFalseEasting, dfFalseNorthing);
    6622             :     }
    6623             : 
    6624           0 :     else if (EQUAL(osProjectionType, "SD")) /* Stereographic */
    6625             :     {
    6626           0 :         double dfCenterLat = Get(poProjDict, "OriginLatitude");
    6627           0 :         double dfCenterLong = Get(poProjDict, "CentralMeridian");
    6628           0 :         double dfScale = 1.0;
    6629           0 :         double dfFalseEasting = Get(poProjDict, "FalseEasting");
    6630           0 :         double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
    6631           0 :         oSRS.SetStereographic(dfCenterLat, dfCenterLong, dfScale,
    6632             :                               dfFalseEasting, dfFalseNorthing);
    6633             :     }
    6634             : 
    6635           0 :     else if (EQUAL(osProjectionType, "TC")) /* Transverse Mercator */
    6636             :     {
    6637           0 :         double dfCenterLat = Get(poProjDict, "OriginLatitude");
    6638           0 :         double dfCenterLong = Get(poProjDict, "CentralMeridian");
    6639           0 :         double dfScale = Get(poProjDict, "ScaleFactor");
    6640           0 :         double dfFalseEasting = Get(poProjDict, "FalseEasting");
    6641           0 :         double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
    6642           0 :         if (dfCenterLat == 0.0 && dfScale == 0.9996 && dfCenterLong >= -180 &&
    6643           0 :             dfCenterLong <= 180 && dfFalseEasting == 500000 &&
    6644           0 :             (dfFalseNorthing == 0.0 || dfFalseNorthing == 10000000.0))
    6645             :         {
    6646           0 :             const int nZone =
    6647           0 :                 static_cast<int>(floor((dfCenterLong + 180.0) / 6.0) + 1);
    6648           0 :             int bNorth = dfFalseNorthing == 0;
    6649           0 :             if (bIsWGS84)
    6650           0 :                 oSRS.importFromEPSG(((bNorth) ? 32600 : 32700) + nZone);
    6651           0 :             else if (bIsNAD83 && bNorth)
    6652           0 :                 oSRS.importFromEPSG(26900 + nZone);
    6653             :             else
    6654           0 :                 oSRS.SetUTM(nZone, bNorth);
    6655             :         }
    6656             :         else
    6657             :         {
    6658           0 :             oSRS.SetTM(dfCenterLat, dfCenterLong, dfScale, dfFalseEasting,
    6659             :                        dfFalseNorthing);
    6660             :         }
    6661             :     }
    6662             : 
    6663             :     /* Unhandled TX : Transverse Cylindrical Equal Area */
    6664             : 
    6665           0 :     else if (EQUAL(osProjectionType, "VA")) /* Van der Grinten */
    6666             :     {
    6667           0 :         double dfCenterLong = Get(poProjDict, "CentralMeridian");
    6668           0 :         double dfFalseEasting = Get(poProjDict, "FalseEasting");
    6669           0 :         double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
    6670           0 :         oSRS.SetVDG(dfCenterLong, dfFalseEasting, dfFalseNorthing);
    6671             :     }
    6672             : 
    6673             :     else
    6674             :     {
    6675           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    6676             :                  "Unhandled (yet) value for ProjectionType : %s",
    6677             :                  osProjectionType.c_str());
    6678           0 :         return FALSE;
    6679             :     }
    6680             : 
    6681             :     /* -------------------------------------------------------------------- */
    6682             :     /*      Extract Units attribute                                         */
    6683             :     /* -------------------------------------------------------------------- */
    6684           0 :     CPLString osUnits;
    6685           0 :     GDALPDFObject *poUnits = poProjDict->Get("Units");
    6686           0 :     if (poUnits != nullptr && poUnits->GetType() == PDFObjectType_String &&
    6687           0 :         !EQUAL(osProjectionType, "GEOGRAPHIC"))
    6688             :     {
    6689           0 :         osUnits = poUnits->GetString();
    6690           0 :         CPLDebug("PDF", "Projection.Units = %s", osUnits.c_str());
    6691             : 
    6692             :         // This is super weird. The false easting/northing of the SRS
    6693             :         // are expressed in the unit, but the geotransform is expressed in
    6694             :         // meters. Hence this hack to have an equivalent SRS definition, but
    6695             :         // with linear units converted in meters.
    6696           0 :         if (EQUAL(osUnits, "M"))
    6697           0 :             oSRS.SetLinearUnits("Meter", 1.0);
    6698           0 :         else if (EQUAL(osUnits, "FT"))
    6699             :         {
    6700           0 :             oSRS.SetLinearUnits("foot", 0.3048);
    6701           0 :             oSRS.SetLinearUnitsAndUpdateParameters("Meter", 1.0);
    6702             :         }
    6703           0 :         else if (EQUAL(osUnits, "USSF"))
    6704             :         {
    6705           0 :             oSRS.SetLinearUnits(SRS_UL_US_FOOT, CPLAtof(SRS_UL_US_FOOT_CONV));
    6706           0 :             oSRS.SetLinearUnitsAndUpdateParameters("Meter", 1.0);
    6707             :         }
    6708             :         else
    6709           0 :             CPLError(CE_Warning, CPLE_AppDefined, "Unhandled unit: %s",
    6710             :                      osUnits.c_str());
    6711             :     }
    6712             : 
    6713             :     /* -------------------------------------------------------------------- */
    6714             :     /*      Export SpatialRef                                               */
    6715             :     /* -------------------------------------------------------------------- */
    6716           0 :     m_oSRS = std::move(oSRS);
    6717             : 
    6718           0 :     return TRUE;
    6719             : }
    6720             : 
    6721             : /************************************************************************/
    6722             : /*                              ParseVP()                               */
    6723             : /************************************************************************/
    6724             : 
    6725         155 : int PDFDataset::ParseVP(GDALPDFObject *poVP, double dfMediaBoxWidth,
    6726             :                         double dfMediaBoxHeight)
    6727             : {
    6728             :     int i;
    6729             : 
    6730         155 :     if (poVP->GetType() != PDFObjectType_Array)
    6731           0 :         return FALSE;
    6732             : 
    6733         155 :     GDALPDFArray *poVPArray = poVP->GetArray();
    6734             : 
    6735         155 :     int nLength = poVPArray->GetLength();
    6736         155 :     CPLDebug("PDF", "VP length = %d", nLength);
    6737         155 :     if (nLength < 1)
    6738           0 :         return FALSE;
    6739             : 
    6740             :     /* -------------------------------------------------------------------- */
    6741             :     /*      Find the largest BBox                                           */
    6742             :     /* -------------------------------------------------------------------- */
    6743             :     const char *pszNeatlineToSelect =
    6744         155 :         GetOption(papszOpenOptions, "NEATLINE", "Map Layers");
    6745             : 
    6746         155 :     int iLargest = 0;
    6747         155 :     int iRequestedVP = -1;
    6748         155 :     double dfLargestArea = 0;
    6749             : 
    6750         321 :     for (i = 0; i < nLength; i++)
    6751             :     {
    6752         166 :         GDALPDFObject *poVPElt = poVPArray->Get(i);
    6753         332 :         if (poVPElt == nullptr ||
    6754         166 :             poVPElt->GetType() != PDFObjectType_Dictionary)
    6755             :         {
    6756           0 :             return FALSE;
    6757             :         }
    6758             : 
    6759         166 :         GDALPDFDictionary *poVPEltDict = poVPElt->GetDictionary();
    6760             : 
    6761         166 :         GDALPDFObject *poMeasure = poVPEltDict->Get("Measure");
    6762         332 :         if (poMeasure == nullptr ||
    6763         166 :             poMeasure->GetType() != PDFObjectType_Dictionary)
    6764             :         {
    6765           0 :             continue;
    6766             :         }
    6767             :         /* --------------------------------------------------------------------
    6768             :          */
    6769             :         /*      Extract Subtype attribute */
    6770             :         /* --------------------------------------------------------------------
    6771             :          */
    6772         166 :         GDALPDFDictionary *poMeasureDict = poMeasure->GetDictionary();
    6773         166 :         GDALPDFObject *poSubtype = poMeasureDict->Get("Subtype");
    6774         166 :         if (poSubtype == nullptr || poSubtype->GetType() != PDFObjectType_Name)
    6775             :         {
    6776           0 :             continue;
    6777             :         }
    6778             : 
    6779         166 :         CPLDebug("PDF", "Subtype = %s", poSubtype->GetName().c_str());
    6780         166 :         if (!EQUAL(poSubtype->GetName().c_str(), "GEO"))
    6781             :         {
    6782           0 :             continue;
    6783             :         }
    6784             : 
    6785         166 :         GDALPDFObject *poName = poVPEltDict->Get("Name");
    6786         166 :         if (poName != nullptr && poName->GetType() == PDFObjectType_String)
    6787             :         {
    6788         164 :             CPLDebug("PDF", "Name = %s", poName->GetString().c_str());
    6789         164 :             if (EQUAL(poName->GetString().c_str(), pszNeatlineToSelect))
    6790             :             {
    6791           0 :                 iRequestedVP = i;
    6792             :             }
    6793             :         }
    6794             : 
    6795         166 :         GDALPDFObject *poBBox = poVPEltDict->Get("BBox");
    6796         166 :         if (poBBox == nullptr || poBBox->GetType() != PDFObjectType_Array)
    6797             :         {
    6798           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot find Bbox object");
    6799           0 :             return FALSE;
    6800             :         }
    6801             : 
    6802         166 :         int nBboxLength = poBBox->GetArray()->GetLength();
    6803         166 :         if (nBboxLength != 4)
    6804             :         {
    6805           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    6806             :                      "Invalid length for Bbox object");
    6807           0 :             return FALSE;
    6808             :         }
    6809             : 
    6810             :         double adfBBox[4];
    6811         166 :         adfBBox[0] = Get(poBBox, 0);
    6812         166 :         adfBBox[1] = Get(poBBox, 1);
    6813         166 :         adfBBox[2] = Get(poBBox, 2);
    6814         166 :         adfBBox[3] = Get(poBBox, 3);
    6815         166 :         double dfArea =
    6816         166 :             fabs(adfBBox[2] - adfBBox[0]) * fabs(adfBBox[3] - adfBBox[1]);
    6817         166 :         if (dfArea > dfLargestArea)
    6818             :         {
    6819         155 :             iLargest = i;
    6820         155 :             dfLargestArea = dfArea;
    6821             :         }
    6822             :     }
    6823             : 
    6824         155 :     if (nLength > 1)
    6825             :     {
    6826          11 :         CPLDebug("PDF", "Largest BBox in VP array is element %d", iLargest);
    6827             :     }
    6828             : 
    6829         155 :     GDALPDFObject *poVPElt = nullptr;
    6830             : 
    6831         155 :     if (iRequestedVP > -1)
    6832             :     {
    6833           0 :         CPLDebug("PDF", "Requested NEATLINE BBox in VP array is element %d",
    6834             :                  iRequestedVP);
    6835           0 :         poVPElt = poVPArray->Get(iRequestedVP);
    6836             :     }
    6837             :     else
    6838             :     {
    6839         155 :         poVPElt = poVPArray->Get(iLargest);
    6840             :     }
    6841             : 
    6842         155 :     if (poVPElt == nullptr || poVPElt->GetType() != PDFObjectType_Dictionary)
    6843             :     {
    6844           0 :         return FALSE;
    6845             :     }
    6846             : 
    6847         155 :     GDALPDFDictionary *poVPEltDict = poVPElt->GetDictionary();
    6848             : 
    6849         155 :     GDALPDFObject *poBBox = poVPEltDict->Get("BBox");
    6850         155 :     if (poBBox == nullptr || poBBox->GetType() != PDFObjectType_Array)
    6851             :     {
    6852           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find Bbox object");
    6853           0 :         return FALSE;
    6854             :     }
    6855             : 
    6856         155 :     int nBboxLength = poBBox->GetArray()->GetLength();
    6857         155 :     if (nBboxLength != 4)
    6858             :     {
    6859           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid length for Bbox object");
    6860           0 :         return FALSE;
    6861             :     }
    6862             : 
    6863         155 :     double dfULX = Get(poBBox, 0);
    6864         155 :     double dfULY = dfMediaBoxHeight - Get(poBBox, 1);
    6865         155 :     double dfLRX = Get(poBBox, 2);
    6866         155 :     double dfLRY = dfMediaBoxHeight - Get(poBBox, 3);
    6867             : 
    6868             :     /* -------------------------------------------------------------------- */
    6869             :     /*      Extract Measure attribute                                       */
    6870             :     /* -------------------------------------------------------------------- */
    6871         155 :     GDALPDFObject *poMeasure = poVPEltDict->Get("Measure");
    6872         310 :     if (poMeasure == nullptr ||
    6873         155 :         poMeasure->GetType() != PDFObjectType_Dictionary)
    6874             :     {
    6875           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find Measure object");
    6876           0 :         return FALSE;
    6877             :     }
    6878             : 
    6879         155 :     int bRet = ParseMeasure(poMeasure, dfMediaBoxWidth, dfMediaBoxHeight, dfULX,
    6880             :                             dfULY, dfLRX, dfLRY);
    6881             : 
    6882             :     /* -------------------------------------------------------------------- */
    6883             :     /*      Extract PointData attribute                                     */
    6884             :     /* -------------------------------------------------------------------- */
    6885         155 :     GDALPDFObject *poPointData = poVPEltDict->Get("PtData");
    6886         155 :     if (poPointData != nullptr &&
    6887           0 :         poPointData->GetType() == PDFObjectType_Dictionary)
    6888             :     {
    6889           0 :         CPLDebug("PDF", "Found PointData");
    6890             :     }
    6891             : 
    6892         155 :     return bRet;
    6893             : }
    6894             : 
    6895             : /************************************************************************/
    6896             : /*                           ParseMeasure()                             */
    6897             : /************************************************************************/
    6898             : 
    6899         155 : int PDFDataset::ParseMeasure(GDALPDFObject *poMeasure, double dfMediaBoxWidth,
    6900             :                              double dfMediaBoxHeight, double dfULX,
    6901             :                              double dfULY, double dfLRX, double dfLRY)
    6902             : {
    6903         155 :     GDALPDFDictionary *poMeasureDict = poMeasure->GetDictionary();
    6904             : 
    6905             :     /* -------------------------------------------------------------------- */
    6906             :     /*      Extract Subtype attribute                                       */
    6907             :     /* -------------------------------------------------------------------- */
    6908         155 :     GDALPDFObject *poSubtype = poMeasureDict->Get("Subtype");
    6909         155 :     if (poSubtype == nullptr || poSubtype->GetType() != PDFObjectType_Name)
    6910             :     {
    6911           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find Subtype object");
    6912           0 :         return FALSE;
    6913             :     }
    6914             : 
    6915         155 :     CPLDebug("PDF", "Subtype = %s", poSubtype->GetName().c_str());
    6916         155 :     if (!EQUAL(poSubtype->GetName().c_str(), "GEO"))
    6917           0 :         return FALSE;
    6918             : 
    6919             :     /* -------------------------------------------------------------------- */
    6920             :     /*      Extract Bounds attribute (optional)                             */
    6921             :     /* -------------------------------------------------------------------- */
    6922             : 
    6923             :     /* http://acrobatusers.com/sites/default/files/gallery_pictures/SEVERODVINSK.pdf
    6924             :      */
    6925             :     /* has lgit:LPTS, lgit:GPTS and lgit:Bounds that have more precision than */
    6926             :     /* LPTS, GPTS and Bounds. Use those ones */
    6927             : 
    6928         155 :     GDALPDFObject *poBounds = poMeasureDict->Get("lgit:Bounds");
    6929         155 :     if (poBounds != nullptr && poBounds->GetType() == PDFObjectType_Array)
    6930             :     {
    6931           0 :         CPLDebug("PDF", "Using lgit:Bounds");
    6932             :     }
    6933         308 :     else if ((poBounds = poMeasureDict->Get("Bounds")) == nullptr ||
    6934         153 :              poBounds->GetType() != PDFObjectType_Array)
    6935             :     {
    6936           2 :         poBounds = nullptr;
    6937             :     }
    6938             : 
    6939         155 :     if (poBounds != nullptr)
    6940             :     {
    6941         153 :         int nBoundsLength = poBounds->GetArray()->GetLength();
    6942         153 :         if (nBoundsLength == 8)
    6943             :         {
    6944             :             double adfBounds[8];
    6945        1296 :             for (int i = 0; i < 8; i++)
    6946             :             {
    6947        1152 :                 adfBounds[i] = Get(poBounds, i);
    6948        1152 :                 CPLDebug("PDF", "Bounds[%d] = %f", i, adfBounds[i]);
    6949             :             }
    6950             : 
    6951             :             // TODO we should use it to restrict the neatline but
    6952             :             // I have yet to set a sample where bounds are not the four
    6953             :             // corners of the unit square.
    6954             :         }
    6955             :     }
    6956             : 
    6957             :     /* -------------------------------------------------------------------- */
    6958             :     /*      Extract GPTS attribute                                          */
    6959             :     /* -------------------------------------------------------------------- */
    6960         155 :     GDALPDFObject *poGPTS = poMeasureDict->Get("lgit:GPTS");
    6961         155 :     if (poGPTS != nullptr && poGPTS->GetType() == PDFObjectType_Array)
    6962             :     {
    6963           0 :         CPLDebug("PDF", "Using lgit:GPTS");
    6964             :     }
    6965         310 :     else if ((poGPTS = poMeasureDict->Get("GPTS")) == nullptr ||
    6966         155 :              poGPTS->GetType() != PDFObjectType_Array)
    6967             :     {
    6968           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find GPTS object");
    6969           0 :         return FALSE;
    6970             :     }
    6971             : 
    6972         155 :     int nGPTSLength = poGPTS->GetArray()->GetLength();
    6973         155 :     if ((nGPTSLength % 2) != 0 || nGPTSLength < 6)
    6974             :     {
    6975           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid length for GPTS object");
    6976           0 :         return FALSE;
    6977             :     }
    6978             : 
    6979         310 :     std::vector<double> adfGPTS(nGPTSLength);
    6980        1395 :     for (int i = 0; i < nGPTSLength; i++)
    6981             :     {
    6982        1240 :         adfGPTS[i] = Get(poGPTS, i);
    6983        1240 :         CPLDebug("PDF", "GPTS[%d] = %.18f", i, adfGPTS[i]);
    6984             :     }
    6985             : 
    6986             :     /* -------------------------------------------------------------------- */
    6987             :     /*      Extract LPTS attribute                                          */
    6988             :     /* -------------------------------------------------------------------- */
    6989         155 :     GDALPDFObject *poLPTS = poMeasureDict->Get("lgit:LPTS");
    6990         155 :     if (poLPTS != nullptr && poLPTS->GetType() == PDFObjectType_Array)
    6991             :     {
    6992           0 :         CPLDebug("PDF", "Using lgit:LPTS");
    6993             :     }
    6994         310 :     else if ((poLPTS = poMeasureDict->Get("LPTS")) == nullptr ||
    6995         155 :              poLPTS->GetType() != PDFObjectType_Array)
    6996             :     {
    6997           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find LPTS object");
    6998           0 :         return FALSE;
    6999             :     }
    7000             : 
    7001         155 :     int nLPTSLength = poLPTS->GetArray()->GetLength();
    7002         155 :     if (nLPTSLength != nGPTSLength)
    7003             :     {
    7004           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid length for LPTS object");
    7005           0 :         return FALSE;
    7006             :     }
    7007             : 
    7008         310 :     std::vector<double> adfLPTS(nLPTSLength);
    7009        1395 :     for (int i = 0; i < nLPTSLength; i++)
    7010             :     {
    7011        1240 :         adfLPTS[i] = Get(poLPTS, i);
    7012        1240 :         CPLDebug("PDF", "LPTS[%d] = %f", i, adfLPTS[i]);
    7013             :     }
    7014             : 
    7015             :     /* -------------------------------------------------------------------- */
    7016             :     /*      Extract GCS attribute                                           */
    7017             :     /* -------------------------------------------------------------------- */
    7018         155 :     GDALPDFObject *poGCS = poMeasureDict->Get("GCS");
    7019         155 :     if (poGCS == nullptr || poGCS->GetType() != PDFObjectType_Dictionary)
    7020             :     {
    7021           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find GCS object");
    7022           0 :         return FALSE;
    7023             :     }
    7024             : 
    7025         155 :     GDALPDFDictionary *poGCSDict = poGCS->GetDictionary();
    7026             : 
    7027             :     /* -------------------------------------------------------------------- */
    7028             :     /*      Extract GCS.Type attribute                                      */
    7029             :     /* -------------------------------------------------------------------- */
    7030         155 :     GDALPDFObject *poGCSType = poGCSDict->Get("Type");
    7031         155 :     if (poGCSType == nullptr || poGCSType->GetType() != PDFObjectType_Name)
    7032             :     {
    7033           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find GCS.Type object");
    7034           0 :         return FALSE;
    7035             :     }
    7036             : 
    7037         155 :     CPLDebug("PDF", "GCS.Type = %s", poGCSType->GetName().c_str());
    7038             : 
    7039             :     /* -------------------------------------------------------------------- */
    7040             :     /*      Extract EPSG attribute                                          */
    7041             :     /* -------------------------------------------------------------------- */
    7042         155 :     GDALPDFObject *poEPSG = poGCSDict->Get("EPSG");
    7043         155 :     int nEPSGCode = 0;
    7044         155 :     if (poEPSG != nullptr && poEPSG->GetType() == PDFObjectType_Int)
    7045             :     {
    7046         129 :         nEPSGCode = poEPSG->GetInt();
    7047         129 :         CPLDebug("PDF", "GCS.EPSG = %d", nEPSGCode);
    7048             :     }
    7049             : 
    7050             :     /* -------------------------------------------------------------------- */
    7051             :     /*      Extract GCS.WKT attribute                                       */
    7052             :     /* -------------------------------------------------------------------- */
    7053         155 :     GDALPDFObject *poGCSWKT = poGCSDict->Get("WKT");
    7054         155 :     if (poGCSWKT != nullptr && poGCSWKT->GetType() != PDFObjectType_String)
    7055             :     {
    7056           0 :         poGCSWKT = nullptr;
    7057             :     }
    7058             : 
    7059         155 :     if (poGCSWKT != nullptr)
    7060         153 :         CPLDebug("PDF", "GCS.WKT = %s", poGCSWKT->GetString().c_str());
    7061             : 
    7062         155 :     if (nEPSGCode <= 0 && poGCSWKT == nullptr)
    7063             :     {
    7064           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    7065             :                  "Cannot find GCS.WKT or GCS.EPSG objects");
    7066           0 :         return FALSE;
    7067             :     }
    7068             : 
    7069         155 :     if (poGCSWKT != nullptr)
    7070             :     {
    7071         153 :         m_oSRS.importFromWkt(poGCSWKT->GetString().c_str());
    7072             :     }
    7073             : 
    7074         155 :     bool bSRSOK = false;
    7075         155 :     if (nEPSGCode != 0)
    7076             :     {
    7077             :         // At time of writing EPSG CRS codes are <= 32767.
    7078             :         // The usual practice is that codes >= 100000 are in the ESRI namespace
    7079             :         // instead
    7080         129 :         if (nEPSGCode >= 100000)
    7081             :         {
    7082           4 :             CPLErrorHandlerPusher oHandler(CPLQuietErrorHandler);
    7083           4 :             OGRSpatialReference oSRS_ESRI;
    7084           2 :             oSRS_ESRI.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    7085           2 :             if (oSRS_ESRI.SetFromUserInput(CPLSPrintf("ESRI:%d", nEPSGCode)) ==
    7086             :                 OGRERR_NONE)
    7087             :             {
    7088           2 :                 bSRSOK = true;
    7089             : 
    7090             :                 // Check consistency of ESRI:xxxx and WKT definitions
    7091           2 :                 if (poGCSWKT != nullptr)
    7092             :                 {
    7093           3 :                     if (!m_oSRS.GetName() ||
    7094           1 :                         (!EQUAL(oSRS_ESRI.GetName(), m_oSRS.GetName()) &&
    7095           0 :                          !oSRS_ESRI.IsSame(&m_oSRS)))
    7096             :                     {
    7097           1 :                         CPLDebug("PDF",
    7098             :                                  "Definition from ESRI:%d and WKT=%s do not "
    7099             :                                  "match. Using WKT string",
    7100           1 :                                  nEPSGCode, poGCSWKT->GetString().c_str());
    7101           1 :                         bSRSOK = false;
    7102             :                     }
    7103             :                 }
    7104           2 :                 if (bSRSOK)
    7105             :                 {
    7106           1 :                     m_oSRS = std::move(oSRS_ESRI);
    7107             :                 }
    7108             :             }
    7109             :         }
    7110         127 :         else if (m_oSRS.importFromEPSG(nEPSGCode) == OGRERR_NONE)
    7111             :         {
    7112         127 :             bSRSOK = true;
    7113             :         }
    7114             :     }
    7115             : 
    7116         155 :     if (!bSRSOK)
    7117             :     {
    7118          27 :         if (poGCSWKT == nullptr)
    7119             :         {
    7120           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    7121             :                      "Cannot resolve EPSG object, and GCS.WKT not found");
    7122           0 :             return FALSE;
    7123             :         }
    7124             : 
    7125          27 :         if (m_oSRS.importFromWkt(poGCSWKT->GetString().c_str()) != OGRERR_NONE)
    7126             :         {
    7127           1 :             m_oSRS.Clear();
    7128           1 :             return FALSE;
    7129             :         }
    7130             :     }
    7131             : 
    7132             :     /* -------------------------------------------------------------------- */
    7133             :     /*      Compute geotransform                                            */
    7134             :     /* -------------------------------------------------------------------- */
    7135         154 :     OGRSpatialReference *poSRSGeog = m_oSRS.CloneGeogCS();
    7136             : 
    7137             :     /* Files found at
    7138             :      * http://carto.iict.ch/blog/publications-cartographiques-au-format-geospatial-pdf/
    7139             :      */
    7140             :     /* are in a PROJCS. However the coordinates in GPTS array are not in (lat,
    7141             :      * long) as required by the */
    7142             :     /* ISO 32000 supplement spec, but in (northing, easting). Adobe reader is
    7143             :      * able to understand that, */
    7144             :     /* so let's also try to do it with a heuristics. */
    7145             : 
    7146         154 :     bool bReproject = true;
    7147         154 :     if (m_oSRS.IsProjected())
    7148             :     {
    7149         560 :         for (int i = 0; i < nGPTSLength / 2; i++)
    7150             :         {
    7151         448 :             if (fabs(adfGPTS[2 * i]) > 91 || fabs(adfGPTS[2 * i + 1]) > 361)
    7152             :             {
    7153           0 :                 CPLDebug("PDF", "GPTS coordinates seems to be in (northing, "
    7154             :                                 "easting), which is non-standard");
    7155           0 :                 bReproject = false;
    7156           0 :                 break;
    7157             :             }
    7158             :         }
    7159             :     }
    7160             : 
    7161         154 :     OGRCoordinateTransformation *poCT = nullptr;
    7162         154 :     if (bReproject)
    7163             :     {
    7164         154 :         poCT = OGRCreateCoordinateTransformation(poSRSGeog, &m_oSRS);
    7165         154 :         if (poCT == nullptr)
    7166             :         {
    7167           0 :             delete poSRSGeog;
    7168           0 :             m_oSRS.Clear();
    7169           0 :             return FALSE;
    7170             :         }
    7171             :     }
    7172             : 
    7173         308 :     std::vector<GDAL_GCP> asGCPS(nGPTSLength / 2);
    7174             : 
    7175             :     /* Create NEATLINE */
    7176         154 :     OGRLinearRing *poRing = nullptr;
    7177         154 :     if (nGPTSLength == 8)
    7178             :     {
    7179         154 :         m_poNeatLine = new OGRPolygon();
    7180         154 :         poRing = new OGRLinearRing();
    7181         154 :         m_poNeatLine->addRingDirectly(poRing);
    7182             :     }
    7183             : 
    7184         770 :     for (int i = 0; i < nGPTSLength / 2; i++)
    7185             :     {
    7186             :         /* We probably assume LPTS is 0 or 1 */
    7187        1232 :         asGCPS[i].dfGCPPixel =
    7188         616 :             (dfULX * (1 - adfLPTS[2 * i + 0]) + dfLRX * adfLPTS[2 * i + 0]) /
    7189         616 :             dfMediaBoxWidth * nRasterXSize;
    7190        1232 :         asGCPS[i].dfGCPLine =
    7191         616 :             (dfULY * (1 - adfLPTS[2 * i + 1]) + dfLRY * adfLPTS[2 * i + 1]) /
    7192         616 :             dfMediaBoxHeight * nRasterYSize;
    7193             : 
    7194         616 :         double lat = adfGPTS[2 * i];
    7195         616 :         double lon = adfGPTS[2 * i + 1];
    7196         616 :         double x = lon;
    7197         616 :         double y = lat;
    7198         616 :         if (bReproject)
    7199             :         {
    7200         616 :             if (!poCT->Transform(1, &x, &y, nullptr))
    7201             :             {
    7202           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    7203             :                          "Cannot reproject (%f, %f)", lon, lat);
    7204           0 :                 delete poSRSGeog;
    7205           0 :                 delete poCT;
    7206           0 :                 m_oSRS.Clear();
    7207           0 :                 return FALSE;
    7208             :             }
    7209             :         }
    7210             : 
    7211         616 :         x = ROUND_IF_CLOSE(x);
    7212         616 :         y = ROUND_IF_CLOSE(y);
    7213             : 
    7214         616 :         asGCPS[i].dfGCPX = x;
    7215         616 :         asGCPS[i].dfGCPY = y;
    7216             : 
    7217         616 :         if (poRing)
    7218         616 :             poRing->addPoint(x, y);
    7219             :     }
    7220             : 
    7221         154 :     delete poSRSGeog;
    7222         154 :     delete poCT;
    7223             : 
    7224         154 :     if (!GDALGCPsToGeoTransform(nGPTSLength / 2, asGCPS.data(), m_gt.data(),
    7225             :                                 FALSE))
    7226             :     {
    7227           0 :         CPLDebug("PDF",
    7228             :                  "Could not compute GT with exact match. Try with approximate");
    7229           0 :         if (!GDALGCPsToGeoTransform(nGPTSLength / 2, asGCPS.data(), m_gt.data(),
    7230             :                                     TRUE))
    7231             :         {
    7232           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    7233             :                      "Could not compute GT with approximate match.");
    7234           0 :             return FALSE;
    7235             :         }
    7236             :     }
    7237         154 :     m_bGeoTransformValid = true;
    7238             : 
    7239             :     // If the non scaling terms of the geotransform are significantly smaller
    7240             :     // than the pixel size, then nullify them as being just artifacts of
    7241             :     //  reprojection and GDALGCPsToGeoTransform() numerical imprecisions.
    7242         154 :     const double dfPixelSize = std::min(fabs(m_gt[1]), fabs(m_gt[5]));
    7243         154 :     const double dfRotationShearTerm = std::max(fabs(m_gt[2]), fabs(m_gt[4]));
    7244         161 :     if (dfRotationShearTerm < 1e-5 * dfPixelSize ||
    7245           7 :         (m_bUseLib.test(PDFLIB_PDFIUM) &&
    7246         154 :          std::min(fabs(m_gt[2]), fabs(m_gt[4])) < 1e-5 * dfPixelSize))
    7247             :     {
    7248         147 :         dfLRX = m_gt[0] + nRasterXSize * m_gt[1] + nRasterYSize * m_gt[2];
    7249         147 :         dfLRY = m_gt[3] + nRasterXSize * m_gt[4] + nRasterYSize * m_gt[5];
    7250         147 :         m_gt[1] = (dfLRX - m_gt[0]) / nRasterXSize;
    7251         147 :         m_gt[5] = (dfLRY - m_gt[3]) / nRasterYSize;
    7252         147 :         m_gt[2] = m_gt[4] = 0;
    7253             :     }
    7254             : 
    7255         154 :     return TRUE;
    7256             : }
    7257             : 
    7258             : /************************************************************************/
    7259             : /*                          GetSpatialRef()                            */
    7260             : /************************************************************************/
    7261             : 
    7262         225 : const OGRSpatialReference *PDFDataset::GetSpatialRef() const
    7263             : {
    7264         225 :     const auto poSRS = GDALPamDataset::GetSpatialRef();
    7265         225 :     if (poSRS)
    7266         200 :         return poSRS;
    7267             : 
    7268          25 :     if (!m_oSRS.IsEmpty() && m_bGeoTransformValid)
    7269          24 :         return &m_oSRS;
    7270           1 :     return nullptr;
    7271             : }
    7272             : 
    7273             : /************************************************************************/
    7274             : /*                          GetGeoTransform()                           */
    7275             : /************************************************************************/
    7276             : 
    7277          23 : CPLErr PDFDataset::GetGeoTransform(GDALGeoTransform &gt) const
    7278             : 
    7279             : {
    7280          23 :     if (GDALPamDataset::GetGeoTransform(gt) == CE_None)
    7281             :     {
    7282           3 :         return CE_None;
    7283             :     }
    7284             : 
    7285          20 :     gt = m_gt;
    7286          20 :     return ((m_bGeoTransformValid) ? CE_None : CE_Failure);
    7287             : }
    7288             : 
    7289             : /************************************************************************/
    7290             : /*                            SetSpatialRef()                           */
    7291             : /************************************************************************/
    7292             : 
    7293           6 : CPLErr PDFDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
    7294             : {
    7295           6 :     if (eAccess == GA_ReadOnly)
    7296           2 :         GDALPamDataset::SetSpatialRef(poSRS);
    7297             : 
    7298           6 :     m_oSRS.Clear();
    7299           6 :     if (poSRS)
    7300           5 :         m_oSRS = *poSRS;
    7301           6 :     m_bProjDirty = true;
    7302           6 :     return CE_None;
    7303             : }
    7304             : 
    7305             : /************************************************************************/
    7306             : /*                          SetGeoTransform()                           */
    7307             : /************************************************************************/
    7308             : 
    7309           5 : CPLErr PDFDataset::SetGeoTransform(const GDALGeoTransform &gt)
    7310             : {
    7311           5 :     if (eAccess == GA_ReadOnly)
    7312           2 :         GDALPamDataset::SetGeoTransform(gt);
    7313             : 
    7314           5 :     m_gt = gt;
    7315           5 :     m_bGeoTransformValid = true;
    7316           5 :     m_bProjDirty = true;
    7317             : 
    7318             :     /* Reset NEATLINE if not explicitly set by the user */
    7319           5 :     if (!m_bNeatLineDirty)
    7320           5 :         SetMetadataItem("NEATLINE", nullptr);
    7321           5 :     return CE_None;
    7322             : }
    7323             : 
    7324             : /************************************************************************/
    7325             : /*                      GetMetadataDomainList()                         */
    7326             : /************************************************************************/
    7327             : 
    7328           1 : char **PDFDataset::GetMetadataDomainList()
    7329             : {
    7330           1 :     return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
    7331             :                                    TRUE, "xml:XMP", "LAYERS",
    7332           1 :                                    "EMBEDDED_METADATA", nullptr);
    7333             : }
    7334             : 
    7335             : /************************************************************************/
    7336             : /*                           GetMetadata()                              */
    7337             : /************************************************************************/
    7338             : 
    7339        1898 : char **PDFDataset::GetMetadata(const char *pszDomain)
    7340             : {
    7341        1898 :     if (pszDomain != nullptr && EQUAL(pszDomain, "EMBEDDED_METADATA"))
    7342             :     {
    7343           1 :         char **papszRet = m_oMDMD_PDF.GetMetadata(pszDomain);
    7344           1 :         if (papszRet)
    7345           0 :             return papszRet;
    7346             : 
    7347           1 :         GDALPDFObject *poCatalog = GetCatalog();
    7348           1 :         if (poCatalog == nullptr)
    7349           0 :             return nullptr;
    7350             :         GDALPDFObject *poFirstElt =
    7351           1 :             poCatalog->LookupObject("Names.EmbeddedFiles.Names[0]");
    7352             :         GDALPDFObject *poF =
    7353           1 :             poCatalog->LookupObject("Names.EmbeddedFiles.Names[1].EF.F");
    7354             : 
    7355           1 :         if (poFirstElt == nullptr ||
    7356           1 :             poFirstElt->GetType() != PDFObjectType_String ||
    7357           0 :             poFirstElt->GetString() != "Metadata")
    7358           1 :             return nullptr;
    7359           0 :         if (poF == nullptr || poF->GetType() != PDFObjectType_Dictionary)
    7360           0 :             return nullptr;
    7361           0 :         GDALPDFStream *poStream = poF->GetStream();
    7362           0 :         if (poStream == nullptr)
    7363           0 :             return nullptr;
    7364             : 
    7365           0 :         char *apszMetadata[2] = {nullptr, nullptr};
    7366           0 :         apszMetadata[0] = poStream->GetBytes();
    7367           0 :         m_oMDMD_PDF.SetMetadata(apszMetadata, pszDomain);
    7368           0 :         VSIFree(apszMetadata[0]);
    7369           0 :         return m_oMDMD_PDF.GetMetadata(pszDomain);
    7370             :     }
    7371        1897 :     if (pszDomain == nullptr || EQUAL(pszDomain, ""))
    7372             :     {
    7373         115 :         char **papszPAMMD = GDALPamDataset::GetMetadata(pszDomain);
    7374         118 :         for (char **papszIter = papszPAMMD; papszIter && *papszIter;
    7375             :              ++papszIter)
    7376             :         {
    7377           3 :             char *pszKey = nullptr;
    7378           3 :             const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
    7379           3 :             if (pszKey && pszValue)
    7380             :             {
    7381           3 :                 if (m_oMDMD_PDF.GetMetadataItem(pszKey, pszDomain) == nullptr)
    7382           2 :                     m_oMDMD_PDF.SetMetadataItem(pszKey, pszValue, pszDomain);
    7383             :             }
    7384           3 :             CPLFree(pszKey);
    7385             :         }
    7386         115 :         return m_oMDMD_PDF.GetMetadata(pszDomain);
    7387             :     }
    7388        1782 :     if (EQUAL(pszDomain, "LAYERS") || EQUAL(pszDomain, "xml:XMP") ||
    7389        1754 :         EQUAL(pszDomain, "SUBDATASETS"))
    7390             :     {
    7391          29 :         return m_oMDMD_PDF.GetMetadata(pszDomain);
    7392             :     }
    7393        1753 :     return GDALPamDataset::GetMetadata(pszDomain);
    7394             : }
    7395             : 
    7396             : /************************************************************************/
    7397             : /*                            SetMetadata()                             */
    7398             : /************************************************************************/
    7399             : 
    7400          70 : CPLErr PDFDataset::SetMetadata(char **papszMetadata, const char *pszDomain)
    7401             : {
    7402          70 :     if (pszDomain == nullptr || EQUAL(pszDomain, ""))
    7403             :     {
    7404          46 :         char **papszMetadataDup = CSLDuplicate(papszMetadata);
    7405          46 :         m_oMDMD_PDF.SetMetadata(nullptr, pszDomain);
    7406             : 
    7407         144 :         for (char **papszIter = papszMetadataDup; papszIter && *papszIter;
    7408             :              ++papszIter)
    7409             :         {
    7410          98 :             char *pszKey = nullptr;
    7411          98 :             const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
    7412          98 :             if (pszKey && pszValue)
    7413             :             {
    7414          95 :                 SetMetadataItem(pszKey, pszValue, pszDomain);
    7415             :             }
    7416          98 :             CPLFree(pszKey);
    7417             :         }
    7418          46 :         CSLDestroy(papszMetadataDup);
    7419          46 :         return CE_None;
    7420             :     }
    7421          24 :     else if (EQUAL(pszDomain, "xml:XMP"))
    7422             :     {
    7423          22 :         m_bXMPDirty = true;
    7424          22 :         return m_oMDMD_PDF.SetMetadata(papszMetadata, pszDomain);
    7425             :     }
    7426           2 :     else if (EQUAL(pszDomain, "SUBDATASETS"))
    7427             :     {
    7428           2 :         return m_oMDMD_PDF.SetMetadata(papszMetadata, pszDomain);
    7429             :     }
    7430             :     else
    7431             :     {
    7432           0 :         return GDALPamDataset::SetMetadata(papszMetadata, pszDomain);
    7433             :     }
    7434             : }
    7435             : 
    7436             : /************************************************************************/
    7437             : /*                          GetMetadataItem()                           */
    7438             : /************************************************************************/
    7439             : 
    7440        1805 : const char *PDFDataset::GetMetadataItem(const char *pszName,
    7441             :                                         const char *pszDomain)
    7442             : {
    7443        1805 :     if (pszDomain != nullptr && EQUAL(pszDomain, "_INTERNAL_") &&
    7444           0 :         pszName != nullptr && EQUAL(pszName, "PDF_LIB"))
    7445             :     {
    7446           0 :         if (m_bUseLib.test(PDFLIB_POPPLER))
    7447           0 :             return "POPPLER";
    7448           0 :         if (m_bUseLib.test(PDFLIB_PODOFO))
    7449           0 :             return "PODOFO";
    7450           0 :         if (m_bUseLib.test(PDFLIB_PDFIUM))
    7451           0 :             return "PDFIUM";
    7452             :     }
    7453        1805 :     return CSLFetchNameValue(GetMetadata(pszDomain), pszName);
    7454             : }
    7455             : 
    7456             : /************************************************************************/
    7457             : /*                          SetMetadataItem()                           */
    7458             : /************************************************************************/
    7459             : 
    7460         724 : CPLErr PDFDataset::SetMetadataItem(const char *pszName, const char *pszValue,
    7461             :                                    const char *pszDomain)
    7462             : {
    7463         724 :     if (pszDomain == nullptr || EQUAL(pszDomain, ""))
    7464             :     {
    7465         676 :         if (EQUAL(pszName, "NEATLINE"))
    7466             :         {
    7467             :             const char *pszOldValue =
    7468         194 :                 m_oMDMD_PDF.GetMetadataItem(pszName, pszDomain);
    7469         194 :             if ((pszValue == nullptr && pszOldValue != nullptr) ||
    7470         192 :                 (pszValue != nullptr && pszOldValue == nullptr) ||
    7471           1 :                 (pszValue != nullptr && pszOldValue != nullptr &&
    7472           1 :                  strcmp(pszValue, pszOldValue) != 0))
    7473             :             {
    7474         190 :                 m_bProjDirty = true;
    7475         190 :                 m_bNeatLineDirty = true;
    7476             :             }
    7477         194 :             return m_oMDMD_PDF.SetMetadataItem(pszName, pszValue, pszDomain);
    7478             :         }
    7479             :         else
    7480             :         {
    7481         482 :             if (EQUAL(pszName, "AUTHOR") || EQUAL(pszName, "PRODUCER") ||
    7482         459 :                 EQUAL(pszName, "CREATOR") || EQUAL(pszName, "CREATION_DATE") ||
    7483         405 :                 EQUAL(pszName, "SUBJECT") || EQUAL(pszName, "TITLE") ||
    7484         386 :                 EQUAL(pszName, "KEYWORDS"))
    7485             :             {
    7486         102 :                 if (pszValue == nullptr)
    7487           1 :                     pszValue = "";
    7488             :                 const char *pszOldValue =
    7489         102 :                     m_oMDMD_PDF.GetMetadataItem(pszName, pszDomain);
    7490         102 :                 if (pszOldValue == nullptr ||
    7491           2 :                     strcmp(pszValue, pszOldValue) != 0)
    7492             :                 {
    7493         102 :                     m_bInfoDirty = true;
    7494             :                 }
    7495         102 :                 return m_oMDMD_PDF.SetMetadataItem(pszName, pszValue,
    7496         102 :                                                    pszDomain);
    7497             :             }
    7498         380 :             else if (EQUAL(pszName, "DPI"))
    7499             :             {
    7500         378 :                 return m_oMDMD_PDF.SetMetadataItem(pszName, pszValue,
    7501         378 :                                                    pszDomain);
    7502             :             }
    7503             :             else
    7504             :             {
    7505           2 :                 m_oMDMD_PDF.SetMetadataItem(pszName, pszValue, pszDomain);
    7506           2 :                 return GDALPamDataset::SetMetadataItem(pszName, pszValue,
    7507           2 :                                                        pszDomain);
    7508             :             }
    7509             :         }
    7510             :     }
    7511          48 :     else if (EQUAL(pszDomain, "xml:XMP"))
    7512             :     {
    7513           0 :         m_bXMPDirty = true;
    7514           0 :         return m_oMDMD_PDF.SetMetadataItem(pszName, pszValue, pszDomain);
    7515             :     }
    7516          48 :     else if (EQUAL(pszDomain, "SUBDATASETS"))
    7517             :     {
    7518           0 :         return m_oMDMD_PDF.SetMetadataItem(pszName, pszValue, pszDomain);
    7519             :     }
    7520             :     else
    7521             :     {
    7522          48 :         return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
    7523             :     }
    7524             : }
    7525             : 
    7526             : /************************************************************************/
    7527             : /*                            GetGCPCount()                             */
    7528             : /************************************************************************/
    7529             : 
    7530          11 : int PDFDataset::GetGCPCount()
    7531             : {
    7532          11 :     return m_nGCPCount;
    7533             : }
    7534             : 
    7535             : /************************************************************************/
    7536             : /*                          GetGCPSpatialRef()                          */
    7537             : /************************************************************************/
    7538             : 
    7539           2 : const OGRSpatialReference *PDFDataset::GetGCPSpatialRef() const
    7540             : {
    7541           2 :     if (!m_oSRS.IsEmpty() && m_nGCPCount != 0)
    7542           1 :         return &m_oSRS;
    7543           1 :     return nullptr;
    7544             : }
    7545             : 
    7546             : /************************************************************************/
    7547             : /*                              GetGCPs()                               */
    7548             : /************************************************************************/
    7549             : 
    7550           2 : const GDAL_GCP *PDFDataset::GetGCPs()
    7551             : {
    7552           2 :     return m_pasGCPList;
    7553             : }
    7554             : 
    7555             : /************************************************************************/
    7556             : /*                               SetGCPs()                              */
    7557             : /************************************************************************/
    7558             : 
    7559           1 : CPLErr PDFDataset::SetGCPs(int nGCPCountIn, const GDAL_GCP *pasGCPListIn,
    7560             :                            const OGRSpatialReference *poSRS)
    7561             : {
    7562             :     const char *pszGEO_ENCODING =
    7563           1 :         CPLGetConfigOption("GDAL_PDF_GEO_ENCODING", "ISO32000");
    7564           1 :     if (nGCPCountIn != 4 && EQUAL(pszGEO_ENCODING, "ISO32000"))
    7565             :     {
    7566           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    7567             :                  "PDF driver only supports writing 4 GCPs when "
    7568             :                  "GDAL_PDF_GEO_ENCODING=ISO32000.");
    7569           0 :         return CE_Failure;
    7570             :     }
    7571             : 
    7572             :     /* Free previous GCPs */
    7573           1 :     GDALDeinitGCPs(m_nGCPCount, m_pasGCPList);
    7574           1 :     CPLFree(m_pasGCPList);
    7575             : 
    7576             :     /* Duplicate in GCPs */
    7577           1 :     m_nGCPCount = nGCPCountIn;
    7578           1 :     m_pasGCPList = GDALDuplicateGCPs(m_nGCPCount, pasGCPListIn);
    7579             : 
    7580           1 :     m_oSRS.Clear();
    7581           1 :     if (poSRS)
    7582           1 :         m_oSRS = *poSRS;
    7583             : 
    7584           1 :     m_bProjDirty = true;
    7585             : 
    7586             :     /* Reset NEATLINE if not explicitly set by the user */
    7587           1 :     if (!m_bNeatLineDirty)
    7588           1 :         SetMetadataItem("NEATLINE", nullptr);
    7589             : 
    7590           1 :     return CE_None;
    7591             : }
    7592             : 
    7593             : #endif  // #ifdef HAVE_PDF_READ_SUPPORT
    7594             : 
    7595             : /************************************************************************/
    7596             : /*                          GDALPDFOpen()                               */
    7597             : /************************************************************************/
    7598             : 
    7599          53 : GDALDataset *GDALPDFOpen(
    7600             : #ifdef HAVE_PDF_READ_SUPPORT
    7601             :     const char *pszFilename, GDALAccess eAccess
    7602             : #else
    7603             :     CPL_UNUSED const char *pszFilename, CPL_UNUSED GDALAccess eAccess
    7604             : #endif
    7605             : )
    7606             : {
    7607             : #ifdef HAVE_PDF_READ_SUPPORT
    7608         106 :     GDALOpenInfo oOpenInfo(pszFilename, eAccess);
    7609         106 :     return PDFDataset::Open(&oOpenInfo);
    7610             : #else
    7611             :     return nullptr;
    7612             : #endif
    7613             : }
    7614             : 
    7615             : /************************************************************************/
    7616             : /*                       GDALPDFUnloadDriver()                          */
    7617             : /************************************************************************/
    7618             : 
    7619           8 : static void GDALPDFUnloadDriver(CPL_UNUSED GDALDriver *poDriver)
    7620             : {
    7621             : #ifdef HAVE_POPPLER
    7622           8 :     if (hGlobalParamsMutex != nullptr)
    7623           3 :         CPLDestroyMutex(hGlobalParamsMutex);
    7624             : #endif
    7625             : #ifdef HAVE_PDFIUM
    7626             :     if (PDFDataset::g_bPdfiumInit)
    7627             :     {
    7628             :         CPLCreateOrAcquireMutex(&g_oPdfiumLoadDocMutex, PDFIUM_MUTEX_TIMEOUT);
    7629             :         // Destroy every loaded document or page
    7630             :         TMapPdfiumDatasets::iterator itDoc;
    7631             :         TMapPdfiumPages::iterator itPage;
    7632             :         for (itDoc = g_mPdfiumDatasets.begin();
    7633             :              itDoc != g_mPdfiumDatasets.end(); ++itDoc)
    7634             :         {
    7635             :             TPdfiumDocumentStruct *pDoc = itDoc->second;
    7636             :             for (itPage = pDoc->pages.begin(); itPage != pDoc->pages.end();
    7637             :                  ++itPage)
    7638             :             {
    7639             :                 TPdfiumPageStruct *pPage = itPage->second;
    7640             : 
    7641             :                 CPLCreateOrAcquireMutex(&g_oPdfiumReadMutex,
    7642             :                                         PDFIUM_MUTEX_TIMEOUT);
    7643             :                 CPLCreateOrAcquireMutex(&(pPage->readMutex),
    7644             :                                         PDFIUM_MUTEX_TIMEOUT);
    7645             :                 CPLReleaseMutex(pPage->readMutex);
    7646             :                 CPLDestroyMutex(pPage->readMutex);
    7647             :                 FPDF_ClosePage(FPDFPageFromIPDFPage(pPage->page));
    7648             :                 delete pPage;
    7649             :                 CPLReleaseMutex(g_oPdfiumReadMutex);
    7650             :             }  // ~ foreach page
    7651             : 
    7652             :             FPDF_CloseDocument(FPDFDocumentFromCPDFDocument(pDoc->doc));
    7653             :             CPLFree(pDoc->filename);
    7654             :             VSIFCloseL(static_cast<VSILFILE *>(pDoc->psFileAccess->m_Param));
    7655             :             delete pDoc->psFileAccess;
    7656             :             pDoc->pages.clear();
    7657             : 
    7658             :             delete pDoc;
    7659             :         }  // ~ foreach document
    7660             :         g_mPdfiumDatasets.clear();
    7661             :         FPDF_DestroyLibrary();
    7662             :         PDFDataset::g_bPdfiumInit = FALSE;
    7663             : 
    7664             :         CPLReleaseMutex(g_oPdfiumLoadDocMutex);
    7665             : 
    7666             :         if (g_oPdfiumReadMutex)
    7667             :             CPLDestroyMutex(g_oPdfiumReadMutex);
    7668             :         CPLDestroyMutex(g_oPdfiumLoadDocMutex);
    7669             :     }
    7670             : #endif
    7671           8 : }
    7672             : 
    7673             : /************************************************************************/
    7674             : /*                           PDFSanitizeLayerName()                     */
    7675             : /************************************************************************/
    7676             : 
    7677         660 : CPLString PDFSanitizeLayerName(const char *pszName)
    7678             : {
    7679         660 :     if (!CPLTestBool(CPLGetConfigOption("GDAL_PDF_LAUNDER_LAYER_NAMES", "YES")))
    7680           0 :         return pszName;
    7681             : 
    7682        1320 :     CPLString osName;
    7683       17194 :     for (int i = 0; pszName[i] != '\0'; i++)
    7684             :     {
    7685       16534 :         if (pszName[i] == ' ' || pszName[i] == '.' || pszName[i] == ',')
    7686         976 :             osName += "_";
    7687       15558 :         else if (pszName[i] != '"')
    7688       15558 :             osName += pszName[i];
    7689             :     }
    7690         660 :     if (osName.empty())
    7691           2 :         osName = "unnamed";
    7692         660 :     return osName;
    7693             : }
    7694             : 
    7695             : /************************************************************************/
    7696             : /*                    GDALPDFListLayersAlgorithm                        */
    7697             : /************************************************************************/
    7698             : 
    7699             : #ifdef HAVE_PDF_READ_SUPPORT
    7700             : 
    7701             : class GDALPDFListLayersAlgorithm final : public GDALAlgorithm
    7702             : {
    7703             :   public:
    7704          25 :     GDALPDFListLayersAlgorithm()
    7705          25 :         : GDALAlgorithm("list-layers",
    7706          50 :                         std::string("List layers of a PDF dataset"),
    7707          75 :                         "/drivers/raster/pdf.html")
    7708             :     {
    7709          25 :         AddInputDatasetArg(&m_dataset, GDAL_OF_RASTER | GDAL_OF_VECTOR);
    7710          25 :         AddOutputFormatArg(&m_format).SetDefault(m_format).SetChoices("json",
    7711          25 :                                                                       "text");
    7712          25 :         AddOutputStringArg(&m_output);
    7713          25 :     }
    7714             : 
    7715             :   protected:
    7716             :     bool RunImpl(GDALProgressFunc, void *) override;
    7717             : 
    7718             :   private:
    7719             :     GDALArgDatasetValue m_dataset{};
    7720             :     std::string m_format = "json";
    7721             :     std::string m_output{};
    7722             : };
    7723             : 
    7724           3 : bool GDALPDFListLayersAlgorithm::RunImpl(GDALProgressFunc, void *)
    7725             : {
    7726           3 :     auto poDS = dynamic_cast<PDFDataset *>(m_dataset.GetDatasetRef());
    7727           3 :     if (!poDS)
    7728             :     {
    7729           1 :         ReportError(CE_Failure, CPLE_AppDefined, "%s is not a PDF",
    7730           1 :                     m_dataset.GetName().c_str());
    7731           1 :         return false;
    7732             :     }
    7733           2 :     if (m_format == "json")
    7734             :     {
    7735           2 :         CPLJSonStreamingWriter oWriter(nullptr, nullptr);
    7736           1 :         oWriter.StartArray();
    7737          10 :         for (const auto &[key, value] : cpl::IterateNameValue(
    7738          11 :                  const_cast<CSLConstList>(poDS->GetMetadata("LAYERS"))))
    7739             :         {
    7740           5 :             CPL_IGNORE_RET_VAL(key);
    7741           5 :             oWriter.Add(value);
    7742             :         }
    7743           1 :         oWriter.EndArray();
    7744           1 :         m_output = oWriter.GetString();
    7745           1 :         m_output += '\n';
    7746             :     }
    7747             :     else
    7748             :     {
    7749          10 :         for (const auto &[key, value] : cpl::IterateNameValue(
    7750          11 :                  const_cast<CSLConstList>(poDS->GetMetadata("LAYERS"))))
    7751             :         {
    7752           5 :             CPL_IGNORE_RET_VAL(key);
    7753           5 :             m_output += value;
    7754           5 :             m_output += '\n';
    7755             :         }
    7756             :     }
    7757           2 :     return true;
    7758             : }
    7759             : 
    7760             : /************************************************************************/
    7761             : /*                    GDALPDFInstantiateAlgorithm()                     */
    7762             : /************************************************************************/
    7763             : 
    7764             : static GDALAlgorithm *
    7765          25 : GDALPDFInstantiateAlgorithm(const std::vector<std::string> &aosPath)
    7766             : {
    7767          25 :     if (aosPath.size() == 1 && aosPath[0] == "list-layers")
    7768             :     {
    7769          25 :         return std::make_unique<GDALPDFListLayersAlgorithm>().release();
    7770             :     }
    7771             :     else
    7772             :     {
    7773           0 :         return nullptr;
    7774             :     }
    7775             : }
    7776             : 
    7777             : #endif  // HAVE_PDF_READ_SUPPORT
    7778             : 
    7779             : /************************************************************************/
    7780             : /*                         GDALRegister_PDF()                           */
    7781             : /************************************************************************/
    7782             : 
    7783          17 : void GDALRegister_PDF()
    7784             : 
    7785             : {
    7786          17 :     if (!GDAL_CHECK_VERSION("PDF driver"))
    7787           0 :         return;
    7788             : 
    7789          17 :     if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
    7790           0 :         return;
    7791             : 
    7792          17 :     GDALDriver *poDriver = new GDALDriver();
    7793          17 :     PDFDriverSetCommonMetadata(poDriver);
    7794             : 
    7795             : #ifdef HAVE_PDF_READ_SUPPORT
    7796          17 :     poDriver->pfnOpen = PDFDataset::OpenWrapper;
    7797          17 :     poDriver->pfnInstantiateAlgorithm = GDALPDFInstantiateAlgorithm;
    7798             : #endif  // HAVE_PDF_READ_SUPPORT
    7799             : 
    7800          17 :     poDriver->pfnCreateCopy = GDALPDFCreateCopy;
    7801          17 :     poDriver->pfnCreate = PDFWritableVectorDataset::Create;
    7802          17 :     poDriver->pfnUnloadDriver = GDALPDFUnloadDriver;
    7803             : 
    7804          17 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    7805             : }

Generated by: LCOV version 1.14