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

Generated by: LCOV version 1.14