• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 15885686134

25 Jun 2025 07:44PM UTC coverage: 71.084%. Remained the same
15885686134

push

github

rouault
gdal_priv.h: fix C++11 compatibility

573814 of 807237 relevant lines covered (71.08%)

250621.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.4
/frmts/pdf/pdfreadvectors.cpp
1
/******************************************************************************
2
 *
3
 * Project:  PDF driver
4
 * Purpose:  GDALDataset driver for PDF dataset (read vector features)
5
 * Author:   Even Rouault, <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2010-2014, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12

13
#include "gdal_pdf.h"
14

15
#include <algorithm>
16
#include <array>
17

18
#define SQUARE(x) ((x) * (x))
19
#define EPSILON 1e-5
20

21
// #define DEBUG_VERBOSE
22

23
#ifdef HAVE_PDF_READ_SUPPORT
24

25
constexpr int BEZIER_STEPS = 10;
26

27
/************************************************************************/
28
/*                        OpenVectorLayers()                            */
29
/************************************************************************/
30

31
bool PDFDataset::OpenVectorLayers(GDALPDFDictionary *poPageDict)
69,107✔
32
{
33
    if (m_bHasLoadedLayers)
69,107✔
34
        return true;
69,086✔
35
    m_bHasLoadedLayers = true;
21✔
36

37
    if (poPageDict == nullptr)
21✔
38
    {
39
        poPageDict = m_poPageObj->GetDictionary();
3✔
40
        if (poPageDict == nullptr)
3✔
41
            return false;
×
42
    }
43

44
    GetCatalog();
21✔
45
    if (m_poCatalogObject == nullptr ||
42✔
46
        m_poCatalogObject->GetType() != PDFObjectType_Dictionary)
21✔
47
        return false;
×
48

49
    GDALPDFObject *poContents = poPageDict->Get("Contents");
21✔
50
    if (poContents == nullptr)
21✔
51
        return false;
×
52

53
    if (poContents->GetType() != PDFObjectType_Dictionary &&
22✔
54
        poContents->GetType() != PDFObjectType_Array)
1✔
55
        return false;
×
56

57
    GDALPDFObject *poResources = poPageDict->Get("Resources");
21✔
58
    if (poResources == nullptr ||
42✔
59
        poResources->GetType() != PDFObjectType_Dictionary)
21✔
60
        return false;
×
61

62
    GDALPDFObject *poStructTreeRoot =
63
        m_poCatalogObject->GetDictionary()->Get("StructTreeRoot");
21✔
64
    if (CPLTestBool(CPLGetConfigOption("OGR_PDF_READ_NON_STRUCTURED", "NO")) ||
40✔
65
        poStructTreeRoot == nullptr ||
40✔
66
        poStructTreeRoot->GetType() != PDFObjectType_Dictionary)
8✔
67
    {
68
        ExploreContentsNonStructured(poContents, poResources);
13✔
69
    }
70
    else
71
    {
72
        bool bHasFeatures;
73
        {
74
            std::set<std::pair<int, int>> aoSetAlreadyVisited;
8✔
75
            bHasFeatures = ExploreTree(poStructTreeRoot, aoSetAlreadyVisited, 0,
8✔
76
                                       /* bDryRun = */ true);
77
        }
78
        if (bHasFeatures)
8✔
79
        {
80
            int nDepth = 0;
8✔
81
            int nVisited = 0;
8✔
82
            bool bStop = false;
8✔
83
            ExploreContents(poContents, poResources, nDepth, nVisited, bStop);
8✔
84
            std::set<std::pair<int, int>> aoSetAlreadyVisited;
16✔
85
            ExploreTree(poStructTreeRoot, aoSetAlreadyVisited, 0,
8✔
86
                        /* bDryRun = */ false);
87
        }
88
        else
89
        {
90
            ExploreContentsNonStructured(poContents, poResources);
×
91
        }
92
    }
93

94
    CleanupIntermediateResources();
21✔
95

96
    bool bEmptyDS = true;
21✔
97
    for (auto &poLayer : m_apoLayers)
21✔
98
    {
99
        if (poLayer->GetFeatureCount(false) != 0)
18✔
100
        {
101
            bEmptyDS = false;
18✔
102
            break;
18✔
103
        }
104
    }
105
    return !bEmptyDS;
21✔
106
}
107

108
/************************************************************************/
109
/*                   CleanupIntermediateResources()                     */
110
/************************************************************************/
111

112
void PDFDataset::CleanupIntermediateResources()
403✔
113
{
114
    for (const auto &oIter : m_oMapMCID)
462✔
115
        delete oIter.second;
59✔
116
    m_oMapMCID.clear();
403✔
117
}
403✔
118

119
/************************************************************************/
120
/*                          InitMapOperators()                          */
121
/************************************************************************/
122

123
typedef struct
124
{
125
    char szOpName[4];
126
    int nArgs;
127
} PDFOperator;
128

129
static const PDFOperator asPDFOperators[] = {
130
    {"b", 0},
131
    {"B", 0},
132
    {"b*", 0},
133
    {"B*", 0},
134
    {"BDC", 2},
135
    // BI
136
    {"BMC", 1},
137
    // BT
138
    {"BX", 0},
139
    {"c", 6},
140
    {"cm", 6},
141
    {"CS", 1},
142
    {"cs", 1},
143
    {"d", 1}, /* we have ignored the first arg which is an array */
144
    // d0
145
    // d1
146
    {"Do", 1},
147
    {"DP", 2},
148
    // EI
149
    {"EMC", 0},
150
    // ET
151
    {"EX", 0},
152
    {"f", 0},
153
    {"F", 0},
154
    {"f*", 0},
155
    {"G", 1},
156
    {"g", 1},
157
    {"gs", 1},
158
    {"h", 0},
159
    {"i", 1},
160
    // ID
161
    {"j", 1},
162
    {"J", 1},
163
    {"K", 4},
164
    {"k", 4},
165
    {"l", 2},
166
    {"m", 2},
167
    {"M", 1},
168
    {"MP", 1},
169
    {"n", 0},
170
    {"q", 0},
171
    {"Q", 0},
172
    {"re", 4},
173
    {"RG", 3},
174
    {"rg", 3},
175
    {"ri", 1},
176
    {"s", 0},
177
    {"S", 0},
178
    {"SC", -1},
179
    {"sc", -1},
180
    {"SCN", -1},
181
    {"scn", -1},
182
    {"sh", 1},
183
    // T*
184
    {"Tc", 1},
185
    {"Td", 2},
186
    {"TD", 2},
187
    {"Tf", 1},
188
    {"Tj", 1},
189
    {"TJ", 1},
190
    {"TL", 1},
191
    {"Tm", 6},
192
    {"Tr", 1},
193
    {"Ts", 1},
194
    {"Tw", 1},
195
    {"Tz", 1},
196
    {"v", 4},
197
    {"w", 1},
198
    {"W", 0},
199
    {"W*", 0},
200
    {"y", 4},
201
    // '
202
    // "
203
};
204

205
void PDFDataset::InitMapOperators()
382✔
206
{
207
    for (const auto &sPDFOperator : asPDFOperators)
24,448✔
208
        m_oMapOperators[sPDFOperator.szOpName] = sPDFOperator.nArgs;
24,066✔
209
}
382✔
210

211
/************************************************************************/
212
/*                           TestCapability()                           */
213
/************************************************************************/
214

215
int PDFDataset::TestCapability(CPL_UNUSED const char *pszCap)
×
216
{
217
    return FALSE;
×
218
}
219

220
/************************************************************************/
221
/*                              GetLayer()                              */
222
/************************************************************************/
223

224
OGRLayer *PDFDataset::GetLayer(int iLayer)
34,348✔
225

226
{
227
    OpenVectorLayers(nullptr);
34,348✔
228
    if (iLayer < 0 || iLayer >= static_cast<int>(m_apoLayers.size()))
34,348✔
229
        return nullptr;
×
230

231
    return m_apoLayers[iLayer].get();
34,348✔
232
}
233

234
/************************************************************************/
235
/*                            GetLayerCount()                           */
236
/************************************************************************/
237

238
int PDFDataset::GetLayerCount()
34,741✔
239
{
240
    OpenVectorLayers(nullptr);
34,741✔
241
    return static_cast<int>(m_apoLayers.size());
34,741✔
242
}
243

244
/************************************************************************/
245
/*                            ExploreTree()                             */
246
/************************************************************************/
247

248
bool PDFDataset::ExploreTree(GDALPDFObject *poObj,
32✔
249
                             std::set<std::pair<int, int>> &aoSetAlreadyVisited,
250
                             int nRecLevel, bool bDryRun)
251
{
252
    if (nRecLevel == 16)
32✔
253
        return false;
×
254

255
    std::pair<int, int> oObjPair(poObj->GetRefNum().toInt(),
32✔
256
                                 poObj->GetRefGen());
64✔
257
    if (aoSetAlreadyVisited.find(oObjPair) != aoSetAlreadyVisited.end())
32✔
258
        return false;
×
259
    aoSetAlreadyVisited.insert(oObjPair);
32✔
260

261
    if (poObj->GetType() != PDFObjectType_Dictionary)
32✔
262
        return false;
×
263

264
    GDALPDFDictionary *poDict = poObj->GetDictionary();
32✔
265

266
    GDALPDFObject *poS = poDict->Get("S");
32✔
267
    std::string osS;
64✔
268
    if (poS != nullptr && poS->GetType() == PDFObjectType_Name)
32✔
269
    {
270
        osS = poS->GetName();
16✔
271
    }
272

273
    GDALPDFObject *poT = poDict->Get("T");
32✔
274
    std::string osT;
64✔
275
    if (poT != nullptr && poT->GetType() == PDFObjectType_String)
32✔
276
    {
277
        osT = poT->GetString();
16✔
278
    }
279

280
    GDALPDFObject *poK = poDict->Get("K");
32✔
281
    if (poK == nullptr)
32✔
282
        return false;
×
283

284
    bool bRet = false;
32✔
285
    if (poK->GetType() == PDFObjectType_Array)
32✔
286
    {
287
        GDALPDFArray *poArray = poK->GetArray();
32✔
288
        if (poArray->GetLength() > 0 && poArray->Get(0) &&
64✔
289
            poArray->Get(0)->GetType() == PDFObjectType_Dictionary &&
32✔
290
            poArray->Get(0)->GetDictionary()->Get("K") != nullptr &&
96✔
291
            poArray->Get(0)->GetDictionary()->Get("K")->GetType() ==
32✔
292
                PDFObjectType_Int)
293
        {
294
            if (bDryRun)
16✔
295
            {
296
                for (int i = 0; i < poArray->GetLength(); i++)
8✔
297
                {
298
                    auto poFeatureObj = poArray->Get(i);
8✔
299
                    if (poFeatureObj &&
16✔
300
                        poFeatureObj->GetType() == PDFObjectType_Dictionary)
8✔
301
                    {
302
                        auto poA = poFeatureObj->GetDictionary()->Get("A");
8✔
303
                        if (poA && poA->GetType() == PDFObjectType_Dictionary)
8✔
304
                        {
305
                            auto poO = poA->GetDictionary()->Get("O");
8✔
306
                            if (poO && poO->GetType() == PDFObjectType_Name &&
16✔
307
                                poO->GetName() == "UserProperties")
8✔
308
                            {
309
                                return true;
8✔
310
                            }
311
                        }
312
                    }
313
                }
314
                return false;
×
315
            }
316

317
            std::string osLayerName;
16✔
318
            if (!osT.empty())
8✔
319
                osLayerName = std::move(osT);
8✔
320
            else
321
            {
322
                if (!osS.empty())
×
323
                    osLayerName = std::move(osS);
×
324
                else
325
                    osLayerName = CPLSPrintf(
326
                        "Layer%d", static_cast<int>(m_apoLayers.size()) + 1);
×
327
            }
328

329
            auto poSRSOri = GetSpatialRef();
8✔
330
            OGRSpatialReference *poSRS = poSRSOri ? poSRSOri->Clone() : nullptr;
8✔
331
            auto poLayer = std::make_unique<OGRPDFLayer>(
332
                this, osLayerName.c_str(), poSRS, wkbUnknown);
8✔
333
            if (poSRS)
8✔
334
                poSRS->Release();
8✔
335

336
            poLayer->Fill(poArray);
8✔
337

338
            m_apoLayers.emplace_back(std::move(poLayer));
8✔
339
            bRet = true;
8✔
340
        }
341
        else
342
        {
343
            for (int i = 0; i < poArray->GetLength(); i++)
24✔
344
            {
345
                auto poSubObj = poArray->Get(i);
16✔
346
                if (poSubObj)
16✔
347
                {
348
                    if (ExploreTree(poSubObj, aoSetAlreadyVisited,
16✔
349
                                    nRecLevel + 1, bDryRun) &&
16✔
350
                        bDryRun)
351
                        return true;
8✔
352
                }
353
            }
354
        }
355
    }
356
    else if (poK->GetType() == PDFObjectType_Dictionary)
×
357
    {
358
        if (ExploreTree(poK, aoSetAlreadyVisited, nRecLevel + 1, bDryRun) &&
×
359
            bDryRun)
360
            return true;
×
361
    }
362

363
    return bRet;
16✔
364
}
365

366
/************************************************************************/
367
/*                        GetGeometryFromMCID()                         */
368
/************************************************************************/
369

370
OGRGeometry *PDFDataset::GetGeometryFromMCID(int nMCID)
126✔
371
{
372
    auto oMapIter = m_oMapMCID.find(nMCID);
126✔
373
    if (oMapIter != m_oMapMCID.end())
126✔
374
        return oMapIter->second;
61✔
375
    else
376
        return nullptr;
65✔
377
}
378

379
/************************************************************************/
380
/*                    GraphicState::PreMultiplyBy()                     */
381
/************************************************************************/
382

383
void PDFDataset::GraphicState::PreMultiplyBy(double adfMatrix[6])
21,729✔
384
{
385
    /*
386
    [ a b 0 ]     [ a' b' 0]     [ aa' + bc'       ab' + bd'       0 ]
387
    [ c d 0 ]  *  [ c' d' 0]  =  [ ca' + dc'       cb' + dd'       0 ]
388
    [ e f 1 ]     [ e' f' 1]     [ ea' + fc' + e'  eb' + fd' + f'  1 ]
389
    */
390

391
    // Be careful about the multiplication order!
392
    // PDF reference version 1.7, page 209:
393
    // when a sequence of transformations is carried out, the matrix
394
    // representing the combined transformation (M′) is calculated
395
    // by premultiplying the matrix representing the additional transformation (MT)
396
    // with the one representing all previously existing transformations (M)
397

398
    double a = adfMatrix[0];
21,729✔
399
    double b = adfMatrix[1];
21,729✔
400
    double c = adfMatrix[2];
21,729✔
401
    double d = adfMatrix[3];
21,729✔
402
    double e = adfMatrix[4];
21,729✔
403
    double f = adfMatrix[5];
21,729✔
404
    double ap = adfCM[0];
21,729✔
405
    double bp = adfCM[1];
21,729✔
406
    double cp = adfCM[2];
21,729✔
407
    double dp = adfCM[3];
21,729✔
408
    double ep = adfCM[4];
21,729✔
409
    double fp = adfCM[5];
21,729✔
410
    adfCM[0] = a * ap + b * cp;
21,729✔
411
    adfCM[1] = a * bp + b * dp;
21,729✔
412
    adfCM[2] = c * ap + d * cp;
21,729✔
413
    adfCM[3] = c * bp + d * dp;
21,729✔
414
    adfCM[4] = e * ap + f * cp + ep;
21,729✔
415
    adfCM[5] = e * bp + f * dp + fp;
21,729✔
416
}
21,729✔
417

418
/************************************************************************/
419
/*                   GraphicState::ApplyMatrix()                        */
420
/************************************************************************/
421

422
void PDFDataset::GraphicState::ApplyMatrix(double adfCoords[2]) const
6,617,850✔
423
{
424
    double x = adfCoords[0];
6,617,850✔
425
    double y = adfCoords[1];
6,617,850✔
426

427
    adfCoords[0] = x * adfCM[0] + y * adfCM[2] + adfCM[4];
6,617,850✔
428
    adfCoords[1] = x * adfCM[1] + y * adfCM[3] + adfCM[5];
6,617,850✔
429
}
6,617,850✔
430

431
/************************************************************************/
432
/*                         PDFCoordsToSRSCoords()                       */
433
/************************************************************************/
434

435
void PDFDataset::PDFCoordsToSRSCoords(double x, double y, double &X, double &Y)
12,589,000✔
436
{
437
    x = x / m_dfPageWidth * nRasterXSize;
12,589,000✔
438
    if (m_bGeoTransformValid)
12,589,000✔
439
        y = (1 - y / m_dfPageHeight) * nRasterYSize;
12,587,500✔
440
    else
441
        y = (y / m_dfPageHeight) * nRasterYSize;
1,496✔
442

443
    X = m_gt[0] + x * m_gt[1] + y * m_gt[2];
12,589,000✔
444
    Y = m_gt[3] + x * m_gt[4] + y * m_gt[5];
12,589,000✔
445

446
    if (fabs(X - std::round(X)) < 1e-8)
12,589,000✔
447
        X = std::round(X);
121✔
448
    if (fabs(Y - std::round(Y)) < 1e-8)
12,589,000✔
449
        Y = std::round(Y);
118✔
450
}
12,589,000✔
451

452
/************************************************************************/
453
/*                         PDFGetCircleCenter()                         */
454
/************************************************************************/
455

456
/* Return the center of a circle, or NULL if it is not recognized */
457

458
static OGRPoint *PDFGetCircleCenter(OGRLineString *poLS)
2,493✔
459
{
460
    if (poLS == nullptr || poLS->getNumPoints() != 1 + 4 * BEZIER_STEPS)
2,493✔
461
        return nullptr;
×
462

463
    if (poLS->getY(0 * BEZIER_STEPS) == poLS->getY(2 * BEZIER_STEPS) &&
2,517✔
464
        poLS->getX(1 * BEZIER_STEPS) == poLS->getX(3 * BEZIER_STEPS) &&
24✔
465
        fabs((poLS->getX(0 * BEZIER_STEPS) + poLS->getX(2 * BEZIER_STEPS)) / 2 -
48✔
466
             poLS->getX(1 * BEZIER_STEPS)) < EPSILON &&
2,541✔
467
        fabs((poLS->getY(1 * BEZIER_STEPS) + poLS->getY(3 * BEZIER_STEPS)) / 2 -
48✔
468
             poLS->getY(0 * BEZIER_STEPS)) < EPSILON)
24✔
469
    {
470
        return new OGRPoint(
471
            (poLS->getX(0 * BEZIER_STEPS) + poLS->getX(2 * BEZIER_STEPS)) / 2,
24✔
472
            (poLS->getY(1 * BEZIER_STEPS) + poLS->getY(3 * BEZIER_STEPS)) / 2);
24✔
473
    }
474
    return nullptr;
2,469✔
475
}
476

477
/************************************************************************/
478
/*                         PDFGetSquareCenter()                         */
479
/************************************************************************/
480

481
/* Return the center of a square, or NULL if it is not recognized */
482

483
static OGRPoint *PDFGetSquareCenter(OGRLineString *poLS)
93,443✔
484
{
485
    if (poLS == nullptr || poLS->getNumPoints() < 4 || poLS->getNumPoints() > 5)
93,443✔
486
        return nullptr;
×
487

488
    if (poLS->getX(0) == poLS->getX(3) && poLS->getY(0) == poLS->getY(1) &&
95,054✔
489
        poLS->getX(1) == poLS->getX(2) && poLS->getY(2) == poLS->getY(3) &&
95,067✔
490
        fabs(fabs(poLS->getX(0) - poLS->getX(1)) -
13✔
491
             fabs(poLS->getY(0) - poLS->getY(3))) < EPSILON)
13✔
492
    {
493
        return new OGRPoint((poLS->getX(0) + poLS->getX(1)) / 2,
12✔
494
                            (poLS->getY(0) + poLS->getY(3)) / 2);
12✔
495
    }
496
    return nullptr;
93,431✔
497
}
498

499
/************************************************************************/
500
/*                        PDFGetTriangleCenter()                        */
501
/************************************************************************/
502

503
/* Return the center of a equilateral triangle, or NULL if it is not recognized
504
 */
505

506
static OGRPoint *PDFGetTriangleCenter(OGRLineString *poLS)
15,497✔
507
{
508
    if (poLS == nullptr || poLS->getNumPoints() < 3 || poLS->getNumPoints() > 4)
15,497✔
509
        return nullptr;
×
510

511
    double dfSqD1 = SQUARE(poLS->getX(0) - poLS->getX(1)) +
15,497✔
512
                    SQUARE(poLS->getY(0) - poLS->getY(1));
15,497✔
513
    double dfSqD2 = SQUARE(poLS->getX(1) - poLS->getX(2)) +
15,497✔
514
                    SQUARE(poLS->getY(1) - poLS->getY(2));
15,497✔
515
    double dfSqD3 = SQUARE(poLS->getX(0) - poLS->getX(2)) +
15,497✔
516
                    SQUARE(poLS->getY(0) - poLS->getY(2));
15,497✔
517
    if (fabs(dfSqD1 - dfSqD2) < EPSILON && fabs(dfSqD2 - dfSqD3) < EPSILON)
15,497✔
518
    {
519
        return new OGRPoint((poLS->getX(0) + poLS->getX(1) + poLS->getX(2)) / 3,
12✔
520
                            (poLS->getY(0) + poLS->getY(1) + poLS->getY(2)) /
12✔
521
                                3);
12✔
522
    }
523
    return nullptr;
15,485✔
524
}
525

526
/************************************************************************/
527
/*                          PDFGetStarCenter()                          */
528
/************************************************************************/
529

530
/* Return the center of a 5-point star, or NULL if it is not recognized */
531

532
static OGRPoint *PDFGetStarCenter(OGRLineString *poLS)
14,541✔
533
{
534
    if (poLS == nullptr || poLS->getNumPoints() < 10 ||
29,082✔
535
        poLS->getNumPoints() > 11)
14,541✔
536
        return nullptr;
×
537

538
    double dfSqD01 = SQUARE(poLS->getX(0) - poLS->getX(1)) +
14,541✔
539
                     SQUARE(poLS->getY(0) - poLS->getY(1));
14,541✔
540
    double dfSqD02 = SQUARE(poLS->getX(0) - poLS->getX(2)) +
14,541✔
541
                     SQUARE(poLS->getY(0) - poLS->getY(2));
14,541✔
542
    double dfSqD13 = SQUARE(poLS->getX(1) - poLS->getX(3)) +
14,541✔
543
                     SQUARE(poLS->getY(1) - poLS->getY(3));
14,541✔
544
    const double dfSin18divSin126 = 0.38196601125;
14,541✔
545
    if (dfSqD02 == 0)
14,541✔
546
        return nullptr;
3✔
547
    int bOK = fabs(dfSqD13 / dfSqD02 - SQUARE(dfSin18divSin126)) < EPSILON;
14,538✔
548
    for (int i = 1; i < 10 && bOK; i++)
14,646✔
549
    {
550
        double dfSqDiip1 = SQUARE(poLS->getX(i) - poLS->getX((i + 1) % 10)) +
108✔
551
                           SQUARE(poLS->getY(i) - poLS->getY((i + 1) % 10));
108✔
552
        if (fabs(dfSqDiip1 - dfSqD01) > EPSILON)
108✔
553
        {
554
            bOK = FALSE;
×
555
        }
556
        double dfSqDiip2 = SQUARE(poLS->getX(i) - poLS->getX((i + 2) % 10)) +
108✔
557
                           SQUARE(poLS->getY(i) - poLS->getY((i + 2) % 10));
108✔
558
        if ((i % 2) == 1 && fabs(dfSqDiip2 - dfSqD13) > EPSILON)
108✔
559
        {
560
            bOK = FALSE;
×
561
        }
562
        if ((i % 2) == 0 && fabs(dfSqDiip2 - dfSqD02) > EPSILON)
108✔
563
        {
564
            bOK = FALSE;
×
565
        }
566
    }
567
    if (bOK)
14,538✔
568
    {
569
        return new OGRPoint((poLS->getX(0) + poLS->getX(2) + poLS->getX(4) +
12✔
570
                             poLS->getX(6) + poLS->getX(8)) /
12✔
571
                                5,
572
                            (poLS->getY(0) + poLS->getY(2) + poLS->getY(4) +
12✔
573
                             poLS->getY(6) + poLS->getY(8)) /
12✔
574
                                5);
12✔
575
    }
576
    return nullptr;
14,526✔
577
}
578

579
/************************************************************************/
580
/*                            UnstackTokens()                           */
581
/************************************************************************/
582

583
int PDFDataset::UnstackTokens(
4,933,540✔
584
    const char *pszToken, int nRequiredArgs,
585
    char aszTokenStack[TOKEN_STACK_SIZE][MAX_TOKEN_SIZE], int &nTokenStackSize,
586
    double *adfCoords)
587
{
588
    if (nTokenStackSize < nRequiredArgs)
4,933,540✔
589
    {
590
        CPLDebug("PDF", "not enough arguments for %s", pszToken);
×
591
        return FALSE;
×
592
    }
593
    nTokenStackSize -= nRequiredArgs;
4,933,540✔
594
    for (int i = 0; i < nRequiredArgs; i++)
18,300,200✔
595
    {
596
        adfCoords[i] = CPLAtof(aszTokenStack[nTokenStackSize + i]);
13,366,700✔
597
    }
598
    return TRUE;
4,933,540✔
599
}
600

601
/************************************************************************/
602
/*                           AddBezierCurve()                           */
603
/************************************************************************/
604

605
static void AddBezierCurve(std::vector<double> &oCoords, const double *x0_y0,
853,117✔
606
                           const double *x1_y1, const double *x2_y2,
607
                           const double *x3_y3)
608
{
609
    double x0 = x0_y0[0];
853,117✔
610
    double y0 = x0_y0[1];
853,117✔
611
    double x1 = x1_y1[0];
853,117✔
612
    double y1 = x1_y1[1];
853,117✔
613
    double x2 = x2_y2[0];
853,117✔
614
    double y2 = x2_y2[1];
853,117✔
615
    double x3 = x3_y3[0];
853,117✔
616
    double y3 = x3_y3[1];
853,117✔
617
    for (int i = 1; i < BEZIER_STEPS; i++)
8,531,170✔
618
    {
619
        const double t = static_cast<double>(i) / BEZIER_STEPS;
7,678,050✔
620
        const double t2 = t * t;
7,678,050✔
621
        const double t3 = t2 * t;
7,678,050✔
622
        const double oneMinust = 1 - t;
7,678,050✔
623
        const double oneMinust2 = oneMinust * oneMinust;
7,678,050✔
624
        const double oneMinust3 = oneMinust2 * oneMinust;
7,678,050✔
625
        const double three_t_oneMinust = 3 * t * oneMinust;
7,678,050✔
626
        const double x = oneMinust3 * x0 +
7,678,050✔
627
                         three_t_oneMinust * (oneMinust * x1 + t * x2) +
7,678,050✔
628
                         t3 * x3;
7,678,050✔
629
        const double y = oneMinust3 * y0 +
7,678,050✔
630
                         three_t_oneMinust * (oneMinust * y1 + t * y2) +
7,678,050✔
631
                         t3 * y3;
7,678,050✔
632
        oCoords.push_back(x);
7,678,050✔
633
        oCoords.push_back(y);
7,678,050✔
634
    }
635
    oCoords.push_back(x3);
853,117✔
636
    oCoords.push_back(y3);
853,117✔
637
}
853,117✔
638

639
/************************************************************************/
640
/*                           ParseContent()                             */
641
/************************************************************************/
642

643
#define NEW_SUBPATH -99
644
#define CLOSE_SUBPATH -98
645
#define FILL_SUBPATH -97
646

647
OGRGeometry *PDFDataset::ParseContent(
259✔
648
    const char *pszContent, GDALPDFObject *poResources, bool bCollectAllObjects,
649
    bool bInitBDCStack, bool bMatchQ,
650
    const std::map<CPLString, OGRPDFLayer *> &oMapPropertyToLayer,
651
    const std::map<std::pair<int, int>, OGRPDFLayer *> &oMapNumGenToLayer,
652
    const GraphicState &graphicStateIn, OGRPDFLayer *poCurLayer, int nRecLevel)
653
{
654
    if (nRecLevel == 32)
259✔
655
    {
656
        CPLError(CE_Failure, CPLE_AppDefined,
×
657
                 "Too many recursion levels in ParseContent()");
658
        return nullptr;
×
659
    }
660
    if (CPLTestBool(CPLGetConfigOption("PDF_DUMP_CONTENT", "NO")))
259✔
661
    {
662
        static int counter = 1;
663
        FILE *f = fopen(CPLSPrintf("content%d.txt", counter), "wb");
×
664
        ++counter;
×
665
        fwrite(pszContent, 1, strlen(pszContent), f);
×
666
        fclose(f);
×
667
    }
668
    const char *pszContentIni = pszContent;
259✔
669
#ifdef DEBUG_VERBOSE
670
    CPLDebug("PDF", "Initial layer: %s",
671
             poCurLayer ? poCurLayer->GetName() : "(null)");
672
#endif
673

674
#define PUSH(aszTokenStack, str, strlen)                                       \
675
    do                                                                         \
676
    {                                                                          \
677
        if (nTokenStackSize < TOKEN_STACK_SIZE)                                \
678
            memcpy(aszTokenStack[nTokenStackSize++], str, strlen + 1);         \
679
        else                                                                   \
680
        {                                                                      \
681
            CPLError(CE_Failure, CPLE_AppDefined,                              \
682
                     "Max token stack size reached");                          \
683
            return nullptr;                                                    \
684
        };                                                                     \
685
    } while (false)
686

687
#define ADD_CHAR(szToken, c)                                                   \
688
    do                                                                         \
689
    {                                                                          \
690
        if (nTokenSize < MAX_TOKEN_SIZE - 1)                                   \
691
        {                                                                      \
692
            szToken[nTokenSize++] = c;                                         \
693
            szToken[nTokenSize] = '\0';                                        \
694
        }                                                                      \
695
        else                                                                   \
696
        {                                                                      \
697
            CPLError(CE_Failure, CPLE_AppDefined, "Max token size reached");   \
698
            return nullptr;                                                    \
699
        };                                                                     \
700
    } while (false)
701

702
    char szToken[MAX_TOKEN_SIZE];
703
    int nTokenSize = 0;
259✔
704
    char ch;
705
    char aszTokenStack[TOKEN_STACK_SIZE][MAX_TOKEN_SIZE];
706
    int nTokenStackSize = 0;
259✔
707
    int bInString = FALSE;
259✔
708
    int nBDCOrBMCLevel = 0;
259✔
709
    int nParenthesisLevel = 0;
259✔
710
    int nArrayLevel = 0;
259✔
711
    int nBTLevel = 0;
259✔
712

713
    GraphicState oGS(graphicStateIn);
259✔
714
    std::stack<GraphicState> oGSStack;
518✔
715
    std::stack<OGRPDFLayer *> oLayerStack;
518✔
716

717
    std::vector<double> oCoords;
518✔
718
    int bHasFoundFill = FALSE;
259✔
719
    int bHasMultiPart = FALSE;
259✔
720

721
    szToken[0] = '\0';
259✔
722

723
    if (bInitBDCStack)
259✔
724
    {
725
        PUSH(aszTokenStack, "dummy", 5);
62✔
726
        PUSH(aszTokenStack, "dummy", 5);
62✔
727
        oLayerStack.push(nullptr);
62✔
728
    }
729

730
    int nLineNumber = 0;
259✔
731

732
    while ((ch = *pszContent) != '\0')
122,615,000✔
733
    {
734
        int bPushToken = FALSE;
122,615,000✔
735

736
        if (!bInString && ch == '%')
122,615,000✔
737
        {
738
            /* Skip comments until end-of-line */
739
            while ((ch = *pszContent) != '\0')
52✔
740
            {
741
                if (ch == '\r' || ch == '\n')
52✔
742
                    break;
743
                pszContent++;
48✔
744
            }
745
            if (ch == 0)
4✔
746
                break;
×
747
            ++nLineNumber;
4✔
748
            if (ch == '\r' && pszContent[1] == '\n')
4✔
749
            {
750
                ++pszContent;
×
751
            }
752
        }
753
        else if (!bInString && (ch == ' ' || ch == '\r' || ch == '\n'))
122,615,000✔
754
        {
755
            if (ch == '\r')
20,515,400✔
756
            {
757
                ++nLineNumber;
×
758
                if (pszContent[1] == '\n')
×
759
                {
760
                    ++pszContent;
×
761
                }
762
            }
763
            else if (ch == '\n')
20,515,400✔
764
                ++nLineNumber;
6,027,630✔
765
            bPushToken = TRUE;
20,515,400✔
766
        }
767

768
        /* Ignore arrays */
769
        else if (!bInString && nTokenSize == 0 && ch == '[')
102,099,000✔
770
        {
771
            nArrayLevel++;
275,911✔
772
        }
773
        else if (!bInString && nArrayLevel && ch == ']')
101,823,000✔
774
        {
775
            nArrayLevel--;
275,911✔
776
            nTokenSize = 0;  // completely ignore content in arrays
275,911✔
777
        }
778

779
        else if (!bInString && nTokenSize == 0 && ch == '(')
101,547,000✔
780
        {
781
            bInString = TRUE;
12✔
782
            nParenthesisLevel++;
12✔
783
            ADD_CHAR(szToken, ch);
12✔
784
        }
785
        else if (bInString && ch == '(')
101,547,000✔
786
        {
787
            nParenthesisLevel++;
×
788
            ADD_CHAR(szToken, ch);
×
789
        }
790
        else if (bInString && ch == ')')
101,547,000✔
791
        {
792
            nParenthesisLevel--;
12✔
793
            ADD_CHAR(szToken, ch);
12✔
794
            if (nParenthesisLevel == 0)
12✔
795
            {
796
                bInString = FALSE;
12✔
797
                bPushToken = TRUE;
12✔
798
            }
799
        }
800
        else if (bInString && ch == '\\')
101,547,000✔
801
        {
802
            const auto nextCh = pszContent[1];
×
803
            if (nextCh == 'n')
×
804
            {
805
                ADD_CHAR(szToken, '\n');
×
806
                pszContent++;
×
807
            }
808
            else if (nextCh == 'r')
×
809
            {
810
                ADD_CHAR(szToken, '\r');
×
811
                pszContent++;
×
812
            }
813
            else if (nextCh == 't')
×
814
            {
815
                ADD_CHAR(szToken, '\t');
×
816
                pszContent++;
×
817
            }
818
            else if (nextCh == 'b')
×
819
            {
820
                ADD_CHAR(szToken, '\b');
×
821
                pszContent++;
×
822
            }
823
            else if (nextCh == '(' || nextCh == ')' || nextCh == '\\')
×
824
            {
825
                ADD_CHAR(szToken, nextCh);
×
826
                pszContent++;
×
827
            }
828
            else if (nextCh >= '0' && nextCh <= '7' && pszContent[2] >= '0' &&
×
829
                     pszContent[2] <= '7' && pszContent[3] >= '0' &&
×
830
                     pszContent[3] <= '7')
×
831
            {
832
                ADD_CHAR(szToken,
×
833
                         ((nextCh - '\0') * 64 + (pszContent[2] - '\0') * 8 +
834
                          pszContent[3] - '\0'));
835
                pszContent += 3;
×
836
            }
837
            else if (nextCh == '\n')
×
838
            {
839
                if (pszContent[2] == '\r')
×
840
                    pszContent += 2;
×
841
                else
842
                    pszContent++;
×
843
            }
844
            else if (nextCh == '\r')
×
845
            {
846
                pszContent++;
×
847
            }
×
848
        }
849
        else if (ch == '<' && pszContent[1] == '<' && nTokenSize == 0)
101,547,000✔
850
        {
851
            int nDictDepth = 0;
1✔
852

853
            while (*pszContent != '\0')
9✔
854
            {
855
                if (pszContent[0] == '<' && pszContent[1] == '<')
9✔
856
                {
857
                    ADD_CHAR(szToken, '<');
1✔
858
                    ADD_CHAR(szToken, '<');
1✔
859
                    nDictDepth++;
1✔
860
                    pszContent += 2;
1✔
861
                }
862
                else if (pszContent[0] == '>' && pszContent[1] == '>')
8✔
863
                {
864
                    ADD_CHAR(szToken, '>');
1✔
865
                    ADD_CHAR(szToken, '>');
1✔
866
                    nDictDepth--;
1✔
867
                    pszContent += 2;
1✔
868
                    if (nDictDepth == 0)
1✔
869
                        break;
1✔
870
                }
871
                else
872
                {
873
                    ADD_CHAR(szToken, *pszContent);
7✔
874
                    pszContent++;
7✔
875
                }
876
            }
877
            if (nDictDepth == 0)
1✔
878
            {
879
                bPushToken = TRUE;
1✔
880
                pszContent--;
1✔
881
            }
882
            else
883
                break;
1✔
884
        }
885
        else
886
        {
887
            // Do not create too long tokens in arrays, that we will ignore
888
            // anyway
889
            if (nArrayLevel == 0 || nTokenSize == 0)
101,547,000✔
890
            {
891
                ADD_CHAR(szToken, ch);
100,854,000✔
892
            }
893
        }
894

895
        pszContent++;
122,615,000✔
896
        if (pszContent[0] == '\0')
122,615,000✔
897
            bPushToken = TRUE;
197✔
898

899
#define EQUAL1(szToken, s) (szToken[0] == s[0] && szToken[1] == '\0')
900
#define EQUAL2(szToken, s)                                                     \
901
    (szToken[0] == s[0] && szToken[1] == s[1] && szToken[2] == '\0')
902
#define EQUAL3(szToken, s)                                                     \
903
    (szToken[0] == s[0] && szToken[1] == s[1] && szToken[2] == s[2] &&         \
904
     szToken[3] == '\0')
905

906
        if (bPushToken && nTokenSize)
122,615,000✔
907
        {
908
            if (EQUAL2(szToken, "BI"))
19,970,600✔
909
            {
910
                while (*pszContent != '\0')
×
911
                {
912
                    if (pszContent[0] == 'E' && pszContent[1] == 'I' &&
×
913
                        pszContent[2] == ' ')
×
914
                    {
915
                        break;
×
916
                    }
917
                    pszContent++;
×
918
                }
919
                if (pszContent[0] == 'E')
×
920
                    pszContent += 3;
×
921
                else
922
                {
923
                    CPLDebug("PDF",
×
924
                             "ParseContent(), line %d: return at line %d of "
925
                             "content stream",
926
                             __LINE__, nLineNumber);
927
                    return nullptr;
×
928
                }
929
            }
930
            else if (EQUAL3(szToken, "BDC"))
19,970,600✔
931
            {
932
                if (nTokenStackSize < 2)
274✔
933
                {
934
                    CPLDebug("PDF", "not enough arguments for %s", szToken);
×
935
                    CPLDebug("PDF",
×
936
                             "ParseContent(), line %d: return at line %d of "
937
                             "content stream",
938
                             __LINE__, nLineNumber);
939
                    return nullptr;
×
940
                }
941
                nTokenStackSize -= 2;
274✔
942
                const char *pszOC = aszTokenStack[nTokenStackSize];
274✔
943
                const char *pszOCGName = aszTokenStack[nTokenStackSize + 1];
274✔
944

945
                nBDCOrBMCLevel++;
274✔
946

947
                if (EQUAL3(pszOC, "/OC") && pszOCGName[0] == '/')
274✔
948
                {
949
                    const auto oIter = oMapPropertyToLayer.find(pszOCGName + 1);
211✔
950
                    if (oIter != oMapPropertyToLayer.end())
211✔
951
                    {
952
                        poCurLayer = oIter->second;
150✔
953
                    }
954
                }
955
#ifdef DEBUG_VERBOSE
956
                CPLDebug("PDF", "%s %s BDC -> Cur layer : %s", pszOC,
957
                         pszOCGName,
958
                         poCurLayer ? poCurLayer->GetName() : "(null)");
959
#endif
960
                oLayerStack.push(poCurLayer);
274✔
961
            }
962
            else if (EQUAL3(szToken, "BMC"))
19,970,400✔
963
            {
964
                if (nTokenStackSize < 1)
×
965
                {
966
                    CPLDebug("PDF", "not enough arguments for %s", szToken);
×
967
                    CPLDebug("PDF",
×
968
                             "ParseContent(), line %d: return at line %d of "
969
                             "content stream",
970
                             __LINE__, nLineNumber);
971
                    return nullptr;
×
972
                }
973
                nTokenStackSize -= 1;
×
974

975
                nBDCOrBMCLevel++;
×
976
                oLayerStack.push(poCurLayer);
×
977
            }
978
            else if (EQUAL3(szToken, "EMC"))
19,970,400✔
979
            {
980
                // CPLDebug("PDF", "EMC");
981
                if (!oLayerStack.empty())
215✔
982
                {
983
                    oLayerStack.pop();
215✔
984
                    if (!oLayerStack.empty())
215✔
985
                        poCurLayer = oLayerStack.top();
190✔
986
                    else
987
                        poCurLayer = nullptr;
25✔
988

989
#ifdef DEBUG_VERBOSE
990
                    CPLDebug("PDF", "EMC -> Cur layer : %s",
991
                             poCurLayer ? poCurLayer->GetName() : "(null)");
992
#endif
993
                }
994
                else
995
                {
996
                    CPLDebug(
×
997
                        "PDF",
998
                        "Should not happen at line %d: offset %d in stream",
999
                        __LINE__, int(pszContent - pszContentIni));
×
1000
                    poCurLayer = nullptr;
×
1001
                    // return NULL;
1002
                }
1003

1004
                nBDCOrBMCLevel--;
215✔
1005
                if (nBDCOrBMCLevel == 0 && bInitBDCStack)
215✔
1006
                    break;
3✔
1007
            }
1008

1009
            /* Ignore any text stuff */
1010
            else if (EQUAL2(szToken, "BT"))
19,970,100✔
1011
                nBTLevel++;
8,652✔
1012
            else if (EQUAL2(szToken, "ET"))
19,961,500✔
1013
            {
1014
                nBTLevel--;
8,652✔
1015
                if (nBTLevel < 0)
8,652✔
1016
                {
1017
                    CPLDebug(
×
1018
                        "PDF",
1019
                        "Should not happen at line %d: offset %d in stream",
1020
                        __LINE__, int(pszContent - pszContentIni));
×
1021
                    CPLDebug("PDF",
×
1022
                             "ParseContent(), line %d: return at line %d of "
1023
                             "content stream",
1024
                             __LINE__, nLineNumber);
1025
                    return nullptr;
×
1026
                }
1027
            }
1028
            else if (!nArrayLevel && !nBTLevel)
19,952,800✔
1029
            {
1030
                int bEmitFeature = FALSE;
19,585,300✔
1031

1032
                if (szToken[0] < 'A')
19,585,300✔
1033
                {
1034
                    PUSH(aszTokenStack, szToken, nTokenSize);
13,688,500✔
1035
                }
1036
                else if (EQUAL1(szToken, "q"))
5,896,810✔
1037
                {
1038
                    oGSStack.push(oGS);
380✔
1039
                }
1040
                else if (EQUAL1(szToken, "Q"))
5,896,430✔
1041
                {
1042
                    if (oGSStack.empty())
380✔
1043
                    {
1044
                        CPLDebug("PDF", "not enough arguments for %s", szToken);
×
1045
                        CPLDebug("PDF",
×
1046
                                 "ParseContent(), line %d: return at line %d "
1047
                                 "of content stream",
1048
                                 __LINE__, nLineNumber);
1049
                        return nullptr;
×
1050
                    }
1051

1052
                    oGS = oGSStack.top();
380✔
1053
                    oGSStack.pop();
380✔
1054

1055
                    if (oGSStack.empty() && bMatchQ)
380✔
1056
                        break;
×
1057
                }
1058
                else if (EQUAL2(szToken, "cm"))
5,896,050✔
1059
                {
1060
                    double adfMatrix[6];
1061
                    if (!UnstackTokens(szToken, 6, aszTokenStack,
21,729✔
1062
                                       nTokenStackSize, adfMatrix))
1063
                    {
1064
                        CPLDebug(
×
1065
                            "PDF",
1066
                            "Should not happen at line %d: offset %d in stream",
1067
                            __LINE__, int(pszContent - pszContentIni));
×
1068
                        CPLDebug("PDF",
×
1069
                                 "ParseContent(), line %d: return at line %d "
1070
                                 "of content stream",
1071
                                 __LINE__, nLineNumber);
1072
                        return nullptr;
×
1073
                    }
1074

1075
                    oGS.PreMultiplyBy(adfMatrix);
21,729✔
1076
                }
1077
                else if (EQUAL1(szToken, "b") || /* closepath, fill, stroke */
5,874,320✔
1078
                         EQUAL2(szToken, "b*") /* closepath, eofill, stroke */)
5,874,320✔
1079
                {
1080
                    if (!(!oCoords.empty() &&
102✔
1081
                          oCoords[oCoords.size() - 2] == CLOSE_SUBPATH &&
51✔
1082
                          oCoords.back() == CLOSE_SUBPATH))
15✔
1083
                    {
1084
                        oCoords.push_back(CLOSE_SUBPATH);
36✔
1085
                        oCoords.push_back(CLOSE_SUBPATH);
36✔
1086
                    }
1087
                    oCoords.push_back(FILL_SUBPATH);
51✔
1088
                    oCoords.push_back(FILL_SUBPATH);
51✔
1089
                    bHasFoundFill = TRUE;
51✔
1090

1091
                    bEmitFeature = TRUE;
51✔
1092
                }
1093
                else if (EQUAL1(szToken, "B") ||  /* fill, stroke */
5,874,270✔
1094
                         EQUAL2(szToken, "B*") || /* eofill, stroke */
5,874,270✔
1095
                         EQUAL1(szToken, "f") ||  /* fill */
5,873,910✔
1096
                         EQUAL1(szToken, "F") ||  /* fill */
5,846,670✔
1097
                         EQUAL2(szToken, "f*") /* eofill */)
5,846,670✔
1098
                {
1099
                    oCoords.push_back(FILL_SUBPATH);
47,367✔
1100
                    oCoords.push_back(FILL_SUBPATH);
47,367✔
1101
                    bHasFoundFill = TRUE;
47,367✔
1102

1103
                    bEmitFeature = TRUE;
47,367✔
1104
                }
1105
                else if (EQUAL1(szToken, "h")) /* close subpath */
5,826,900✔
1106
                {
1107
                    if (!(!oCoords.empty() &&
680,846✔
1108
                          oCoords[oCoords.size() - 2] == CLOSE_SUBPATH &&
340,423✔
1109
                          oCoords.back() == CLOSE_SUBPATH))
×
1110
                    {
1111
                        oCoords.push_back(CLOSE_SUBPATH);
340,423✔
1112
                        oCoords.push_back(CLOSE_SUBPATH);
340,423✔
1113
                    }
1114
                }
1115
                else if (EQUAL1(
5,486,480✔
1116
                             szToken,
1117
                             "n")) /* new subpath without stroking or filling */
1118
                {
1119
                    oCoords.resize(0);
171✔
1120
                }
1121
                else if (EQUAL1(szToken, "s")) /* close and stroke */
5,486,310✔
1122
                {
1123
                    if (!(!oCoords.empty() &&
48✔
1124
                          oCoords[oCoords.size() - 2] == CLOSE_SUBPATH &&
24✔
1125
                          oCoords.back() == CLOSE_SUBPATH))
×
1126
                    {
1127
                        oCoords.push_back(CLOSE_SUBPATH);
24✔
1128
                        oCoords.push_back(CLOSE_SUBPATH);
24✔
1129
                    }
1130

1131
                    bEmitFeature = TRUE;
24✔
1132
                }
1133
                else if (EQUAL1(szToken, "S")) /* stroke */
5,486,280✔
1134
                {
1135
                    bEmitFeature = TRUE;
272,508✔
1136
                }
1137
                else if (EQUAL1(szToken, "m") || EQUAL1(szToken, "l"))
5,213,780✔
1138
                {
1139
                    double adfCoords[2];
1140
                    if (!UnstackTokens(szToken, 2, aszTokenStack,
4,058,480✔
1141
                                       nTokenStackSize, adfCoords))
1142
                    {
1143
                        CPLDebug(
×
1144
                            "PDF",
1145
                            "Should not happen at line %d: offset %d in stream",
1146
                            __LINE__, int(pszContent - pszContentIni));
×
1147
                        CPLDebug("PDF",
×
1148
                                 "ParseContent(), line %d: return at line %d "
1149
                                 "of content stream",
1150
                                 __LINE__, nLineNumber);
1151
                        return nullptr;
×
1152
                    }
1153

1154
                    if (EQUAL1(szToken, "m"))
4,058,480✔
1155
                    {
1156
                        if (!oCoords.empty())
384,935✔
1157
                            bHasMultiPart = TRUE;
64,824✔
1158
                        oCoords.push_back(NEW_SUBPATH);
384,935✔
1159
                        oCoords.push_back(NEW_SUBPATH);
384,935✔
1160
                    }
1161

1162
                    oGS.ApplyMatrix(adfCoords);
4,058,480✔
1163
                    oCoords.push_back(adfCoords[0]);
4,058,480✔
1164
                    oCoords.push_back(adfCoords[1]);
4,058,480✔
1165
                }
1166
                else if (EQUAL1(szToken, "c")) /* Bezier curve */
1,155,290✔
1167
                {
1168
                    double adfCoords[6];
1169
                    if (!UnstackTokens(szToken, 6, aszTokenStack,
853,117✔
1170
                                       nTokenStackSize, adfCoords))
1171
                    {
1172
                        CPLDebug(
×
1173
                            "PDF",
1174
                            "Should not happen at line %d: offset %d in stream",
1175
                            __LINE__, int(pszContent - pszContentIni));
×
1176
                        CPLDebug("PDF",
×
1177
                                 "ParseContent(), line %d: return at line %d "
1178
                                 "of content stream",
1179
                                 __LINE__, nLineNumber);
1180
                        return nullptr;
×
1181
                    }
1182

1183
                    oGS.ApplyMatrix(adfCoords + 0);
853,117✔
1184
                    oGS.ApplyMatrix(adfCoords + 2);
853,117✔
1185
                    oGS.ApplyMatrix(adfCoords + 4);
853,117✔
1186
                    AddBezierCurve(oCoords,
1,706,230✔
1187
                                   oCoords.empty()
853,117✔
1188
                                       ? &adfCoords[0]
1189
                                       : &oCoords[oCoords.size() - 2],
853,117✔
1190
                                   &adfCoords[0], &adfCoords[2], &adfCoords[4]);
853,117✔
1191
                }
1192
                else if (EQUAL1(szToken, "v")) /* Bezier curve */
302,176✔
1193
                {
1194
                    double adfCoords[4];
1195
                    if (!UnstackTokens(szToken, 4, aszTokenStack,
×
1196
                                       nTokenStackSize, adfCoords))
1197
                    {
1198
                        CPLDebug(
×
1199
                            "PDF",
1200
                            "Should not happen at line %d: offset %d in stream",
1201
                            __LINE__, int(pszContent - pszContentIni));
×
1202
                        CPLDebug("PDF",
×
1203
                                 "ParseContent(), line %d: return at line %d "
1204
                                 "of content stream",
1205
                                 __LINE__, nLineNumber);
1206
                        return nullptr;
×
1207
                    }
1208

1209
                    oGS.ApplyMatrix(adfCoords + 0);
×
1210
                    oGS.ApplyMatrix(adfCoords + 2);
×
1211
                    AddBezierCurve(
×
1212
                        oCoords,
1213
                        oCoords.empty() ? &adfCoords[0]
×
1214
                                        : &oCoords[oCoords.size() - 2],
×
1215
                        oCoords.empty() ? &adfCoords[0]
×
1216
                                        : &oCoords[oCoords.size() - 2],
×
1217
                        &adfCoords[0], &adfCoords[2]);
×
1218
                }
1219
                else if (EQUAL1(szToken, "y")) /* Bezier curve */
302,176✔
1220
                {
1221
                    double adfCoords[4];
1222
                    if (!UnstackTokens(szToken, 4, aszTokenStack,
×
1223
                                       nTokenStackSize, adfCoords))
1224
                    {
1225
                        CPLDebug(
×
1226
                            "PDF",
1227
                            "Should not happen at line %d: offset %d in stream",
1228
                            __LINE__, int(pszContent - pszContentIni));
×
1229
                        CPLDebug("PDF",
×
1230
                                 "ParseContent(), line %d: return at line %d "
1231
                                 "of content stream",
1232
                                 __LINE__, nLineNumber);
1233
                        return nullptr;
×
1234
                    }
1235

1236
                    oGS.ApplyMatrix(adfCoords + 0);
×
1237
                    oGS.ApplyMatrix(adfCoords + 2);
×
1238
                    AddBezierCurve(oCoords,
×
1239
                                   oCoords.empty()
×
1240
                                       ? &adfCoords[0]
1241
                                       : &oCoords[oCoords.size() - 2],
×
1242
                                   &adfCoords[0], &adfCoords[2], &adfCoords[2]);
×
1243
                }
1244
                else if (EQUAL2(szToken, "re")) /* Rectangle */
302,176✔
1245
                {
1246
                    double adfCoords[4];
1247
                    if (!UnstackTokens(szToken, 4, aszTokenStack,
8✔
1248
                                       nTokenStackSize, adfCoords))
1249
                    {
1250
                        CPLDebug(
×
1251
                            "PDF",
1252
                            "Should not happen at line %d: offset %d in stream",
1253
                            __LINE__, int(pszContent - pszContentIni));
×
1254
                        CPLDebug("PDF",
×
1255
                                 "ParseContent(), line %d: return at line %d "
1256
                                 "of content stream",
1257
                                 __LINE__, nLineNumber);
1258
                        return nullptr;
×
1259
                    }
1260

1261
                    adfCoords[2] += adfCoords[0];
8✔
1262
                    adfCoords[3] += adfCoords[1];
8✔
1263

1264
                    oGS.ApplyMatrix(adfCoords);
8✔
1265
                    oGS.ApplyMatrix(adfCoords + 2);
8✔
1266

1267
                    if (!oCoords.empty())
8✔
1268
                        bHasMultiPart = TRUE;
×
1269
                    oCoords.push_back(NEW_SUBPATH);
8✔
1270
                    oCoords.push_back(NEW_SUBPATH);
8✔
1271
                    oCoords.push_back(adfCoords[0]);
8✔
1272
                    oCoords.push_back(adfCoords[1]);
8✔
1273
                    oCoords.push_back(adfCoords[2]);
8✔
1274
                    oCoords.push_back(adfCoords[1]);
8✔
1275
                    oCoords.push_back(adfCoords[2]);
8✔
1276
                    oCoords.push_back(adfCoords[3]);
8✔
1277
                    oCoords.push_back(adfCoords[0]);
8✔
1278
                    oCoords.push_back(adfCoords[3]);
8✔
1279
                    oCoords.push_back(CLOSE_SUBPATH);
8✔
1280
                    oCoords.push_back(CLOSE_SUBPATH);
8✔
1281
                }
1282

1283
                else if (EQUAL2(szToken, "Do"))
302,168✔
1284
                {
1285
                    if (nTokenStackSize == 0)
197✔
1286
                    {
1287
                        CPLDebug("PDF", "not enough arguments for %s", szToken);
×
1288
                        CPLDebug("PDF",
×
1289
                                 "ParseContent(), line %d: return at line %d "
1290
                                 "of content stream",
1291
                                 __LINE__, nLineNumber);
1292
                        return nullptr;
59✔
1293
                    }
1294

1295
                    CPLString osObjectName = aszTokenStack[--nTokenStackSize];
197✔
1296

1297
                    if (osObjectName[0] != '/')
197✔
1298
                    {
1299
                        CPLDebug(
×
1300
                            "PDF",
1301
                            "Should not happen at line %d: offset %d in stream",
1302
                            __LINE__, int(pszContent - pszContentIni));
×
1303
                        CPLDebug("PDF",
×
1304
                                 "ParseContent(), line %d: return at line %d "
1305
                                 "of content stream",
1306
                                 __LINE__, nLineNumber);
1307
                        return nullptr;
×
1308
                    }
1309

1310
                    if (osObjectName.find("/SymImage") == 0)
197✔
1311
                    {
1312
                        oCoords.push_back(oGS.adfCM[4] + oGS.adfCM[0] / 2);
6✔
1313
                        oCoords.push_back(oGS.adfCM[5] + oGS.adfCM[3] / 2);
6✔
1314

1315
                        szToken[0] = '\0';
6✔
1316
                        nTokenSize = 0;
6✔
1317

1318
                        if (poCurLayer != nullptr)
6✔
1319
                            bEmitFeature = TRUE;
3✔
1320
                        else
1321
                            continue;
3✔
1322
                    }
1323
                    else if (poResources == nullptr)
191✔
1324
                    {
1325
                        szToken[0] = '\0';
×
1326
                        nTokenSize = 0;
×
1327

1328
                        CPLDebug("PDF", "Skipping unknown object %s at line %d",
×
1329
                                 osObjectName.c_str(), nLineNumber);
1330
                        continue;
×
1331
                    }
1332

1333
                    if (!bEmitFeature)
194✔
1334
                    {
1335
                        GDALPDFObject *poXObject =
1336
                            poResources->GetDictionary()->Get("XObject");
191✔
1337
                        if (poXObject == nullptr ||
382✔
1338
                            poXObject->GetType() != PDFObjectType_Dictionary)
191✔
1339
                        {
1340
                            CPLDebug("PDF",
×
1341
                                     "Should not happen at line %d: offset %d "
1342
                                     "in stream",
1343
                                     __LINE__, int(pszContent - pszContentIni));
×
1344
                            CPLDebug("PDF",
×
1345
                                     "ParseContent(), line %d: return at line "
1346
                                     "%d of content stream",
1347
                                     __LINE__, nLineNumber);
1348
                            return nullptr;
×
1349
                        }
1350

1351
                        GDALPDFObject *poObject =
1352
                            poXObject->GetDictionary()->Get(
382✔
1353
                                osObjectName.c_str() + 1);
191✔
1354
                        if (poObject == nullptr)
191✔
1355
                        {
1356
                            CPLDebug("PDF",
×
1357
                                     "Should not happen at line %d: offset %d "
1358
                                     "in stream",
1359
                                     __LINE__, int(pszContent - pszContentIni));
×
1360
                            CPLDebug("PDF",
×
1361
                                     "ParseContent(), line %d: return at line "
1362
                                     "%d of content stream",
1363
                                     __LINE__, nLineNumber);
1364
                            return nullptr;
×
1365
                        }
1366

1367
                        int bParseStream = TRUE;
191✔
1368
                        GDALPDFObject *poSubResources = nullptr;
191✔
1369
                        /* Check if the object is an image. If so, no need to
1370
                         * try to parse */
1371
                        /* it. */
1372
                        if (poObject->GetType() == PDFObjectType_Dictionary)
191✔
1373
                        {
1374
                            GDALPDFObject *poSubtype =
1375
                                poObject->GetDictionary()->Get("Subtype");
191✔
1376
                            if (poSubtype != nullptr &&
382✔
1377
                                poSubtype->GetType() == PDFObjectType_Name &&
382✔
1378
                                poSubtype->GetName() == "Image")
191✔
1379
                            {
1380
                                bParseStream = FALSE;
1✔
1381
                            }
1382

1383
                            poSubResources =
1384
                                poObject->GetDictionary()->Get("Resources");
191✔
1385
                            if (poSubResources && poSubResources->GetType() !=
191✔
1386
                                                      PDFObjectType_Dictionary)
1387
                            {
1388
                                poSubResources = nullptr;
×
1389
                            }
1390
                        }
1391

1392
                        if (bParseStream)
191✔
1393
                        {
1394
                            GDALPDFStream *poStream = poObject->GetStream();
190✔
1395
                            if (!poStream)
190✔
1396
                            {
1397
                                CPLDebug("PDF",
×
1398
                                         "Should not happen at line %d: offset "
1399
                                         "%d in stream",
1400
                                         __LINE__,
1401
                                         int(pszContent - pszContentIni));
×
1402
                                CPLDebug("PDF",
×
1403
                                         "ParseContent(), line %d: return at "
1404
                                         "line %d of content stream",
1405
                                         __LINE__, nLineNumber);
1406
                                return nullptr;
×
1407
                            }
1408

1409
                            OGRPDFLayer *poCurLayerRec = poCurLayer;
190✔
1410

1411
                            if (poObject->GetType() == PDFObjectType_Dictionary)
190✔
1412
                            {
1413
                                auto poOC =
1414
                                    poObject->GetDictionary()->Get("OC");
190✔
1415
                                if (poOC &&
193✔
1416
                                    poOC->GetType() ==
3✔
1417
                                        PDFObjectType_Dictionary &&
193✔
1418
                                    poOC->GetRefNum().toBool())
193✔
1419
                                {
1420
                                    const auto oIterNumGenToLayer =
1421
                                        oMapNumGenToLayer.find(
1422
                                            std::pair(poOC->GetRefNum().toInt(),
3✔
1423
                                                      poOC->GetRefGen()));
6✔
1424
                                    if (oIterNumGenToLayer !=
3✔
1425
                                        oMapNumGenToLayer.end())
6✔
1426
                                    {
1427
                                        poCurLayerRec =
3✔
1428
                                            oIterNumGenToLayer->second;
3✔
1429
                                    }
1430
                                }
1431
                            }
1432

1433
                            char *pszStr = poStream->GetBytes();
190✔
1434
                            if (pszStr)
190✔
1435
                            {
1436
                                CPLDebug("PDF", "Starting parsing %s",
187✔
1437
                                         osObjectName.c_str());
1438
                                OGRGeometry *poGeom = ParseContent(
187✔
1439
                                    pszStr, poSubResources, bCollectAllObjects,
1440
                                    false, false, oMapPropertyToLayer,
1441
                                    oMapNumGenToLayer, oGS, poCurLayerRec,
1442
                                    nRecLevel + 1);
1443
                                CPLDebug("PDF", "End of parsing of %s",
187✔
1444
                                         osObjectName.c_str());
1445
                                CPLFree(pszStr);
187✔
1446
                                if (poGeom && !bCollectAllObjects)
187✔
1447
                                    return poGeom;
59✔
1448
                                delete poGeom;
128✔
1449
                            }
1450
                        }
1451
                    }
135✔
1452
                }
1453
                else if (EQUAL2(szToken, "RG") || EQUAL2(szToken, "rg"))
301,971✔
1454
                {
1455
                    double *padf = (EQUAL2(szToken, "RG"))
99✔
1456
                                       ? &oGS.adfStrokeColor[0]
296✔
1457
                                       : &oGS.adfFillColor[0];
98✔
1458
                    if (!UnstackTokens(szToken, 3, aszTokenStack,
197✔
1459
                                       nTokenStackSize, padf))
1460
                    {
1461
                        CPLDebug(
×
1462
                            "PDF",
1463
                            "Should not happen at line %d: offset %d in stream",
1464
                            __LINE__, int(pszContent - pszContentIni));
×
1465
                        CPLDebug("PDF",
×
1466
                                 "ParseContent(), line %d: return at line %d "
1467
                                 "of content stream",
1468
                                 __LINE__, nLineNumber);
1469
                        return nullptr;
×
1470
                    }
197✔
1471
                }
1472
                else if (m_oMapOperators.find(szToken) != m_oMapOperators.end())
301,774✔
1473
                {
1474
                    int nArgs = m_oMapOperators[szToken];
301,774✔
1475
                    if (nArgs < 0)
301,774✔
1476
                    {
1477
                        while (nTokenStackSize != 0)
39,142✔
1478
                        {
1479
                            CPLString osTopToken =
1480
                                aszTokenStack[--nTokenStackSize];
29,335✔
1481
                            if (m_oMapOperators.find(osTopToken) !=
29,335✔
1482
                                m_oMapOperators.end())
58,670✔
1483
                                break;
×
1484
                        }
1485
                    }
1486
                    else
1487
                    {
1488
                        if (nArgs > nTokenStackSize)
291,967✔
1489
                        {
1490
                            CPLDebug("PDF", "not enough arguments for %s",
×
1491
                                     szToken);
1492
                            CPLDebug("PDF",
×
1493
                                     "ParseContent(), line %d: return at line "
1494
                                     "%d of content stream",
1495
                                     __LINE__, nLineNumber);
1496
                            return nullptr;
×
1497
                        }
1498
                        nTokenStackSize -= nArgs;
291,967✔
1499
                    }
1500
                }
1501
                else
1502
                {
1503
                    PUSH(aszTokenStack, szToken, nTokenSize);
×
1504
                }
1505

1506
                if (bEmitFeature && poCurLayer != nullptr)
19,585,200✔
1507
                {
1508
                    OGRGeometry *poGeom =
1509
                        BuildGeometry(oCoords, bHasFoundFill, bHasMultiPart);
319,893✔
1510
                    bHasFoundFill = FALSE;
319,893✔
1511
                    bHasMultiPart = FALSE;
319,893✔
1512
                    if (poGeom)
319,893✔
1513
                    {
1514
                        OGRFeature *poFeature =
1515
                            new OGRFeature(poCurLayer->GetLayerDefn());
319,892✔
1516
                        if (m_bSetStyle)
319,892✔
1517
                        {
1518
                            OGRwkbGeometryType eType =
1519
                                wkbFlatten(poGeom->getGeometryType());
319,892✔
1520
                            if (eType == wkbLineString ||
319,892✔
1521
                                eType == wkbMultiLineString)
1522
                            {
1523
                                poFeature->SetStyleString(CPLSPrintf(
272,489✔
1524
                                    "PEN(c:#%02X%02X%02X)",
1525
                                    static_cast<int>(
1526
                                        oGS.adfStrokeColor[0] * 255 + 0.5),
272,489✔
1527
                                    static_cast<int>(
1528
                                        oGS.adfStrokeColor[1] * 255 + 0.5),
272,489✔
1529
                                    static_cast<int>(
1530
                                        oGS.adfStrokeColor[2] * 255 + 0.5)));
272,489✔
1531
                            }
1532
                            else if (eType == wkbPolygon ||
47,403✔
1533
                                     eType == wkbMultiPolygon)
1534
                            {
1535
                                poFeature->SetStyleString(CPLSPrintf(
47,370✔
1536
                                    "PEN(c:#%02X%02X%02X);BRUSH(fc:#%02X%02X%"
1537
                                    "02X)",
1538
                                    static_cast<int>(
1539
                                        oGS.adfStrokeColor[0] * 255 + 0.5),
47,370✔
1540
                                    static_cast<int>(
1541
                                        oGS.adfStrokeColor[1] * 255 + 0.5),
47,370✔
1542
                                    static_cast<int>(
1543
                                        oGS.adfStrokeColor[2] * 255 + 0.5),
47,370✔
1544
                                    static_cast<int>(oGS.adfFillColor[0] * 255 +
47,370✔
1545
                                                     0.5),
1546
                                    static_cast<int>(oGS.adfFillColor[1] * 255 +
47,370✔
1547
                                                     0.5),
1548
                                    static_cast<int>(oGS.adfFillColor[2] * 255 +
47,370✔
1549
                                                     0.5)));
47,370✔
1550
                            }
1551
                        }
1552
                        poGeom->assignSpatialReference(
639,784✔
1553
                            poCurLayer->GetSpatialRef());
319,892✔
1554
                        poFeature->SetGeometryDirectly(poGeom);
319,892✔
1555
                        CPL_IGNORE_RET_VAL(
319,892✔
1556
                            poCurLayer->CreateFeature(poFeature));
319,892✔
1557
                        delete poFeature;
319,892✔
1558
                    }
1559

1560
                    oCoords.resize(0);
319,893✔
1561
                }
1562
            }
1563

1564
            szToken[0] = '\0';
19,970,600✔
1565
            nTokenSize = 0;
19,970,600✔
1566
        }
1567
    }
1568

1569
    CPLDebug("PDF", "ParseContent(): reached line %d", nLineNumber);
200✔
1570
    if (!oGSStack.empty())
200✔
1571
        CPLDebug("PDF", "GSStack not empty");
×
1572

1573
    if (nTokenStackSize != 0)
200✔
1574
    {
1575
        while (nTokenStackSize != 0)
×
1576
        {
1577
            nTokenStackSize--;
×
1578
            CPLDebug("PDF", "Remaining values in stack : %s",
×
1579
                     aszTokenStack[nTokenStackSize]);
×
1580
        }
1581
        return nullptr;
×
1582
    }
1583

1584
    if (bCollectAllObjects)
200✔
1585
        return nullptr;
135✔
1586

1587
    return BuildGeometry(oCoords, bHasFoundFill, bHasMultiPart);
65✔
1588
}
1589

1590
/************************************************************************/
1591
/*                           BuildGeometry()                            */
1592
/************************************************************************/
1593

1594
OGRGeometry *PDFDataset::BuildGeometry(std::vector<double> &oCoords,
319,958✔
1595
                                       int bHasFoundFill, int bHasMultiPart)
1596
{
1597
    OGRGeometry *poGeom = nullptr;
319,958✔
1598

1599
    if (!oCoords.size())
319,958✔
1600
        return nullptr;
7✔
1601

1602
    if (oCoords.size() == 2)
319,951✔
1603
    {
1604
        double X, Y;
1605
        PDFCoordsToSRSCoords(oCoords[0], oCoords[1], X, Y);
6✔
1606
        poGeom = new OGRPoint(X, Y);
6✔
1607
    }
1608
    else if (!bHasFoundFill)
319,945✔
1609
    {
1610
        OGRLineString *poLS = nullptr;
272,531✔
1611
        OGRMultiLineString *poMLS = nullptr;
272,531✔
1612
        if (bHasMultiPart)
272,531✔
1613
        {
1614
            poMLS = new OGRMultiLineString();
15,650✔
1615
            poGeom = poMLS;
15,650✔
1616
        }
1617

1618
        for (size_t i = 0; i < oCoords.size(); i += 2)
7,848,820✔
1619
        {
1620
            if (oCoords[i] == NEW_SUBPATH && oCoords[i + 1] == NEW_SUBPATH)
7,576,290✔
1621
            {
1622
                if (poMLS)
293,683✔
1623
                {
1624
                    poLS = new OGRLineString();
36,802✔
1625
                    poMLS->addGeometryDirectly(poLS);
36,802✔
1626
                }
1627
                else
1628
                {
1629
                    delete poLS;
256,881✔
1630
                    poLS = new OGRLineString();
256,881✔
1631
                    poGeom = poLS;
256,881✔
1632
                }
1633
            }
1634
            else if (oCoords[i] == CLOSE_SUBPATH &&
7,531,850✔
1635
                     oCoords[i + 1] == CLOSE_SUBPATH)
249,241✔
1636
            {
1637
                if (poLS && poLS->getNumPoints() >= 2 &&
740,401✔
1638
                    !(poLS->getX(0) == poLS->getX(poLS->getNumPoints() - 1) &&
491,160✔
1639
                      poLS->getY(0) == poLS->getY(poLS->getNumPoints() - 1)))
241,940✔
1640
                {
1641
                    poLS->addPoint(poLS->getX(0), poLS->getY(0));
7,286✔
1642
                }
1643
            }
1644
            else if (oCoords[i] == FILL_SUBPATH &&
7,033,370✔
1645
                     oCoords[i + 1] == FILL_SUBPATH)
×
1646
            {
1647
                /* Should not happen */
1648
            }
1649
            else
1650
            {
1651
                if (poLS)
7,033,370✔
1652
                {
1653
                    double X, Y;
1654
                    PDFCoordsToSRSCoords(oCoords[i], oCoords[i + 1], X, Y);
7,033,370✔
1655

1656
                    poLS->addPoint(X, Y);
7,033,370✔
1657
                }
1658
            }
1659
        }
1660

1661
        // Recognize points as written by GDAL (ogr-sym-2 : circle (not filled))
1662
        OGRGeometry *poCenter = nullptr;
272,531✔
1663
        if (poCenter == nullptr && poLS != nullptr &&
545,062✔
1664
            poLS->getNumPoints() == 1 + BEZIER_STEPS * 4)
272,531✔
1665
        {
1666
            poCenter = PDFGetCircleCenter(poLS);
129✔
1667
        }
1668

1669
        // Recognize points as written by GDAL (ogr-sym-4: square (not filled))
1670
        if (poCenter == nullptr && poLS != nullptr &&
545,056✔
1671
            (poLS->getNumPoints() == 4 || poLS->getNumPoints() == 5))
272,525✔
1672
        {
1673
            poCenter = PDFGetSquareCenter(poLS);
84,934✔
1674
        }
1675

1676
        // Recognize points as written by GDAL (ogr-sym-6: triangle (not
1677
        // filled))
1678
        if (poCenter == nullptr && poLS != nullptr &&
545,050✔
1679
            (poLS->getNumPoints() == 3 || poLS->getNumPoints() == 4))
272,519✔
1680
        {
1681
            poCenter = PDFGetTriangleCenter(poLS);
9,562✔
1682
        }
1683

1684
        // Recognize points as written by GDAL (ogr-sym-8: star (not filled))
1685
        if (poCenter == nullptr && poLS != nullptr &&
545,044✔
1686
            (poLS->getNumPoints() == 10 || poLS->getNumPoints() == 11))
272,513✔
1687
        {
1688
            poCenter = PDFGetStarCenter(poLS);
14,115✔
1689
        }
1690

1691
        if (poCenter == nullptr && poMLS != nullptr &&
288,181✔
1692
            poMLS->getNumGeometries() == 2)
15,650✔
1693
        {
1694
            const OGRLineString *poLS1 = poMLS->getGeometryRef(0);
13,301✔
1695
            const OGRLineString *poLS2 = poMLS->getGeometryRef(1);
13,301✔
1696

1697
            // Recognize points as written by GDAL (ogr-sym-0: cross (+) ).
1698
            if (poLS1->getNumPoints() == 2 && poLS2->getNumPoints() == 2 &&
13,649✔
1699
                poLS1->getY(0) == poLS1->getY(1) &&
89✔
1700
                poLS2->getX(0) == poLS2->getX(1) &&
6✔
1701
                fabs(fabs(poLS1->getX(0) - poLS1->getX(1)) -
6✔
1702
                     fabs(poLS2->getY(0) - poLS2->getY(1))) < EPSILON &&
6✔
1703
                fabs((poLS1->getX(0) + poLS1->getX(1)) / 2 - poLS2->getX(0)) <
6✔
1704
                    EPSILON &&
13,566✔
1705
                fabs((poLS2->getY(0) + poLS2->getY(1)) / 2 - poLS1->getY(0)) <
6✔
1706
                    EPSILON)
1707
            {
1708
                poCenter = new OGRPoint(poLS2->getX(0), poLS1->getY(0));
6✔
1709
            }
1710
            // Recognize points as written by GDAL (ogr-sym-1: diagcross (X) ).
1711
            else if (poLS1->getNumPoints() == 2 && poLS2->getNumPoints() == 2 &&
13,631✔
1712
                     poLS1->getX(0) == poLS2->getX(0) &&
83✔
1713
                     poLS1->getY(0) == poLS2->getY(1) &&
12✔
1714
                     poLS1->getX(1) == poLS2->getX(1) &&
12✔
1715
                     poLS1->getY(1) == poLS2->getY(0) &&
13,560✔
1716
                     fabs(fabs(poLS1->getX(0) - poLS1->getX(1)) -
6✔
1717
                          fabs(poLS1->getY(0) - poLS1->getY(1))) < EPSILON)
6✔
1718
            {
1719
                poCenter = new OGRPoint((poLS1->getX(0) + poLS1->getX(1)) / 2,
12✔
1720
                                        (poLS1->getY(0) + poLS1->getY(1)) / 2);
6✔
1721
            }
1722
        }
1723

1724
        if (poCenter)
272,531✔
1725
        {
1726
            delete poGeom;
36✔
1727
            poGeom = poCenter;
36✔
1728
        }
1729
    }
1730
    else
1731
    {
1732
        OGRLinearRing *poLS = nullptr;
47,414✔
1733
        int nPolys = 0;
47,414✔
1734
        OGRGeometry **papoPoly = nullptr;
47,414✔
1735

1736
        for (size_t i = 0; i < oCoords.size(); i += 2)
5,832,530✔
1737
        {
1738
            if (oCoords[i] == NEW_SUBPATH && oCoords[i + 1] == NEW_SUBPATH)
5,785,150✔
1739
            {
1740
                if (poLS && poLS->getNumPoints() >= 3)
91,086✔
1741
                {
1742
                    OGRPolygon *poPoly = new OGRPolygon();
3✔
1743
                    poPoly->addRingDirectly(poLS);
3✔
1744
                    poLS = nullptr;
3✔
1745

1746
                    papoPoly = static_cast<OGRGeometry **>(CPLRealloc(
6✔
1747
                        papoPoly, (nPolys + 1) * sizeof(OGRGeometry *)));
3✔
1748
                    papoPoly[nPolys++] = poPoly;
3✔
1749
                }
1750
                delete poLS;
91,086✔
1751
                poLS = new OGRLinearRing();
91,086✔
1752
            }
1753
            else if ((oCoords[i] == CLOSE_SUBPATH &&
5,694,060✔
1754
                      oCoords[i + 1] == CLOSE_SUBPATH) ||
11,297,100✔
1755
                     (oCoords[i] == FILL_SUBPATH &&
5,602,990✔
1756
                      oCoords[i + 1] == FILL_SUBPATH))
47,379✔
1757
            {
1758
                if (poLS)
138,456✔
1759
                {
1760
                    poLS->closeRings();
91,083✔
1761

1762
                    std::unique_ptr<OGRPoint> poCenter;
×
1763

1764
                    if (nPolys == 0 && poLS &&
138,494✔
1765
                        poLS->getNumPoints() == 1 + BEZIER_STEPS * 4)
47,411✔
1766
                    {
1767
                        // Recognize points as written by GDAL (ogr-sym-3 :
1768
                        // circle (filled))
1769
                        poCenter.reset(PDFGetCircleCenter(poLS));
2,364✔
1770
                    }
1771

1772
                    if (nPolys == 0 && poCenter == nullptr && poLS &&
138,476✔
1773
                        poLS->getNumPoints() == 5)
47,393✔
1774
                    {
1775
                        // Recognize points as written by GDAL (ogr-sym-5:
1776
                        // square (filled))
1777
                        poCenter.reset(PDFGetSquareCenter(poLS));
8,509✔
1778

1779
                        /* ESRI points */
1780
                        if (poCenter == nullptr && oCoords.size() == 14 &&
22,440✔
1781
                            poLS->getY(0) == poLS->getY(1) &&
5,428✔
1782
                            poLS->getX(1) == poLS->getX(2) &&
×
1783
                            poLS->getY(2) == poLS->getY(3) &&
17,012✔
1784
                            poLS->getX(3) == poLS->getX(0))
×
1785
                        {
1786
                            poCenter.reset(new OGRPoint(
×
1787
                                (poLS->getX(0) + poLS->getX(1)) / 2,
×
1788
                                (poLS->getY(0) + poLS->getY(2)) / 2));
×
1789
                        }
1790
                    }
1791
                    // Recognize points as written by GDAL (ogr-sym-7: triangle
1792
                    // (filled))
1793
                    else if (nPolys == 0 && poLS && poLS->getNumPoints() == 4)
82,574✔
1794
                    {
1795
                        poCenter.reset(PDFGetTriangleCenter(poLS));
5,935✔
1796
                    }
1797
                    // Recognize points as written by GDAL (ogr-sym-9: star
1798
                    // (filled))
1799
                    else if (nPolys == 0 && poLS && poLS->getNumPoints() == 11)
76,639✔
1800
                    {
1801
                        poCenter.reset(PDFGetStarCenter(poLS));
426✔
1802
                    }
1803

1804
                    if (poCenter)
91,083✔
1805
                    {
1806
                        delete poGeom;
36✔
1807
                        poGeom = poCenter.release();
36✔
1808
                        break;
36✔
1809
                    }
1810

1811
                    if (poLS->getNumPoints() >= 3)
91,047✔
1812
                    {
1813
                        OGRPolygon *poPoly = new OGRPolygon();
91,043✔
1814
                        poPoly->addRingDirectly(poLS);
91,043✔
1815
                        poLS = nullptr;
91,043✔
1816

1817
                        papoPoly = static_cast<OGRGeometry **>(CPLRealloc(
182,086✔
1818
                            papoPoly, (nPolys + 1) * sizeof(OGRGeometry *)));
91,043✔
1819
                        papoPoly[nPolys++] = poPoly;
91,043✔
1820
                    }
1821
                    else
1822
                    {
1823
                        delete poLS;
4✔
1824
                        poLS = nullptr;
4✔
1825
                    }
1826
                }
1827
            }
1828
            else
1829
            {
1830
                if (poLS)
5,555,610✔
1831
                {
1832
                    double X, Y;
1833
                    PDFCoordsToSRSCoords(oCoords[i], oCoords[i + 1], X, Y);
5,555,610✔
1834

1835
                    poLS->addPoint(X, Y);
5,555,610✔
1836
                }
1837
            }
1838
        }
1839

1840
        delete poLS;
47,414✔
1841

1842
        int bIsValidGeometry;
1843
        if (nPolys == 2 &&
60,639✔
1844
            papoPoly[0]->toPolygon()->getNumInteriorRings() == 0 &&
60,639✔
1845
            papoPoly[1]->toPolygon()->getNumInteriorRings() == 0)
13,225✔
1846
        {
1847
            OGRLinearRing *poRing0 =
1848
                papoPoly[0]->toPolygon()->getExteriorRing();
13,225✔
1849
            OGRLinearRing *poRing1 =
1850
                papoPoly[1]->toPolygon()->getExteriorRing();
13,225✔
1851
            if (poRing0->getNumPoints() == poRing1->getNumPoints())
13,225✔
1852
            {
1853
                int bSameRing = TRUE;
4,615✔
1854
                for (int i = 0; i < poRing0->getNumPoints(); i++)
4,615✔
1855
                {
1856
                    if (poRing0->getX(i) != poRing1->getX(i))
4,615✔
1857
                    {
1858
                        bSameRing = FALSE;
4,615✔
1859
                        break;
4,615✔
1860
                    }
1861
                    if (poRing0->getY(i) != poRing1->getY(i))
×
1862
                    {
1863
                        bSameRing = FALSE;
×
1864
                        break;
×
1865
                    }
1866
                }
1867

1868
                /* Just keep on ring if they are identical */
1869
                if (bSameRing)
4,615✔
1870
                {
1871
                    delete papoPoly[1];
×
1872
                    nPolys = 1;
×
1873
                }
1874
            }
1875
        }
1876
        if (nPolys)
47,414✔
1877
        {
1878
            poGeom = OGRGeometryFactory::organizePolygons(
47,378✔
1879
                papoPoly, nPolys, &bIsValidGeometry, nullptr);
1880
        }
1881
        CPLFree(papoPoly);
47,414✔
1882
    }
1883

1884
    return poGeom;
319,951✔
1885
}
1886

1887
/************************************************************************/
1888
/*                          ExploreContents()                           */
1889
/************************************************************************/
1890

1891
void PDFDataset::ExploreContents(GDALPDFObject *poObj,
8✔
1892
                                 GDALPDFObject *poResources, int nDepth,
1893
                                 int &nVisited, bool &bStop)
1894
{
1895
    std::map<CPLString, OGRPDFLayer *> oMapPropertyToLayer;
8✔
1896
    if (nDepth == 10 || nVisited == 1000)
8✔
1897
    {
1898
        CPLError(CE_Failure, CPLE_AppDefined,
×
1899
                 "ExploreContents(): too deep exploration or too many items");
1900
        bStop = true;
×
1901
        return;
×
1902
    }
1903
    if (bStop)
8✔
1904
        return;
×
1905

1906
    if (poObj->GetType() == PDFObjectType_Array)
8✔
1907
    {
1908
        GDALPDFArray *poArray = poObj->GetArray();
×
1909
        for (int i = 0; i < poArray->GetLength(); i++)
×
1910
        {
1911
            GDALPDFObject *poSubObj = poArray->Get(i);
×
1912
            if (poSubObj)
×
1913
            {
1914
                nVisited++;
×
1915
                ExploreContents(poSubObj, poResources, nDepth + 1, nVisited,
×
1916
                                bStop);
1917
                if (bStop)
×
1918
                    return;
×
1919
            }
1920
        }
1921
    }
1922

1923
    if (poObj->GetType() != PDFObjectType_Dictionary)
8✔
1924
        return;
×
1925

1926
    GDALPDFStream *poStream = poObj->GetStream();
8✔
1927
    if (!poStream)
8✔
1928
        return;
×
1929

1930
    char *pszStr = poStream->GetBytes();
8✔
1931
    if (!pszStr)
8✔
1932
        return;
×
1933

1934
    const char *pszMCID = pszStr;
8✔
1935
    while ((pszMCID = strstr(pszMCID, "/MCID")) != nullptr)
72✔
1936
    {
1937
        const char *pszBDC = strstr(pszMCID, "BDC");
64✔
1938
        if (pszBDC)
64✔
1939
        {
1940
            /* Hack for
1941
             * http://www.avenza.com/sites/default/files/spatialpdf/US_County_Populations.pdf
1942
             */
1943
            /* FIXME: that logic is too fragile. */
1944
            const char *pszStartParsing = pszBDC;
64✔
1945
            const char *pszAfterBDC = pszBDC + 3;
64✔
1946
            int bMatchQ = FALSE;
64✔
1947
            while (pszAfterBDC[0] == ' ' || pszAfterBDC[0] == '\r' ||
128✔
1948
                   pszAfterBDC[0] == '\n')
128✔
1949
                pszAfterBDC++;
64✔
1950
            if (STARTS_WITH(pszAfterBDC, "0 0 m"))
64✔
1951
            {
1952
                const char *pszLastq = pszBDC;
×
1953
                while (pszLastq > pszStr && *pszLastq != 'q')
×
1954
                    pszLastq--;
×
1955

1956
                if (pszLastq > pszStr && *pszLastq == 'q' &&
×
1957
                    (pszLastq[-1] == ' ' || pszLastq[-1] == '\r' ||
×
1958
                     pszLastq[-1] == '\n') &&
×
1959
                    (pszLastq[1] == ' ' || pszLastq[1] == '\r' ||
×
1960
                     pszLastq[1] == '\n'))
×
1961
                {
1962
                    pszStartParsing = pszLastq;
×
1963
                    bMatchQ = TRUE;
×
1964
                }
1965
            }
1966

1967
            int nMCID = atoi(pszMCID + 6);
64✔
1968
            if (GetGeometryFromMCID(nMCID) == nullptr)
64✔
1969
            {
1970
                OGRGeometry *poGeom = ParseContent(
62✔
1971
                    pszStartParsing, poResources, false, !bMatchQ, bMatchQ,
1972
                    oMapPropertyToLayer, {}, GraphicState(), nullptr, 0);
62✔
1973
                if (poGeom != nullptr)
62✔
1974
                {
1975
                    /* Save geometry in map */
1976
                    m_oMapMCID[nMCID] = poGeom;
59✔
1977
                }
1978
            }
1979
        }
1980
        pszMCID += 5;
64✔
1981
    }
1982
    CPLFree(pszStr);
8✔
1983
}
1984

1985
/************************************************************************/
1986
/*                   ExploreContentsNonStructured()                     */
1987
/************************************************************************/
1988

1989
void PDFDataset::ExploreContentsNonStructuredInternal(
10✔
1990
    GDALPDFObject *poContents, GDALPDFObject *poResources,
1991
    const std::map<CPLString, OGRPDFLayer *> &oMapPropertyToLayer,
1992
    const std::map<std::pair<int, int>, OGRPDFLayer *> &oMapNumGenToLayer,
1993
    OGRPDFLayer *poSingleLayer)
1994
{
1995
    if (poContents->GetType() == PDFObjectType_Array)
10✔
1996
    {
1997
        GDALPDFArray *poArray = poContents->GetArray();
1✔
1998
        char *pszConcatStr = nullptr;
1✔
1999
        size_t nConcatLen = 0;
1✔
2000
        for (int i = 0; i < poArray->GetLength(); i++)
2✔
2001
        {
2002
            GDALPDFObject *poObj = poArray->Get(i);
1✔
2003
            if (poObj == nullptr ||
2✔
2004
                poObj->GetType() != PDFObjectType_Dictionary)
1✔
2005
                break;
×
2006
            GDALPDFStream *poStream = poObj->GetStream();
1✔
2007
            if (!poStream)
1✔
2008
                break;
×
2009
            char *pszStr = poStream->GetBytes();
1✔
2010
            if (!pszStr)
1✔
2011
                break;
×
2012
            const size_t nLen = strlen(pszStr);
1✔
2013
            char *pszConcatStrNew = static_cast<char *>(
2014
                CPLRealloc(pszConcatStr, nConcatLen + nLen + 1));
1✔
2015
            if (pszConcatStrNew == nullptr)
1✔
2016
            {
2017
                CPLFree(pszStr);
×
2018
                break;
×
2019
            }
2020
            pszConcatStr = pszConcatStrNew;
1✔
2021
            memcpy(pszConcatStr + nConcatLen, pszStr, nLen + 1);
1✔
2022
            nConcatLen += nLen;
1✔
2023
            CPLFree(pszStr);
1✔
2024
        }
2025
        if (pszConcatStr)
1✔
2026
            ParseContent(pszConcatStr, poResources, poResources != nullptr,
1✔
2027
                         false, false, oMapPropertyToLayer, oMapNumGenToLayer,
2028
                         GraphicState(), poSingleLayer, 0);
2✔
2029
        CPLFree(pszConcatStr);
1✔
2030
        return;
1✔
2031
    }
2032

2033
    if (poContents->GetType() != PDFObjectType_Dictionary)
9✔
2034
        return;
×
2035

2036
    GDALPDFStream *poStream = poContents->GetStream();
9✔
2037
    if (!poStream)
9✔
2038
        return;
×
2039

2040
    char *pszStr = poStream->GetBytes();
9✔
2041
    if (!pszStr)
9✔
2042
        return;
×
2043
    ParseContent(pszStr, poResources, poResources != nullptr, false, false,
9✔
2044
                 oMapPropertyToLayer, oMapNumGenToLayer, GraphicState(),
9✔
2045
                 poSingleLayer, 0);
2046
    CPLFree(pszStr);
9✔
2047
}
2048

2049
/************************************************************************/
2050
/*                         ExploreResourceProperty()                    */
2051
/************************************************************************/
2052

2053
static void ExploreResourceProperty(
147✔
2054
    const char *pszKey, GDALPDFObject *poObj, const std::string &osType,
2055
    const std::map<std::pair<int, int>, OGRPDFLayer *> &oMapNumGenToLayer,
2056
    std::map<CPLString, OGRPDFLayer *> &oMapPropertyToLayer, int nRecLevel)
2057
{
2058
    if (nRecLevel == 2)
147✔
2059
        return;
×
2060

2061
    if (osType == "OCG" && poObj->GetRefNum().toBool())
147✔
2062
    {
2063
        const auto oIterNumGenToLayer = oMapNumGenToLayer.find(
2064
            std::pair(poObj->GetRefNum().toInt(), poObj->GetRefGen()));
146✔
2065
        if (oIterNumGenToLayer != oMapNumGenToLayer.end())
146✔
2066
        {
2067
            auto poLayer = oIterNumGenToLayer->second;
146✔
2068
#ifdef DEBUG_VERBOSE
2069
            CPLDebug("PDF", "Associating OCG %s to layer %s", pszKey,
2070
                     poLayer->GetName());
2071
#endif
2072
            oMapPropertyToLayer[pszKey] = poLayer;
146✔
2073
        }
2074
        else
2075
        {
2076
            CPLDebug("PDF",
×
2077
                     "Resource.Properties[%s] referencing "
2078
                     "OGC %d not tied with a layer",
2079
                     pszKey, poObj->GetRefNum().toInt());
×
2080
        }
2081
    }
2082
    else if (osType == "OCMD")
1✔
2083
    {
2084
        // Optional Content Group Membership Dictionary
2085
        // Deal with constructs like
2086
        /*
2087
             Item[0] : MC0
2088
              Type = dictionary, Num = 331, Gen = 0
2089
               Item[0] : OCGs
2090
                Type = array
2091
                 Item[0]:
2092
                  Type = dictionary, Num = 251, Gen = 0
2093
                   Item[0] : Intent = View (name)
2094
                   Item[1] : Name = Orthoimage (string)
2095
                   Item[2] : Type = OCG (name)
2096
                 Item[1]:
2097
                  Type = dictionary, Num = 250, Gen = 0
2098
                   Item[0] : Intent = View (name)
2099
                   Item[1] : Name = Images (string)
2100
                   Item[2] : Type = OCG (name)
2101
               Item[1] : P = AllOn (name)
2102
               Item[2] : Type = OCMD (name)
2103
        */
2104
        // where the OCG Orthoimage is actually a child
2105
        // of Images (which will be named Orthoimage.Images)
2106
        // In which case we only associate MC0 to
2107
        // Orthoimage.Images
2108
        // Cf https://github.com/OSGeo/gdal/issues/8372
2109
        // and https://prd-tnm.s3.amazonaws.com/StagedProducts/Maps/USTopo/PDF/ID/ID_Big_Baldy_20200409_TM_geo.pdf
2110
        auto poOCGs = poObj->GetDictionary()->Get("OCGs");
1✔
2111
        if (poOCGs && poOCGs->GetType() == PDFObjectType_Array)
1✔
2112
        {
2113
            auto poOCGsArray = poOCGs->GetArray();
1✔
2114
            const int nLength = poOCGsArray->GetLength();
1✔
2115
            size_t nMaxNameLength = 0;
1✔
2116
            OGRPDFLayer *poCandidateLayer = nullptr;
1✔
2117
            std::vector<std::string> aosLayerNames;
2✔
2118
            for (int i = 0; i < nLength; ++i)
3✔
2119
            {
2120
                auto poOCG = poOCGsArray->Get(i);
2✔
2121
                if (poOCG && poOCG->GetType() == PDFObjectType_Dictionary)
2✔
2122
                {
2123
                    auto poP = poOCG->GetDictionary()->Get("P");
2✔
2124
                    if (poP && poP->GetType() == PDFObjectType_Name)
2✔
2125
                    {
2126
                        // Visibility Policy
2127
                        const auto &osP = poP->GetName();
×
2128
                        if (osP != "AllOn" && osP != "AnyOn")
×
2129
                        {
2130
                            CPLDebug("PDF",
×
2131
                                     "Resource.Properties[%s] "
2132
                                     "has unhandled visibility policy %s",
2133
                                     pszKey, osP.c_str());
2134
                        }
2135
                    }
2136
                    auto poOCGType = poOCG->GetDictionary()->Get("Type");
2✔
2137
                    if (poOCGType && poOCGType->GetType() == PDFObjectType_Name)
2✔
2138
                    {
2139
                        const std::string &osOCGType = poOCGType->GetName();
2✔
2140
                        if (osOCGType == "OCG" && poOCG->GetRefNum().toBool())
2✔
2141
                        {
2142
                            const auto oIterNumGenToLayer =
2143
                                oMapNumGenToLayer.find(
2144
                                    std::pair(poOCG->GetRefNum().toInt(),
2✔
2145
                                              poOCG->GetRefGen()));
4✔
2146
                            if (oIterNumGenToLayer != oMapNumGenToLayer.end())
2✔
2147
                            {
2148
                                auto poLayer = oIterNumGenToLayer->second;
2✔
2149
                                aosLayerNames.emplace_back(poLayer->GetName());
2✔
2150
                                if (strlen(poLayer->GetName()) > nMaxNameLength)
2✔
2151
                                {
2152
                                    nMaxNameLength = strlen(poLayer->GetName());
2✔
2153
                                    poCandidateLayer = poLayer;
2✔
2154
                                }
2155
                            }
2156
                            else
2157
                            {
2158
                                CPLDebug("PDF",
×
2159
                                         "Resource.Properties[%s][%d] "
2160
                                         "referencing OGC %d not tied with "
2161
                                         "a layer",
2162
                                         pszKey, i, poOCG->GetRefNum().toInt());
×
2163
                            }
2164
                        }
2165
                        else
2166
                        {
2167
                            CPLDebug(
×
2168
                                "PDF",
2169
                                "Resource.Properties[%s][%d] has unhandled "
2170
                                "Type member: %s",
2171
                                pszKey, i, osOCGType.c_str());
2172
                        }
2173
                    }
2174
                }
2175
            }
2176

2177
            if (!aosLayerNames.empty())
1✔
2178
            {
2179
                // Sort layer names and if each one starts
2180
                // with the previous ones, then the OCGs
2181
                // are part of a hierarchy, and we can
2182
                // associate the property name with the
2183
                // last one.
2184
                std::sort(aosLayerNames.begin(), aosLayerNames.end());
1✔
2185
                bool bOK = true;
1✔
2186
                for (size_t i = 1; i < aosLayerNames.size(); ++i)
2✔
2187
                {
2188
                    if (aosLayerNames[i].find(aosLayerNames[i - 1]) != 0)
1✔
2189
                    {
2190
                        bOK = false;
×
2191
                        break;
×
2192
                    }
2193
                }
2194
                if (bOK)
1✔
2195
                {
2196
                    CPLAssert(poCandidateLayer);
1✔
2197
#ifdef DEBUG_VERBOSE
2198
                    CPLDebug("PDF", "Associating OCG %s to layer %s", pszKey,
2199
                             poCandidateLayer->GetName());
2200
#endif
2201
                    oMapPropertyToLayer[pszKey] = poCandidateLayer;
1✔
2202
                }
2203
                else
2204
                {
2205
                    CPLDebug("PDF",
×
2206
                             "Resource.Properties[%s] "
2207
                             "contains a OCMD that cannot "
2208
                             "be mapped to a single layer",
2209
                             pszKey);
2210
                }
2211
            }
2212
            else
2213
            {
2214
                CPLDebug("PDF",
×
2215
                         "Resource.Properties[%s] contains "
2216
                         "a OCMD without OCGs",
2217
                         pszKey);
2218
            }
2219
        }
2220
        else if (poOCGs && poOCGs->GetType() == PDFObjectType_Dictionary)
×
2221
        {
2222
            auto poOGGsType = poOCGs->GetDictionary()->Get("Type");
×
2223
            if (poOGGsType && poOGGsType->GetType() == PDFObjectType_Name)
×
2224
            {
2225
                ExploreResourceProperty(pszKey, poOCGs, poOGGsType->GetName(),
×
2226
                                        oMapNumGenToLayer, oMapPropertyToLayer,
2227
                                        nRecLevel + 1);
2228
            }
2229
            else
2230
            {
2231
                CPLDebug("PDF",
×
2232
                         "Resource.Properties[%s] contains a OGCs member with "
2233
                         "no Type member",
2234
                         pszKey);
2235
            }
2236
        }
2237
        else if (poOCGs)
×
2238
        {
2239
            CPLDebug("PDF",
×
2240
                     "Resource.Properties[%s] contains a OCMD "
2241
                     "with a OGCs member of unhandled type: %s",
2242
                     pszKey, poOCGs->GetTypeName());
×
2243
        }
2244
        else
2245
        {
2246
            // Could have a VE (visibility expression)
2247
            // expression instead, but  we don't handle that
2248
            CPLDebug("PDF",
×
2249
                     "Resource.Properties[%s] contains a "
2250
                     "OCMD with a missing OGC (perhaps has a VE?)",
2251
                     pszKey);
2252
        }
2253
    }
2254
    else
2255
    {
2256
        CPLDebug("PDF",
×
2257
                 "Resource.Properties[%s] has unhandled "
2258
                 "Type member: %s",
2259
                 pszKey, osType.c_str());
2260
    }
2261
}
2262

2263
/************************************************************************/
2264
/*                   ExploreContentsNonStructured()                     */
2265
/************************************************************************/
2266

2267
void PDFDataset::ExploreContentsNonStructured(GDALPDFObject *poContents,
13✔
2268
                                              GDALPDFObject *poResources)
2269
{
2270
    std::map<CPLString, OGRPDFLayer *> oMapPropertyToLayer;
13✔
2271
    std::map<std::pair<int, int>, OGRPDFLayer *> oMapNumGenToLayer;
13✔
2272

2273
    const auto BuildMapNumGenToLayer = [this, &oMapNumGenToLayer]()
1,226✔
2274
    {
2275
        for (const auto &oLayerWithref : m_aoLayerWithRef)
223✔
2276
        {
2277
            CPLString osSanitizedName(
2278
                PDFSanitizeLayerName(oLayerWithref.osName));
215✔
2279

2280
            OGRPDFLayer *poPDFLayer = dynamic_cast<OGRPDFLayer *>(
197✔
2281
                GetLayerByName(osSanitizedName.c_str()));
215✔
2282
            if (!poPDFLayer)
215✔
2283
            {
2284
                auto poSRSOri = GetSpatialRef();
197✔
2285
                OGRSpatialReference *poSRS =
2286
                    poSRSOri ? poSRSOri->Clone() : nullptr;
197✔
2287
                auto poPDFLayerUniquePtr = std::make_unique<OGRPDFLayer>(
2288
                    this, osSanitizedName.c_str(), poSRS, wkbUnknown);
394✔
2289
                if (poSRS)
197✔
2290
                    poSRS->Release();
197✔
2291

2292
                m_apoLayers.emplace_back(std::move(poPDFLayerUniquePtr));
197✔
2293
                poPDFLayer = m_apoLayers.back().get();
197✔
2294
            }
2295

2296
            oMapNumGenToLayer[std::pair(oLayerWithref.nOCGNum.toInt(),
215✔
2297
                                        oLayerWithref.nOCGGen)] = poPDFLayer;
430✔
2298
        }
2299
    };
8✔
2300

2301
    if (poResources != nullptr &&
26✔
2302
        poResources->GetType() == PDFObjectType_Dictionary)
13✔
2303
    {
2304
        auto poResourcesDict = poResources->GetDictionary();
13✔
2305
        GDALPDFObject *poTopProperties = poResourcesDict->Get("Properties");
13✔
2306
        if (poTopProperties != nullptr &&
20✔
2307
            poTopProperties->GetType() == PDFObjectType_Dictionary)
7✔
2308
        {
2309
            BuildMapNumGenToLayer();
7✔
2310

2311
            for (const auto &[osKey, poObj] :
20✔
2312
                 poTopProperties->GetDictionary()->GetValues())
27✔
2313
            {
2314
                const char *pszKey = osKey.c_str();
10✔
2315
                if (poObj->GetType() == PDFObjectType_Dictionary)
10✔
2316
                {
2317
                    auto poType = poObj->GetDictionary()->Get("Type");
10✔
2318
                    if (poType && poType->GetType() == PDFObjectType_Name)
10✔
2319
                    {
2320
                        const auto &osType = poType->GetName();
10✔
2321
                        ExploreResourceProperty(pszKey, poObj, osType,
10✔
2322
                                                oMapNumGenToLayer,
2323
                                                oMapPropertyToLayer, 0);
2324
                    }
2325
                    else
2326
                    {
2327
                        CPLDebug("PDF",
×
2328
                                 "Resource.Properties[%s] has no Type member",
2329
                                 pszKey);
2330
                    }
2331
                }
2332
            }
2333
        }
2334
        else
2335
        {
2336
            // Code path taken for datasets mentioned at https://github.com/OSGeo/gdal/issues/9870
2337
            // generated by ArcGIS 12.9
2338
            const auto poXObject = poResourcesDict->Get("XObject");
6✔
2339
            if (poXObject && poXObject->GetType() == PDFObjectType_Dictionary)
6✔
2340
            {
2341
                for (const auto &oNameObjectPair :
3✔
2342
                     poXObject->GetDictionary()->GetValues())
10✔
2343
                {
2344
                    const auto poProperties =
2345
                        oNameObjectPair.second->LookupObject(
4✔
2346
                            "Resources.Properties");
2347
                    if (poProperties &&
5✔
2348
                        poProperties->GetType() == PDFObjectType_Dictionary)
1✔
2349
                    {
2350
                        BuildMapNumGenToLayer();
1✔
2351

2352
                        const auto &oMap =
2353
                            poProperties->GetDictionary()->GetValues();
1✔
2354
                        for (const auto &[osKey, poObj] : oMap)
138✔
2355
                        {
2356
                            const char *pszKey = osKey.c_str();
137✔
2357
                            if (poObj->GetType() == PDFObjectType_Dictionary)
137✔
2358
                            {
2359
                                GDALPDFObject *poType =
2360
                                    poObj->GetDictionary()->Get("Type");
137✔
2361
                                if (poType &&
274✔
2362
                                    poType->GetType() == PDFObjectType_Name)
137✔
2363
                                {
2364
                                    const auto &osType = poType->GetName();
137✔
2365
                                    ExploreResourceProperty(
137✔
2366
                                        pszKey, poObj, osType,
2367
                                        oMapNumGenToLayer, oMapPropertyToLayer,
2368
                                        0);
2369
                                }
2370
                            }
2371
                        }
2372

2373
                        break;
1✔
2374
                    }
2375
                }
2376
            }
2377
        }
2378
    }
2379

2380
    OGRPDFLayer *poSingleLayer = nullptr;
13✔
2381
    if (m_apoLayers.empty())
13✔
2382
    {
2383
        const char *pszReadNonStructured =
2384
            CPLGetConfigOption("OGR_PDF_READ_NON_STRUCTURED", nullptr);
5✔
2385
        if (pszReadNonStructured && CPLTestBool(pszReadNonStructured))
5✔
2386
        {
2387
            auto poLayer = std::make_unique<OGRPDFLayer>(this, "content",
2388
                                                         nullptr, wkbUnknown);
4✔
2389
            m_apoLayers.emplace_back(std::move(poLayer));
2✔
2390
            poSingleLayer = m_apoLayers.back().get();
2✔
2391
        }
2392
        else
2393
        {
2394
            if (!pszReadNonStructured)
3✔
2395
            {
2396
                CPLDebug(
3✔
2397
                    "PDF",
2398
                    "No structured content nor PDF layers detected, hence "
2399
                    "vector content detection is disabled. You may force "
2400
                    "vector content detection by setting the "
2401
                    "OGR_PDF_READ_NON_STRUCTURED configuration option to YES");
2402
            }
2403
            return;
3✔
2404
        }
2405
    }
2406

2407
    ExploreContentsNonStructuredInternal(poContents, poResources,
10✔
2408
                                         oMapPropertyToLayer, oMapNumGenToLayer,
2409
                                         poSingleLayer);
2410

2411
    /* Remove empty layers */
2412
    for (auto oIter = m_apoLayers.begin(); oIter != m_apoLayers.end();
209✔
2413
         /* do nothing */)
2414
    {
2415
        if ((*oIter)->GetFeatureCount(false) == 0)
199✔
2416
        {
2417
            oIter = m_apoLayers.erase(oIter);
124✔
2418
        }
2419
        else
2420
        {
2421
            ++oIter;
75✔
2422
        }
2423
    }
2424
}
2425

2426
#endif /* HAVE_PDF_READ_SUPPORT */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc