LCOV - code coverage report
Current view: top level - frmts/pdf - pdfdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2215 3525 62.8 %
Date: 2025-02-20 10:14:44 Functions: 111 134 82.8 %

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

Generated by: LCOV version 1.14