LCOV - code coverage report
Current view: top level - frmts/pdf - pdfdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2418 3513 68.8 %
Date: 2024-05-04 12:52:34 Functions: 116 135 85.9 %

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

Generated by: LCOV version 1.14