LCOV - code coverage report
Current view: top level - frmts/pdf - pdfreadvectors.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 819 1053 77.8 %
Date: 2024-05-03 15:49:35 Functions: 23 24 95.8 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  PDF driver
       4             :  * Purpose:  GDALDataset driver for PDF dataset (read vector features)
       5             :  * Author:   Even Rouault, <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2010-2014, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * Permission is hereby granted, free of charge, to any person obtaining a
      11             :  * copy of this software and associated documentation files (the "Software"),
      12             :  * to deal in the Software without restriction, including without limitation
      13             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      14             :  * and/or sell copies of the Software, and to permit persons to whom the
      15             :  * Software is furnished to do so, subject to the following conditions:
      16             :  *
      17             :  * The above copyright notice and this permission notice shall be included
      18             :  * in all copies or substantial portions of the Software.
      19             :  *
      20             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      21             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      22             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      23             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      24             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      25             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      26             :  * DEALINGS IN THE SOFTWARE.
      27             :  ****************************************************************************/
      28             : 
      29             : #include "gdal_pdf.h"
      30             : 
      31             : #include <algorithm>
      32             : #include <array>
      33             : 
      34             : #define SQUARE(x) ((x) * (x))
      35             : #define EPSILON 1e-5
      36             : 
      37             : // #define DEBUG_VERBOSE
      38             : 
      39             : #ifdef HAVE_PDF_READ_SUPPORT
      40             : 
      41             : constexpr int BEZIER_STEPS = 10;
      42             : 
      43             : /************************************************************************/
      44             : /*                        OpenVectorLayers()                            */
      45             : /************************************************************************/
      46             : 
      47          42 : int PDFDataset::OpenVectorLayers(GDALPDFDictionary *poPageDict)
      48             : {
      49          42 :     if (m_bHasLoadedLayers)
      50          30 :         return TRUE;
      51          12 :     m_bHasLoadedLayers = true;
      52             : 
      53          12 :     if (poPageDict == nullptr)
      54             :     {
      55           2 :         poPageDict = m_poPageObj->GetDictionary();
      56           2 :         if (poPageDict == nullptr)
      57           0 :             return FALSE;
      58             :     }
      59             : 
      60          12 :     GetCatalog();
      61          24 :     if (m_poCatalogObject == nullptr ||
      62          12 :         m_poCatalogObject->GetType() != PDFObjectType_Dictionary)
      63           0 :         return FALSE;
      64             : 
      65          12 :     GDALPDFObject *poContents = poPageDict->Get("Contents");
      66          12 :     if (poContents == nullptr)
      67           0 :         return FALSE;
      68             : 
      69          13 :     if (poContents->GetType() != PDFObjectType_Dictionary &&
      70           1 :         poContents->GetType() != PDFObjectType_Array)
      71           0 :         return FALSE;
      72             : 
      73          12 :     GDALPDFObject *poResources = poPageDict->Get("Resources");
      74          24 :     if (poResources == nullptr ||
      75          12 :         poResources->GetType() != PDFObjectType_Dictionary)
      76           0 :         return FALSE;
      77             : 
      78             :     GDALPDFObject *poStructTreeRoot =
      79          12 :         m_poCatalogObject->GetDictionary()->Get("StructTreeRoot");
      80          22 :     if (CPLTestBool(CPLGetConfigOption("OGR_PDF_READ_NON_STRUCTURED", "NO")) ||
      81          22 :         poStructTreeRoot == nullptr ||
      82           6 :         poStructTreeRoot->GetType() != PDFObjectType_Dictionary)
      83             :     {
      84           6 :         ExploreContentsNonStructured(poContents, poResources);
      85             :     }
      86             :     else
      87             :     {
      88             :         bool bHasFeatures;
      89             :         {
      90           6 :             std::set<std::pair<int, int>> aoSetAlreadyVisited;
      91           6 :             bHasFeatures = ExploreTree(poStructTreeRoot, aoSetAlreadyVisited, 0,
      92             :                                        /* bDryRun = */ true);
      93             :         }
      94           6 :         if (bHasFeatures)
      95             :         {
      96           6 :             int nDepth = 0;
      97           6 :             int nVisited = 0;
      98           6 :             bool bStop = false;
      99           6 :             ExploreContents(poContents, poResources, nDepth, nVisited, bStop);
     100          12 :             std::set<std::pair<int, int>> aoSetAlreadyVisited;
     101           6 :             ExploreTree(poStructTreeRoot, aoSetAlreadyVisited, 0,
     102             :                         /* bDryRun = */ false);
     103             :         }
     104             :         else
     105             :         {
     106           0 :             ExploreContentsNonStructured(poContents, poResources);
     107             :         }
     108             :     }
     109             : 
     110          12 :     CleanupIntermediateResources();
     111             : 
     112          12 :     int bEmptyDS = TRUE;
     113          12 :     for (int i = 0; i < m_nLayers; i++)
     114             :     {
     115          10 :         if (m_papoLayers[i]->GetFeatureCount() != 0)
     116             :         {
     117          10 :             bEmptyDS = FALSE;
     118          10 :             break;
     119             :         }
     120             :     }
     121          12 :     return !bEmptyDS;
     122             : }
     123             : 
     124             : /************************************************************************/
     125             : /*                   CleanupIntermediateResources()                     */
     126             : /************************************************************************/
     127             : 
     128         425 : void PDFDataset::CleanupIntermediateResources()
     129             : {
     130         456 :     for (const auto &oIter : m_oMapMCID)
     131          31 :         delete oIter.second;
     132         425 :     m_oMapMCID.clear();
     133         425 : }
     134             : 
     135             : /************************************************************************/
     136             : /*                          InitMapOperators()                          */
     137             : /************************************************************************/
     138             : 
     139             : typedef struct
     140             : {
     141             :     char szOpName[4];
     142             :     int nArgs;
     143             : } PDFOperator;
     144             : 
     145             : static const PDFOperator asPDFOperators[] = {
     146             :     {"b", 0},
     147             :     {"B", 0},
     148             :     {"b*", 0},
     149             :     {"B*", 0},
     150             :     {"BDC", 2},
     151             :     // BI
     152             :     {"BMC", 1},
     153             :     // BT
     154             :     {"BX", 0},
     155             :     {"c", 6},
     156             :     {"cm", 6},
     157             :     {"CS", 1},
     158             :     {"cs", 1},
     159             :     {"d", 1}, /* we have ignored the first arg which is an array */
     160             :     // d0
     161             :     // d1
     162             :     {"Do", 1},
     163             :     {"DP", 2},
     164             :     // EI
     165             :     {"EMC", 0},
     166             :     // ET
     167             :     {"EX", 0},
     168             :     {"f", 0},
     169             :     {"F", 0},
     170             :     {"f*", 0},
     171             :     {"G", 1},
     172             :     {"g", 1},
     173             :     {"gs", 1},
     174             :     {"h", 0},
     175             :     {"i", 1},
     176             :     // ID
     177             :     {"j", 1},
     178             :     {"J", 1},
     179             :     {"K", 4},
     180             :     {"k", 4},
     181             :     {"l", 2},
     182             :     {"m", 2},
     183             :     {"M", 1},
     184             :     {"MP", 1},
     185             :     {"n", 0},
     186             :     {"q", 0},
     187             :     {"Q", 0},
     188             :     {"re", 4},
     189             :     {"RG", 3},
     190             :     {"rg", 3},
     191             :     {"ri", 1},
     192             :     {"s", 0},
     193             :     {"S", 0},
     194             :     {"SC", -1},
     195             :     {"sc", -1},
     196             :     {"SCN", -1},
     197             :     {"scn", -1},
     198             :     {"sh", 1},
     199             :     // T*
     200             :     {"Tc", 1},
     201             :     {"Td", 2},
     202             :     {"TD", 2},
     203             :     {"Tf", 1},
     204             :     {"Tj", 1},
     205             :     {"TJ", 1},
     206             :     {"TL", 1},
     207             :     {"Tm", 6},
     208             :     {"Tr", 1},
     209             :     {"Ts", 1},
     210             :     {"Tw", 1},
     211             :     {"Tz", 1},
     212             :     {"v", 4},
     213             :     {"w", 1},
     214             :     {"W", 0},
     215             :     {"W*", 0},
     216             :     {"y", 4},
     217             :     // '
     218             :     // "
     219             : };
     220             : 
     221         413 : void PDFDataset::InitMapOperators()
     222             : {
     223       26432 :     for (const auto &sPDFOperator : asPDFOperators)
     224       26019 :         m_oMapOperators[sPDFOperator.szOpName] = sPDFOperator.nArgs;
     225         413 : }
     226             : 
     227             : /************************************************************************/
     228             : /*                           TestCapability()                           */
     229             : /************************************************************************/
     230             : 
     231           0 : int PDFDataset::TestCapability(CPL_UNUSED const char *pszCap)
     232             : {
     233           0 :     return FALSE;
     234             : }
     235             : 
     236             : /************************************************************************/
     237             : /*                              GetLayer()                              */
     238             : /************************************************************************/
     239             : 
     240          15 : OGRLayer *PDFDataset::GetLayer(int iLayer)
     241             : 
     242             : {
     243          15 :     OpenVectorLayers(nullptr);
     244          15 :     if (iLayer < 0 || iLayer >= m_nLayers)
     245           0 :         return nullptr;
     246             : 
     247          15 :     return m_papoLayers[iLayer];
     248             : }
     249             : 
     250             : /************************************************************************/
     251             : /*                            GetLayerCount()                           */
     252             : /************************************************************************/
     253             : 
     254          17 : int PDFDataset::GetLayerCount()
     255             : {
     256          17 :     OpenVectorLayers(nullptr);
     257          17 :     return m_nLayers;
     258             : }
     259             : 
     260             : /************************************************************************/
     261             : /*                            ExploreTree()                             */
     262             : /************************************************************************/
     263             : 
     264          24 : bool PDFDataset::ExploreTree(GDALPDFObject *poObj,
     265             :                              std::set<std::pair<int, int>> &aoSetAlreadyVisited,
     266             :                              int nRecLevel, bool bDryRun)
     267             : {
     268          24 :     if (nRecLevel == 16)
     269           0 :         return false;
     270             : 
     271          24 :     std::pair<int, int> oObjPair(poObj->GetRefNum().toInt(),
     272          48 :                                  poObj->GetRefGen());
     273          24 :     if (aoSetAlreadyVisited.find(oObjPair) != aoSetAlreadyVisited.end())
     274           0 :         return false;
     275          24 :     aoSetAlreadyVisited.insert(oObjPair);
     276             : 
     277          24 :     if (poObj->GetType() != PDFObjectType_Dictionary)
     278           0 :         return false;
     279             : 
     280          24 :     GDALPDFDictionary *poDict = poObj->GetDictionary();
     281             : 
     282          24 :     GDALPDFObject *poS = poDict->Get("S");
     283          48 :     std::string osS;
     284          24 :     if (poS != nullptr && poS->GetType() == PDFObjectType_Name)
     285             :     {
     286          12 :         osS = poS->GetName();
     287             :     }
     288             : 
     289          24 :     GDALPDFObject *poT = poDict->Get("T");
     290          48 :     std::string osT;
     291          24 :     if (poT != nullptr && poT->GetType() == PDFObjectType_String)
     292             :     {
     293          12 :         osT = poT->GetString();
     294             :     }
     295             : 
     296          24 :     GDALPDFObject *poK = poDict->Get("K");
     297          24 :     if (poK == nullptr)
     298           0 :         return false;
     299             : 
     300          24 :     bool bRet = false;
     301          24 :     if (poK->GetType() == PDFObjectType_Array)
     302             :     {
     303          24 :         GDALPDFArray *poArray = poK->GetArray();
     304          48 :         if (poArray->GetLength() > 0 && poArray->Get(0) &&
     305          24 :             poArray->Get(0)->GetType() == PDFObjectType_Dictionary &&
     306          72 :             poArray->Get(0)->GetDictionary()->Get("K") != nullptr &&
     307          24 :             poArray->Get(0)->GetDictionary()->Get("K")->GetType() ==
     308             :                 PDFObjectType_Int)
     309             :         {
     310          12 :             if (bDryRun)
     311             :             {
     312           6 :                 for (int i = 0; i < poArray->GetLength(); i++)
     313             :                 {
     314           6 :                     auto poFeatureObj = poArray->Get(i);
     315          12 :                     if (poFeatureObj &&
     316           6 :                         poFeatureObj->GetType() == PDFObjectType_Dictionary)
     317             :                     {
     318           6 :                         auto poA = poFeatureObj->GetDictionary()->Get("A");
     319           6 :                         if (poA && poA->GetType() == PDFObjectType_Dictionary)
     320             :                         {
     321           6 :                             auto poO = poA->GetDictionary()->Get("O");
     322          12 :                             if (poO && poO->GetType() == PDFObjectType_Name &&
     323           6 :                                 poO->GetName() == "UserProperties")
     324             :                             {
     325           6 :                                 return true;
     326             :                             }
     327             :                         }
     328             :                     }
     329             :                 }
     330           0 :                 return false;
     331             :             }
     332             : 
     333           6 :             std::string osLayerName;
     334           6 :             if (!osT.empty())
     335           6 :                 osLayerName = std::move(osT);
     336             :             else
     337             :             {
     338           0 :                 if (!osS.empty())
     339           0 :                     osLayerName = std::move(osS);
     340             :                 else
     341           0 :                     osLayerName = CPLSPrintf("Layer%d", m_nLayers + 1);
     342             :             }
     343             : 
     344           6 :             auto poSRSOri = GetSpatialRef();
     345           6 :             OGRSpatialReference *poSRS = poSRSOri ? poSRSOri->Clone() : nullptr;
     346             :             OGRPDFLayer *poLayer =
     347           6 :                 new OGRPDFLayer(this, osLayerName.c_str(), poSRS, wkbUnknown);
     348           6 :             if (poSRS)
     349           6 :                 poSRS->Release();
     350             : 
     351           6 :             poLayer->Fill(poArray);
     352             : 
     353          12 :             m_papoLayers = (OGRLayer **)CPLRealloc(
     354           6 :                 m_papoLayers, (m_nLayers + 1) * sizeof(OGRLayer *));
     355           6 :             m_papoLayers[m_nLayers] = poLayer;
     356           6 :             m_nLayers++;
     357           6 :             bRet = true;
     358             :         }
     359             :         else
     360             :         {
     361          18 :             for (int i = 0; i < poArray->GetLength(); i++)
     362             :             {
     363          12 :                 auto poSubObj = poArray->Get(i);
     364          12 :                 if (poSubObj)
     365             :                 {
     366          12 :                     if (ExploreTree(poSubObj, aoSetAlreadyVisited,
     367          12 :                                     nRecLevel + 1, bDryRun) &&
     368             :                         bDryRun)
     369           6 :                         return true;
     370             :                 }
     371             :             }
     372             :         }
     373             :     }
     374           0 :     else if (poK->GetType() == PDFObjectType_Dictionary)
     375             :     {
     376           0 :         if (ExploreTree(poK, aoSetAlreadyVisited, nRecLevel + 1, bDryRun) &&
     377             :             bDryRun)
     378           0 :             return true;
     379             :     }
     380             : 
     381          12 :     return bRet;
     382             : }
     383             : 
     384             : /************************************************************************/
     385             : /*                        GetGeometryFromMCID()                         */
     386             : /************************************************************************/
     387             : 
     388          66 : OGRGeometry *PDFDataset::GetGeometryFromMCID(int nMCID)
     389             : {
     390          66 :     auto oMapIter = m_oMapMCID.find(nMCID);
     391          66 :     if (oMapIter != m_oMapMCID.end())
     392          33 :         return oMapIter->second;
     393             :     else
     394          33 :         return nullptr;
     395             : }
     396             : 
     397             : /************************************************************************/
     398             : /*                            GraphicState                              */
     399             : /************************************************************************/
     400             : 
     401             : class GraphicState
     402             : {
     403             :   public:
     404             :     std::array<double, 6> adfCM;
     405             :     std::array<double, 3> adfStrokeColor;
     406             :     std::array<double, 3> adfFillColor;
     407             : 
     408          84 :     GraphicState()
     409          84 :     {
     410          84 :         adfCM[0] = 1;
     411          84 :         adfCM[1] = 0;
     412          84 :         adfCM[2] = 0;
     413          84 :         adfCM[3] = 1;
     414          84 :         adfCM[4] = 0;
     415          84 :         adfCM[5] = 0;
     416          84 :         adfStrokeColor[0] = 0.0;
     417          84 :         adfStrokeColor[1] = 0.0;
     418          84 :         adfStrokeColor[2] = 0.0;
     419          84 :         adfFillColor[0] = 1.0;
     420          84 :         adfFillColor[1] = 1.0;
     421          84 :         adfFillColor[2] = 1.0;
     422          84 :     }
     423             : 
     424           8 :     void MultiplyBy(double adfMatrix[6])
     425             :     {
     426             :         /*
     427             :         [ a b 0 ]     [ a' b' 0]     [ aa' + bc'       ab' + bd'       0 ]
     428             :         [ c d 0 ]  *  [ c' d' 0]  =  [ ca' + dc'       cb' + dd'       0 ]
     429             :         [ e f 1 ]     [ e' f' 1]     [ ea' + fc' + e'  eb' + fd' + f'  1 ]
     430             :         */
     431             : 
     432           8 :         double a = adfCM[0];
     433           8 :         double b = adfCM[1];
     434           8 :         double c = adfCM[2];
     435           8 :         double d = adfCM[3];
     436           8 :         double e = adfCM[4];
     437           8 :         double f = adfCM[5];
     438           8 :         double ap = adfMatrix[0];
     439           8 :         double bp = adfMatrix[1];
     440           8 :         double cp = adfMatrix[2];
     441           8 :         double dp = adfMatrix[3];
     442           8 :         double ep = adfMatrix[4];
     443           8 :         double fp = adfMatrix[5];
     444           8 :         adfCM[0] = a * ap + b * cp;
     445           8 :         adfCM[1] = a * bp + b * dp;
     446           8 :         adfCM[2] = c * ap + d * cp;
     447           8 :         adfCM[3] = c * bp + d * dp;
     448           8 :         adfCM[4] = e * ap + f * cp + ep;
     449           8 :         adfCM[5] = e * bp + f * dp + fp;
     450           8 :     }
     451             : 
     452         877 :     void ApplyMatrix(double adfCoords[2])
     453             :     {
     454         877 :         double x = adfCoords[0];
     455         877 :         double y = adfCoords[1];
     456             : 
     457         877 :         adfCoords[0] = x * adfCM[0] + y * adfCM[2] + adfCM[4];
     458         877 :         adfCoords[1] = x * adfCM[1] + y * adfCM[3] + adfCM[5];
     459         877 :     }
     460             : };
     461             : 
     462             : /************************************************************************/
     463             : /*                         PDFCoordsToSRSCoords()                       */
     464             : /************************************************************************/
     465             : 
     466        2328 : void PDFDataset::PDFCoordsToSRSCoords(double x, double y, double &X, double &Y)
     467             : {
     468        2328 :     x = x / m_dfPageWidth * nRasterXSize;
     469        2328 :     if (m_bGeoTransformValid)
     470         832 :         y = (1 - y / m_dfPageHeight) * nRasterYSize;
     471             :     else
     472        1496 :         y = (y / m_dfPageHeight) * nRasterYSize;
     473             : 
     474        2328 :     X = m_adfGeoTransform[0] + x * m_adfGeoTransform[1] +
     475        2328 :         y * m_adfGeoTransform[2];
     476        2328 :     Y = m_adfGeoTransform[3] + x * m_adfGeoTransform[4] +
     477        2328 :         y * m_adfGeoTransform[5];
     478             : 
     479        2328 :     if (fabs(X - (int)floor(X + 0.5)) < 1e-8)
     480          65 :         X = (int)floor(X + 0.5);
     481        2328 :     if (fabs(Y - (int)floor(Y + 0.5)) < 1e-8)
     482          69 :         Y = (int)floor(Y + 0.5);
     483        2328 : }
     484             : 
     485             : /************************************************************************/
     486             : /*                         PDFGetCircleCenter()                         */
     487             : /************************************************************************/
     488             : 
     489             : /* Return the center of a circle, or NULL if it is not recognized */
     490             : 
     491          18 : static OGRPoint *PDFGetCircleCenter(OGRLineString *poLS)
     492             : {
     493          18 :     if (poLS == nullptr || poLS->getNumPoints() != 1 + 4 * BEZIER_STEPS)
     494           0 :         return nullptr;
     495             : 
     496          34 :     if (poLS->getY(0 * BEZIER_STEPS) == poLS->getY(2 * BEZIER_STEPS) &&
     497          16 :         poLS->getX(1 * BEZIER_STEPS) == poLS->getX(3 * BEZIER_STEPS) &&
     498          32 :         fabs((poLS->getX(0 * BEZIER_STEPS) + poLS->getX(2 * BEZIER_STEPS)) / 2 -
     499          50 :              poLS->getX(1 * BEZIER_STEPS)) < EPSILON &&
     500          32 :         fabs((poLS->getY(1 * BEZIER_STEPS) + poLS->getY(3 * BEZIER_STEPS)) / 2 -
     501          16 :              poLS->getY(0 * BEZIER_STEPS)) < EPSILON)
     502             :     {
     503             :         return new OGRPoint(
     504          16 :             (poLS->getX(0 * BEZIER_STEPS) + poLS->getX(2 * BEZIER_STEPS)) / 2,
     505          16 :             (poLS->getY(1 * BEZIER_STEPS) + poLS->getY(3 * BEZIER_STEPS)) / 2);
     506             :     }
     507           2 :     return nullptr;
     508             : }
     509             : 
     510             : /************************************************************************/
     511             : /*                         PDFGetSquareCenter()                         */
     512             : /************************************************************************/
     513             : 
     514             : /* Return the center of a square, or NULL if it is not recognized */
     515             : 
     516          13 : static OGRPoint *PDFGetSquareCenter(OGRLineString *poLS)
     517             : {
     518          13 :     if (poLS == nullptr || poLS->getNumPoints() < 4 || poLS->getNumPoints() > 5)
     519           0 :         return nullptr;
     520             : 
     521          25 :     if (poLS->getX(0) == poLS->getX(3) && poLS->getY(0) == poLS->getY(1) &&
     522          30 :         poLS->getX(1) == poLS->getX(2) && poLS->getY(2) == poLS->getY(3) &&
     523           5 :         fabs(fabs(poLS->getX(0) - poLS->getX(1)) -
     524           5 :              fabs(poLS->getY(0) - poLS->getY(3))) < EPSILON)
     525             :     {
     526           4 :         return new OGRPoint((poLS->getX(0) + poLS->getX(1)) / 2,
     527           4 :                             (poLS->getY(0) + poLS->getY(3)) / 2);
     528             :     }
     529           9 :     return nullptr;
     530             : }
     531             : 
     532             : /************************************************************************/
     533             : /*                        PDFGetTriangleCenter()                        */
     534             : /************************************************************************/
     535             : 
     536             : /* Return the center of a equilateral triangle, or NULL if it is not recognized
     537             :  */
     538             : 
     539           4 : static OGRPoint *PDFGetTriangleCenter(OGRLineString *poLS)
     540             : {
     541           4 :     if (poLS == nullptr || poLS->getNumPoints() < 3 || poLS->getNumPoints() > 4)
     542           0 :         return nullptr;
     543             : 
     544           4 :     double dfSqD1 = SQUARE(poLS->getX(0) - poLS->getX(1)) +
     545           4 :                     SQUARE(poLS->getY(0) - poLS->getY(1));
     546           4 :     double dfSqD2 = SQUARE(poLS->getX(1) - poLS->getX(2)) +
     547           4 :                     SQUARE(poLS->getY(1) - poLS->getY(2));
     548           4 :     double dfSqD3 = SQUARE(poLS->getX(0) - poLS->getX(2)) +
     549           4 :                     SQUARE(poLS->getY(0) - poLS->getY(2));
     550           4 :     if (fabs(dfSqD1 - dfSqD2) < EPSILON && fabs(dfSqD2 - dfSqD3) < EPSILON)
     551             :     {
     552           4 :         return new OGRPoint((poLS->getX(0) + poLS->getX(1) + poLS->getX(2)) / 3,
     553           4 :                             (poLS->getY(0) + poLS->getY(1) + poLS->getY(2)) /
     554           4 :                                 3);
     555             :     }
     556           0 :     return nullptr;
     557             : }
     558             : 
     559             : /************************************************************************/
     560             : /*                          PDFGetStarCenter()                          */
     561             : /************************************************************************/
     562             : 
     563             : /* Return the center of a 5-point star, or NULL if it is not recognized */
     564             : 
     565           4 : static OGRPoint *PDFGetStarCenter(OGRLineString *poLS)
     566             : {
     567           8 :     if (poLS == nullptr || poLS->getNumPoints() < 10 ||
     568           4 :         poLS->getNumPoints() > 11)
     569           0 :         return nullptr;
     570             : 
     571           4 :     double dfSqD01 = SQUARE(poLS->getX(0) - poLS->getX(1)) +
     572           4 :                      SQUARE(poLS->getY(0) - poLS->getY(1));
     573           4 :     double dfSqD02 = SQUARE(poLS->getX(0) - poLS->getX(2)) +
     574           4 :                      SQUARE(poLS->getY(0) - poLS->getY(2));
     575           4 :     double dfSqD13 = SQUARE(poLS->getX(1) - poLS->getX(3)) +
     576           4 :                      SQUARE(poLS->getY(1) - poLS->getY(3));
     577           4 :     const double dfSin18divSin126 = 0.38196601125;
     578           4 :     if (dfSqD02 == 0)
     579           0 :         return nullptr;
     580           4 :     int bOK = fabs(dfSqD13 / dfSqD02 - SQUARE(dfSin18divSin126)) < EPSILON;
     581          40 :     for (int i = 1; i < 10 && bOK; i++)
     582             :     {
     583          36 :         double dfSqDiip1 = SQUARE(poLS->getX(i) - poLS->getX((i + 1) % 10)) +
     584          36 :                            SQUARE(poLS->getY(i) - poLS->getY((i + 1) % 10));
     585          36 :         if (fabs(dfSqDiip1 - dfSqD01) > EPSILON)
     586             :         {
     587           0 :             bOK = FALSE;
     588             :         }
     589          36 :         double dfSqDiip2 = SQUARE(poLS->getX(i) - poLS->getX((i + 2) % 10)) +
     590          36 :                            SQUARE(poLS->getY(i) - poLS->getY((i + 2) % 10));
     591          36 :         if ((i % 2) == 1 && fabs(dfSqDiip2 - dfSqD13) > EPSILON)
     592             :         {
     593           0 :             bOK = FALSE;
     594             :         }
     595          36 :         if ((i % 2) == 0 && fabs(dfSqDiip2 - dfSqD02) > EPSILON)
     596             :         {
     597           0 :             bOK = FALSE;
     598             :         }
     599             :     }
     600           4 :     if (bOK)
     601             :     {
     602           4 :         return new OGRPoint((poLS->getX(0) + poLS->getX(2) + poLS->getX(4) +
     603           4 :                              poLS->getX(6) + poLS->getX(8)) /
     604             :                                 5,
     605           4 :                             (poLS->getY(0) + poLS->getY(2) + poLS->getY(4) +
     606           4 :                              poLS->getY(6) + poLS->getY(8)) /
     607           4 :                                 5);
     608             :     }
     609           0 :     return nullptr;
     610             : }
     611             : 
     612             : /************************************************************************/
     613             : /*                            UnstackTokens()                           */
     614             : /************************************************************************/
     615             : 
     616         561 : int PDFDataset::UnstackTokens(
     617             :     const char *pszToken, int nRequiredArgs,
     618             :     char aszTokenStack[TOKEN_STACK_SIZE][MAX_TOKEN_SIZE], int &nTokenStackSize,
     619             :     double *adfCoords)
     620             : {
     621         561 :     if (nTokenStackSize < nRequiredArgs)
     622             :     {
     623           0 :         CPLDebug("PDF", "not enough arguments for %s", pszToken);
     624           0 :         return FALSE;
     625             :     }
     626         561 :     nTokenStackSize -= nRequiredArgs;
     627        2642 :     for (int i = 0; i < nRequiredArgs; i++)
     628             :     {
     629        2081 :         adfCoords[i] = CPLAtof(aszTokenStack[nTokenStackSize + i]);
     630             :     }
     631         561 :     return TRUE;
     632             : }
     633             : 
     634             : /************************************************************************/
     635             : /*                           AddBezierCurve()                           */
     636             : /************************************************************************/
     637             : 
     638         208 : static void AddBezierCurve(std::vector<double> &oCoords, const double *x0_y0,
     639             :                            const double *x1_y1, const double *x2_y2,
     640             :                            const double *x3_y3)
     641             : {
     642         208 :     double x0 = x0_y0[0];
     643         208 :     double y0 = x0_y0[1];
     644         208 :     double x1 = x1_y1[0];
     645         208 :     double y1 = x1_y1[1];
     646         208 :     double x2 = x2_y2[0];
     647         208 :     double y2 = x2_y2[1];
     648         208 :     double x3 = x3_y3[0];
     649         208 :     double y3 = x3_y3[1];
     650        2080 :     for (int i = 1; i < BEZIER_STEPS; i++)
     651             :     {
     652        1872 :         const double t = static_cast<double>(i) / BEZIER_STEPS;
     653        1872 :         const double t2 = t * t;
     654        1872 :         const double t3 = t2 * t;
     655        1872 :         const double oneMinust = 1 - t;
     656        1872 :         const double oneMinust2 = oneMinust * oneMinust;
     657        1872 :         const double oneMinust3 = oneMinust2 * oneMinust;
     658        1872 :         const double three_t_oneMinust = 3 * t * oneMinust;
     659        1872 :         const double x = oneMinust3 * x0 +
     660        1872 :                          three_t_oneMinust * (oneMinust * x1 + t * x2) +
     661        1872 :                          t3 * x3;
     662        1872 :         const double y = oneMinust3 * y0 +
     663        1872 :                          three_t_oneMinust * (oneMinust * y1 + t * y2) +
     664        1872 :                          t3 * y3;
     665        1872 :         oCoords.push_back(x);
     666        1872 :         oCoords.push_back(y);
     667             :     }
     668         208 :     oCoords.push_back(x3);
     669         208 :     oCoords.push_back(y3);
     670         208 : }
     671             : 
     672             : /************************************************************************/
     673             : /*                           ParseContent()                             */
     674             : /************************************************************************/
     675             : 
     676             : #define NEW_SUBPATH -99
     677             : #define CLOSE_SUBPATH -98
     678             : #define FILL_SUBPATH -97
     679             : 
     680          84 : OGRGeometry *PDFDataset::ParseContent(
     681             :     const char *pszContent, GDALPDFObject *poResources, int bInitBDCStack,
     682             :     int bMatchQ, std::map<CPLString, OGRPDFLayer *> &oMapPropertyToLayer,
     683             :     OGRPDFLayer *poCurLayer)
     684             : {
     685          84 :     if (CPLTestBool(CPLGetConfigOption("PDF_DUMP_CONTENT", "NO")))
     686             :     {
     687             :         static int counter = 1;
     688           0 :         FILE *f = fopen(CPLSPrintf("content%d.txt", counter), "wb");
     689           0 :         ++counter;
     690           0 :         fwrite(pszContent, 1, strlen(pszContent), f);
     691           0 :         fclose(f);
     692             :     }
     693          84 :     const char *pszContentIni = pszContent;
     694             : #ifdef DEBUG_VERBOSE
     695             :     CPLDebug("PDF", "Initial layer: %s",
     696             :              poCurLayer ? poCurLayer->GetName() : "(null)");
     697             : #endif
     698             : 
     699             : #define PUSH(aszTokenStack, str, strlen)                                       \
     700             :     do                                                                         \
     701             :     {                                                                          \
     702             :         if (nTokenStackSize < TOKEN_STACK_SIZE)                                \
     703             :             memcpy(aszTokenStack[nTokenStackSize++], str, strlen + 1);         \
     704             :         else                                                                   \
     705             :         {                                                                      \
     706             :             CPLError(CE_Failure, CPLE_AppDefined,                              \
     707             :                      "Max token stack size reached");                          \
     708             :             return nullptr;                                                    \
     709             :         };                                                                     \
     710             :     } while (false)
     711             : 
     712             : #define ADD_CHAR(szToken, c)                                                   \
     713             :     do                                                                         \
     714             :     {                                                                          \
     715             :         if (nTokenSize < MAX_TOKEN_SIZE - 1)                                   \
     716             :         {                                                                      \
     717             :             szToken[nTokenSize++] = c;                                         \
     718             :             szToken[nTokenSize] = '\0';                                        \
     719             :         }                                                                      \
     720             :         else                                                                   \
     721             :         {                                                                      \
     722             :             CPLError(CE_Failure, CPLE_AppDefined, "Max token size reached");   \
     723             :             return nullptr;                                                    \
     724             :         };                                                                     \
     725             :     } while (false)
     726             : 
     727             :     char szToken[MAX_TOKEN_SIZE];
     728          84 :     int nTokenSize = 0;
     729             :     char ch;
     730             :     char aszTokenStack[TOKEN_STACK_SIZE][MAX_TOKEN_SIZE];
     731          84 :     int nTokenStackSize = 0;
     732          84 :     int bInString = FALSE;
     733          84 :     int nBDCOrBMCLevel = 0;
     734          84 :     int nParenthesisLevel = 0;
     735          84 :     int nArrayLevel = 0;
     736          84 :     int nBTLevel = 0;
     737             : 
     738          84 :     int bCollectAllObjects =
     739          84 :         poResources != nullptr && !bInitBDCStack && !bMatchQ;
     740             : 
     741          84 :     GraphicState oGS;
     742         168 :     std::stack<GraphicState> oGSStack;
     743         168 :     std::stack<OGRPDFLayer *> oLayerStack;
     744             : 
     745         168 :     std::vector<double> oCoords;
     746          84 :     int bHasFoundFill = FALSE;
     747          84 :     int bHasMultiPart = FALSE;
     748             : 
     749          84 :     szToken[0] = '\0';
     750             : 
     751          84 :     if (bInitBDCStack)
     752             :     {
     753          32 :         PUSH(aszTokenStack, "dummy", 5);
     754          32 :         PUSH(aszTokenStack, "dummy", 5);
     755          32 :         oLayerStack.push(nullptr);
     756             :     }
     757             : 
     758       19813 :     while ((ch = *pszContent) != '\0')
     759             :     {
     760       19761 :         int bPushToken = FALSE;
     761             : 
     762       19761 :         if (!bInString && ch == '%')
     763             :         {
     764             :             /* Skip comments until end-of-line */
     765           0 :             while ((ch = *pszContent) != '\0')
     766             :             {
     767           0 :                 if (ch == '\r' || ch == '\n')
     768             :                     break;
     769           0 :                 pszContent++;
     770             :             }
     771           0 :             if (ch == 0)
     772           0 :                 break;
     773             :         }
     774       19761 :         else if (!bInString && (ch == ' ' || ch == '\r' || ch == '\n'))
     775             :         {
     776        3601 :             bPushToken = TRUE;
     777             :         }
     778             : 
     779             :         /* Ignore arrays */
     780       16160 :         else if (!bInString && nTokenSize == 0 && ch == '[')
     781             :         {
     782          44 :             nArrayLevel++;
     783             :         }
     784       16116 :         else if (!bInString && nArrayLevel && ch == ']')
     785             :         {
     786          44 :             nArrayLevel--;
     787          44 :             nTokenSize = 0;  // completely ignore content in arrays
     788             :         }
     789             : 
     790       16072 :         else if (!bInString && nTokenSize == 0 && ch == '(')
     791             :         {
     792           8 :             bInString = TRUE;
     793           8 :             nParenthesisLevel++;
     794           8 :             ADD_CHAR(szToken, ch);
     795             :         }
     796       16064 :         else if (bInString && ch == '(')
     797             :         {
     798           0 :             nParenthesisLevel++;
     799           0 :             ADD_CHAR(szToken, ch);
     800             :         }
     801       16064 :         else if (bInString && ch == ')')
     802             :         {
     803           8 :             nParenthesisLevel--;
     804           8 :             ADD_CHAR(szToken, ch);
     805           8 :             if (nParenthesisLevel == 0)
     806             :             {
     807           8 :                 bInString = FALSE;
     808           8 :                 bPushToken = TRUE;
     809             :             }
     810             :         }
     811       16056 :         else if (bInString && ch == '\\')
     812             :         {
     813           0 :             const auto nextCh = pszContent[1];
     814           0 :             if (nextCh == 'n')
     815             :             {
     816           0 :                 ADD_CHAR(szToken, '\n');
     817           0 :                 pszContent++;
     818             :             }
     819           0 :             else if (nextCh == 'r')
     820             :             {
     821           0 :                 ADD_CHAR(szToken, '\r');
     822           0 :                 pszContent++;
     823             :             }
     824           0 :             else if (nextCh == 't')
     825             :             {
     826           0 :                 ADD_CHAR(szToken, '\t');
     827           0 :                 pszContent++;
     828             :             }
     829           0 :             else if (nextCh == 'b')
     830             :             {
     831           0 :                 ADD_CHAR(szToken, '\b');
     832           0 :                 pszContent++;
     833             :             }
     834           0 :             else if (nextCh == '(' || nextCh == ')' || nextCh == '\\')
     835             :             {
     836           0 :                 ADD_CHAR(szToken, nextCh);
     837           0 :                 pszContent++;
     838             :             }
     839           0 :             else if (nextCh >= '0' && nextCh <= '7' && pszContent[2] >= '0' &&
     840           0 :                      pszContent[2] <= '7' && pszContent[3] >= '0' &&
     841           0 :                      pszContent[3] <= '7')
     842             :             {
     843           0 :                 ADD_CHAR(szToken,
     844             :                          ((nextCh - '\0') * 64 + (pszContent[2] - '\0') * 8 +
     845             :                           pszContent[3] - '\0'));
     846           0 :                 pszContent += 3;
     847             :             }
     848           0 :             else if (nextCh == '\n')
     849             :             {
     850           0 :                 if (pszContent[2] == '\r')
     851           0 :                     pszContent += 2;
     852             :                 else
     853           0 :                     pszContent++;
     854             :             }
     855           0 :             else if (nextCh == '\r')
     856             :             {
     857           0 :                 pszContent++;
     858           0 :             }
     859             :         }
     860       16056 :         else if (ch == '<' && pszContent[1] == '<' && nTokenSize == 0)
     861             :         {
     862           1 :             int nDictDepth = 0;
     863             : 
     864           9 :             while (*pszContent != '\0')
     865             :             {
     866           9 :                 if (pszContent[0] == '<' && pszContent[1] == '<')
     867             :                 {
     868           1 :                     ADD_CHAR(szToken, '<');
     869           1 :                     ADD_CHAR(szToken, '<');
     870           1 :                     nDictDepth++;
     871           1 :                     pszContent += 2;
     872             :                 }
     873           8 :                 else if (pszContent[0] == '>' && pszContent[1] == '>')
     874             :                 {
     875           1 :                     ADD_CHAR(szToken, '>');
     876           1 :                     ADD_CHAR(szToken, '>');
     877           1 :                     nDictDepth--;
     878           1 :                     pszContent += 2;
     879           1 :                     if (nDictDepth == 0)
     880           1 :                         break;
     881             :                 }
     882             :                 else
     883             :                 {
     884           7 :                     ADD_CHAR(szToken, *pszContent);
     885           7 :                     pszContent++;
     886             :                 }
     887             :             }
     888           1 :             if (nDictDepth == 0)
     889             :             {
     890           1 :                 bPushToken = TRUE;
     891           1 :                 pszContent--;
     892             :             }
     893             :             else
     894           1 :                 break;
     895             :         }
     896             :         else
     897             :         {
     898             :             // Do not create too long tokens in arrays, that we will ignore
     899             :             // anyway
     900       16055 :             if (nArrayLevel == 0 || nTokenSize == 0)
     901             :             {
     902       16027 :                 ADD_CHAR(szToken, ch);
     903             :             }
     904             :         }
     905             : 
     906       19761 :         pszContent++;
     907       19761 :         if (pszContent[0] == '\0')
     908          52 :             bPushToken = TRUE;
     909             : 
     910             : #define EQUAL1(szToken, s) (szToken[0] == s[0] && szToken[1] == '\0')
     911             : #define EQUAL2(szToken, s)                                                     \
     912             :     (szToken[0] == s[0] && szToken[1] == s[1] && szToken[2] == '\0')
     913             : #define EQUAL3(szToken, s)                                                     \
     914             :     (szToken[0] == s[0] && szToken[1] == s[1] && szToken[2] == s[2] &&         \
     915             :      szToken[3] == '\0')
     916             : 
     917       19761 :         if (bPushToken && nTokenSize)
     918             :         {
     919        3656 :             if (EQUAL2(szToken, "BI"))
     920             :             {
     921           0 :                 while (*pszContent != '\0')
     922             :                 {
     923           0 :                     if (pszContent[0] == 'E' && pszContent[1] == 'I' &&
     924           0 :                         pszContent[2] == ' ')
     925             :                     {
     926           0 :                         break;
     927             :                     }
     928           0 :                     pszContent++;
     929             :                 }
     930           0 :                 if (pszContent[0] == 'E')
     931           0 :                     pszContent += 3;
     932             :                 else
     933           0 :                     return nullptr;
     934             :             }
     935        3656 :             else if (EQUAL3(szToken, "BDC"))
     936             :             {
     937          37 :                 if (nTokenStackSize < 2)
     938             :                 {
     939           0 :                     CPLDebug("PDF", "not enough arguments for %s", szToken);
     940           0 :                     return nullptr;
     941             :                 }
     942          37 :                 nTokenStackSize -= 2;
     943          37 :                 const char *pszOC = aszTokenStack[nTokenStackSize];
     944          37 :                 const char *pszOCGName = aszTokenStack[nTokenStackSize + 1];
     945             : 
     946          37 :                 nBDCOrBMCLevel++;
     947             : 
     948          37 :                 if (EQUAL3(pszOC, "/OC") && pszOCGName[0] == '/')
     949             :                 {
     950           4 :                     const auto oIter = oMapPropertyToLayer.find(pszOCGName + 1);
     951           4 :                     if (oIter != oMapPropertyToLayer.end())
     952             :                     {
     953           4 :                         poCurLayer = oIter->second;
     954             :                     }
     955             :                 }
     956             : #ifdef DEBUG_VERBOSE
     957             :                 CPLDebug("PDF", "%s %s BDC -> Cur layer : %s", pszOC,
     958             :                          pszOCGName,
     959             :                          poCurLayer ? poCurLayer->GetName() : "(null)");
     960             : #endif
     961          37 :                 oLayerStack.push(poCurLayer);
     962             :             }
     963        3619 :             else if (EQUAL3(szToken, "BMC"))
     964             :             {
     965           0 :                 if (nTokenStackSize < 1)
     966             :                 {
     967           0 :                     CPLDebug("PDF", "not enough arguments for %s", szToken);
     968           0 :                     return nullptr;
     969             :                 }
     970           0 :                 nTokenStackSize -= 1;
     971             : 
     972           0 :                 nBDCOrBMCLevel++;
     973           0 :                 oLayerStack.push(poCurLayer);
     974             :             }
     975        3619 :             else if (EQUAL3(szToken, "EMC"))
     976             :             {
     977             :                 // CPLDebug("PDF", "EMC");
     978           6 :                 if (!oLayerStack.empty())
     979             :                 {
     980           6 :                     oLayerStack.pop();
     981           6 :                     if (!oLayerStack.empty())
     982           3 :                         poCurLayer = oLayerStack.top();
     983             :                     else
     984           3 :                         poCurLayer = nullptr;
     985             : 
     986             : #ifdef DEBUG_VERBOSE
     987             :                     CPLDebug("PDF", "EMC -> Cur layer : %s",
     988             :                              poCurLayer ? poCurLayer->GetName() : "(null)");
     989             : #endif
     990             :                 }
     991             :                 else
     992             :                 {
     993           0 :                     CPLDebug(
     994             :                         "PDF",
     995             :                         "Should not happen at line %d: offset %d in stream",
     996           0 :                         __LINE__, int(pszContent - pszContentIni));
     997           0 :                     poCurLayer = nullptr;
     998             :                     // return NULL;
     999             :                 }
    1000             : 
    1001           6 :                 nBDCOrBMCLevel--;
    1002           6 :                 if (nBDCOrBMCLevel == 0 && bInitBDCStack)
    1003           1 :                     break;
    1004             :             }
    1005             : 
    1006             :             /* Ignore any text stuff */
    1007        3613 :             else if (EQUAL2(szToken, "BT"))
    1008           3 :                 nBTLevel++;
    1009        3610 :             else if (EQUAL2(szToken, "ET"))
    1010             :             {
    1011           3 :                 nBTLevel--;
    1012           3 :                 if (nBTLevel < 0)
    1013             :                 {
    1014           0 :                     CPLDebug(
    1015             :                         "PDF",
    1016             :                         "Should not happen at line %d: offset %d in stream",
    1017           0 :                         __LINE__, int(pszContent - pszContentIni));
    1018           0 :                     return nullptr;
    1019             :                 }
    1020             :             }
    1021        3607 :             else if (!nArrayLevel && !nBTLevel)
    1022             :             {
    1023        3522 :                 int bEmitFeature = FALSE;
    1024             : 
    1025        3522 :                 if (szToken[0] < 'A')
    1026             :                 {
    1027        2423 :                     PUSH(aszTokenStack, szToken, nTokenSize);
    1028             :                 }
    1029        1099 :                 else if (EQUAL1(szToken, "q"))
    1030             :                 {
    1031          61 :                     oGSStack.push(oGS);
    1032             :                 }
    1033        1038 :                 else if (EQUAL1(szToken, "Q"))
    1034             :                 {
    1035          61 :                     if (oGSStack.empty())
    1036             :                     {
    1037           0 :                         CPLDebug("PDF", "not enough arguments for %s", szToken);
    1038           0 :                         return nullptr;
    1039             :                     }
    1040             : 
    1041          61 :                     oGS = oGSStack.top();
    1042          61 :                     oGSStack.pop();
    1043             : 
    1044          61 :                     if (oGSStack.empty() && bMatchQ)
    1045           0 :                         break;
    1046             :                 }
    1047         977 :                 else if (EQUAL2(szToken, "cm"))
    1048             :                 {
    1049             :                     double adfMatrix[6];
    1050           8 :                     if (!UnstackTokens(szToken, 6, aszTokenStack,
    1051             :                                        nTokenStackSize, adfMatrix))
    1052             :                     {
    1053           0 :                         CPLDebug(
    1054             :                             "PDF",
    1055             :                             "Should not happen at line %d: offset %d in stream",
    1056           0 :                             __LINE__, int(pszContent - pszContentIni));
    1057           0 :                         return nullptr;
    1058             :                     }
    1059             : 
    1060           8 :                     oGS.MultiplyBy(adfMatrix);
    1061             :                 }
    1062         969 :                 else if (EQUAL1(szToken, "b") || /* closepath, fill, stroke */
    1063         969 :                          EQUAL2(szToken, "b*") /* closepath, eofill, stroke */)
    1064             :                 {
    1065          54 :                     if (!(!oCoords.empty() &&
    1066          27 :                           oCoords[oCoords.size() - 2] == CLOSE_SUBPATH &&
    1067           7 :                           oCoords.back() == CLOSE_SUBPATH))
    1068             :                     {
    1069          20 :                         oCoords.push_back(CLOSE_SUBPATH);
    1070          20 :                         oCoords.push_back(CLOSE_SUBPATH);
    1071             :                     }
    1072          27 :                     oCoords.push_back(FILL_SUBPATH);
    1073          27 :                     oCoords.push_back(FILL_SUBPATH);
    1074          27 :                     bHasFoundFill = TRUE;
    1075             : 
    1076          27 :                     bEmitFeature = TRUE;
    1077             :                 }
    1078         942 :                 else if (EQUAL1(szToken, "B") ||  /* fill, stroke */
    1079         942 :                          EQUAL2(szToken, "B*") || /* eofill, stroke */
    1080         942 :                          EQUAL1(szToken, "f") ||  /* fill */
    1081         942 :                          EQUAL1(szToken, "F") ||  /* fill */
    1082         942 :                          EQUAL2(szToken, "f*") /* eofill */)
    1083             :                 {
    1084           9 :                     oCoords.push_back(FILL_SUBPATH);
    1085           9 :                     oCoords.push_back(FILL_SUBPATH);
    1086           9 :                     bHasFoundFill = TRUE;
    1087             : 
    1088           9 :                     bEmitFeature = TRUE;
    1089             :                 }
    1090         933 :                 else if (EQUAL1(szToken, "h")) /* close subpath */
    1091             :                 {
    1092          44 :                     if (!(!oCoords.empty() &&
    1093          22 :                           oCoords[oCoords.size() - 2] == CLOSE_SUBPATH &&
    1094           0 :                           oCoords.back() == CLOSE_SUBPATH))
    1095             :                     {
    1096          22 :                         oCoords.push_back(CLOSE_SUBPATH);
    1097          22 :                         oCoords.push_back(CLOSE_SUBPATH);
    1098             :                     }
    1099             :                 }
    1100         911 :                 else if (EQUAL1(
    1101             :                              szToken,
    1102             :                              "n")) /* new subpath without stroking or filling */
    1103             :                 {
    1104           2 :                     oCoords.resize(0);
    1105             :                 }
    1106         909 :                 else if (EQUAL1(szToken, "s")) /* close and stroke */
    1107             :                 {
    1108          16 :                     if (!(!oCoords.empty() &&
    1109           8 :                           oCoords[oCoords.size() - 2] == CLOSE_SUBPATH &&
    1110           0 :                           oCoords.back() == CLOSE_SUBPATH))
    1111             :                     {
    1112           8 :                         oCoords.push_back(CLOSE_SUBPATH);
    1113           8 :                         oCoords.push_back(CLOSE_SUBPATH);
    1114             :                     }
    1115             : 
    1116           8 :                     bEmitFeature = TRUE;
    1117             :                 }
    1118         901 :                 else if (EQUAL1(szToken, "S")) /* stroke */
    1119             :                 {
    1120          14 :                     bEmitFeature = TRUE;
    1121             :                 }
    1122         887 :                 else if (EQUAL1(szToken, "m") || EQUAL1(szToken, "l"))
    1123             :                 {
    1124             :                     double adfCoords[2];
    1125         251 :                     if (!UnstackTokens(szToken, 2, aszTokenStack,
    1126             :                                        nTokenStackSize, adfCoords))
    1127             :                     {
    1128           0 :                         CPLDebug(
    1129             :                             "PDF",
    1130             :                             "Should not happen at line %d: offset %d in stream",
    1131           0 :                             __LINE__, int(pszContent - pszContentIni));
    1132           0 :                         return nullptr;
    1133             :                     }
    1134             : 
    1135         251 :                     if (EQUAL1(szToken, "m"))
    1136             :                     {
    1137          74 :                         if (!oCoords.empty())
    1138          15 :                             bHasMultiPart = TRUE;
    1139          74 :                         oCoords.push_back(NEW_SUBPATH);
    1140          74 :                         oCoords.push_back(NEW_SUBPATH);
    1141             :                     }
    1142             : 
    1143         251 :                     oGS.ApplyMatrix(adfCoords);
    1144         251 :                     oCoords.push_back(adfCoords[0]);
    1145         251 :                     oCoords.push_back(adfCoords[1]);
    1146             :                 }
    1147         636 :                 else if (EQUAL1(szToken, "c")) /* Bezier curve */
    1148             :                 {
    1149             :                     double adfCoords[6];
    1150         208 :                     if (!UnstackTokens(szToken, 6, aszTokenStack,
    1151             :                                        nTokenStackSize, adfCoords))
    1152             :                     {
    1153           0 :                         CPLDebug(
    1154             :                             "PDF",
    1155             :                             "Should not happen at line %d: offset %d in stream",
    1156           0 :                             __LINE__, int(pszContent - pszContentIni));
    1157           0 :                         return nullptr;
    1158             :                     }
    1159             : 
    1160         208 :                     oGS.ApplyMatrix(adfCoords + 0);
    1161         208 :                     oGS.ApplyMatrix(adfCoords + 2);
    1162         208 :                     oGS.ApplyMatrix(adfCoords + 4);
    1163         416 :                     AddBezierCurve(oCoords,
    1164         208 :                                    oCoords.empty()
    1165             :                                        ? &adfCoords[0]
    1166         208 :                                        : &oCoords[oCoords.size() - 2],
    1167         208 :                                    &adfCoords[0], &adfCoords[2], &adfCoords[4]);
    1168             :                 }
    1169         428 :                 else if (EQUAL1(szToken, "v")) /* Bezier curve */
    1170             :                 {
    1171             :                     double adfCoords[4];
    1172           0 :                     if (!UnstackTokens(szToken, 4, aszTokenStack,
    1173             :                                        nTokenStackSize, adfCoords))
    1174             :                     {
    1175           0 :                         CPLDebug(
    1176             :                             "PDF",
    1177             :                             "Should not happen at line %d: offset %d in stream",
    1178           0 :                             __LINE__, int(pszContent - pszContentIni));
    1179           0 :                         return nullptr;
    1180             :                     }
    1181             : 
    1182           0 :                     oGS.ApplyMatrix(adfCoords + 0);
    1183           0 :                     oGS.ApplyMatrix(adfCoords + 2);
    1184           0 :                     AddBezierCurve(
    1185             :                         oCoords,
    1186           0 :                         oCoords.empty() ? &adfCoords[0]
    1187           0 :                                         : &oCoords[oCoords.size() - 2],
    1188           0 :                         oCoords.empty() ? &adfCoords[0]
    1189           0 :                                         : &oCoords[oCoords.size() - 2],
    1190           0 :                         &adfCoords[0], &adfCoords[2]);
    1191             :                 }
    1192         428 :                 else if (EQUAL1(szToken, "y")) /* Bezier curve */
    1193             :                 {
    1194             :                     double adfCoords[4];
    1195           0 :                     if (!UnstackTokens(szToken, 4, aszTokenStack,
    1196             :                                        nTokenStackSize, adfCoords))
    1197             :                     {
    1198           0 :                         CPLDebug(
    1199             :                             "PDF",
    1200             :                             "Should not happen at line %d: offset %d in stream",
    1201           0 :                             __LINE__, int(pszContent - pszContentIni));
    1202           0 :                         return nullptr;
    1203             :                     }
    1204             : 
    1205           0 :                     oGS.ApplyMatrix(adfCoords + 0);
    1206           0 :                     oGS.ApplyMatrix(adfCoords + 2);
    1207           0 :                     AddBezierCurve(oCoords,
    1208           0 :                                    oCoords.empty()
    1209             :                                        ? &adfCoords[0]
    1210           0 :                                        : &oCoords[oCoords.size() - 2],
    1211           0 :                                    &adfCoords[0], &adfCoords[2], &adfCoords[2]);
    1212             :                 }
    1213         428 :                 else if (EQUAL2(szToken, "re")) /* Rectangle */
    1214             :                 {
    1215             :                     double adfCoords[4];
    1216           1 :                     if (!UnstackTokens(szToken, 4, aszTokenStack,
    1217             :                                        nTokenStackSize, adfCoords))
    1218             :                     {
    1219           0 :                         CPLDebug(
    1220             :                             "PDF",
    1221             :                             "Should not happen at line %d: offset %d in stream",
    1222           0 :                             __LINE__, int(pszContent - pszContentIni));
    1223           0 :                         return nullptr;
    1224             :                     }
    1225             : 
    1226           1 :                     adfCoords[2] += adfCoords[0];
    1227           1 :                     adfCoords[3] += adfCoords[1];
    1228             : 
    1229           1 :                     oGS.ApplyMatrix(adfCoords);
    1230           1 :                     oGS.ApplyMatrix(adfCoords + 2);
    1231             : 
    1232           1 :                     if (!oCoords.empty())
    1233           0 :                         bHasMultiPart = TRUE;
    1234           1 :                     oCoords.push_back(NEW_SUBPATH);
    1235           1 :                     oCoords.push_back(NEW_SUBPATH);
    1236           1 :                     oCoords.push_back(adfCoords[0]);
    1237           1 :                     oCoords.push_back(adfCoords[1]);
    1238           1 :                     oCoords.push_back(adfCoords[2]);
    1239           1 :                     oCoords.push_back(adfCoords[1]);
    1240           1 :                     oCoords.push_back(adfCoords[2]);
    1241           1 :                     oCoords.push_back(adfCoords[3]);
    1242           1 :                     oCoords.push_back(adfCoords[0]);
    1243           1 :                     oCoords.push_back(adfCoords[3]);
    1244           1 :                     oCoords.push_back(CLOSE_SUBPATH);
    1245           1 :                     oCoords.push_back(CLOSE_SUBPATH);
    1246             :                 }
    1247             : 
    1248         427 :                 else if (EQUAL2(szToken, "Do"))
    1249             :                 {
    1250          50 :                     if (nTokenStackSize == 0)
    1251             :                     {
    1252           0 :                         CPLDebug("PDF", "not enough arguments for %s", szToken);
    1253          31 :                         return nullptr;
    1254             :                     }
    1255             : 
    1256          50 :                     CPLString osObjectName = aszTokenStack[--nTokenStackSize];
    1257             : 
    1258          50 :                     if (osObjectName[0] != '/')
    1259             :                     {
    1260           0 :                         CPLDebug(
    1261             :                             "PDF",
    1262             :                             "Should not happen at line %d: offset %d in stream",
    1263           0 :                             __LINE__, int(pszContent - pszContentIni));
    1264           0 :                         return nullptr;
    1265             :                     }
    1266             : 
    1267          50 :                     if (poResources == nullptr)
    1268             :                     {
    1269           2 :                         if (osObjectName.find("/SymImage") == 0)
    1270             :                         {
    1271           2 :                             oCoords.push_back(oGS.adfCM[4] + oGS.adfCM[0] / 2);
    1272           2 :                             oCoords.push_back(oGS.adfCM[5] + oGS.adfCM[3] / 2);
    1273             : 
    1274           2 :                             szToken[0] = '\0';
    1275           2 :                             nTokenSize = 0;
    1276             : 
    1277           2 :                             if (poCurLayer != nullptr)
    1278           1 :                                 bEmitFeature = TRUE;
    1279             :                             else
    1280           1 :                                 continue;
    1281             :                         }
    1282             :                         else
    1283             :                         {
    1284             :                             // CPLDebug("PDF", "Should not happen at line %d",
    1285             :                             // __LINE__);
    1286           0 :                             return nullptr;
    1287             :                         }
    1288             :                     }
    1289             : 
    1290          49 :                     if (!bEmitFeature)
    1291             :                     {
    1292             :                         GDALPDFObject *poXObject =
    1293          48 :                             poResources->GetDictionary()->Get("XObject");
    1294          96 :                         if (poXObject == nullptr ||
    1295          48 :                             poXObject->GetType() != PDFObjectType_Dictionary)
    1296             :                         {
    1297           0 :                             CPLDebug("PDF",
    1298             :                                      "Should not happen at line %d: offset %d "
    1299             :                                      "in stream",
    1300           0 :                                      __LINE__, int(pszContent - pszContentIni));
    1301           0 :                             return nullptr;
    1302             :                         }
    1303             : 
    1304             :                         GDALPDFObject *poObject =
    1305          96 :                             poXObject->GetDictionary()->Get(
    1306          48 :                                 osObjectName.c_str() + 1);
    1307          48 :                         if (poObject == nullptr)
    1308             :                         {
    1309           0 :                             CPLDebug("PDF",
    1310             :                                      "Should not happen at line %d: offset %d "
    1311             :                                      "in stream",
    1312           0 :                                      __LINE__, int(pszContent - pszContentIni));
    1313           0 :                             return nullptr;
    1314             :                         }
    1315             : 
    1316          48 :                         int bParseStream = TRUE;
    1317             :                         /* Check if the object is an image. If so, no need to
    1318             :                          * try to parse */
    1319             :                         /* it. */
    1320          48 :                         if (poObject->GetType() == PDFObjectType_Dictionary)
    1321             :                         {
    1322             :                             GDALPDFObject *poSubtype =
    1323          48 :                                 poObject->GetDictionary()->Get("Subtype");
    1324          96 :                             if (poSubtype != nullptr &&
    1325          96 :                                 poSubtype->GetType() == PDFObjectType_Name &&
    1326          48 :                                 poSubtype->GetName() == "Image")
    1327             :                             {
    1328           0 :                                 bParseStream = FALSE;
    1329             :                             }
    1330             :                         }
    1331             : 
    1332          48 :                         if (bParseStream)
    1333             :                         {
    1334          48 :                             GDALPDFStream *poStream = poObject->GetStream();
    1335          48 :                             if (!poStream)
    1336             :                             {
    1337           0 :                                 CPLDebug("PDF",
    1338             :                                          "Should not happen at line %d: offset "
    1339             :                                          "%d in stream",
    1340             :                                          __LINE__,
    1341           0 :                                          int(pszContent - pszContentIni));
    1342           0 :                                 return nullptr;
    1343             :                             }
    1344             : 
    1345          48 :                             char *pszStr = poStream->GetBytes();
    1346          48 :                             if (pszStr)
    1347             :                             {
    1348          48 :                                 OGRGeometry *poGeom = ParseContent(
    1349             :                                     pszStr, nullptr, FALSE, FALSE,
    1350             :                                     oMapPropertyToLayer, poCurLayer);
    1351          48 :                                 CPLFree(pszStr);
    1352          48 :                                 if (poGeom && !bCollectAllObjects)
    1353          31 :                                     return poGeom;
    1354          17 :                                 delete poGeom;
    1355             :                             }
    1356             :                         }
    1357          18 :                     }
    1358             :                 }
    1359         377 :                 else if (EQUAL2(szToken, "RG") || EQUAL2(szToken, "rg"))
    1360             :                 {
    1361          47 :                     double *padf = (EQUAL2(szToken, "RG"))
    1362         140 :                                        ? &oGS.adfStrokeColor[0]
    1363          46 :                                        : &oGS.adfFillColor[0];
    1364          93 :                     if (!UnstackTokens(szToken, 3, aszTokenStack,
    1365             :                                        nTokenStackSize, padf))
    1366             :                     {
    1367           0 :                         CPLDebug(
    1368             :                             "PDF",
    1369             :                             "Should not happen at line %d: offset %d in stream",
    1370           0 :                             __LINE__, int(pszContent - pszContentIni));
    1371           0 :                         return nullptr;
    1372          93 :                     }
    1373             :                 }
    1374         284 :                 else if (m_oMapOperators.find(szToken) != m_oMapOperators.end())
    1375             :                 {
    1376         284 :                     int nArgs = m_oMapOperators[szToken];
    1377         284 :                     if (nArgs < 0)
    1378             :                     {
    1379           0 :                         while (nTokenStackSize != 0)
    1380             :                         {
    1381             :                             CPLString osTopToken =
    1382           0 :                                 aszTokenStack[--nTokenStackSize];
    1383           0 :                             if (m_oMapOperators.find(osTopToken) !=
    1384           0 :                                 m_oMapOperators.end())
    1385           0 :                                 break;
    1386             :                         }
    1387             :                     }
    1388             :                     else
    1389             :                     {
    1390         284 :                         if (nArgs > nTokenStackSize)
    1391             :                         {
    1392           0 :                             CPLDebug("PDF", "not enough arguments for %s",
    1393             :                                      szToken);
    1394           0 :                             return nullptr;
    1395             :                         }
    1396         284 :                         nTokenStackSize -= nArgs;
    1397             :                     }
    1398             :                 }
    1399             :                 else
    1400             :                 {
    1401           0 :                     PUSH(aszTokenStack, szToken, nTokenSize);
    1402             :                 }
    1403             : 
    1404        3490 :                 if (bEmitFeature && poCurLayer != nullptr)
    1405             :                 {
    1406             :                     OGRGeometry *poGeom =
    1407          29 :                         BuildGeometry(oCoords, bHasFoundFill, bHasMultiPart);
    1408          29 :                     bHasFoundFill = FALSE;
    1409          29 :                     bHasMultiPart = FALSE;
    1410          29 :                     if (poGeom)
    1411             :                     {
    1412             :                         OGRFeature *poFeature =
    1413          29 :                             new OGRFeature(poCurLayer->GetLayerDefn());
    1414          29 :                         if (m_bSetStyle)
    1415             :                         {
    1416             :                             OGRwkbGeometryType eType =
    1417          29 :                                 wkbFlatten(poGeom->getGeometryType());
    1418          29 :                             if (eType == wkbLineString ||
    1419             :                                 eType == wkbMultiLineString)
    1420             :                             {
    1421           6 :                                 poFeature->SetStyleString(CPLSPrintf(
    1422             :                                     "PEN(c:#%02X%02X%02X)",
    1423           6 :                                     (int)(oGS.adfStrokeColor[0] * 255 + 0.5),
    1424           6 :                                     (int)(oGS.adfStrokeColor[1] * 255 + 0.5),
    1425           6 :                                     (int)(oGS.adfStrokeColor[2] * 255 + 0.5)));
    1426             :                             }
    1427          23 :                             else if (eType == wkbPolygon ||
    1428             :                                      eType == wkbMultiPolygon)
    1429             :                             {
    1430          12 :                                 poFeature->SetStyleString(CPLSPrintf(
    1431             :                                     "PEN(c:#%02X%02X%02X);BRUSH(fc:#%02X%02X%"
    1432             :                                     "02X)",
    1433          12 :                                     (int)(oGS.adfStrokeColor[0] * 255 + 0.5),
    1434          12 :                                     (int)(oGS.adfStrokeColor[1] * 255 + 0.5),
    1435          12 :                                     (int)(oGS.adfStrokeColor[2] * 255 + 0.5),
    1436          12 :                                     (int)(oGS.adfFillColor[0] * 255 + 0.5),
    1437          12 :                                     (int)(oGS.adfFillColor[1] * 255 + 0.5),
    1438          12 :                                     (int)(oGS.adfFillColor[2] * 255 + 0.5)));
    1439             :                             }
    1440             :                         }
    1441          58 :                         poGeom->assignSpatialReference(
    1442          29 :                             poCurLayer->GetSpatialRef());
    1443          29 :                         poFeature->SetGeometryDirectly(poGeom);
    1444          29 :                         CPL_IGNORE_RET_VAL(
    1445          29 :                             poCurLayer->CreateFeature(poFeature));
    1446          29 :                         delete poFeature;
    1447             :                     }
    1448             : 
    1449          29 :                     oCoords.resize(0);
    1450             :                 }
    1451             :             }
    1452             : 
    1453        3623 :             szToken[0] = '\0';
    1454        3623 :             nTokenSize = 0;
    1455             :         }
    1456             :     }
    1457             : 
    1458          53 :     if (nTokenStackSize != 0)
    1459             :     {
    1460           0 :         while (nTokenStackSize != 0)
    1461             :         {
    1462           0 :             nTokenStackSize--;
    1463           0 :             CPLDebug("PDF", "Remaining values in stack : %s",
    1464           0 :                      aszTokenStack[nTokenStackSize]);
    1465             :         }
    1466           0 :         return nullptr;
    1467             :     }
    1468             : 
    1469          53 :     if (bCollectAllObjects)
    1470           4 :         return nullptr;
    1471             : 
    1472          49 :     return BuildGeometry(oCoords, bHasFoundFill, bHasMultiPart);
    1473             : }
    1474             : 
    1475             : /************************************************************************/
    1476             : /*                           BuildGeometry()                            */
    1477             : /************************************************************************/
    1478             : 
    1479          78 : OGRGeometry *PDFDataset::BuildGeometry(std::vector<double> &oCoords,
    1480             :                                        int bHasFoundFill, int bHasMultiPart)
    1481             : {
    1482          78 :     OGRGeometry *poGeom = nullptr;
    1483             : 
    1484          78 :     if (!oCoords.size())
    1485          18 :         return nullptr;
    1486             : 
    1487          60 :     if (oCoords.size() == 2)
    1488             :     {
    1489             :         double X, Y;
    1490           2 :         PDFCoordsToSRSCoords(oCoords[0], oCoords[1], X, Y);
    1491           2 :         poGeom = new OGRPoint(X, Y);
    1492             :     }
    1493          58 :     else if (!bHasFoundFill)
    1494             :     {
    1495          22 :         OGRLineString *poLS = nullptr;
    1496          22 :         OGRMultiLineString *poMLS = nullptr;
    1497          22 :         if (bHasMultiPart)
    1498             :         {
    1499           4 :             poMLS = new OGRMultiLineString();
    1500           4 :             poGeom = poMLS;
    1501             :         }
    1502             : 
    1503         255 :         for (size_t i = 0; i < oCoords.size(); i += 2)
    1504             :         {
    1505         233 :             if (oCoords[i] == NEW_SUBPATH && oCoords[i + 1] == NEW_SUBPATH)
    1506             :             {
    1507          26 :                 if (poMLS)
    1508             :                 {
    1509           8 :                     poLS = new OGRLineString();
    1510           8 :                     poMLS->addGeometryDirectly(poLS);
    1511             :                 }
    1512             :                 else
    1513             :                 {
    1514          18 :                     delete poLS;
    1515          18 :                     poLS = new OGRLineString();
    1516          18 :                     poGeom = poLS;
    1517             :                 }
    1518             :             }
    1519         219 :             else if (oCoords[i] == CLOSE_SUBPATH &&
    1520          12 :                      oCoords[i + 1] == CLOSE_SUBPATH)
    1521             :             {
    1522          32 :                 if (poLS && poLS->getNumPoints() >= 2 &&
    1523          20 :                     !(poLS->getX(0) == poLS->getX(poLS->getNumPoints() - 1) &&
    1524           8 :                       poLS->getY(0) == poLS->getY(poLS->getNumPoints() - 1)))
    1525             :                 {
    1526           6 :                     poLS->addPoint(poLS->getX(0), poLS->getY(0));
    1527             :                 }
    1528             :             }
    1529         195 :             else if (oCoords[i] == FILL_SUBPATH &&
    1530           0 :                      oCoords[i + 1] == FILL_SUBPATH)
    1531             :             {
    1532             :                 /* Should not happen */
    1533             :             }
    1534             :             else
    1535             :             {
    1536         195 :                 if (poLS)
    1537             :                 {
    1538             :                     double X, Y;
    1539         195 :                     PDFCoordsToSRSCoords(oCoords[i], oCoords[i + 1], X, Y);
    1540             : 
    1541         195 :                     poLS->addPoint(X, Y);
    1542             :                 }
    1543             :             }
    1544             :         }
    1545             : 
    1546             :         // Recognize points as written by GDAL (ogr-sym-2 : circle (not filled))
    1547          22 :         OGRGeometry *poCenter = nullptr;
    1548          44 :         if (poCenter == nullptr && poLS != nullptr &&
    1549          22 :             poLS->getNumPoints() == 1 + BEZIER_STEPS * 4)
    1550             :         {
    1551           3 :             poCenter = PDFGetCircleCenter(poLS);
    1552             :         }
    1553             : 
    1554             :         // Recognize points as written by GDAL (ogr-sym-4: square (not filled))
    1555          42 :         if (poCenter == nullptr && poLS != nullptr &&
    1556          20 :             (poLS->getNumPoints() == 4 || poLS->getNumPoints() == 5))
    1557             :         {
    1558           4 :             poCenter = PDFGetSquareCenter(poLS);
    1559             :         }
    1560             : 
    1561             :         // Recognize points as written by GDAL (ogr-sym-6: triangle (not
    1562             :         // filled))
    1563          40 :         if (poCenter == nullptr && poLS != nullptr &&
    1564          18 :             (poLS->getNumPoints() == 3 || poLS->getNumPoints() == 4))
    1565             :         {
    1566           2 :             poCenter = PDFGetTriangleCenter(poLS);
    1567             :         }
    1568             : 
    1569             :         // Recognize points as written by GDAL (ogr-sym-8: star (not filled))
    1570          38 :         if (poCenter == nullptr && poLS != nullptr &&
    1571          16 :             (poLS->getNumPoints() == 10 || poLS->getNumPoints() == 11))
    1572             :         {
    1573           2 :             poCenter = PDFGetStarCenter(poLS);
    1574             :         }
    1575             : 
    1576          26 :         if (poCenter == nullptr && poMLS != nullptr &&
    1577           4 :             poMLS->getNumGeometries() == 2)
    1578             :         {
    1579           4 :             const OGRLineString *poLS1 = poMLS->getGeometryRef(0);
    1580           4 :             const OGRLineString *poLS2 = poMLS->getGeometryRef(1);
    1581             : 
    1582             :             // Recognize points as written by GDAL (ogr-sym-0: cross (+) ).
    1583          12 :             if (poLS1->getNumPoints() == 2 && poLS2->getNumPoints() == 2 &&
    1584           6 :                 poLS1->getY(0) == poLS1->getY(1) &&
    1585           2 :                 poLS2->getX(0) == poLS2->getX(1) &&
    1586           2 :                 fabs(fabs(poLS1->getX(0) - poLS1->getX(1)) -
    1587           2 :                      fabs(poLS2->getY(0) - poLS2->getY(1))) < EPSILON &&
    1588           2 :                 fabs((poLS1->getX(0) + poLS1->getX(1)) / 2 - poLS2->getX(0)) <
    1589           8 :                     EPSILON &&
    1590           2 :                 fabs((poLS2->getY(0) + poLS2->getY(1)) / 2 - poLS1->getY(0)) <
    1591             :                     EPSILON)
    1592             :             {
    1593           2 :                 poCenter = new OGRPoint(poLS2->getX(0), poLS1->getY(0));
    1594             :             }
    1595             :             // Recognize points as written by GDAL (ogr-sym-1: diagcross (X) ).
    1596           6 :             else if (poLS1->getNumPoints() == 2 && poLS2->getNumPoints() == 2 &&
    1597           4 :                      poLS1->getX(0) == poLS2->getX(0) &&
    1598           4 :                      poLS1->getY(0) == poLS2->getY(1) &&
    1599           4 :                      poLS1->getX(1) == poLS2->getX(1) &&
    1600           6 :                      poLS1->getY(1) == poLS2->getY(0) &&
    1601           2 :                      fabs(fabs(poLS1->getX(0) - poLS1->getX(1)) -
    1602           2 :                           fabs(poLS1->getY(0) - poLS1->getY(1))) < EPSILON)
    1603             :             {
    1604           4 :                 poCenter = new OGRPoint((poLS1->getX(0) + poLS1->getX(1)) / 2,
    1605           2 :                                         (poLS1->getY(0) + poLS1->getY(1)) / 2);
    1606             :             }
    1607             :         }
    1608             : 
    1609          22 :         if (poCenter)
    1610             :         {
    1611          12 :             delete poGeom;
    1612          12 :             poGeom = poCenter;
    1613             :         }
    1614             :     }
    1615             :     else
    1616             :     {
    1617          36 :         OGRLinearRing *poLS = nullptr;
    1618          36 :         int nPolys = 0;
    1619          36 :         OGRGeometry **papoPoly = nullptr;
    1620             : 
    1621        2248 :         for (size_t i = 0; i < oCoords.size(); i += 2)
    1622             :         {
    1623        2232 :             if (oCoords[i] == NEW_SUBPATH && oCoords[i + 1] == NEW_SUBPATH)
    1624             :             {
    1625          47 :                 if (poLS && poLS->getNumPoints() >= 3)
    1626             :                 {
    1627           3 :                     OGRPolygon *poPoly = new OGRPolygon();
    1628           3 :                     poPoly->addRingDirectly(poLS);
    1629           3 :                     poLS = nullptr;
    1630             : 
    1631           6 :                     papoPoly = (OGRGeometry **)CPLRealloc(
    1632           3 :                         papoPoly, (nPolys + 1) * sizeof(OGRGeometry *));
    1633           3 :                     papoPoly[nPolys++] = poPoly;
    1634             :                 }
    1635          47 :                 delete poLS;
    1636          47 :                 poLS = new OGRLinearRing();
    1637             :             }
    1638        2185 :             else if ((oCoords[i] == CLOSE_SUBPATH &&
    1639        4332 :                       oCoords[i + 1] == CLOSE_SUBPATH) ||
    1640        2147 :                      (oCoords[i] == FILL_SUBPATH &&
    1641          16 :                       oCoords[i + 1] == FILL_SUBPATH))
    1642             :             {
    1643          54 :                 if (poLS)
    1644             :                 {
    1645          44 :                     poLS->closeRings();
    1646             : 
    1647           0 :                     std::unique_ptr<OGRPoint> poCenter;
    1648             : 
    1649          77 :                     if (nPolys == 0 && poLS &&
    1650          33 :                         poLS->getNumPoints() == 1 + BEZIER_STEPS * 4)
    1651             :                     {
    1652             :                         // Recognize points as written by GDAL (ogr-sym-3 :
    1653             :                         // circle (filled))
    1654          15 :                         poCenter.reset(PDFGetCircleCenter(poLS));
    1655             :                     }
    1656             : 
    1657          63 :                     if (nPolys == 0 && poCenter == nullptr && poLS &&
    1658          19 :                         poLS->getNumPoints() == 5)
    1659             :                     {
    1660             :                         // Recognize points as written by GDAL (ogr-sym-5:
    1661             :                         // square (filled))
    1662           9 :                         poCenter.reset(PDFGetSquareCenter(poLS));
    1663             : 
    1664             :                         /* ESRI points */
    1665          16 :                         if (poCenter == nullptr && oCoords.size() == 14 &&
    1666           0 :                             poLS->getY(0) == poLS->getY(1) &&
    1667           0 :                             poLS->getX(1) == poLS->getX(2) &&
    1668          16 :                             poLS->getY(2) == poLS->getY(3) &&
    1669           0 :                             poLS->getX(3) == poLS->getX(0))
    1670             :                         {
    1671           0 :                             poCenter.reset(new OGRPoint(
    1672           0 :                                 (poLS->getX(0) + poLS->getX(1)) / 2,
    1673           0 :                                 (poLS->getY(0) + poLS->getY(2)) / 2));
    1674             :                         }
    1675             :                     }
    1676             :                     // Recognize points as written by GDAL (ogr-sym-7: triangle
    1677             :                     // (filled))
    1678          35 :                     else if (nPolys == 0 && poLS && poLS->getNumPoints() == 4)
    1679             :                     {
    1680           2 :                         poCenter.reset(PDFGetTriangleCenter(poLS));
    1681             :                     }
    1682             :                     // Recognize points as written by GDAL (ogr-sym-9: star
    1683             :                     // (filled))
    1684          33 :                     else if (nPolys == 0 && poLS && poLS->getNumPoints() == 11)
    1685             :                     {
    1686           2 :                         poCenter.reset(PDFGetStarCenter(poLS));
    1687             :                     }
    1688             : 
    1689          44 :                     if (poCenter)
    1690             :                     {
    1691          20 :                         delete poGeom;
    1692          20 :                         poGeom = poCenter.release();
    1693          20 :                         break;
    1694             :                     }
    1695             : 
    1696          24 :                     if (poLS->getNumPoints() >= 3)
    1697             :                     {
    1698          22 :                         OGRPolygon *poPoly = new OGRPolygon();
    1699          22 :                         poPoly->addRingDirectly(poLS);
    1700          22 :                         poLS = nullptr;
    1701             : 
    1702          44 :                         papoPoly = (OGRGeometry **)CPLRealloc(
    1703          22 :                             papoPoly, (nPolys + 1) * sizeof(OGRGeometry *));
    1704          22 :                         papoPoly[nPolys++] = poPoly;
    1705             :                     }
    1706             :                     else
    1707             :                     {
    1708           2 :                         delete poLS;
    1709           2 :                         poLS = nullptr;
    1710             :                     }
    1711             :                 }
    1712             :             }
    1713             :             else
    1714             :             {
    1715        2131 :                 if (poLS)
    1716             :                 {
    1717             :                     double X, Y;
    1718        2131 :                     PDFCoordsToSRSCoords(oCoords[i], oCoords[i + 1], X, Y);
    1719             : 
    1720        2131 :                     poLS->addPoint(X, Y);
    1721             :                 }
    1722             :             }
    1723             :         }
    1724             : 
    1725          36 :         delete poLS;
    1726             : 
    1727             :         int bIsValidGeometry;
    1728          41 :         if (nPolys == 2 &&
    1729          41 :             papoPoly[0]->toPolygon()->getNumInteriorRings() == 0 &&
    1730           5 :             papoPoly[1]->toPolygon()->getNumInteriorRings() == 0)
    1731             :         {
    1732             :             OGRLinearRing *poRing0 =
    1733           5 :                 papoPoly[0]->toPolygon()->getExteriorRing();
    1734             :             OGRLinearRing *poRing1 =
    1735           5 :                 papoPoly[1]->toPolygon()->getExteriorRing();
    1736           5 :             if (poRing0->getNumPoints() == poRing1->getNumPoints())
    1737             :             {
    1738           2 :                 int bSameRing = TRUE;
    1739           2 :                 for (int i = 0; i < poRing0->getNumPoints(); i++)
    1740             :                 {
    1741           2 :                     if (poRing0->getX(i) != poRing1->getX(i))
    1742             :                     {
    1743           2 :                         bSameRing = FALSE;
    1744           2 :                         break;
    1745             :                     }
    1746           0 :                     if (poRing0->getY(i) != poRing1->getY(i))
    1747             :                     {
    1748           0 :                         bSameRing = FALSE;
    1749           0 :                         break;
    1750             :                     }
    1751             :                 }
    1752             : 
    1753             :                 /* Just keep on ring if they are identical */
    1754           2 :                 if (bSameRing)
    1755             :                 {
    1756           0 :                     delete papoPoly[1];
    1757           0 :                     nPolys = 1;
    1758             :                 }
    1759             :             }
    1760             :         }
    1761          36 :         if (nPolys)
    1762             :         {
    1763          16 :             poGeom = OGRGeometryFactory::organizePolygons(
    1764             :                 papoPoly, nPolys, &bIsValidGeometry, nullptr);
    1765             :         }
    1766          36 :         CPLFree(papoPoly);
    1767             :     }
    1768             : 
    1769          60 :     return poGeom;
    1770             : }
    1771             : 
    1772             : /************************************************************************/
    1773             : /*                          ExploreContents()                           */
    1774             : /************************************************************************/
    1775             : 
    1776           6 : void PDFDataset::ExploreContents(GDALPDFObject *poObj,
    1777             :                                  GDALPDFObject *poResources, int nDepth,
    1778             :                                  int &nVisited, bool &bStop)
    1779             : {
    1780           6 :     std::map<CPLString, OGRPDFLayer *> oMapPropertyToLayer;
    1781           6 :     if (nDepth == 10 || nVisited == 1000)
    1782             :     {
    1783           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1784             :                  "ExploreContents(): too deep exploration or too many items");
    1785           0 :         bStop = true;
    1786           0 :         return;
    1787             :     }
    1788           6 :     if (bStop)
    1789           0 :         return;
    1790             : 
    1791           6 :     if (poObj->GetType() == PDFObjectType_Array)
    1792             :     {
    1793           0 :         GDALPDFArray *poArray = poObj->GetArray();
    1794           0 :         for (int i = 0; i < poArray->GetLength(); i++)
    1795             :         {
    1796           0 :             GDALPDFObject *poSubObj = poArray->Get(i);
    1797           0 :             if (poSubObj)
    1798             :             {
    1799           0 :                 nVisited++;
    1800           0 :                 ExploreContents(poSubObj, poResources, nDepth + 1, nVisited,
    1801             :                                 bStop);
    1802           0 :                 if (bStop)
    1803           0 :                     return;
    1804             :             }
    1805             :         }
    1806             :     }
    1807             : 
    1808           6 :     if (poObj->GetType() != PDFObjectType_Dictionary)
    1809           0 :         return;
    1810             : 
    1811           6 :     GDALPDFStream *poStream = poObj->GetStream();
    1812           6 :     if (!poStream)
    1813           0 :         return;
    1814             : 
    1815           6 :     char *pszStr = poStream->GetBytes();
    1816           6 :     if (!pszStr)
    1817           0 :         return;
    1818             : 
    1819           6 :     const char *pszMCID = (const char *)pszStr;
    1820          40 :     while ((pszMCID = strstr(pszMCID, "/MCID")) != nullptr)
    1821             :     {
    1822          34 :         const char *pszBDC = strstr(pszMCID, "BDC");
    1823          34 :         if (pszBDC)
    1824             :         {
    1825             :             /* Hack for
    1826             :              * http://www.avenza.com/sites/default/files/spatialpdf/US_County_Populations.pdf
    1827             :              */
    1828             :             /* FIXME: that logic is too fragile. */
    1829          34 :             const char *pszStartParsing = pszBDC;
    1830          34 :             const char *pszAfterBDC = pszBDC + 3;
    1831          34 :             int bMatchQ = FALSE;
    1832          68 :             while (pszAfterBDC[0] == ' ' || pszAfterBDC[0] == '\r' ||
    1833          68 :                    pszAfterBDC[0] == '\n')
    1834          34 :                 pszAfterBDC++;
    1835          34 :             if (STARTS_WITH(pszAfterBDC, "0 0 m"))
    1836             :             {
    1837           0 :                 const char *pszLastq = pszBDC;
    1838           0 :                 while (pszLastq > pszStr && *pszLastq != 'q')
    1839           0 :                     pszLastq--;
    1840             : 
    1841           0 :                 if (pszLastq > pszStr && *pszLastq == 'q' &&
    1842           0 :                     (pszLastq[-1] == ' ' || pszLastq[-1] == '\r' ||
    1843           0 :                      pszLastq[-1] == '\n') &&
    1844           0 :                     (pszLastq[1] == ' ' || pszLastq[1] == '\r' ||
    1845           0 :                      pszLastq[1] == '\n'))
    1846             :                 {
    1847           0 :                     pszStartParsing = pszLastq;
    1848           0 :                     bMatchQ = TRUE;
    1849             :                 }
    1850             :             }
    1851             : 
    1852          34 :             int nMCID = atoi(pszMCID + 6);
    1853          34 :             if (GetGeometryFromMCID(nMCID) == nullptr)
    1854             :             {
    1855             :                 OGRGeometry *poGeom =
    1856          32 :                     ParseContent(pszStartParsing, poResources, !bMatchQ,
    1857             :                                  bMatchQ, oMapPropertyToLayer, nullptr);
    1858          32 :                 if (poGeom != nullptr)
    1859             :                 {
    1860             :                     /* Save geometry in map */
    1861          31 :                     m_oMapMCID[nMCID] = poGeom;
    1862             :                 }
    1863             :             }
    1864             :         }
    1865          34 :         pszMCID += 5;
    1866             :     }
    1867           6 :     CPLFree(pszStr);
    1868             : }
    1869             : 
    1870             : /************************************************************************/
    1871             : /*                   ExploreContentsNonStructured()                     */
    1872             : /************************************************************************/
    1873             : 
    1874           4 : void PDFDataset::ExploreContentsNonStructuredInternal(
    1875             :     GDALPDFObject *poContents, GDALPDFObject *poResources,
    1876             :     std::map<CPLString, OGRPDFLayer *> &oMapPropertyToLayer,
    1877             :     OGRPDFLayer *poSingleLayer)
    1878             : {
    1879           4 :     if (poContents->GetType() == PDFObjectType_Array)
    1880             :     {
    1881           1 :         GDALPDFArray *poArray = poContents->GetArray();
    1882           1 :         char *pszConcatStr = nullptr;
    1883           1 :         int nConcatLen = 0;
    1884           2 :         for (int i = 0; i < poArray->GetLength(); i++)
    1885             :         {
    1886           1 :             GDALPDFObject *poObj = poArray->Get(i);
    1887           2 :             if (poObj == nullptr ||
    1888           1 :                 poObj->GetType() != PDFObjectType_Dictionary)
    1889           0 :                 break;
    1890           1 :             GDALPDFStream *poStream = poObj->GetStream();
    1891           1 :             if (!poStream)
    1892           0 :                 break;
    1893           1 :             char *pszStr = poStream->GetBytes();
    1894           1 :             if (!pszStr)
    1895           0 :                 break;
    1896           1 :             int nLen = (int)strlen(pszStr);
    1897             :             char *pszConcatStrNew =
    1898           1 :                 (char *)CPLRealloc(pszConcatStr, nConcatLen + nLen + 1);
    1899           1 :             if (pszConcatStrNew == nullptr)
    1900             :             {
    1901           0 :                 CPLFree(pszStr);
    1902           0 :                 break;
    1903             :             }
    1904           1 :             pszConcatStr = pszConcatStrNew;
    1905           1 :             memcpy(pszConcatStr + nConcatLen, pszStr, nLen + 1);
    1906           1 :             nConcatLen += nLen;
    1907           1 :             CPLFree(pszStr);
    1908             :         }
    1909           1 :         if (pszConcatStr)
    1910           1 :             ParseContent(pszConcatStr, poResources, FALSE, FALSE,
    1911             :                          oMapPropertyToLayer, poSingleLayer);
    1912           1 :         CPLFree(pszConcatStr);
    1913           1 :         return;
    1914             :     }
    1915             : 
    1916           3 :     if (poContents->GetType() != PDFObjectType_Dictionary)
    1917           0 :         return;
    1918             : 
    1919           3 :     GDALPDFStream *poStream = poContents->GetStream();
    1920           3 :     if (!poStream)
    1921           0 :         return;
    1922             : 
    1923           3 :     char *pszStr = poStream->GetBytes();
    1924           3 :     if (!pszStr)
    1925           0 :         return;
    1926           3 :     ParseContent(pszStr, poResources, FALSE, FALSE, oMapPropertyToLayer,
    1927             :                  poSingleLayer);
    1928           3 :     CPLFree(pszStr);
    1929             : }
    1930             : 
    1931             : /************************************************************************/
    1932             : /*                         ExploreResourceProperty()                    */
    1933             : /************************************************************************/
    1934             : 
    1935           3 : static void ExploreResourceProperty(
    1936             :     const char *pszKey, GDALPDFObject *poObj, const std::string &osType,
    1937             :     const std::map<std::pair<int, int>, OGRPDFLayer *> &oMapNumGenToLayer,
    1938             :     std::map<CPLString, OGRPDFLayer *> &oMapPropertyToLayer, int nRecLevel)
    1939             : {
    1940           3 :     if (nRecLevel == 2)
    1941           0 :         return;
    1942             : 
    1943           3 :     if (osType == "OCG" && poObj->GetRefNum().toBool())
    1944             :     {
    1945             :         const auto oIterNumGenToLayer = oMapNumGenToLayer.find(
    1946           2 :             std::pair(poObj->GetRefNum().toInt(), poObj->GetRefGen()));
    1947           2 :         if (oIterNumGenToLayer != oMapNumGenToLayer.end())
    1948             :         {
    1949           2 :             auto poLayer = oIterNumGenToLayer->second;
    1950             : #ifdef DEBUG_VERBOSE
    1951             :             CPLDebug("PDF", "Associating OCG %s to layer %s", pszKey,
    1952             :                      poLayer->GetName());
    1953             : #endif
    1954           2 :             oMapPropertyToLayer[pszKey] = poLayer;
    1955             :         }
    1956             :         else
    1957             :         {
    1958           0 :             CPLDebug("PDF",
    1959             :                      "Resource.Properties[%s] referencing "
    1960             :                      "OGC %d not tied with a layer",
    1961           0 :                      pszKey, poObj->GetRefNum().toInt());
    1962             :         }
    1963             :     }
    1964           1 :     else if (osType == "OCMD")
    1965             :     {
    1966             :         // Optional Content Group Membership Dictionary
    1967             :         // Deal with constructs like
    1968             :         /*
    1969             :              Item[0] : MC0
    1970             :               Type = dictionary, Num = 331, Gen = 0
    1971             :                Item[0] : OCGs
    1972             :                 Type = array
    1973             :                  Item[0]:
    1974             :                   Type = dictionary, Num = 251, Gen = 0
    1975             :                    Item[0] : Intent = View (name)
    1976             :                    Item[1] : Name = Orthoimage (string)
    1977             :                    Item[2] : Type = OCG (name)
    1978             :                  Item[1]:
    1979             :                   Type = dictionary, Num = 250, Gen = 0
    1980             :                    Item[0] : Intent = View (name)
    1981             :                    Item[1] : Name = Images (string)
    1982             :                    Item[2] : Type = OCG (name)
    1983             :                Item[1] : P = AllOn (name)
    1984             :                Item[2] : Type = OCMD (name)
    1985             :         */
    1986             :         // where the OCG Orthoimage is actually a child
    1987             :         // of Images (which will be named Orthoimage.Images)
    1988             :         // In which case we only associate MC0 to
    1989             :         // Orthoimage.Images
    1990             :         // Cf https://github.com/OSGeo/gdal/issues/8372
    1991             :         // and https://prd-tnm.s3.amazonaws.com/StagedProducts/Maps/USTopo/PDF/ID/ID_Big_Baldy_20200409_TM_geo.pdf
    1992           1 :         auto poOCGs = poObj->GetDictionary()->Get("OCGs");
    1993           1 :         if (poOCGs && poOCGs->GetType() == PDFObjectType_Array)
    1994             :         {
    1995           1 :             auto poOCGsArray = poOCGs->GetArray();
    1996           1 :             const int nLength = poOCGsArray->GetLength();
    1997           1 :             size_t nMaxNameLength = 0;
    1998           1 :             OGRPDFLayer *poCandidateLayer = nullptr;
    1999           2 :             std::vector<std::string> aosLayerNames;
    2000           3 :             for (int i = 0; i < nLength; ++i)
    2001             :             {
    2002           2 :                 auto poOCG = poOCGsArray->Get(i);
    2003           2 :                 if (poOCG && poOCG->GetType() == PDFObjectType_Dictionary)
    2004             :                 {
    2005           2 :                     auto poP = poOCG->GetDictionary()->Get("P");
    2006           2 :                     if (poP && poP->GetType() == PDFObjectType_Name)
    2007             :                     {
    2008             :                         // Visibility Policy
    2009           0 :                         const auto &osP = poP->GetName();
    2010           0 :                         if (osP != "AllOn" && osP != "AnyOn")
    2011             :                         {
    2012           0 :                             CPLDebug("PDF",
    2013             :                                      "Resource.Properties[%s] "
    2014             :                                      "has unhandled visibility policy %s",
    2015             :                                      pszKey, osP.c_str());
    2016             :                         }
    2017             :                     }
    2018           2 :                     auto poOCGType = poOCG->GetDictionary()->Get("Type");
    2019           2 :                     if (poOCGType && poOCGType->GetType() == PDFObjectType_Name)
    2020             :                     {
    2021           2 :                         const std::string &osOCGType = poOCGType->GetName();
    2022           2 :                         if (osOCGType == "OCG" && poOCG->GetRefNum().toBool())
    2023             :                         {
    2024             :                             const auto oIterNumGenToLayer =
    2025             :                                 oMapNumGenToLayer.find(
    2026           2 :                                     std::pair(poOCG->GetRefNum().toInt(),
    2027           4 :                                               poOCG->GetRefGen()));
    2028           2 :                             if (oIterNumGenToLayer != oMapNumGenToLayer.end())
    2029             :                             {
    2030           2 :                                 auto poLayer = oIterNumGenToLayer->second;
    2031           2 :                                 aosLayerNames.emplace_back(poLayer->GetName());
    2032           2 :                                 if (strlen(poLayer->GetName()) > nMaxNameLength)
    2033             :                                 {
    2034           2 :                                     nMaxNameLength = strlen(poLayer->GetName());
    2035           2 :                                     poCandidateLayer = poLayer;
    2036             :                                 }
    2037             :                             }
    2038             :                             else
    2039             :                             {
    2040           0 :                                 CPLDebug("PDF",
    2041             :                                          "Resource.Properties[%s][%d] "
    2042             :                                          "referencing OGC %d not tied with "
    2043             :                                          "a layer",
    2044           0 :                                          pszKey, i, poOCG->GetRefNum().toInt());
    2045             :                             }
    2046             :                         }
    2047             :                         else
    2048             :                         {
    2049           0 :                             CPLDebug(
    2050             :                                 "PDF",
    2051             :                                 "Resource.Properties[%s][%d] has unhandled "
    2052             :                                 "Type member: %s",
    2053             :                                 pszKey, i, osOCGType.c_str());
    2054             :                         }
    2055             :                     }
    2056             :                 }
    2057             :             }
    2058             : 
    2059           1 :             if (!aosLayerNames.empty())
    2060             :             {
    2061             :                 // Sort layer names and if each one starts
    2062             :                 // with the previous ones, then the OCGs
    2063             :                 // are part of a hierarchy, and we can
    2064             :                 // associate the property name with the
    2065             :                 // last one.
    2066           1 :                 std::sort(aosLayerNames.begin(), aosLayerNames.end());
    2067           1 :                 bool bOK = true;
    2068           2 :                 for (size_t i = 1; i < aosLayerNames.size(); ++i)
    2069             :                 {
    2070           1 :                     if (aosLayerNames[i].find(aosLayerNames[i - 1]) != 0)
    2071             :                     {
    2072           0 :                         bOK = false;
    2073           0 :                         break;
    2074             :                     }
    2075             :                 }
    2076           1 :                 if (bOK)
    2077             :                 {
    2078           1 :                     CPLAssert(poCandidateLayer);
    2079             : #ifdef DEBUG_VERBOSE
    2080             :                     CPLDebug("PDF", "Associating OCG %s to layer %s", pszKey,
    2081             :                              poCandidateLayer->GetName());
    2082             : #endif
    2083           1 :                     oMapPropertyToLayer[pszKey] = poCandidateLayer;
    2084             :                 }
    2085             :                 else
    2086             :                 {
    2087           0 :                     CPLDebug("PDF",
    2088             :                              "Resource.Properties[%s] "
    2089             :                              "contains a OCMD that cannot "
    2090             :                              "be mapped to a single layer",
    2091             :                              pszKey);
    2092             :                 }
    2093             :             }
    2094             :             else
    2095             :             {
    2096           0 :                 CPLDebug("PDF",
    2097             :                          "Resource.Properties[%s] contains "
    2098             :                          "a OCMD without OCGs",
    2099             :                          pszKey);
    2100             :             }
    2101             :         }
    2102           0 :         else if (poOCGs && poOCGs->GetType() == PDFObjectType_Dictionary)
    2103             :         {
    2104           0 :             auto poOGGsType = poOCGs->GetDictionary()->Get("Type");
    2105           0 :             if (poOGGsType && poOGGsType->GetType() == PDFObjectType_Name)
    2106             :             {
    2107           0 :                 ExploreResourceProperty(pszKey, poOCGs, poOGGsType->GetName(),
    2108             :                                         oMapNumGenToLayer, oMapPropertyToLayer,
    2109             :                                         nRecLevel + 1);
    2110             :             }
    2111             :             else
    2112             :             {
    2113           0 :                 CPLDebug("PDF",
    2114             :                          "Resource.Properties[%s] contains a OGCs member with "
    2115             :                          "no Type member",
    2116             :                          pszKey);
    2117             :             }
    2118             :         }
    2119           0 :         else if (poOCGs)
    2120             :         {
    2121           0 :             CPLDebug("PDF",
    2122             :                      "Resource.Properties[%s] contains a OCMD "
    2123             :                      "with a OGCs member of unhandled type: %s",
    2124           0 :                      pszKey, poOCGs->GetTypeName());
    2125             :         }
    2126             :         else
    2127             :         {
    2128             :             // Could have a VE (visibility expression)
    2129             :             // expression instead, but  we don't handle that
    2130           0 :             CPLDebug("PDF",
    2131             :                      "Resource.Properties[%s] contains a "
    2132             :                      "OCMD with a missing OGC (perhaps has a VE?)",
    2133             :                      pszKey);
    2134             :         }
    2135             :     }
    2136             :     else
    2137             :     {
    2138           0 :         CPLDebug("PDF",
    2139             :                  "Resource.Properties[%s] has unhandled "
    2140             :                  "Type member: %s",
    2141             :                  pszKey, osType.c_str());
    2142             :     }
    2143             : }
    2144             : 
    2145             : /************************************************************************/
    2146             : /*                   ExploreContentsNonStructured()                     */
    2147             : /************************************************************************/
    2148             : 
    2149           6 : void PDFDataset::ExploreContentsNonStructured(GDALPDFObject *poContents,
    2150             :                                               GDALPDFObject *poResources)
    2151             : {
    2152           6 :     std::map<CPLString, OGRPDFLayer *> oMapPropertyToLayer;
    2153          12 :     if (poResources != nullptr &&
    2154           6 :         poResources->GetType() == PDFObjectType_Dictionary)
    2155             :     {
    2156             :         GDALPDFObject *poProperties =
    2157           6 :             poResources->GetDictionary()->Get("Properties");
    2158           8 :         if (poProperties != nullptr &&
    2159           2 :             poProperties->GetType() == PDFObjectType_Dictionary)
    2160             :         {
    2161           4 :             std::map<std::pair<int, int>, OGRPDFLayer *> oMapNumGenToLayer;
    2162           6 :             for (const auto &oLayerWithref : m_aoLayerWithRef)
    2163             :             {
    2164             :                 CPLString osSanitizedName(
    2165           4 :                     PDFSanitizeLayerName(oLayerWithref.osName));
    2166             : 
    2167             :                 OGRPDFLayer *poLayer =
    2168           4 :                     (OGRPDFLayer *)GetLayerByName(osSanitizedName.c_str());
    2169           4 :                 if (poLayer == nullptr)
    2170             :                 {
    2171           4 :                     auto poSRSOri = GetSpatialRef();
    2172             :                     OGRSpatialReference *poSRS =
    2173           4 :                         poSRSOri ? poSRSOri->Clone() : nullptr;
    2174           4 :                     poLayer = new OGRPDFLayer(this, osSanitizedName.c_str(),
    2175           4 :                                               poSRS, wkbUnknown);
    2176           4 :                     if (poSRS)
    2177           4 :                         poSRS->Release();
    2178             : 
    2179           8 :                     m_papoLayers = (OGRLayer **)CPLRealloc(
    2180           4 :                         m_papoLayers, (m_nLayers + 1) * sizeof(OGRLayer *));
    2181           4 :                     m_papoLayers[m_nLayers] = poLayer;
    2182           4 :                     m_nLayers++;
    2183             :                 }
    2184             : 
    2185           4 :                 oMapNumGenToLayer[std::pair(oLayerWithref.nOCGNum.toInt(),
    2186           8 :                                             oLayerWithref.nOCGGen)] = poLayer;
    2187             :             }
    2188             : 
    2189           6 :             for (const auto &[osKey, poObj] :
    2190           8 :                  poProperties->GetDictionary()->GetValues())
    2191             :             {
    2192           3 :                 const char *pszKey = osKey.c_str();
    2193           3 :                 if (poObj->GetType() == PDFObjectType_Dictionary)
    2194             :                 {
    2195           3 :                     auto poType = poObj->GetDictionary()->Get("Type");
    2196           3 :                     if (poType && poType->GetType() == PDFObjectType_Name)
    2197             :                     {
    2198           3 :                         const auto &osType = poType->GetName();
    2199           3 :                         ExploreResourceProperty(pszKey, poObj, osType,
    2200             :                                                 oMapNumGenToLayer,
    2201             :                                                 oMapPropertyToLayer, 0);
    2202             :                     }
    2203             :                     else
    2204             :                     {
    2205           0 :                         CPLDebug("PDF",
    2206             :                                  "Resource.Properties[%s] has no Type member",
    2207             :                                  pszKey);
    2208             :                     }
    2209             :                 }
    2210             :             }
    2211             :         }
    2212             :     }
    2213             : 
    2214           6 :     OGRPDFLayer *poSingleLayer = nullptr;
    2215           6 :     if (m_nLayers == 0)
    2216             :     {
    2217           4 :         if (CPLTestBool(
    2218             :                 CPLGetConfigOption("OGR_PDF_READ_NON_STRUCTURED", "NO")))
    2219             :         {
    2220             :             OGRPDFLayer *poLayer =
    2221           2 :                 new OGRPDFLayer(this, "content", nullptr, wkbUnknown);
    2222           4 :             m_papoLayers = (OGRLayer **)CPLRealloc(
    2223           2 :                 m_papoLayers, (m_nLayers + 1) * sizeof(OGRLayer *));
    2224           2 :             m_papoLayers[m_nLayers] = poLayer;
    2225           2 :             m_nLayers++;
    2226           2 :             poSingleLayer = poLayer;
    2227             :         }
    2228             :         else
    2229             :         {
    2230           2 :             return;
    2231             :         }
    2232             :     }
    2233             : 
    2234           4 :     ExploreContentsNonStructuredInternal(poContents, poResources,
    2235             :                                          oMapPropertyToLayer, poSingleLayer);
    2236             : 
    2237             :     /* Remove empty layers */
    2238           4 :     int i = 0;
    2239          10 :     while (i < m_nLayers)
    2240             :     {
    2241           6 :         if (m_papoLayers[i]->GetFeatureCount() == 0)
    2242             :         {
    2243           2 :             delete m_papoLayers[i];
    2244           2 :             if (i < m_nLayers - 1)
    2245             :             {
    2246           1 :                 memmove(m_papoLayers + i, m_papoLayers + i + 1,
    2247           1 :                         (m_nLayers - 1 - i) * sizeof(OGRPDFLayer *));
    2248             :             }
    2249           2 :             m_nLayers--;
    2250             :         }
    2251             :         else
    2252           4 :             i++;
    2253             :     }
    2254             : }
    2255             : 
    2256             : #endif /* HAVE_PDF_READ_SUPPORT */

Generated by: LCOV version 1.14