Coverage Report

Created: 2026-08-11 08:26

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