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

Generated by: LCOV version 1.14