Coverage Report

Created: 2025-06-09 08:44

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