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

Generated by: LCOV version 1.14