Coverage Report

Created: 2026-02-14 09:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/pdf/pdfdataset.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  PDF driver
4
 * Purpose:  GDALDataset driver for PDF dataset.
5
 * Author:   Even Rouault, <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 *
9
 * Support for open-source PDFium library
10
 *
11
 * Copyright (C) 2015 Klokan Technologies GmbH (http://www.klokantech.com/)
12
 * Author: Martin Mikita <martin.mikita@klokantech.com>, xmikit00 @ FIT VUT Brno
13
 *
14
 ******************************************************************************
15
 * Copyright (c) 2010-2014, Even Rouault <even dot rouault at spatialys.com>
16
 *
17
 * SPDX-License-Identifier: MIT
18
 ****************************************************************************/
19
20
#include "gdal_pdf.h"
21
22
#include "cpl_json_streaming_writer.h"
23
#include "cpl_vsi_virtual.h"
24
#include "cpl_spawn.h"
25
#include "cpl_string.h"
26
#include "gdal_frmts.h"
27
#include "gdalalgorithm.h"
28
#include "ogr_spatialref.h"
29
#include "ogr_geometry.h"
30
31
#ifdef HAVE_POPPLER
32
#include "cpl_multiproc.h"
33
#include "pdfio.h"
34
#endif  // HAVE_POPPLER
35
36
#include "pdfcreatecopy.h"
37
38
#include "pdfdrivercore.h"
39
40
#include <algorithm>
41
#include <array>
42
#include <cassert>
43
#include <cmath>
44
#include <limits>
45
#include <set>
46
47
#ifdef HAVE_PDFIUM
48
// To be able to use
49
// https://github.com/rouault/pdfium_build_gdal_3_5/releases/download/v1_pdfium_5106/install-win10-vs2019-x64-rev5106.zip
50
// with newer Visual Studio versions.
51
// Trick from https://github.com/conan-io/conan-center-index/issues/4826
52
#if _MSC_VER >= 1932  // Visual Studio 2022 version 17.2+
53
#pragma comment(                                                               \
54
    linker,                                                                    \
55
    "/alternatename:__imp___std_init_once_complete=__imp_InitOnceComplete")
56
#pragma comment(                                                               \
57
    linker,                                                                    \
58
    "/alternatename:__imp___std_init_once_begin_initialize=__imp_InitOnceBeginInitialize")
59
#endif
60
#endif
61
62
/* g++ -fPIC -g -Wall frmts/pdf/pdfdataset.cpp -shared -o gdal_PDF.so -Iport
63
 * -Igcore -Iogr -L. -lgdal -lpoppler -I/usr/include/poppler */
64
65
#ifdef HAVE_PDF_READ_SUPPORT
66
67
static double Get(GDALPDFObject *poObj, int nIndice = -1);
68
69
#ifdef HAVE_POPPLER
70
71
static CPLMutex *hGlobalParamsMutex = nullptr;
72
73
/************************************************************************/
74
/*                           GDALPDFOutputDev                           */
75
/************************************************************************/
76
77
class GDALPDFOutputDev final : public SplashOutputDev
78
{
79
  private:
80
    int bEnableVector;
81
    int bEnableText;
82
    int bEnableBitmap;
83
84
    void skipBytes(Stream *str, int width, int height, int nComps, int nBits)
85
0
    {
86
0
        int nVals = width * nComps;
87
0
        int nLineSize = (nVals * nBits + 7) >> 3;
88
0
        int nBytes = nLineSize * height;
89
0
        for (int i = 0; i < nBytes; i++)
90
0
        {
91
0
            if (str->getChar() == EOF)
92
0
                break;
93
0
        }
94
0
    }
95
96
  public:
97
    GDALPDFOutputDev(SplashColorMode colorModeA, int bitmapRowPadA,
98
                     [[maybe_unused]] bool reverseVideoA,
99
                     SplashColorPtr paperColorA)
100
7.32k
        : SplashOutputDev(colorModeA, bitmapRowPadA,
101
7.32k
#if POPPLER_MAJOR_VERSION < 26 ||                                              \
102
7.32k
    (POPPLER_MAJOR_VERSION == 26 && POPPLER_MINOR_VERSION < 2)
103
7.32k
                          reverseVideoA,
104
7.32k
#endif
105
7.32k
                          paperColorA),
106
7.32k
          bEnableVector(TRUE), bEnableText(TRUE), bEnableBitmap(TRUE)
107
7.32k
    {
108
7.32k
    }
109
110
    void SetEnableVector(int bFlag)
111
14.6k
    {
112
14.6k
        bEnableVector = bFlag;
113
14.6k
    }
114
115
    void SetEnableText(int bFlag)
116
7.32k
    {
117
7.32k
        bEnableText = bFlag;
118
7.32k
    }
119
120
    void SetEnableBitmap(int bFlag)
121
14.6k
    {
122
14.6k
        bEnableBitmap = bFlag;
123
14.6k
    }
124
125
    void startPage(int pageNum, GfxState *state, XRef *xrefIn) override;
126
127
    void stroke(GfxState *state) override
128
1.12M
    {
129
1.12M
        if (bEnableVector)
130
1.12M
            SplashOutputDev::stroke(state);
131
1.12M
    }
132
133
    void fill(GfxState *state) override
134
5.82M
    {
135
5.82M
        if (bEnableVector)
136
5.82M
            SplashOutputDev::fill(state);
137
5.82M
    }
138
139
    void eoFill(GfxState *state) override
140
4.03k
    {
141
4.03k
        if (bEnableVector)
142
4.03k
            SplashOutputDev::eoFill(state);
143
4.03k
    }
144
145
    virtual void drawChar(GfxState *state, double x, double y, double dx,
146
                          double dy, double originX, double originY,
147
                          CharCode code, int nBytes, const Unicode *u,
148
                          int uLen) override
149
1.55M
    {
150
1.55M
        if (bEnableText)
151
0
            SplashOutputDev::drawChar(state, x, y, dx, dy, originX, originY,
152
0
                                      code, nBytes, u, uLen);
153
1.55M
    }
154
155
    void beginTextObject(GfxState *state) override
156
150k
    {
157
150k
        if (bEnableText)
158
0
            SplashOutputDev::beginTextObject(state);
159
150k
    }
160
161
    void endTextObject(GfxState *state) override
162
155k
    {
163
155k
        if (bEnableText)
164
0
            SplashOutputDev::endTextObject(state);
165
155k
    }
166
167
    virtual void drawImageMask(GfxState *state, Object *ref, Stream *str,
168
                               int width, int height, bool invert,
169
                               bool interpolate, bool inlineImg) override
170
30.5k
    {
171
30.5k
        if (bEnableBitmap)
172
30.5k
            SplashOutputDev::drawImageMask(state, ref, str, width, height,
173
30.5k
                                           invert, interpolate, inlineImg);
174
0
        else
175
0
        {
176
0
            VSIPDFFileStream::resetNoCheckReturnValue(str);
177
0
            if (inlineImg)
178
0
            {
179
0
                skipBytes(str, width, height, 1, 1);
180
0
            }
181
0
            str->close();
182
0
        }
183
30.5k
    }
184
185
#if POPPLER_MAJOR_VERSION > 26 ||                                              \
186
    (POPPLER_MAJOR_VERSION == 26 && POPPLER_MINOR_VERSION >= 2)
187
    void setSoftMaskFromImageMask(GfxState *state, Object *ref, Stream *str,
188
                                  int width, int height, bool invert,
189
                                  bool inlineImg,
190
                                  std::array<double, 6> &baseMatrix) override
191
#else
192
    void setSoftMaskFromImageMask(GfxState *state, Object *ref, Stream *str,
193
                                  int width, int height, bool invert,
194
                                  bool inlineImg, double *baseMatrix) override
195
#endif
196
1.96M
    {
197
1.96M
        if (bEnableBitmap)
198
1.96M
            SplashOutputDev::setSoftMaskFromImageMask(
199
1.96M
                state, ref, str, width, height, invert, inlineImg, baseMatrix);
200
0
        else
201
0
            str->close();
202
1.96M
    }
203
204
#if POPPLER_MAJOR_VERSION > 26 ||                                              \
205
    (POPPLER_MAJOR_VERSION == 26 && POPPLER_MINOR_VERSION >= 2)
206
    void unsetSoftMaskFromImageMask(GfxState *state,
207
                                    std::array<double, 6> &baseMatrix) override
208
#else
209
    void unsetSoftMaskFromImageMask(GfxState *state,
210
                                    double *baseMatrix) override
211
#endif
212
1.96M
    {
213
1.96M
        if (bEnableBitmap)
214
1.96M
            SplashOutputDev::unsetSoftMaskFromImageMask(state, baseMatrix);
215
1.96M
    }
216
217
    virtual void drawImage(GfxState *state, Object *ref, Stream *str, int width,
218
                           int height, GfxImageColorMap *colorMap,
219
                           bool interpolate, const int *maskColors,
220
                           bool inlineImg) override
221
3.54k
    {
222
3.54k
        if (bEnableBitmap)
223
3.54k
            SplashOutputDev::drawImage(state, ref, str, width, height, colorMap,
224
3.54k
                                       interpolate, maskColors, inlineImg);
225
0
        else
226
0
        {
227
0
            VSIPDFFileStream::resetNoCheckReturnValue(str);
228
0
            if (inlineImg)
229
0
            {
230
0
                skipBytes(str, width, height, colorMap->getNumPixelComps(),
231
0
                          colorMap->getBits());
232
0
            }
233
0
            str->close();
234
0
        }
235
3.54k
    }
236
237
    virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str,
238
                                 int width, int height,
239
                                 GfxImageColorMap *colorMap, bool interpolate,
240
                                 Stream *maskStr, int maskWidth, int maskHeight,
241
                                 bool maskInvert, bool maskInterpolate) override
242
81
    {
243
81
        if (bEnableBitmap)
244
81
            SplashOutputDev::drawMaskedImage(
245
81
                state, ref, str, width, height, colorMap, interpolate, maskStr,
246
81
                maskWidth, maskHeight, maskInvert, maskInterpolate);
247
0
        else
248
0
            str->close();
249
81
    }
250
251
    virtual void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str,
252
                                     int width, int height,
253
                                     GfxImageColorMap *colorMap,
254
                                     bool interpolate, Stream *maskStr,
255
                                     int maskWidth, int maskHeight,
256
                                     GfxImageColorMap *maskColorMap,
257
                                     bool maskInterpolate) override
258
3.37k
    {
259
3.37k
        if (bEnableBitmap)
260
3.37k
        {
261
3.37k
            if (maskColorMap->getBits() <=
262
3.37k
                0) /* workaround poppler bug (robustness) */
263
0
            {
264
0
                str->close();
265
0
                return;
266
0
            }
267
3.37k
            SplashOutputDev::drawSoftMaskedImage(
268
3.37k
                state, ref, str, width, height, colorMap, interpolate, maskStr,
269
3.37k
                maskWidth, maskHeight, maskColorMap, maskInterpolate);
270
3.37k
        }
271
0
        else
272
0
            str->close();
273
3.37k
    }
274
};
275
276
void GDALPDFOutputDev::startPage(int pageNum, GfxState *state, XRef *xrefIn)
277
7.32k
{
278
7.32k
    SplashOutputDev::startPage(pageNum, state, xrefIn);
279
7.32k
    SplashBitmap *poBitmap = getBitmap();
280
7.32k
    memset(poBitmap->getDataPtr(), 255,
281
7.32k
           static_cast<size_t>(poBitmap->getRowSize()) * poBitmap->getHeight());
282
7.32k
}
283
284
#endif  // ~ HAVE_POPPLER
285
286
/************************************************************************/
287
/*                            Dump routines                             */
288
/************************************************************************/
289
290
class GDALPDFDumper
291
{
292
  private:
293
    FILE *f = nullptr;
294
    const int nDepthLimit;
295
    std::set<int> aoSetObjectExplored{};
296
    const bool bDumpParent;
297
298
    void DumpSimplified(GDALPDFObject *poObj);
299
300
    CPL_DISALLOW_COPY_ASSIGN(GDALPDFDumper)
301
302
  public:
303
    GDALPDFDumper(const char *pszFilename, const char *pszDumpFile,
304
                  int nDepthLimitIn = -1)
305
0
        : nDepthLimit(nDepthLimitIn),
306
0
          bDumpParent(CPLGetConfigOption("PDF_DUMP_PARENT", "FALSE"))
307
0
    {
308
0
        if (strcmp(pszDumpFile, "stderr") == 0)
309
0
            f = stderr;
310
0
        else if (EQUAL(pszDumpFile, "YES"))
311
0
            f = fopen(CPLSPrintf("dump_%s.txt", CPLGetFilename(pszFilename)),
312
0
                      "wt");
313
0
        else
314
0
            f = fopen(pszDumpFile, "wt");
315
0
        if (f == nullptr)
316
0
            f = stderr;
317
0
    }
318
319
    ~GDALPDFDumper()
320
0
    {
321
0
        if (f != stderr)
322
0
            fclose(f);
323
0
    }
324
325
    void Dump(GDALPDFObject *poObj, int nDepth = 0);
326
    void Dump(GDALPDFDictionary *poDict, int nDepth = 0);
327
    void Dump(GDALPDFArray *poArray, int nDepth = 0);
328
};
329
330
void GDALPDFDumper::Dump(GDALPDFArray *poArray, int nDepth)
331
0
{
332
0
    if (nDepthLimit >= 0 && nDepth > nDepthLimit)
333
0
        return;
334
335
0
    int nLength = poArray->GetLength();
336
0
    int i;
337
0
    CPLString osIndent;
338
0
    for (i = 0; i < nDepth; i++)
339
0
        osIndent += " ";
340
0
    for (i = 0; i < nLength; i++)
341
0
    {
342
0
        fprintf(f, "%sItem[%d]:", osIndent.c_str(), i);
343
0
        GDALPDFObject *poObj = nullptr;
344
0
        if ((poObj = poArray->Get(i)) != nullptr)
345
0
        {
346
0
            if (poObj->GetType() == PDFObjectType_String ||
347
0
                poObj->GetType() == PDFObjectType_Null ||
348
0
                poObj->GetType() == PDFObjectType_Bool ||
349
0
                poObj->GetType() == PDFObjectType_Int ||
350
0
                poObj->GetType() == PDFObjectType_Real ||
351
0
                poObj->GetType() == PDFObjectType_Name)
352
0
            {
353
0
                fprintf(f, " ");
354
0
                DumpSimplified(poObj);
355
0
                fprintf(f, "\n");
356
0
            }
357
0
            else
358
0
            {
359
0
                fprintf(f, "\n");
360
0
                Dump(poObj, nDepth + 1);
361
0
            }
362
0
        }
363
0
    }
364
0
}
365
366
void GDALPDFDumper::DumpSimplified(GDALPDFObject *poObj)
367
0
{
368
0
    switch (poObj->GetType())
369
0
    {
370
0
        case PDFObjectType_String:
371
0
            fprintf(f, "%s (string)", poObj->GetString().c_str());
372
0
            break;
373
374
0
        case PDFObjectType_Null:
375
0
            fprintf(f, "null");
376
0
            break;
377
378
0
        case PDFObjectType_Bool:
379
0
            fprintf(f, "%s (bool)", poObj->GetBool() ? "true" : "false");
380
0
            break;
381
382
0
        case PDFObjectType_Int:
383
0
            fprintf(f, "%d (int)", poObj->GetInt());
384
0
            break;
385
386
0
        case PDFObjectType_Real:
387
0
            fprintf(f, "%f (real)", poObj->GetReal());
388
0
            break;
389
390
0
        case PDFObjectType_Name:
391
0
            fprintf(f, "%s (name)", poObj->GetName().c_str());
392
0
            break;
393
394
0
        default:
395
0
            fprintf(f, "unknown !");
396
0
            break;
397
0
    }
398
0
}
399
400
void GDALPDFDumper::Dump(GDALPDFObject *poObj, int nDepth)
401
0
{
402
0
    if (nDepthLimit >= 0 && nDepth > nDepthLimit)
403
0
        return;
404
405
0
    int i;
406
0
    CPLString osIndent;
407
0
    for (i = 0; i < nDepth; i++)
408
0
        osIndent += " ";
409
0
    fprintf(f, "%sType = %s", osIndent.c_str(), poObj->GetTypeName());
410
0
    int nRefNum = poObj->GetRefNum().toInt();
411
0
    if (nRefNum != 0)
412
0
        fprintf(f, ", Num = %d, Gen = %d", nRefNum, poObj->GetRefGen());
413
0
    fprintf(f, "\n");
414
415
0
    if (nRefNum != 0)
416
0
    {
417
0
        if (aoSetObjectExplored.find(nRefNum) != aoSetObjectExplored.end())
418
0
            return;
419
0
        aoSetObjectExplored.insert(nRefNum);
420
0
    }
421
422
0
    switch (poObj->GetType())
423
0
    {
424
0
        case PDFObjectType_Array:
425
0
            Dump(poObj->GetArray(), nDepth + 1);
426
0
            break;
427
428
0
        case PDFObjectType_Dictionary:
429
0
            Dump(poObj->GetDictionary(), nDepth + 1);
430
0
            break;
431
432
0
        case PDFObjectType_String:
433
0
        case PDFObjectType_Null:
434
0
        case PDFObjectType_Bool:
435
0
        case PDFObjectType_Int:
436
0
        case PDFObjectType_Real:
437
0
        case PDFObjectType_Name:
438
0
            fprintf(f, "%s", osIndent.c_str());
439
0
            DumpSimplified(poObj);
440
0
            fprintf(f, "\n");
441
0
            break;
442
443
0
        default:
444
0
            fprintf(f, "%s", osIndent.c_str());
445
0
            fprintf(f, "unknown !\n");
446
0
            break;
447
0
    }
448
449
0
    GDALPDFStream *poStream = poObj->GetStream();
450
0
    if (poStream != nullptr)
451
0
    {
452
0
        fprintf(f,
453
0
                "%sHas stream (" CPL_FRMT_GIB
454
0
                " uncompressed bytes, " CPL_FRMT_GIB " raw bytes)\n",
455
0
                osIndent.c_str(), static_cast<GIntBig>(poStream->GetLength()),
456
0
                static_cast<GIntBig>(poStream->GetRawLength()));
457
0
    }
458
0
}
459
460
void GDALPDFDumper::Dump(GDALPDFDictionary *poDict, int nDepth)
461
0
{
462
0
    if (nDepthLimit >= 0 && nDepth > nDepthLimit)
463
0
        return;
464
465
0
    CPLString osIndent;
466
0
    for (int i = 0; i < nDepth; i++)
467
0
        osIndent += " ";
468
0
    int i = 0;
469
0
    const auto &oMap = poDict->GetValues();
470
0
    for (const auto &[osKey, poObj] : oMap)
471
0
    {
472
0
        fprintf(f, "%sItem[%d] : %s", osIndent.c_str(), i, osKey.c_str());
473
0
        ++i;
474
0
        if (osKey == "Parent" && !bDumpParent)
475
0
        {
476
0
            if (poObj->GetRefNum().toBool())
477
0
                fprintf(f, ", Num = %d, Gen = %d", poObj->GetRefNum().toInt(),
478
0
                        poObj->GetRefGen());
479
0
            fprintf(f, "\n");
480
0
            continue;
481
0
        }
482
0
        if (poObj != nullptr)
483
0
        {
484
0
            if (poObj->GetType() == PDFObjectType_String ||
485
0
                poObj->GetType() == PDFObjectType_Null ||
486
0
                poObj->GetType() == PDFObjectType_Bool ||
487
0
                poObj->GetType() == PDFObjectType_Int ||
488
0
                poObj->GetType() == PDFObjectType_Real ||
489
0
                poObj->GetType() == PDFObjectType_Name)
490
0
            {
491
0
                fprintf(f, " = ");
492
0
                DumpSimplified(poObj);
493
0
                fprintf(f, "\n");
494
0
            }
495
0
            else
496
0
            {
497
0
                fprintf(f, "\n");
498
0
                Dump(poObj, nDepth + 1);
499
0
            }
500
0
        }
501
0
    }
502
0
}
503
504
/************************************************************************/
505
/*                           PDFRasterBand()                            */
506
/************************************************************************/
507
508
PDFRasterBand::PDFRasterBand(PDFDataset *poDSIn, int nBandIn,
509
                             int nResolutionLevelIn)
510
98.0k
    : nResolutionLevel(nResolutionLevelIn)
511
98.0k
{
512
98.0k
    poDS = poDSIn;
513
98.0k
    nBand = nBandIn;
514
515
98.0k
    eDataType = GDT_UInt8;
516
98.0k
}
517
518
/************************************************************************/
519
/*                              SetSize()                               */
520
/************************************************************************/
521
522
void PDFRasterBand::SetSize(int nXSize, int nYSize)
523
98.0k
{
524
98.0k
    nRasterXSize = nXSize;
525
98.0k
    nRasterYSize = nYSize;
526
527
98.0k
    const auto poPDFDS = cpl::down_cast<const PDFDataset *>(poDS);
528
98.0k
    if (nResolutionLevel > 0)
529
0
    {
530
0
        nBlockXSize = 256;
531
0
        nBlockYSize = 256;
532
0
        poDS->SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
533
0
    }
534
98.0k
    else if (poPDFDS->m_nBlockXSize)
535
56.3k
    {
536
56.3k
        nBlockXSize = poPDFDS->m_nBlockXSize;
537
56.3k
        nBlockYSize = poPDFDS->m_nBlockYSize;
538
56.3k
        poDS->SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
539
56.3k
    }
540
41.6k
    else if (nRasterXSize < 64 * 1024 * 1024 / nRasterYSize)
541
41.5k
    {
542
41.5k
        nBlockXSize = nRasterXSize;
543
41.5k
        nBlockYSize = 1;
544
41.5k
    }
545
81
    else
546
81
    {
547
81
        nBlockXSize = std::min(1024, nRasterXSize);
548
81
        nBlockYSize = std::min(1024, nRasterYSize);
549
81
        poDS->SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
550
81
    }
551
98.0k
}
552
553
/************************************************************************/
554
/*                           InitOverviews()                            */
555
/************************************************************************/
556
557
void PDFDataset::InitOverviews()
558
26.6k
{
559
#ifdef HAVE_PDFIUM
560
    // Only if used pdfium, make "arbitrary overviews"
561
    // Blocks are 256x256
562
    if (m_bUseLib.test(PDFLIB_PDFIUM) && m_apoOvrDS.empty() &&
563
        m_apoOvrDSBackup.empty())
564
    {
565
        int nXSize = nRasterXSize;
566
        int nYSize = nRasterYSize;
567
        constexpr int minSize = 256;
568
        int nDiscard = 1;
569
        while (nXSize > minSize || nYSize > minSize)
570
        {
571
            nXSize = (nXSize + 1) / 2;
572
            nYSize = (nYSize + 1) / 2;
573
574
            auto poOvrDS = std::make_unique<PDFDataset>(this, nXSize, nYSize);
575
576
            for (int i = 0; i < nBands; i++)
577
            {
578
                auto poBand = std::make_unique<PDFRasterBand>(poOvrDS.get(),
579
                                                              i + 1, nDiscard);
580
                poBand->SetSize(nXSize, nYSize);
581
                poOvrDS->SetBand(i + 1, std::move(poBand));
582
            }
583
584
            m_apoOvrDS.emplace_back(std::move(poOvrDS));
585
            ++nDiscard;
586
        }
587
    }
588
#endif
589
26.6k
#if defined(HAVE_POPPLER) || defined(HAVE_PODOFO)
590
26.6k
    if (!m_bUseLib.test(PDFLIB_PDFIUM) && m_apoOvrDS.empty() &&
591
7.36k
        m_apoOvrDSBackup.empty() && m_osUserPwd != "ASK_INTERACTIVE")
592
7.36k
    {
593
7.36k
        int nXSize = nRasterXSize;
594
7.36k
        int nYSize = nRasterYSize;
595
7.36k
        constexpr int minSize = 256;
596
7.36k
        double dfDPI = m_dfDPI;
597
26.1k
        while (nXSize > minSize || nYSize > minSize)
598
18.8k
        {
599
18.8k
            nXSize = (nXSize + 1) / 2;
600
18.8k
            nYSize = (nYSize + 1) / 2;
601
18.8k
            dfDPI /= 2;
602
603
18.8k
            GDALOpenInfo oOpenInfo(GetDescription(), GA_ReadOnly);
604
18.8k
            CPLStringList aosOpenOptions(CSLDuplicate(papszOpenOptions));
605
18.8k
            aosOpenOptions.SetNameValue("DPI", CPLSPrintf("%g", dfDPI));
606
18.8k
            aosOpenOptions.SetNameValue("BANDS", CPLSPrintf("%d", nBands));
607
18.8k
            aosOpenOptions.SetNameValue("@OPEN_FOR_OVERVIEW", "YES");
608
18.8k
            if (!m_osUserPwd.empty())
609
0
                aosOpenOptions.SetNameValue("USER_PWD", m_osUserPwd.c_str());
610
18.8k
            oOpenInfo.papszOpenOptions = aosOpenOptions.List();
611
18.8k
            auto poOvrDS = std::unique_ptr<PDFDataset>(Open(&oOpenInfo));
612
18.8k
            if (!poOvrDS || poOvrDS->nBands != nBands)
613
17
                break;
614
18.7k
            poOvrDS->m_bIsOvrDS = true;
615
18.7k
            m_apoOvrDS.emplace_back(std::move(poOvrDS));
616
18.7k
        }
617
7.36k
    }
618
26.6k
#endif
619
26.6k
}
620
621
/************************************************************************/
622
/*                       GetColorInterpretation()                       */
623
/************************************************************************/
624
625
GDALColorInterp PDFRasterBand::GetColorInterpretation()
626
0
{
627
0
    PDFDataset *poGDS = cpl::down_cast<PDFDataset *>(poDS);
628
0
    if (poGDS->nBands == 1)
629
0
        return GCI_GrayIndex;
630
0
    else
631
0
        return static_cast<GDALColorInterp>(GCI_RedBand + (nBand - 1));
632
0
}
633
634
/************************************************************************/
635
/*                          GetOverviewCount()                          */
636
/************************************************************************/
637
638
int PDFRasterBand::GetOverviewCount()
639
27.7k
{
640
27.7k
    PDFDataset *poGDS = cpl::down_cast<PDFDataset *>(poDS);
641
27.7k
    if (poGDS->m_bIsOvrDS)
642
0
        return 0;
643
27.7k
    if (GDALPamRasterBand::GetOverviewCount() > 0)
644
1.12k
        return GDALPamRasterBand::GetOverviewCount();
645
26.6k
    else
646
26.6k
    {
647
26.6k
        poGDS->InitOverviews();
648
26.6k
        return static_cast<int>(poGDS->m_apoOvrDS.size());
649
26.6k
    }
650
27.7k
}
651
652
/************************************************************************/
653
/*                            GetOverview()                             */
654
/************************************************************************/
655
656
GDALRasterBand *PDFRasterBand::GetOverview(int iOverviewIndex)
657
19.4k
{
658
19.4k
    if (GDALPamRasterBand::GetOverviewCount() > 0)
659
562
        return GDALPamRasterBand::GetOverview(iOverviewIndex);
660
661
18.9k
    else if (iOverviewIndex < 0 || iOverviewIndex >= GetOverviewCount())
662
0
        return nullptr;
663
18.9k
    else
664
18.9k
    {
665
18.9k
        PDFDataset *poGDS = cpl::down_cast<PDFDataset *>(poDS);
666
18.9k
        return poGDS->m_apoOvrDS[iOverviewIndex]->GetRasterBand(nBand);
667
18.9k
    }
668
19.4k
}
669
670
/************************************************************************/
671
/*                           ~PDFRasterBand()                           */
672
/************************************************************************/
673
674
PDFRasterBand::~PDFRasterBand()
675
98.0k
{
676
98.0k
}
677
678
/************************************************************************/
679
/*                         IReadBlockFromTile()                         */
680
/************************************************************************/
681
682
CPLErr PDFRasterBand::IReadBlockFromTile(int nBlockXOff, int nBlockYOff,
683
                                         void *pImage)
684
685
0
{
686
0
    PDFDataset *poGDS = cpl::down_cast<PDFDataset *>(poDS);
687
688
0
    const int nXOff = nBlockXOff * nBlockXSize;
689
0
    const int nReqXSize = std::min(nBlockXSize, nRasterXSize - nXOff);
690
0
    const int nYOff = nBlockYOff * nBlockYSize;
691
0
    const int nReqYSize = std::min(nBlockYSize, nRasterYSize - nYOff);
692
693
0
    const int nXBlocks = DIV_ROUND_UP(nRasterXSize, nBlockXSize);
694
0
    int iTile = poGDS->m_aiTiles[nBlockYOff * nXBlocks + nBlockXOff];
695
0
    if (iTile < 0)
696
0
    {
697
0
        memset(pImage, 0, static_cast<size_t>(nBlockXSize) * nBlockYSize);
698
0
        return CE_None;
699
0
    }
700
701
0
    GDALPDFTileDesc &sTile = poGDS->m_asTiles[iTile];
702
0
    GDALPDFObject *poImage = sTile.poImage;
703
704
0
    if (nBand == 4)
705
0
    {
706
0
        GDALPDFDictionary *poImageDict = poImage->GetDictionary();
707
0
        GDALPDFObject *poSMask = poImageDict->Get("SMask");
708
0
        if (poSMask != nullptr &&
709
0
            poSMask->GetType() == PDFObjectType_Dictionary)
710
0
        {
711
0
            GDALPDFDictionary *poSMaskDict = poSMask->GetDictionary();
712
0
            GDALPDFObject *poWidth = poSMaskDict->Get("Width");
713
0
            GDALPDFObject *poHeight = poSMaskDict->Get("Height");
714
0
            GDALPDFObject *poColorSpace = poSMaskDict->Get("ColorSpace");
715
0
            GDALPDFObject *poBitsPerComponent =
716
0
                poSMaskDict->Get("BitsPerComponent");
717
0
            double dfBits = 0;
718
0
            if (poBitsPerComponent)
719
0
                dfBits = Get(poBitsPerComponent);
720
0
            if (poWidth && Get(poWidth) == nReqXSize && poHeight &&
721
0
                Get(poHeight) == nReqYSize && poColorSpace &&
722
0
                poColorSpace->GetType() == PDFObjectType_Name &&
723
0
                poColorSpace->GetName() == "DeviceGray" &&
724
0
                (dfBits == 1 || dfBits == 8))
725
0
            {
726
0
                GDALPDFStream *poStream = poSMask->GetStream();
727
0
                GByte *pabyStream = nullptr;
728
729
0
                if (poStream == nullptr)
730
0
                    return CE_Failure;
731
732
0
                pabyStream = reinterpret_cast<GByte *>(poStream->GetBytes());
733
0
                if (pabyStream == nullptr)
734
0
                    return CE_Failure;
735
736
0
                const int nReqXSize1 = (nReqXSize + 7) / 8;
737
0
                if ((dfBits == 8 &&
738
0
                     static_cast<size_t>(poStream->GetLength()) !=
739
0
                         static_cast<size_t>(nReqXSize) * nReqYSize) ||
740
0
                    (dfBits == 1 &&
741
0
                     static_cast<size_t>(poStream->GetLength()) !=
742
0
                         static_cast<size_t>(nReqXSize1) * nReqYSize))
743
0
                {
744
0
                    VSIFree(pabyStream);
745
0
                    return CE_Failure;
746
0
                }
747
748
0
                GByte *pabyData = static_cast<GByte *>(pImage);
749
0
                if (nReqXSize != nBlockXSize || nReqYSize != nBlockYSize)
750
0
                {
751
0
                    memset(pabyData, 0,
752
0
                           static_cast<size_t>(nBlockXSize) * nBlockYSize);
753
0
                }
754
755
0
                if (dfBits == 8)
756
0
                {
757
0
                    for (int j = 0; j < nReqYSize; j++)
758
0
                    {
759
0
                        for (int i = 0; i < nReqXSize; i++)
760
0
                        {
761
0
                            pabyData[j * nBlockXSize + i] =
762
0
                                pabyStream[j * nReqXSize + i];
763
0
                        }
764
0
                    }
765
0
                }
766
0
                else
767
0
                {
768
0
                    for (int j = 0; j < nReqYSize; j++)
769
0
                    {
770
0
                        for (int i = 0; i < nReqXSize; i++)
771
0
                        {
772
0
                            if (pabyStream[j * nReqXSize1 + i / 8] &
773
0
                                (1 << (7 - (i % 8))))
774
0
                                pabyData[j * nBlockXSize + i] = 255;
775
0
                            else
776
0
                                pabyData[j * nBlockXSize + i] = 0;
777
0
                        }
778
0
                    }
779
0
                }
780
781
0
                VSIFree(pabyStream);
782
0
                return CE_None;
783
0
            }
784
0
        }
785
786
0
        memset(pImage, 255, static_cast<size_t>(nBlockXSize) * nBlockYSize);
787
0
        return CE_None;
788
0
    }
789
790
0
    if (poGDS->m_nLastBlockXOff == nBlockXOff &&
791
0
        poGDS->m_nLastBlockYOff == nBlockYOff &&
792
0
        poGDS->m_pabyCachedData != nullptr)
793
0
    {
794
#ifdef DEBUG
795
        CPLDebug("PDF", "Using cached block (%d, %d)", nBlockXOff, nBlockYOff);
796
#endif
797
        // do nothing
798
0
    }
799
0
    else
800
0
    {
801
0
        if (!poGDS->m_bTried)
802
0
        {
803
0
            poGDS->m_bTried = true;
804
0
            poGDS->m_pabyCachedData =
805
0
                static_cast<GByte *>(VSIMalloc3(3, nBlockXSize, nBlockYSize));
806
0
        }
807
0
        if (poGDS->m_pabyCachedData == nullptr)
808
0
            return CE_Failure;
809
810
0
        GDALPDFStream *poStream = poImage->GetStream();
811
0
        GByte *pabyStream = nullptr;
812
813
0
        if (poStream == nullptr)
814
0
            return CE_Failure;
815
816
0
        pabyStream = reinterpret_cast<GByte *>(poStream->GetBytes());
817
0
        if (pabyStream == nullptr)
818
0
            return CE_Failure;
819
820
0
        if (static_cast<size_t>(poStream->GetLength()) !=
821
0
            static_cast<size_t>(sTile.nBands) * nReqXSize * nReqYSize)
822
0
        {
823
0
            VSIFree(pabyStream);
824
0
            return CE_Failure;
825
0
        }
826
827
0
        memcpy(poGDS->m_pabyCachedData, pabyStream,
828
0
               static_cast<size_t>(poStream->GetLength()));
829
0
        VSIFree(pabyStream);
830
0
        poGDS->m_nLastBlockXOff = nBlockXOff;
831
0
        poGDS->m_nLastBlockYOff = nBlockYOff;
832
0
    }
833
834
0
    GByte *pabyData = static_cast<GByte *>(pImage);
835
0
    if (nBand != 4 && (nReqXSize != nBlockXSize || nReqYSize != nBlockYSize))
836
0
    {
837
0
        memset(pabyData, 0, static_cast<size_t>(nBlockXSize) * nBlockYSize);
838
0
    }
839
840
0
    if (poGDS->nBands >= 3 && sTile.nBands == 3)
841
0
    {
842
0
        for (int j = 0; j < nReqYSize; j++)
843
0
        {
844
0
            for (int i = 0; i < nReqXSize; i++)
845
0
            {
846
0
                pabyData[j * nBlockXSize + i] =
847
0
                    poGDS
848
0
                        ->m_pabyCachedData[3 * (j * nReqXSize + i) + nBand - 1];
849
0
            }
850
0
        }
851
0
    }
852
0
    else if (sTile.nBands == 1)
853
0
    {
854
0
        for (int j = 0; j < nReqYSize; j++)
855
0
        {
856
0
            for (int i = 0; i < nReqXSize; i++)
857
0
            {
858
0
                pabyData[j * nBlockXSize + i] =
859
0
                    poGDS->m_pabyCachedData[j * nReqXSize + i];
860
0
            }
861
0
        }
862
0
    }
863
864
0
    return CE_None;
865
0
}
866
867
/************************************************************************/
868
/*                   GetSuggestedBlockAccessPattern()                   */
869
/************************************************************************/
870
871
GDALSuggestedBlockAccessPattern
872
PDFRasterBand::GetSuggestedBlockAccessPattern() const
873
0
{
874
0
    PDFDataset *poGDS = cpl::down_cast<PDFDataset *>(poDS);
875
0
    if (!poGDS->m_aiTiles.empty())
876
0
        return GSBAP_RANDOM;
877
0
    return GSBAP_LARGEST_CHUNK_POSSIBLE;
878
0
}
879
880
/************************************************************************/
881
/*                             IReadBlock()                             */
882
/************************************************************************/
883
884
CPLErr PDFRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage)
885
886
9.39M
{
887
9.39M
    PDFDataset *poGDS = cpl::down_cast<PDFDataset *>(poDS);
888
889
9.39M
    if (!poGDS->m_aiTiles.empty())
890
0
    {
891
0
        if (IReadBlockFromTile(nBlockXOff, nBlockYOff, pImage) == CE_None)
892
0
        {
893
0
            return CE_None;
894
0
        }
895
0
        else
896
0
        {
897
0
            poGDS->m_aiTiles.resize(0);
898
0
            poGDS->m_bTried = false;
899
0
            CPLFree(poGDS->m_pabyCachedData);
900
0
            poGDS->m_pabyCachedData = nullptr;
901
0
            poGDS->m_nLastBlockXOff = -1;
902
0
            poGDS->m_nLastBlockYOff = -1;
903
0
        }
904
0
    }
905
906
9.39M
    const int nXOff = nBlockXOff * nBlockXSize;
907
9.39M
    const int nReqXSize = std::min(nBlockXSize, nRasterXSize - nXOff);
908
9.39M
    const int nReqYSize =
909
9.39M
        nBlockYSize == 1
910
9.39M
            ? nRasterYSize
911
9.39M
            : std::min(nBlockYSize, nRasterYSize - nBlockYOff * nBlockYSize);
912
913
9.39M
    if (!poGDS->m_bTried)
914
7.32k
    {
915
7.32k
        poGDS->m_bTried = true;
916
7.32k
        if (nBlockYSize == 1)
917
7.31k
            poGDS->m_pabyCachedData = static_cast<GByte *>(VSIMalloc3(
918
7.31k
                std::max(3, poGDS->nBands), nRasterXSize, nRasterYSize));
919
15
        else
920
15
            poGDS->m_pabyCachedData = static_cast<GByte *>(VSIMalloc3(
921
15
                std::max(3, poGDS->nBands), nBlockXSize, nBlockYSize));
922
7.32k
    }
923
9.39M
    if (poGDS->m_pabyCachedData == nullptr)
924
0
        return CE_Failure;
925
926
9.39M
    if (poGDS->m_nLastBlockXOff == nBlockXOff &&
927
9.38M
        (nBlockYSize == 1 || poGDS->m_nLastBlockYOff == nBlockYOff) &&
928
9.38M
        poGDS->m_pabyCachedData != nullptr)
929
9.38M
    {
930
        /*CPLDebug("PDF", "Using cached block (%d, %d)",
931
                 nBlockXOff, nBlockYOff);*/
932
        // do nothing
933
9.38M
    }
934
7.32k
    else
935
7.32k
    {
936
#ifdef HAVE_PODOFO
937
        if (poGDS->m_bUseLib.test(PDFLIB_PODOFO) && nBand == 4)
938
        {
939
            memset(pImage, 255, nBlockXSize * nBlockYSize);
940
            return CE_None;
941
        }
942
#endif
943
944
7.32k
        const int nReqXOff = nBlockXOff * nBlockXSize;
945
7.32k
        const int nReqYOff = (nBlockYSize == 1) ? 0 : nBlockYOff * nBlockYSize;
946
7.32k
        const GSpacing nPixelSpace = 1;
947
7.32k
        const GSpacing nLineSpace = nBlockXSize;
948
7.32k
        const GSpacing nBandSpace =
949
7.32k
            static_cast<GSpacing>(nBlockXSize) *
950
7.32k
            ((nBlockYSize == 1) ? nRasterYSize : nBlockYSize);
951
952
7.32k
        CPLErr eErr = poGDS->ReadPixels(nReqXOff, nReqYOff, nReqXSize,
953
7.32k
                                        nReqYSize, nPixelSpace, nLineSpace,
954
7.32k
                                        nBandSpace, poGDS->m_pabyCachedData);
955
956
7.32k
        if (eErr == CE_None)
957
7.32k
        {
958
7.32k
            poGDS->m_nLastBlockXOff = nBlockXOff;
959
7.32k
            poGDS->m_nLastBlockYOff = nBlockYOff;
960
7.32k
        }
961
0
        else
962
0
        {
963
0
            CPLFree(poGDS->m_pabyCachedData);
964
0
            poGDS->m_pabyCachedData = nullptr;
965
0
        }
966
7.32k
    }
967
9.39M
    if (poGDS->m_pabyCachedData == nullptr)
968
0
        return CE_Failure;
969
970
9.39M
    if (nBlockYSize == 1)
971
9.39M
        memcpy(pImage,
972
9.39M
               poGDS->m_pabyCachedData +
973
9.39M
                   (nBand - 1) * nBlockXSize * nRasterYSize +
974
9.39M
                   nBlockYOff * nBlockXSize,
975
9.39M
               nBlockXSize);
976
15
    else
977
15
    {
978
15
        memcpy(pImage,
979
15
               poGDS->m_pabyCachedData +
980
15
                   static_cast<size_t>(nBand - 1) * nBlockXSize * nBlockYSize,
981
15
               static_cast<size_t>(nBlockXSize) * nBlockYSize);
982
983
15
        if (poGDS->m_bCacheBlocksForOtherBands && nBand == 1)
984
15
        {
985
45
            for (int iBand = 2; iBand <= poGDS->nBands; ++iBand)
986
30
            {
987
30
                auto poOtherBand = cpl::down_cast<PDFRasterBand *>(
988
30
                    poGDS->papoBands[iBand - 1]);
989
30
                GDALRasterBlock *poBlock =
990
30
                    poOtherBand->TryGetLockedBlockRef(nBlockXOff, nBlockYOff);
991
30
                if (poBlock)
992
0
                {
993
0
                    poBlock->DropLock();
994
0
                }
995
30
                else
996
30
                {
997
30
                    poBlock = poOtherBand->GetLockedBlockRef(nBlockXOff,
998
30
                                                             nBlockYOff, TRUE);
999
30
                    if (poBlock)
1000
30
                    {
1001
30
                        memcpy(poBlock->GetDataRef(),
1002
30
                               poGDS->m_pabyCachedData +
1003
30
                                   static_cast<size_t>(iBand - 1) *
1004
30
                                       nBlockXSize * nBlockYSize,
1005
30
                               static_cast<size_t>(nBlockXSize) * nBlockYSize);
1006
30
                        poBlock->DropLock();
1007
30
                    }
1008
30
                }
1009
30
            }
1010
15
        }
1011
15
    }
1012
1013
9.39M
    return CE_None;
1014
9.39M
}
1015
1016
/************************************************************************/
1017
/*                PDFEnterPasswordFromConsoleIfNeeded()                 */
1018
/************************************************************************/
1019
1020
static const char *PDFEnterPasswordFromConsoleIfNeeded(const char *pszUserPwd)
1021
0
{
1022
0
    if (EQUAL(pszUserPwd, "ASK_INTERACTIVE"))
1023
0
    {
1024
0
        static char szPassword[81];
1025
0
        printf("Enter password (will be echo'ed in the console): "); /*ok*/
1026
0
        if (nullptr == fgets(szPassword, sizeof(szPassword), stdin))
1027
0
        {
1028
0
            fprintf(stderr, "WARNING: Error getting password.\n"); /*ok*/
1029
0
        }
1030
0
        szPassword[sizeof(szPassword) - 1] = 0;
1031
0
        char *sz10 = strchr(szPassword, '\n');
1032
0
        if (sz10)
1033
0
            *sz10 = 0;
1034
0
        return szPassword;
1035
0
    }
1036
0
    return pszUserPwd;
1037
0
}
1038
1039
#ifdef HAVE_PDFIUM
1040
1041
/************************************************************************/
1042
/*                         Pdfium Load/Unload                           */
1043
/* Copyright (C) 2015 Klokan Technologies GmbH (http://www.klokantech.com/) */
1044
/* Author: Martin Mikita <martin.mikita@klokantech.com>                 */
1045
/************************************************************************/
1046
1047
// Flag for calling PDFium Init and Destroy methods
1048
bool PDFDataset::g_bPdfiumInit = false;
1049
1050
// Pdfium global read mutex - Pdfium is not multi-thread
1051
static CPLMutex *g_oPdfiumReadMutex = nullptr;
1052
static CPLMutex *g_oPdfiumLoadDocMutex = nullptr;
1053
1054
// Comparison of char* for std::map
1055
struct cmp_str
1056
{
1057
    bool operator()(char const *a, char const *b) const
1058
    {
1059
        return strcmp(a, b) < 0;
1060
    }
1061
};
1062
1063
static int GDALPdfiumGetBlock(void *param, unsigned long position,
1064
                              unsigned char *pBuf, unsigned long size)
1065
{
1066
    VSILFILE *fp = static_cast<VSILFILE *>(param);
1067
    VSIFSeekL(fp, static_cast<vsi_l_offset>(position), SEEK_SET);
1068
    return VSIFReadL(pBuf, size, 1, fp) == 1;
1069
}
1070
1071
// List of all PDF datasets
1072
typedef std::map<const char *, TPdfiumDocumentStruct *, cmp_str>
1073
    TMapPdfiumDatasets;
1074
static TMapPdfiumDatasets g_mPdfiumDatasets;
1075
1076
/**
1077
 * Loading PDFIUM page
1078
 * - multithreading requires "mutex"
1079
 * - one page can require too much RAM
1080
 * - we will have one document per filename and one object per page
1081
 */
1082
1083
static int LoadPdfiumDocumentPage(const char *pszFilename,
1084
                                  const char *pszUserPwd, int pageNum,
1085
                                  TPdfiumDocumentStruct **doc,
1086
                                  TPdfiumPageStruct **page, int *pnPageCount)
1087
{
1088
    // Prepare nullptr for error returning
1089
    if (doc)
1090
        *doc = nullptr;
1091
    if (page)
1092
        *page = nullptr;
1093
    if (pnPageCount)
1094
        *pnPageCount = 0;
1095
1096
    // Loading document and page must be only in one thread!
1097
    CPLCreateOrAcquireMutex(&g_oPdfiumLoadDocMutex, PDFIUM_MUTEX_TIMEOUT);
1098
1099
    // Library can be destroyed if every PDF dataset was closed!
1100
    if (!PDFDataset::g_bPdfiumInit)
1101
    {
1102
        FPDF_InitLibrary();
1103
        PDFDataset::g_bPdfiumInit = TRUE;
1104
    }
1105
1106
    TMapPdfiumDatasets::iterator it;
1107
    it = g_mPdfiumDatasets.find(pszFilename);
1108
    TPdfiumDocumentStruct *poDoc = nullptr;
1109
    // Load new document if missing
1110
    if (it == g_mPdfiumDatasets.end())
1111
    {
1112
        // Try without password (if PDF not requires password it can fail)
1113
1114
        VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
1115
        if (fp == nullptr)
1116
        {
1117
            CPLReleaseMutex(g_oPdfiumLoadDocMutex);
1118
            return FALSE;
1119
        }
1120
        VSIFSeekL(fp, 0, SEEK_END);
1121
        const auto nFileLen64 = VSIFTellL(fp);
1122
        if constexpr (LONG_MAX < std::numeric_limits<vsi_l_offset>::max())
1123
        {
1124
            if (nFileLen64 > LONG_MAX)
1125
            {
1126
                VSIFCloseL(fp);
1127
                CPLReleaseMutex(g_oPdfiumLoadDocMutex);
1128
                return FALSE;
1129
            }
1130
        }
1131
1132
        FPDF_FILEACCESS *psFileAccess = new FPDF_FILEACCESS;
1133
        psFileAccess->m_Param = fp;
1134
        psFileAccess->m_FileLen = static_cast<unsigned long>(nFileLen64);
1135
        psFileAccess->m_GetBlock = GDALPdfiumGetBlock;
1136
        CPDF_Document *docPdfium = CPDFDocumentFromFPDFDocument(
1137
            FPDF_LoadCustomDocument(psFileAccess, nullptr));
1138
        if (docPdfium == nullptr)
1139
        {
1140
            unsigned long err = FPDF_GetLastError();
1141
            if (err == FPDF_ERR_PASSWORD)
1142
            {
1143
                if (pszUserPwd)
1144
                {
1145
                    pszUserPwd =
1146
                        PDFEnterPasswordFromConsoleIfNeeded(pszUserPwd);
1147
                    docPdfium = CPDFDocumentFromFPDFDocument(
1148
                        FPDF_LoadCustomDocument(psFileAccess, pszUserPwd));
1149
                    if (docPdfium == nullptr)
1150
                        err = FPDF_GetLastError();
1151
                    else
1152
                        err = FPDF_ERR_SUCCESS;
1153
                }
1154
                else
1155
                {
1156
                    CPLError(CE_Failure, CPLE_AppDefined,
1157
                             "A password is needed. You can specify it through "
1158
                             "the PDF_USER_PWD "
1159
                             "configuration option / USER_PWD open option "
1160
                             "(that can be set to ASK_INTERACTIVE)");
1161
1162
                    VSIFCloseL(fp);
1163
                    delete psFileAccess;
1164
                    CPLReleaseMutex(g_oPdfiumLoadDocMutex);
1165
                    return FALSE;
1166
                }
1167
            }  // First Error Password [null password given]
1168
            if (err != FPDF_ERR_SUCCESS)
1169
            {
1170
                if (err == FPDF_ERR_PASSWORD)
1171
                    CPLError(CE_Failure, CPLE_AppDefined,
1172
                             "PDFium Invalid password.");
1173
                else if (err == FPDF_ERR_SECURITY)
1174
                    CPLError(CE_Failure, CPLE_AppDefined,
1175
                             "PDFium Unsupported security scheme.");
1176
                else if (err == FPDF_ERR_FORMAT)
1177
                    CPLError(CE_Failure, CPLE_AppDefined,
1178
                             "PDFium File not in PDF format or corrupted.");
1179
                else if (err == FPDF_ERR_FILE)
1180
                    CPLError(CE_Failure, CPLE_AppDefined,
1181
                             "PDFium File not found or could not be opened.");
1182
                else
1183
                    CPLError(CE_Failure, CPLE_AppDefined,
1184
                             "PDFium Unknown PDF error or invalid PDF.");
1185
1186
                VSIFCloseL(fp);
1187
                delete psFileAccess;
1188
                CPLReleaseMutex(g_oPdfiumLoadDocMutex);
1189
                return FALSE;
1190
            }
1191
        }  // ~ wrong PDF or password required
1192
1193
        // Create new poDoc
1194
        poDoc = new TPdfiumDocumentStruct;
1195
        if (!poDoc)
1196
        {
1197
            CPLError(CE_Failure, CPLE_AppDefined,
1198
                     "Not enough memory for Pdfium Document object");
1199
1200
            VSIFCloseL(fp);
1201
            delete psFileAccess;
1202
            CPLReleaseMutex(g_oPdfiumLoadDocMutex);
1203
            return FALSE;
1204
        }
1205
        poDoc->filename = CPLStrdup(pszFilename);
1206
        poDoc->doc = docPdfium;
1207
        poDoc->psFileAccess = psFileAccess;
1208
1209
        g_mPdfiumDatasets[poDoc->filename] = poDoc;
1210
    }
1211
    // Document already loaded
1212
    else
1213
    {
1214
        poDoc = it->second;
1215
    }
1216
1217
    // Check page num in document
1218
    int nPages = poDoc->doc->GetPageCount();
1219
    if (pageNum < 1 || pageNum > nPages)
1220
    {
1221
        CPLError(CE_Failure, CPLE_AppDefined,
1222
                 "PDFium Invalid page number (%d/%d) for document %s", pageNum,
1223
                 nPages, pszFilename);
1224
1225
        CPLReleaseMutex(g_oPdfiumLoadDocMutex);
1226
        return FALSE;
1227
    }
1228
1229
    /* Sanity check to validate page count */
1230
    if (pageNum != nPages)
1231
    {
1232
        if (poDoc->doc->GetPageDictionary(nPages - 1) == nullptr)
1233
        {
1234
            CPLError(CE_Failure, CPLE_AppDefined,
1235
                     "Invalid PDF : invalid page count");
1236
            CPLReleaseMutex(g_oPdfiumLoadDocMutex);
1237
            return FALSE;
1238
        }
1239
    }
1240
1241
    TMapPdfiumPages::iterator itPage;
1242
    itPage = poDoc->pages.find(pageNum);
1243
    TPdfiumPageStruct *poPage = nullptr;
1244
    // Page not loaded
1245
    if (itPage == poDoc->pages.end())
1246
    {
1247
        auto pDict = poDoc->doc->GetMutablePageDictionary(pageNum - 1);
1248
        if (pDict == nullptr)
1249
        {
1250
            CPLError(CE_Failure, CPLE_AppDefined,
1251
                     "Invalid PDFium : invalid page");
1252
1253
            CPLReleaseMutex(g_oPdfiumLoadDocMutex);
1254
            return FALSE;
1255
        }
1256
        auto pPage = pdfium::MakeRetain<CPDF_Page>(poDoc->doc, pDict);
1257
1258
        poPage = new TPdfiumPageStruct;
1259
        if (!poPage)
1260
        {
1261
            CPLError(CE_Failure, CPLE_AppDefined,
1262
                     "Not enough memory for Pdfium Page object");
1263
1264
            CPLReleaseMutex(g_oPdfiumLoadDocMutex);
1265
            return FALSE;
1266
        }
1267
        poPage->pageNum = pageNum;
1268
        poPage->page = pPage.Leak();
1269
        poPage->readMutex = nullptr;
1270
        poPage->sharedNum = 0;
1271
1272
        poDoc->pages[pageNum] = poPage;
1273
    }
1274
    // Page already loaded
1275
    else
1276
    {
1277
        poPage = itPage->second;
1278
    }
1279
1280
    // Increase number of used
1281
    ++poPage->sharedNum;
1282
1283
    if (doc)
1284
        *doc = poDoc;
1285
    if (page)
1286
        *page = poPage;
1287
    if (pnPageCount)
1288
        *pnPageCount = nPages;
1289
1290
    CPLReleaseMutex(g_oPdfiumLoadDocMutex);
1291
1292
    return TRUE;
1293
}
1294
1295
// ~ static int LoadPdfiumDocumentPage()
1296
1297
static int UnloadPdfiumDocumentPage(TPdfiumDocumentStruct **doc,
1298
                                    TPdfiumPageStruct **page)
1299
{
1300
    if (!doc || !page)
1301
        return FALSE;
1302
1303
    TPdfiumPageStruct *pPage = *page;
1304
    TPdfiumDocumentStruct *pDoc = *doc;
1305
1306
    // Get mutex for loading pdfium
1307
    CPLCreateOrAcquireMutex(&g_oPdfiumLoadDocMutex, PDFIUM_MUTEX_TIMEOUT);
1308
1309
    // Decrease page use
1310
    --pPage->sharedNum;
1311
1312
#ifdef DEBUG
1313
    CPLDebug("PDF", "PDFDataset::UnloadPdfiumDocumentPage: page shared num %d",
1314
             pPage->sharedNum);
1315
#endif
1316
    // Page is used (also document)
1317
    if (pPage->sharedNum != 0)
1318
    {
1319
        CPLReleaseMutex(g_oPdfiumLoadDocMutex);
1320
        return TRUE;
1321
    }
1322
1323
    // Get mutex, release and destroy it
1324
    CPLCreateOrAcquireMutex(&(pPage->readMutex), PDFIUM_MUTEX_TIMEOUT);
1325
    CPLReleaseMutex(pPage->readMutex);
1326
    CPLDestroyMutex(pPage->readMutex);
1327
    // Close page and remove from map
1328
    FPDF_ClosePage(FPDFPageFromIPDFPage(pPage->page));
1329
1330
    pDoc->pages.erase(pPage->pageNum);
1331
    delete pPage;
1332
    pPage = nullptr;
1333
1334
#ifdef DEBUG
1335
    CPLDebug("PDF", "PDFDataset::UnloadPdfiumDocumentPage: pages %lu",
1336
             pDoc->pages.size());
1337
#endif
1338
    // Another page is used
1339
    if (!pDoc->pages.empty())
1340
    {
1341
        CPLReleaseMutex(g_oPdfiumLoadDocMutex);
1342
        return TRUE;
1343
    }
1344
1345
    // Close document and remove from map
1346
    FPDF_CloseDocument(FPDFDocumentFromCPDFDocument(pDoc->doc));
1347
    g_mPdfiumDatasets.erase(pDoc->filename);
1348
    CPLFree(pDoc->filename);
1349
    VSIFCloseL(static_cast<VSILFILE *>(pDoc->psFileAccess->m_Param));
1350
    delete pDoc->psFileAccess;
1351
    delete pDoc;
1352
    pDoc = nullptr;
1353
1354
#ifdef DEBUG
1355
    CPLDebug("PDF", "PDFDataset::UnloadPdfiumDocumentPage: documents %lu",
1356
             g_mPdfiumDatasets.size());
1357
#endif
1358
    // Another document is used
1359
    if (!g_mPdfiumDatasets.empty())
1360
    {
1361
        CPLReleaseMutex(g_oPdfiumLoadDocMutex);
1362
        return TRUE;
1363
    }
1364
1365
#ifdef DEBUG
1366
    CPLDebug("PDF", "PDFDataset::UnloadPdfiumDocumentPage: Nothing loaded, "
1367
                    "destroy Library");
1368
#endif
1369
    // No document loaded, destroy pdfium
1370
    FPDF_DestroyLibrary();
1371
    PDFDataset::g_bPdfiumInit = FALSE;
1372
1373
    CPLReleaseMutex(g_oPdfiumLoadDocMutex);
1374
1375
    return TRUE;
1376
}
1377
1378
// ~ static int UnloadPdfiumDocumentPage()
1379
1380
#endif  // ~ HAVE_PDFIUM
1381
1382
/************************************************************************/
1383
/*                             GetOption()                              */
1384
/************************************************************************/
1385
1386
const char *PDFDataset::GetOption(char **papszOpenOptionsIn,
1387
                                  const char *pszOptionName,
1388
                                  const char *pszDefaultVal)
1389
136k
{
1390
136k
    CPLErr eLastErrType = CPLGetLastErrorType();
1391
136k
    CPLErrorNum nLastErrno = CPLGetLastErrorNo();
1392
136k
    CPLString osLastErrorMsg(CPLGetLastErrorMsg());
1393
136k
    CPLXMLNode *psNode = CPLParseXMLString(PDFGetOpenOptionList());
1394
136k
    CPLErrorSetState(eLastErrType, nLastErrno, osLastErrorMsg);
1395
136k
    if (psNode == nullptr)
1396
0
        return pszDefaultVal;
1397
136k
    CPLXMLNode *psIter = psNode->psChild;
1398
601k
    while (psIter != nullptr)
1399
601k
    {
1400
601k
        if (EQUAL(CPLGetXMLValue(psIter, "name", ""), pszOptionName))
1401
136k
        {
1402
136k
            const char *pszVal =
1403
136k
                CSLFetchNameValue(papszOpenOptionsIn, pszOptionName);
1404
136k
            if (pszVal != nullptr)
1405
37.6k
            {
1406
37.6k
                CPLDestroyXMLNode(psNode);
1407
37.6k
                return pszVal;
1408
37.6k
            }
1409
98.8k
            const char *pszAltConfigOption =
1410
98.8k
                CPLGetXMLValue(psIter, "alt_config_option", nullptr);
1411
98.8k
            if (pszAltConfigOption != nullptr)
1412
98.8k
            {
1413
98.8k
                pszVal = CPLGetConfigOption(pszAltConfigOption, pszDefaultVal);
1414
98.8k
                CPLDestroyXMLNode(psNode);
1415
98.8k
                return pszVal;
1416
98.8k
            }
1417
0
            CPLDestroyXMLNode(psNode);
1418
0
            return pszDefaultVal;
1419
98.8k
        }
1420
465k
        psIter = psIter->psNext;
1421
465k
    }
1422
0
    CPLError(CE_Failure, CPLE_AppDefined,
1423
0
             "Requesting an undocumented open option '%s'", pszOptionName);
1424
0
    CPLDestroyXMLNode(psNode);
1425
0
    return pszDefaultVal;
1426
136k
}
1427
1428
#ifdef HAVE_PDFIUM
1429
1430
/************************************************************************/
1431
/*                         GDALPDFiumOCContext                          */
1432
/************************************************************************/
1433
1434
class GDALPDFiumOCContext final : public CPDF_OCContextInterface
1435
{
1436
    PDFDataset *m_poDS;
1437
    RetainPtr<CPDF_OCContext> m_DefaultOCContext;
1438
1439
    CPL_DISALLOW_COPY_ASSIGN(GDALPDFiumOCContext)
1440
1441
  public:
1442
    GDALPDFiumOCContext(PDFDataset *poDS, CPDF_Document *pDoc,
1443
                        CPDF_OCContext::UsageType usage)
1444
        : m_poDS(poDS),
1445
          m_DefaultOCContext(pdfium::MakeRetain<CPDF_OCContext>(pDoc, usage))
1446
    {
1447
    }
1448
1449
    virtual bool
1450
    CheckOCGDictVisible(const CPDF_Dictionary *pOCGDict) const override
1451
    {
1452
        // CPLDebug("PDF", "CheckOCGDictVisible(%d,%d)",
1453
        //          pOCGDict->GetObjNum(), pOCGDict->GetGenNum() );
1454
        PDFDataset::VisibilityState eVisibility =
1455
            m_poDS->GetVisibilityStateForOGCPdfium(pOCGDict->GetObjNum(),
1456
                                                   pOCGDict->GetGenNum());
1457
        if (eVisibility == PDFDataset::VISIBILITY_ON)
1458
            return true;
1459
        if (eVisibility == PDFDataset::VISIBILITY_OFF)
1460
            return false;
1461
        return m_DefaultOCContext->CheckOCGDictVisible(pOCGDict);
1462
    }
1463
};
1464
1465
/************************************************************************/
1466
/*                     GDALPDFiumRenderDeviceDriver                     */
1467
/************************************************************************/
1468
1469
class GDALPDFiumRenderDeviceDriver final : public RenderDeviceDriverIface
1470
{
1471
    std::unique_ptr<RenderDeviceDriverIface> m_poParent;
1472
    CFX_RenderDevice *device_;
1473
1474
    int bEnableVector;
1475
    int bEnableText;
1476
    int bEnableBitmap;
1477
    int bTemporaryEnableVectorForTextStroking;
1478
1479
    CPL_DISALLOW_COPY_ASSIGN(GDALPDFiumRenderDeviceDriver)
1480
1481
  public:
1482
    GDALPDFiumRenderDeviceDriver(
1483
        std::unique_ptr<RenderDeviceDriverIface> &&poParent,
1484
        CFX_RenderDevice *pDevice)
1485
        : m_poParent(std::move(poParent)), device_(pDevice),
1486
          bEnableVector(TRUE), bEnableText(TRUE), bEnableBitmap(TRUE),
1487
          bTemporaryEnableVectorForTextStroking(FALSE)
1488
    {
1489
    }
1490
1491
    virtual ~GDALPDFiumRenderDeviceDriver() = default;
1492
1493
    void SetEnableVector(int bFlag)
1494
    {
1495
        bEnableVector = bFlag;
1496
    }
1497
1498
    void SetEnableText(int bFlag)
1499
    {
1500
        bEnableText = bFlag;
1501
    }
1502
1503
    void SetEnableBitmap(int bFlag)
1504
    {
1505
        bEnableBitmap = bFlag;
1506
    }
1507
1508
    DeviceType GetDeviceType() const override
1509
    {
1510
        return m_poParent->GetDeviceType();
1511
    }
1512
1513
    int GetDeviceCaps(int caps_id) const override
1514
    {
1515
        return m_poParent->GetDeviceCaps(caps_id);
1516
    }
1517
1518
    void SaveState() override
1519
    {
1520
        m_poParent->SaveState();
1521
    }
1522
1523
    void RestoreState(bool bKeepSaved) override
1524
    {
1525
        m_poParent->RestoreState(bKeepSaved);
1526
    }
1527
1528
    void SetBaseClip(const FX_RECT &rect) override
1529
    {
1530
        m_poParent->SetBaseClip(rect);
1531
    }
1532
1533
    virtual bool
1534
    SetClip_PathFill(const CFX_Path &path, const CFX_Matrix *pObject2Device,
1535
                     const CFX_FillRenderOptions &fill_options) override
1536
    {
1537
        if (!bEnableVector && !bTemporaryEnableVectorForTextStroking)
1538
            return true;
1539
        return m_poParent->SetClip_PathFill(path, pObject2Device, fill_options);
1540
    }
1541
1542
    virtual bool
1543
    SetClip_PathStroke(const CFX_Path &path, const CFX_Matrix *pObject2Device,
1544
                       const CFX_GraphStateData *pGraphState) override
1545
    {
1546
        if (!bEnableVector && !bTemporaryEnableVectorForTextStroking)
1547
            return true;
1548
        return m_poParent->SetClip_PathStroke(path, pObject2Device,
1549
                                              pGraphState);
1550
    }
1551
1552
    virtual bool DrawPath(const CFX_Path &path,
1553
                          const CFX_Matrix *pObject2Device,
1554
                          const CFX_GraphStateData *pGraphState,
1555
                          uint32_t fill_color, uint32_t stroke_color,
1556
                          const CFX_FillRenderOptions &fill_options) override
1557
    {
1558
        if (!bEnableVector && !bTemporaryEnableVectorForTextStroking)
1559
            return true;
1560
        return m_poParent->DrawPath(path, pObject2Device, pGraphState,
1561
                                    fill_color, stroke_color, fill_options);
1562
    }
1563
1564
    bool FillRect(const FX_RECT &rect, uint32_t fill_color) override
1565
    {
1566
        return m_poParent->FillRect(rect, fill_color);
1567
    }
1568
1569
    virtual bool DrawCosmeticLine(const CFX_PointF &ptMoveTo,
1570
                                  const CFX_PointF &ptLineTo,
1571
                                  uint32_t color) override
1572
    {
1573
        if (!bEnableVector && !bTemporaryEnableVectorForTextStroking)
1574
            return TRUE;
1575
        return m_poParent->DrawCosmeticLine(ptMoveTo, ptLineTo, color);
1576
    }
1577
1578
    FX_RECT GetClipBox() const override
1579
    {
1580
        return m_poParent->GetClipBox();
1581
    }
1582
1583
    virtual bool GetDIBits(RetainPtr<CFX_DIBitmap> bitmap, int left,
1584
                           int top) const override
1585
    {
1586
        return m_poParent->GetDIBits(std::move(bitmap), left, top);
1587
    }
1588
1589
    RetainPtr<const CFX_DIBitmap> GetBackDrop() const override
1590
    {
1591
        return m_poParent->GetBackDrop();
1592
    }
1593
1594
    virtual bool SetDIBits(RetainPtr<const CFX_DIBBase> bitmap, uint32_t color,
1595
                           const FX_RECT &src_rect, int dest_left, int dest_top,
1596
                           BlendMode blend_type) override
1597
    {
1598
        if (!bEnableBitmap && !bTemporaryEnableVectorForTextStroking)
1599
            return true;
1600
        return m_poParent->SetDIBits(std::move(bitmap), color, src_rect,
1601
                                     dest_left, dest_top, blend_type);
1602
    }
1603
1604
    virtual bool StretchDIBits(RetainPtr<const CFX_DIBBase> bitmap,
1605
                               uint32_t color, int dest_left, int dest_top,
1606
                               int dest_width, int dest_height,
1607
                               const FX_RECT *pClipRect,
1608
                               const FXDIB_ResampleOptions &options,
1609
                               BlendMode blend_type) override
1610
    {
1611
        if (!bEnableBitmap && !bTemporaryEnableVectorForTextStroking)
1612
            return true;
1613
        return m_poParent->StretchDIBits(std::move(bitmap), color, dest_left,
1614
                                         dest_top, dest_width, dest_height,
1615
                                         pClipRect, options, blend_type);
1616
    }
1617
1618
    virtual StartResult StartDIBits(RetainPtr<const CFX_DIBBase> bitmap,
1619
                                    float alpha, uint32_t color,
1620
                                    const CFX_Matrix &matrix,
1621
                                    const FXDIB_ResampleOptions &options,
1622
                                    BlendMode blend_type) override
1623
    {
1624
        if (!bEnableBitmap && !bTemporaryEnableVectorForTextStroking)
1625
            return StartResult(Result::kSuccess, nullptr);
1626
        return m_poParent->StartDIBits(std::move(bitmap), alpha, color, matrix,
1627
                                       options, blend_type);
1628
    }
1629
1630
    virtual bool ContinueDIBits(CFX_AggImageRenderer *handle,
1631
                                PauseIndicatorIface *pPause) override
1632
    {
1633
        return m_poParent->ContinueDIBits(handle, pPause);
1634
    }
1635
1636
    virtual bool DrawDeviceText(const pdfium::span<const TextCharPos> &pCharPos,
1637
                                CFX_Font *pFont,
1638
                                const CFX_Matrix &mtObject2Device,
1639
                                float font_size, uint32_t color,
1640
                                const CFX_TextRenderOptions &options) override
1641
    {
1642
        if (bEnableText)
1643
        {
1644
            // This is quite tricky. We call again the guy who called us
1645
            // (CFX_RenderDevice::DrawNormalText()) but we set a special flag to
1646
            // allow vector&raster operations so that the rendering will happen
1647
            // in the next phase
1648
            if (bTemporaryEnableVectorForTextStroking)
1649
                return FALSE;  // this is the default behavior of the parent
1650
            bTemporaryEnableVectorForTextStroking = true;
1651
            bool bRet = device_->DrawNormalText(
1652
                pCharPos, pFont, font_size, mtObject2Device, color, options);
1653
            bTemporaryEnableVectorForTextStroking = FALSE;
1654
            return bRet;
1655
        }
1656
        else
1657
            return true;  // pretend that we did the job
1658
    }
1659
1660
    int GetDriverType() const override
1661
    {
1662
        return m_poParent->GetDriverType();
1663
    }
1664
1665
#if defined(_SKIA_SUPPORT_)
1666
    virtual bool DrawShading(const CPDF_ShadingPattern &pattern,
1667
                             const CFX_Matrix &matrix, const FX_RECT &clip_rect,
1668
                             int alpha) override
1669
    {
1670
        if (!bEnableBitmap && !bTemporaryEnableVectorForTextStroking)
1671
            return true;
1672
        return m_poParent->DrawShading(pattern, matrix, clip_rect, alpha);
1673
    }
1674
#endif
1675
1676
    bool MultiplyAlpha(float alpha) override
1677
    {
1678
        return m_poParent->MultiplyAlpha(alpha);
1679
    }
1680
1681
    bool MultiplyAlphaMask(RetainPtr<const CFX_DIBitmap> mask) override
1682
    {
1683
        return m_poParent->MultiplyAlphaMask(std::move(mask));
1684
    }
1685
1686
#if defined(_SKIA_SUPPORT_)
1687
    virtual bool SetBitsWithMask(RetainPtr<const CFX_DIBBase> bitmap,
1688
                                 RetainPtr<const CFX_DIBBase> mask, int left,
1689
                                 int top, float alpha,
1690
                                 BlendMode blend_type) override
1691
    {
1692
        if (!bEnableBitmap && !bTemporaryEnableVectorForTextStroking)
1693
            return true;
1694
        return m_poParent->SetBitsWithMask(std::move(bitmap), std::move(mask),
1695
                                           left, top, alpha, blend_type);
1696
    }
1697
1698
    void SetGroupKnockout(bool group_knockout) override
1699
    {
1700
        m_poParent->SetGroupKnockout(group_knockout);
1701
    }
1702
#endif
1703
#if defined _SKIA_SUPPORT_ || defined _SKIA_SUPPORT_PATHS_
1704
    void Flush() override
1705
    {
1706
        return m_poParent->Flush();
1707
    }
1708
#endif
1709
};
1710
1711
/************************************************************************/
1712
/*                       PDFiumRenderPageBitmap()                       */
1713
/************************************************************************/
1714
1715
/* This method is a customization of RenderPageImpl()
1716
   from pdfium/fpdfsdk/cpdfsdk_renderpage.cpp to allow selection of which OGC/layer are
1717
   active. Thus it inherits the following license */
1718
// Copyright 2014-2020 PDFium Authors. All rights reserved.
1719
//
1720
// Redistribution and use in source and binary forms, with or without
1721
// modification, are permitted provided that the following conditions are
1722
// met:
1723
//
1724
//    * Redistributions of source code must retain the above copyright
1725
// notice, this list of conditions and the following disclaimer.
1726
//    * Redistributions in binary form must reproduce the above
1727
// copyright notice, this list of conditions and the following disclaimer
1728
// in the documentation and/or other materials provided with the
1729
// distribution.
1730
//    * Neither the name of Google Inc. nor the names of its
1731
// contributors may be used to endorse or promote products derived from
1732
// this software without specific prior written permission.
1733
//
1734
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1735
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1736
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
1737
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
1738
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
1739
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
1740
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
1741
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
1742
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
1743
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
1744
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1745
1746
static void myRenderPageImpl(PDFDataset *poDS, CPDF_PageRenderContext *pContext,
1747
                             CPDF_Page *pPage, const CFX_Matrix &matrix,
1748
                             const FX_RECT &clipping_rect, int flags,
1749
                             const FPDF_COLORSCHEME *color_scheme,
1750
                             bool bNeedToRestore, CPDFSDK_PauseAdapter *pause)
1751
{
1752
    if (!pContext->options_)
1753
        pContext->options_ = std::make_unique<CPDF_RenderOptions>();
1754
1755
    auto &options = pContext->options_->GetOptions();
1756
    options.bClearType = !!(flags & FPDF_LCD_TEXT);
1757
    options.bNoNativeText = !!(flags & FPDF_NO_NATIVETEXT);
1758
    options.bLimitedImageCache = !!(flags & FPDF_RENDER_LIMITEDIMAGECACHE);
1759
    options.bForceHalftone = !!(flags & FPDF_RENDER_FORCEHALFTONE);
1760
    options.bNoTextSmooth = !!(flags & FPDF_RENDER_NO_SMOOTHTEXT);
1761
    options.bNoImageSmooth = !!(flags & FPDF_RENDER_NO_SMOOTHIMAGE);
1762
    options.bNoPathSmooth = !!(flags & FPDF_RENDER_NO_SMOOTHPATH);
1763
1764
    // Grayscale output
1765
    if (flags & FPDF_GRAYSCALE)
1766
        pContext->options_->SetColorMode(CPDF_RenderOptions::kGray);
1767
1768
    if (color_scheme)
1769
    {
1770
        pContext->options_->SetColorMode(CPDF_RenderOptions::kForcedColor);
1771
        SetColorFromScheme(color_scheme, pContext->options_.get());
1772
        options.bConvertFillToStroke = !!(flags & FPDF_CONVERT_FILL_TO_STROKE);
1773
    }
1774
1775
    const CPDF_OCContext::UsageType usage = (flags & FPDF_PRINTING)
1776
                                                ? CPDF_OCContext::kPrint
1777
                                                : CPDF_OCContext::kView;
1778
    pContext->options_->SetOCContext(pdfium::MakeRetain<GDALPDFiumOCContext>(
1779
        poDS, pPage->GetDocument(), usage));
1780
1781
    pContext->device_->SaveState();
1782
    pContext->device_->SetBaseClip(clipping_rect);
1783
    pContext->device_->SetClip_Rect(clipping_rect);
1784
    pContext->context_ = std::make_unique<CPDF_RenderContext>(
1785
        pPage->GetDocument(), pPage->GetMutablePageResources(),
1786
        pPage->GetPageImageCache());
1787
1788
    pContext->context_->AppendLayer(pPage, matrix);
1789
1790
    if (flags & FPDF_ANNOT)
1791
    {
1792
        auto pOwnedList = std::make_unique<CPDF_AnnotList>(pPage);
1793
        CPDF_AnnotList *pList = pOwnedList.get();
1794
        pContext->annots_ = std::move(pOwnedList);
1795
        bool bPrinting =
1796
            pContext->device_->GetDeviceType() != DeviceType::kDisplay;
1797
1798
        // TODO(https://crbug.com/pdfium/993) - maybe pass true here.
1799
        const bool bShowWidget = false;
1800
        pList->DisplayAnnots(pContext->context_.get(), bPrinting, matrix,
1801
                             bShowWidget);
1802
    }
1803
1804
    pContext->renderer_ = std::make_unique<CPDF_ProgressiveRenderer>(
1805
        pContext->context_.get(), pContext->device_.get(),
1806
        pContext->options_.get());
1807
    pContext->renderer_->Start(pause);
1808
    if (bNeedToRestore)
1809
        pContext->device_->RestoreState(false);
1810
}
1811
1812
static void
1813
myRenderPageWithContext(PDFDataset *poDS, CPDF_PageRenderContext *pContext,
1814
                        FPDF_PAGE page, int start_x, int start_y, int size_x,
1815
                        int size_y, int rotate, int flags,
1816
                        const FPDF_COLORSCHEME *color_scheme,
1817
                        bool bNeedToRestore, CPDFSDK_PauseAdapter *pause)
1818
{
1819
    CPDF_Page *pPage = CPDFPageFromFPDFPage(page);
1820
    if (!pPage)
1821
        return;
1822
1823
    const FX_RECT rect(start_x, start_y, start_x + size_x, start_y + size_y);
1824
    myRenderPageImpl(poDS, pContext, pPage,
1825
                     pPage->GetDisplayMatrixForRect(rect, rotate), rect, flags,
1826
                     color_scheme, bNeedToRestore, pause);
1827
}
1828
1829
class MyRenderDevice final : public CFX_RenderDevice
1830
{
1831
1832
  public:
1833
    // Substitution for CFX_DefaultRenderDevice::Attach
1834
    bool Attach(const RetainPtr<CFX_DIBitmap> &pBitmap, bool bRgbByteOrder,
1835
                const RetainPtr<CFX_DIBitmap> &pBackdropBitmap,
1836
                bool bGroupKnockout, const char *pszRenderingOptions);
1837
};
1838
1839
bool MyRenderDevice::Attach(const RetainPtr<CFX_DIBitmap> &pBitmap,
1840
                            bool bRgbByteOrder,
1841
                            const RetainPtr<CFX_DIBitmap> &pBackdropBitmap,
1842
                            bool bGroupKnockout,
1843
                            const char *pszRenderingOptions)
1844
{
1845
    SetBitmap(pBitmap);
1846
1847
    std::unique_ptr<RenderDeviceDriverIface> driver =
1848
        std::make_unique<pdfium::CFX_AggDeviceDriver>(
1849
            pBitmap, bRgbByteOrder, pBackdropBitmap, bGroupKnockout);
1850
    if (pszRenderingOptions != nullptr)
1851
    {
1852
        int bEnableVector = FALSE;
1853
        int bEnableText = FALSE;
1854
        int bEnableBitmap = FALSE;
1855
1856
        char **papszTokens = CSLTokenizeString2(pszRenderingOptions, " ,", 0);
1857
        for (int i = 0; papszTokens[i] != nullptr; i++)
1858
        {
1859
            if (EQUAL(papszTokens[i], "VECTOR"))
1860
                bEnableVector = TRUE;
1861
            else if (EQUAL(papszTokens[i], "TEXT"))
1862
                bEnableText = TRUE;
1863
            else if (EQUAL(papszTokens[i], "RASTER") ||
1864
                     EQUAL(papszTokens[i], "BITMAP"))
1865
                bEnableBitmap = TRUE;
1866
            else
1867
            {
1868
                CPLError(CE_Warning, CPLE_NotSupported,
1869
                         "Value %s is not a valid value for "
1870
                         "GDAL_PDF_RENDERING_OPTIONS",
1871
                         papszTokens[i]);
1872
            }
1873
        }
1874
        CSLDestroy(papszTokens);
1875
1876
        if (!bEnableVector || !bEnableText || !bEnableBitmap)
1877
        {
1878
            std::unique_ptr<GDALPDFiumRenderDeviceDriver> poGDALRDDriver =
1879
                std::make_unique<GDALPDFiumRenderDeviceDriver>(
1880
                    std::move(driver), this);
1881
            poGDALRDDriver->SetEnableVector(bEnableVector);
1882
            poGDALRDDriver->SetEnableText(bEnableText);
1883
            poGDALRDDriver->SetEnableBitmap(bEnableBitmap);
1884
            driver = std::move(poGDALRDDriver);
1885
        }
1886
    }
1887
1888
    SetDeviceDriver(std::move(driver));
1889
    return true;
1890
}
1891
1892
void PDFDataset::PDFiumRenderPageBitmap(FPDF_BITMAP bitmap, FPDF_PAGE page,
1893
                                        int start_x, int start_y, int size_x,
1894
                                        int size_y,
1895
                                        const char *pszRenderingOptions)
1896
{
1897
    const int rotate = 0;
1898
    const int flags = 0;
1899
1900
    if (!bitmap)
1901
        return;
1902
1903
    CPDF_Page *pPage = CPDFPageFromFPDFPage(page);
1904
    if (!pPage)
1905
        return;
1906
1907
    auto pOwnedContext = std::make_unique<CPDF_PageRenderContext>();
1908
    CPDF_PageRenderContext *pContext = pOwnedContext.get();
1909
    CPDF_Page::RenderContextClearer clearer(pPage);
1910
    pPage->SetRenderContext(std::move(pOwnedContext));
1911
1912
    auto pOwnedDevice = std::make_unique<MyRenderDevice>();
1913
    auto pDevice = pOwnedDevice.get();
1914
    pContext->device_ = std::move(pOwnedDevice);
1915
1916
    RetainPtr<CFX_DIBitmap> pBitmap(CFXDIBitmapFromFPDFBitmap(bitmap));
1917
1918
    pDevice->Attach(pBitmap, !!(flags & FPDF_REVERSE_BYTE_ORDER), nullptr,
1919
                    false, pszRenderingOptions);
1920
1921
    myRenderPageWithContext(this, pContext, page, start_x, start_y, size_x,
1922
                            size_y, rotate, flags,
1923
                            /*color_scheme=*/nullptr,
1924
                            /*need_to_restore=*/true, /*pause=*/nullptr);
1925
1926
#ifdef _SKIA_SUPPORT_PATHS_
1927
    pDevice->Flush(true);
1928
    pBitmap->UnPreMultiply();
1929
#endif
1930
}
1931
1932
#endif /* HAVE_PDFIUM */
1933
1934
/************************************************************************/
1935
/*                             ReadPixels()                             */
1936
/************************************************************************/
1937
1938
CPLErr PDFDataset::ReadPixels(int nReqXOff, int nReqYOff, int nReqXSize,
1939
                              int nReqYSize, GSpacing nPixelSpace,
1940
                              GSpacing nLineSpace, GSpacing nBandSpace,
1941
                              GByte *pabyData)
1942
7.32k
{
1943
7.32k
    CPLErr eErr = CE_None;
1944
7.32k
    const char *pszRenderingOptions =
1945
7.32k
        GetOption(papszOpenOptions, "RENDERING_OPTIONS", nullptr);
1946
1947
7.32k
#ifdef HAVE_POPPLER
1948
7.32k
    if (m_bUseLib.test(PDFLIB_POPPLER))
1949
7.32k
    {
1950
7.32k
        SplashColor sColor;
1951
7.32k
        sColor[0] = 255;
1952
7.32k
        sColor[1] = 255;
1953
7.32k
        sColor[2] = 255;
1954
7.32k
        GDALPDFOutputDev *poSplashOut = new GDALPDFOutputDev(
1955
7.32k
            (nBands < 4) ? splashModeRGB8 : splashModeXBGR8, 4, false,
1956
7.32k
            (nBands < 4) ? sColor : nullptr);
1957
1958
7.32k
        if (pszRenderingOptions != nullptr)
1959
7.32k
        {
1960
7.32k
            poSplashOut->SetEnableVector(FALSE);
1961
7.32k
            poSplashOut->SetEnableText(FALSE);
1962
7.32k
            poSplashOut->SetEnableBitmap(FALSE);
1963
1964
7.32k
            char **papszTokens =
1965
7.32k
                CSLTokenizeString2(pszRenderingOptions, " ,", 0);
1966
21.9k
            for (int i = 0; papszTokens[i] != nullptr; i++)
1967
14.6k
            {
1968
14.6k
                if (EQUAL(papszTokens[i], "VECTOR"))
1969
7.32k
                    poSplashOut->SetEnableVector(TRUE);
1970
7.32k
                else if (EQUAL(papszTokens[i], "TEXT"))
1971
0
                    poSplashOut->SetEnableText(TRUE);
1972
7.32k
                else if (EQUAL(papszTokens[i], "RASTER") ||
1973
0
                         EQUAL(papszTokens[i], "BITMAP"))
1974
7.32k
                    poSplashOut->SetEnableBitmap(TRUE);
1975
0
                else
1976
0
                {
1977
0
                    CPLError(CE_Warning, CPLE_NotSupported,
1978
0
                             "Value %s is not a valid value for "
1979
0
                             "GDAL_PDF_RENDERING_OPTIONS",
1980
0
                             papszTokens[i]);
1981
0
                }
1982
14.6k
            }
1983
7.32k
            CSLDestroy(papszTokens);
1984
7.32k
        }
1985
1986
7.32k
        PDFDoc *poDoc = m_poDocPoppler;
1987
7.32k
        poSplashOut->startDoc(poDoc);
1988
1989
        // Note: Poppler 25.2 is certainly not the lowest version where we can
1990
        // avoid the hack.
1991
7.32k
#if !(POPPLER_MAJOR_VERSION > 25 ||                                            \
1992
7.32k
      (POPPLER_MAJOR_VERSION == 25 && POPPLER_MINOR_VERSION >= 2))
1993
7.32k
#define USE_OPTCONTENT_HACK
1994
7.32k
#endif
1995
1996
7.32k
#ifdef USE_OPTCONTENT_HACK
1997
        /* EVIL: we modify a private member... */
1998
        /* poppler (at least 0.12 and 0.14 versions) don't render correctly */
1999
        /* some PDFs and display an error message 'Could not find a OCG with
2000
         * Ref' */
2001
        /* in those cases. This processing of optional content is an addition of
2002
         */
2003
        /* poppler in comparison to original xpdf, which hasn't the issue. All
2004
         * in */
2005
        /* all, nullifying optContent removes the error message and improves the
2006
         * rendering */
2007
7.32k
        Catalog *poCatalog = poDoc->getCatalog();
2008
7.32k
        OCGs *poOldOCGs = poCatalog->optContent;
2009
7.32k
        if (!m_bUseOCG)
2010
7.32k
            poCatalog->optContent = nullptr;
2011
7.32k
#endif
2012
7.32k
        try
2013
7.32k
        {
2014
7.32k
            poDoc->displayPageSlice(poSplashOut, m_iPage, m_dfDPI, m_dfDPI, 0,
2015
7.32k
                                    TRUE, false, false, nReqXOff, nReqYOff,
2016
7.32k
                                    nReqXSize, nReqYSize);
2017
7.32k
        }
2018
7.32k
        catch (const std::exception &e)
2019
7.32k
        {
2020
0
            CPLError(CE_Failure, CPLE_AppDefined,
2021
0
                     "PDFDoc::displayPageSlice() failed with %s", e.what());
2022
2023
0
#ifdef USE_OPTCONTENT_HACK
2024
            /* Restore back */
2025
0
            poCatalog->optContent = poOldOCGs;
2026
0
#endif
2027
0
            delete poSplashOut;
2028
0
            return CE_Failure;
2029
0
        }
2030
2031
0
#ifdef USE_OPTCONTENT_HACK
2032
        /* Restore back */
2033
7.32k
        poCatalog->optContent = poOldOCGs;
2034
7.32k
#endif
2035
2036
7.32k
        SplashBitmap *poBitmap = poSplashOut->getBitmap();
2037
7.32k
        if (poBitmap->getWidth() != nReqXSize ||
2038
7.32k
            poBitmap->getHeight() != nReqYSize)
2039
0
        {
2040
0
            CPLError(
2041
0
                CE_Failure, CPLE_AppDefined,
2042
0
                "Bitmap decoded size (%dx%d) doesn't match raster size (%dx%d)",
2043
0
                poBitmap->getWidth(), poBitmap->getHeight(), nReqXSize,
2044
0
                nReqYSize);
2045
0
            delete poSplashOut;
2046
0
            return CE_Failure;
2047
0
        }
2048
2049
7.32k
        GByte *pabyDataR = pabyData;
2050
7.32k
        GByte *pabyDataG = pabyData + nBandSpace;
2051
7.32k
        GByte *pabyDataB = pabyData + 2 * nBandSpace;
2052
7.32k
        GByte *pabyDataA = pabyData + 3 * nBandSpace;
2053
7.32k
        GByte *pabySrc = poBitmap->getDataPtr();
2054
7.32k
        GByte *pabyAlphaSrc =
2055
7.32k
            reinterpret_cast<GByte *>(poBitmap->getAlphaPtr());
2056
7.32k
        int i, j;
2057
9.47M
        for (j = 0; j < nReqYSize; j++)
2058
9.46M
        {
2059
12.1G
            for (i = 0; i < nReqXSize; i++)
2060
12.1G
            {
2061
12.1G
                if (nBands < 4)
2062
12.1G
                {
2063
12.1G
                    pabyDataR[i * nPixelSpace] = pabySrc[i * 3 + 0];
2064
12.1G
                    pabyDataG[i * nPixelSpace] = pabySrc[i * 3 + 1];
2065
12.1G
                    pabyDataB[i * nPixelSpace] = pabySrc[i * 3 + 2];
2066
12.1G
                }
2067
0
                else
2068
0
                {
2069
0
                    pabyDataR[i * nPixelSpace] = pabySrc[i * 4 + 2];
2070
0
                    pabyDataG[i * nPixelSpace] = pabySrc[i * 4 + 1];
2071
0
                    pabyDataB[i * nPixelSpace] = pabySrc[i * 4 + 0];
2072
0
                    pabyDataA[i * nPixelSpace] = pabyAlphaSrc[i];
2073
0
                }
2074
12.1G
            }
2075
9.46M
            pabyDataR += nLineSpace;
2076
9.46M
            pabyDataG += nLineSpace;
2077
9.46M
            pabyDataB += nLineSpace;
2078
9.46M
            pabyDataA += nLineSpace;
2079
9.46M
            pabyAlphaSrc += poBitmap->getAlphaRowSize();
2080
9.46M
            pabySrc += poBitmap->getRowSize();
2081
9.46M
        }
2082
7.32k
        delete poSplashOut;
2083
7.32k
    }
2084
7.32k
#endif  // HAVE_POPPLER
2085
2086
#ifdef HAVE_PODOFO
2087
    if (m_bUseLib.test(PDFLIB_PODOFO))
2088
    {
2089
        if (m_bPdfToPpmFailed)
2090
            return CE_Failure;
2091
2092
        if (pszRenderingOptions != nullptr &&
2093
            !EQUAL(pszRenderingOptions, "RASTER,VECTOR,TEXT"))
2094
        {
2095
            CPLError(CE_Warning, CPLE_NotSupported,
2096
                     "GDAL_PDF_RENDERING_OPTIONS only supported "
2097
                     "when PDF lib is Poppler.");
2098
        }
2099
2100
        CPLString osTmpFilename;
2101
        int nRet;
2102
2103
#ifdef notdef
2104
        int bUseSpawn =
2105
            CPLTestBool(CPLGetConfigOption("GDAL_PDF_USE_SPAWN", "YES"));
2106
        if (!bUseSpawn)
2107
        {
2108
            CPLString osCmd = CPLSPrintf(
2109
                "pdftoppm -r %f -x %d -y %d -W %d -H %d -f %d -l %d \"%s\"",
2110
                dfDPI, nReqXOff, nReqYOff, nReqXSize, nReqYSize, iPage, iPage,
2111
                osFilename.c_str());
2112
2113
            if (!osUserPwd.empty())
2114
            {
2115
                osCmd += " -upw \"";
2116
                osCmd += osUserPwd;
2117
                osCmd += "\"";
2118
            }
2119
2120
            CPLString osTmpFilenamePrefix = CPLGenerateTempFilenameSafe("pdf");
2121
            osTmpFilename =
2122
                CPLSPrintf("%s-%d.ppm", osTmpFilenamePrefix.c_str(), iPage);
2123
            osCmd += CPLSPrintf(" \"%s\"", osTmpFilenamePrefix.c_str());
2124
2125
            CPLDebug("PDF", "Running '%s'", osCmd.c_str());
2126
            nRet = CPLSystem(nullptr, osCmd.c_str());
2127
        }
2128
        else
2129
#endif  // notdef
2130
        {
2131
            char **papszArgs = nullptr;
2132
            papszArgs = CSLAddString(papszArgs, "pdftoppm");
2133
            papszArgs = CSLAddString(papszArgs, "-r");
2134
            papszArgs = CSLAddString(papszArgs, CPLSPrintf("%f", m_dfDPI));
2135
            papszArgs = CSLAddString(papszArgs, "-x");
2136
            papszArgs = CSLAddString(papszArgs, CPLSPrintf("%d", nReqXOff));
2137
            papszArgs = CSLAddString(papszArgs, "-y");
2138
            papszArgs = CSLAddString(papszArgs, CPLSPrintf("%d", nReqYOff));
2139
            papszArgs = CSLAddString(papszArgs, "-W");
2140
            papszArgs = CSLAddString(papszArgs, CPLSPrintf("%d", nReqXSize));
2141
            papszArgs = CSLAddString(papszArgs, "-H");
2142
            papszArgs = CSLAddString(papszArgs, CPLSPrintf("%d", nReqYSize));
2143
            papszArgs = CSLAddString(papszArgs, "-f");
2144
            papszArgs = CSLAddString(papszArgs, CPLSPrintf("%d", m_iPage));
2145
            papszArgs = CSLAddString(papszArgs, "-l");
2146
            papszArgs = CSLAddString(papszArgs, CPLSPrintf("%d", m_iPage));
2147
            if (!m_osUserPwd.empty())
2148
            {
2149
                papszArgs = CSLAddString(papszArgs, "-upw");
2150
                papszArgs = CSLAddString(papszArgs, m_osUserPwd.c_str());
2151
            }
2152
            papszArgs = CSLAddString(papszArgs, m_osFilename.c_str());
2153
2154
            osTmpFilename = VSIMemGenerateHiddenFilename("pdf_temp.ppm");
2155
            VSILFILE *fpOut = VSIFOpenL(osTmpFilename, "wb");
2156
            if (fpOut != nullptr)
2157
            {
2158
                nRet = CPLSpawn(papszArgs, nullptr, fpOut, FALSE);
2159
                VSIFCloseL(fpOut);
2160
            }
2161
            else
2162
                nRet = -1;
2163
2164
            CSLDestroy(papszArgs);
2165
        }
2166
2167
        if (nRet == 0)
2168
        {
2169
            auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
2170
                osTmpFilename, GDAL_OF_RASTER, nullptr, nullptr, nullptr));
2171
            if (poDS)
2172
            {
2173
                if (poDS->GetRasterCount() == 3)
2174
                {
2175
                    eErr = poDS->RasterIO(GF_Read, 0, 0, nReqXSize, nReqYSize,
2176
                                          pabyData, nReqXSize, nReqYSize,
2177
                                          GDT_UInt8, 3, nullptr, nPixelSpace,
2178
                                          nLineSpace, nBandSpace, nullptr);
2179
                }
2180
            }
2181
        }
2182
        else
2183
        {
2184
            CPLDebug("PDF", "Ret code = %d", nRet);
2185
            m_bPdfToPpmFailed = true;
2186
            eErr = CE_Failure;
2187
        }
2188
        VSIUnlink(osTmpFilename);
2189
    }
2190
#endif  // HAVE_PODOFO
2191
#ifdef HAVE_PDFIUM
2192
    if (m_bUseLib.test(PDFLIB_PDFIUM))
2193
    {
2194
        if (!m_poPagePdfium)
2195
        {
2196
            return CE_Failure;
2197
        }
2198
2199
        // Pdfium does not support multithreading
2200
        CPLCreateOrAcquireMutex(&g_oPdfiumReadMutex, PDFIUM_MUTEX_TIMEOUT);
2201
2202
        CPLCreateOrAcquireMutex(&(m_poPagePdfium->readMutex),
2203
                                PDFIUM_MUTEX_TIMEOUT);
2204
2205
        // Parsing content required before rastering
2206
        // can takes too long for PDF with large number of objects/layers
2207
        m_poPagePdfium->page->ParseContent();
2208
2209
        FPDF_BITMAP bitmap =
2210
            FPDFBitmap_Create(nReqXSize, nReqYSize, nBands == 4 /*alpha*/);
2211
        // As coded now, FPDFBitmap_Create cannot allocate more than 1 GB
2212
        if (bitmap == nullptr)
2213
        {
2214
            // Release mutex - following code is thread-safe
2215
            CPLReleaseMutex(m_poPagePdfium->readMutex);
2216
            CPLReleaseMutex(g_oPdfiumReadMutex);
2217
2218
#ifdef notdef
2219
            // If the requested area is not too small, then try subdividing
2220
            if ((GIntBig)nReqXSize * nReqYSize * 4 > 1024 * 1024)
2221
            {
2222
#ifdef DEBUG
2223
                CPLDebug(
2224
                    "PDF",
2225
                    "Subdividing PDFDataset::ReadPixels(%d, %d, %d, %d, "
2226
                    "scaleFactor=%d)",
2227
                    nReqXOff, nReqYOff, nReqXSize, nReqYSize,
2228
                    1 << ((PDFRasterBand *)GetRasterBand(1))->nResolutionLevel);
2229
#endif
2230
                if (nReqXSize >= nReqYSize)
2231
                {
2232
                    eErr = ReadPixels(nReqXOff, nReqYOff, nReqXSize / 2,
2233
                                      nReqYSize, nPixelSpace, nLineSpace,
2234
                                      nBandSpace, pabyData);
2235
                    if (eErr == CE_None)
2236
                    {
2237
                        eErr = ReadPixels(
2238
                            nReqXSize / 2, nReqYOff, nReqXSize - nReqXSize / 2,
2239
                            nReqYSize, nPixelSpace, nLineSpace, nBandSpace,
2240
                            pabyData + nPixelSpace * (nReqXSize / 2));
2241
                    }
2242
                }
2243
                else
2244
                {
2245
                    eErr = ReadPixels(nReqXOff, nReqYOff, nReqXSize,
2246
                                      nReqYSize - nReqYSize / 2, nPixelSpace,
2247
                                      nLineSpace, nBandSpace, pabyData);
2248
                    if (eErr == CE_None)
2249
                    {
2250
                        eErr =
2251
                            ReadPixels(nReqXOff, nReqYSize / 2, nReqXSize,
2252
                                       nReqYSize - nReqYSize / 2, nPixelSpace,
2253
                                       nLineSpace, nBandSpace,
2254
                                       pabyData + nLineSpace * (nReqYSize / 2));
2255
                    }
2256
                }
2257
                return eErr;
2258
            }
2259
#endif
2260
2261
            CPLError(CE_Failure, CPLE_AppDefined,
2262
                     "FPDFBitmap_Create(%d,%d) failed", nReqXSize, nReqYSize);
2263
2264
            return CE_Failure;
2265
        }
2266
        // alpha is 0% which is transported to FF if not alpha
2267
        // Default background color is white
2268
        FPDF_DWORD color = 0x00FFFFFF;  // A,R,G,B
2269
        FPDFBitmap_FillRect(bitmap, 0, 0, nReqXSize, nReqYSize, color);
2270
2271
#ifdef DEBUG
2272
        // start_x, start_y, size_x, size_y, rotate, flags
2273
        CPLDebug("PDF",
2274
                 "PDFDataset::ReadPixels(%d, %d, %d, %d, scaleFactor=%d)",
2275
                 nReqXOff, nReqYOff, nReqXSize, nReqYSize,
2276
                 1 << cpl::down_cast<PDFRasterBand *>(GetRasterBand(1))
2277
                          ->nResolutionLevel);
2278
2279
        CPLDebug("PDF", "FPDF_RenderPageBitmap(%d, %d, %d, %d)", -nReqXOff,
2280
                 -nReqYOff, nRasterXSize, nRasterYSize);
2281
#endif
2282
2283
        // Part of PDF is render with -x, -y, page_width, page_height
2284
        // (not requested size!)
2285
        PDFiumRenderPageBitmap(
2286
            bitmap, FPDFPageFromIPDFPage(m_poPagePdfium->page), -nReqXOff,
2287
            -nReqYOff, nRasterXSize, nRasterYSize, pszRenderingOptions);
2288
2289
        int stride = FPDFBitmap_GetStride(bitmap);
2290
        const GByte *buffer =
2291
            reinterpret_cast<const GByte *>(FPDFBitmap_GetBuffer(bitmap));
2292
2293
        // Release mutex - following code is thread-safe
2294
        CPLReleaseMutex(m_poPagePdfium->readMutex);
2295
        CPLReleaseMutex(g_oPdfiumReadMutex);
2296
2297
        // Source data is B, G, R, unused.
2298
        // Destination data is R, G, B (,A if is alpha)
2299
        GByte *pabyDataR = pabyData;
2300
        GByte *pabyDataG = pabyData + 1 * nBandSpace;
2301
        GByte *pabyDataB = pabyData + 2 * nBandSpace;
2302
        GByte *pabyDataA = pabyData + 3 * nBandSpace;
2303
        // Copied from Poppler
2304
        int i, j;
2305
        for (j = 0; j < nReqYSize; j++)
2306
        {
2307
            for (i = 0; i < nReqXSize; i++)
2308
            {
2309
                pabyDataR[i * nPixelSpace] = buffer[(i * 4) + 2];
2310
                pabyDataG[i * nPixelSpace] = buffer[(i * 4) + 1];
2311
                pabyDataB[i * nPixelSpace] = buffer[(i * 4) + 0];
2312
                if (nBands == 4)
2313
                {
2314
                    pabyDataA[i * nPixelSpace] = buffer[(i * 4) + 3];
2315
                }
2316
            }
2317
            pabyDataR += nLineSpace;
2318
            pabyDataG += nLineSpace;
2319
            pabyDataB += nLineSpace;
2320
            pabyDataA += nLineSpace;
2321
            buffer += stride;
2322
        }
2323
        FPDFBitmap_Destroy(bitmap);
2324
    }
2325
#endif  // ~ HAVE_PDFIUM
2326
2327
7.32k
    return eErr;
2328
7.32k
}
2329
2330
/************************************************************************/
2331
/* ==================================================================== */
2332
/*                        PDFImageRasterBand                            */
2333
/* ==================================================================== */
2334
/************************************************************************/
2335
2336
class PDFImageRasterBand final : public PDFRasterBand
2337
{
2338
    friend class PDFDataset;
2339
2340
  public:
2341
    PDFImageRasterBand(PDFDataset *, int);
2342
2343
    CPLErr IReadBlock(int, int, void *) override;
2344
};
2345
2346
/************************************************************************/
2347
/*                         PDFImageRasterBand()                         */
2348
/************************************************************************/
2349
2350
PDFImageRasterBand::PDFImageRasterBand(PDFDataset *poDSIn, int nBandIn)
2351
0
    : PDFRasterBand(poDSIn, nBandIn, 0)
2352
0
{
2353
0
}
2354
2355
/************************************************************************/
2356
/*                             IReadBlock()                             */
2357
/************************************************************************/
2358
2359
CPLErr PDFImageRasterBand::IReadBlock(int /* nBlockXOff */, int nBlockYOff,
2360
                                      void *pImage)
2361
0
{
2362
0
    PDFDataset *poGDS = cpl::down_cast<PDFDataset *>(poDS);
2363
0
    CPLAssert(poGDS->m_poImageObj != nullptr);
2364
2365
0
    if (!poGDS->m_bTried)
2366
0
    {
2367
0
        int nBands = (poGDS->nBands == 1) ? 1 : 3;
2368
0
        poGDS->m_bTried = true;
2369
0
        if (nBands == 3)
2370
0
        {
2371
0
            poGDS->m_pabyCachedData = static_cast<GByte *>(
2372
0
                VSIMalloc3(nBands, nRasterXSize, nRasterYSize));
2373
0
            if (poGDS->m_pabyCachedData == nullptr)
2374
0
                return CE_Failure;
2375
0
        }
2376
2377
0
        GDALPDFStream *poStream = poGDS->m_poImageObj->GetStream();
2378
0
        GByte *pabyStream = nullptr;
2379
2380
0
        if (poStream == nullptr ||
2381
0
            static_cast<size_t>(poStream->GetLength()) !=
2382
0
                static_cast<size_t>(nBands) * nRasterXSize * nRasterYSize ||
2383
0
            (pabyStream = reinterpret_cast<GByte *>(poStream->GetBytes())) ==
2384
0
                nullptr)
2385
0
        {
2386
0
            VSIFree(poGDS->m_pabyCachedData);
2387
0
            poGDS->m_pabyCachedData = nullptr;
2388
0
            return CE_Failure;
2389
0
        }
2390
2391
0
        if (nBands == 3)
2392
0
        {
2393
            /* pixel interleaved to band interleaved */
2394
0
            for (size_t i = 0;
2395
0
                 i < static_cast<size_t>(nRasterXSize) * nRasterYSize; i++)
2396
0
            {
2397
0
                poGDS->m_pabyCachedData[0 * static_cast<size_t>(nRasterXSize) *
2398
0
                                            nRasterYSize +
2399
0
                                        i] = pabyStream[3 * i + 0];
2400
0
                poGDS->m_pabyCachedData[1 * static_cast<size_t>(nRasterXSize) *
2401
0
                                            nRasterYSize +
2402
0
                                        i] = pabyStream[3 * i + 1];
2403
0
                poGDS->m_pabyCachedData[2 * static_cast<size_t>(nRasterXSize) *
2404
0
                                            nRasterYSize +
2405
0
                                        i] = pabyStream[3 * i + 2];
2406
0
            }
2407
0
            VSIFree(pabyStream);
2408
0
        }
2409
0
        else
2410
0
            poGDS->m_pabyCachedData = pabyStream;
2411
0
    }
2412
2413
0
    if (poGDS->m_pabyCachedData == nullptr)
2414
0
        return CE_Failure;
2415
2416
0
    if (nBand == 4)
2417
0
        memset(pImage, 255, nRasterXSize);
2418
0
    else
2419
0
        memcpy(pImage,
2420
0
               poGDS->m_pabyCachedData +
2421
0
                   static_cast<size_t>(nBand - 1) * nRasterXSize *
2422
0
                       nRasterYSize +
2423
0
                   static_cast<size_t>(nBlockYOff) * nRasterXSize,
2424
0
               nRasterXSize);
2425
2426
0
    return CE_None;
2427
0
}
2428
2429
/************************************************************************/
2430
/*                             PDFDataset()                             */
2431
/************************************************************************/
2432
2433
PDFDataset::PDFDataset(PDFDataset *poParentDSIn, int nXSize, int nYSize)
2434
32.6k
    : m_bIsOvrDS(poParentDSIn != nullptr),
2435
#ifdef HAVE_PDFIUM
2436
      m_poDocPdfium(poParentDSIn ? poParentDSIn->m_poDocPdfium : nullptr),
2437
      m_poPagePdfium(poParentDSIn ? poParentDSIn->m_poPagePdfium : nullptr),
2438
#endif
2439
32.6k
      m_bSetStyle(CPLTestBool(CPLGetConfigOption("OGR_PDF_SET_STYLE", "YES")))
2440
32.6k
{
2441
32.6k
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2442
32.6k
    nRasterXSize = nXSize;
2443
32.6k
    nRasterYSize = nYSize;
2444
32.6k
    if (poParentDSIn)
2445
0
        m_bUseLib = poParentDSIn->m_bUseLib;
2446
2447
32.6k
    InitMapOperators();
2448
32.6k
}
2449
2450
/************************************************************************/
2451
/*                          IBuildOverviews()                           */
2452
/************************************************************************/
2453
2454
CPLErr PDFDataset::IBuildOverviews(const char *pszResampling, int nOverviews,
2455
                                   const int *panOverviewList, int nListBands,
2456
                                   const int *panBandList,
2457
                                   GDALProgressFunc pfnProgress,
2458
                                   void *pProgressData,
2459
                                   CSLConstList papszOptions)
2460
2461
0
{
2462
    /* -------------------------------------------------------------------- */
2463
    /*      In order for building external overviews to work properly we    */
2464
    /*      discard any concept of internal overviews when the user         */
2465
    /*      first requests to build external overviews.                     */
2466
    /* -------------------------------------------------------------------- */
2467
0
    if (!m_apoOvrDS.empty())
2468
0
    {
2469
0
        m_apoOvrDSBackup = std::move(m_apoOvrDS);
2470
0
        m_apoOvrDS.clear();
2471
0
    }
2472
2473
    // Prevents InitOverviews() to run
2474
0
    m_apoOvrDSBackup.emplace_back(nullptr);
2475
0
    const CPLErr eErr = GDALPamDataset::IBuildOverviews(
2476
0
        pszResampling, nOverviews, panOverviewList, nListBands, panBandList,
2477
0
        pfnProgress, pProgressData, papszOptions);
2478
0
    m_apoOvrDSBackup.pop_back();
2479
0
    return eErr;
2480
0
}
2481
2482
/************************************************************************/
2483
/*                             PDFFreeDoc()                             */
2484
/************************************************************************/
2485
2486
#ifdef HAVE_POPPLER
2487
static void PDFFreeDoc(PDFDoc *poDoc)
2488
43.4k
{
2489
43.4k
    if (poDoc)
2490
43.4k
    {
2491
43.4k
#if POPPLER_MAJOR_VERSION < 26 ||                                              \
2492
43.4k
    (POPPLER_MAJOR_VERSION == 26 && POPPLER_MINOR_VERSION < 2)
2493
        /* hack to avoid potential cross heap issues on Win32 */
2494
        /* str is the VSIPDFFileStream object passed in the constructor of
2495
         * PDFDoc */
2496
        // NOTE: This is potentially very dangerous. See comment in
2497
        // VSIPDFFileStream::FillBuffer() */
2498
43.4k
        delete poDoc->str;
2499
43.4k
        poDoc->str = nullptr;
2500
43.4k
#endif
2501
2502
43.4k
        delete poDoc;
2503
43.4k
    }
2504
43.4k
}
2505
#endif
2506
2507
/************************************************************************/
2508
/*                             GetCatalog()                             */
2509
/************************************************************************/
2510
2511
GDALPDFObject *PDFDataset::GetCatalog()
2512
78.6k
{
2513
78.6k
    if (m_poCatalogObject)
2514
45.9k
        return m_poCatalogObject;
2515
2516
32.6k
#ifdef HAVE_POPPLER
2517
32.6k
    if (m_bUseLib.test(PDFLIB_POPPLER) && m_poDocPoppler)
2518
32.6k
    {
2519
32.6k
        m_poCatalogObjectPoppler =
2520
32.6k
            std::make_unique<Object>(m_poDocPoppler->getXRef()->getCatalog());
2521
32.6k
        if (!m_poCatalogObjectPoppler->isNull())
2522
32.6k
            m_poCatalogObject =
2523
32.6k
                new GDALPDFObjectPoppler(m_poCatalogObjectPoppler.get(), FALSE);
2524
32.6k
    }
2525
32.6k
#endif
2526
2527
#ifdef HAVE_PODOFO
2528
    if (m_bUseLib.test(PDFLIB_PODOFO) && m_poDocPodofo)
2529
    {
2530
        int nCatalogNum = 0;
2531
        int nCatalogGen = 0;
2532
        VSILFILE *fp = VSIFOpenL(m_osFilename.c_str(), "rb");
2533
        if (fp != nullptr)
2534
        {
2535
            GDALPDFUpdateWriter oWriter(fp);
2536
            if (oWriter.ParseTrailerAndXRef())
2537
            {
2538
                nCatalogNum = oWriter.GetCatalogNum().toInt();
2539
                nCatalogGen = oWriter.GetCatalogGen();
2540
            }
2541
            oWriter.Close();
2542
        }
2543
2544
        PoDoFo::PdfObject *poCatalogPodofo =
2545
            m_poDocPodofo->GetObjects().GetObject(
2546
                PoDoFo::PdfReference(nCatalogNum, nCatalogGen));
2547
        if (poCatalogPodofo)
2548
            m_poCatalogObject = new GDALPDFObjectPodofo(
2549
                poCatalogPodofo, m_poDocPodofo->GetObjects());
2550
    }
2551
#endif
2552
2553
#ifdef HAVE_PDFIUM
2554
    if (m_bUseLib.test(PDFLIB_PDFIUM) && m_poDocPdfium)
2555
    {
2556
        RetainPtr<CPDF_Dictionary> catalog =
2557
            m_poDocPdfium->doc->GetMutableRoot();
2558
        if (catalog)
2559
            m_poCatalogObject = GDALPDFObjectPdfium::Build(catalog);
2560
    }
2561
#endif  // ~ HAVE_PDFIUM
2562
2563
32.6k
    return m_poCatalogObject;
2564
78.6k
}
2565
2566
/************************************************************************/
2567
/*                            ~PDFDataset()                             */
2568
/************************************************************************/
2569
2570
PDFDataset::~PDFDataset()
2571
32.6k
{
2572
#ifdef HAVE_PDFIUM
2573
    m_apoOvrDS.clear();
2574
    m_apoOvrDSBackup.clear();
2575
#endif
2576
2577
32.6k
    CPLFree(m_pabyCachedData);
2578
32.6k
    m_pabyCachedData = nullptr;
2579
2580
32.6k
    delete m_poNeatLine;
2581
32.6k
    m_poNeatLine = nullptr;
2582
2583
    /* Collect data necessary to update */
2584
32.6k
    int nNum = 0;
2585
32.6k
    int nGen = 0;
2586
32.6k
    GDALPDFDictionaryRW *poPageDictCopy = nullptr;
2587
32.6k
    GDALPDFDictionaryRW *poCatalogDictCopy = nullptr;
2588
32.6k
    if (m_poPageObj)
2589
32.6k
    {
2590
32.6k
        nNum = m_poPageObj->GetRefNum().toInt();
2591
32.6k
        nGen = m_poPageObj->GetRefGen();
2592
32.6k
        if (eAccess == GA_Update &&
2593
0
            (m_bProjDirty || m_bNeatLineDirty || m_bInfoDirty || m_bXMPDirty) &&
2594
0
            nNum != 0 && m_poPageObj != nullptr &&
2595
0
            m_poPageObj->GetType() == PDFObjectType_Dictionary)
2596
0
        {
2597
0
            poPageDictCopy = m_poPageObj->GetDictionary()->Clone();
2598
2599
0
            if (m_bXMPDirty)
2600
0
            {
2601
                /* We need the catalog because it points to the XMP Metadata
2602
                 * object */
2603
0
                GetCatalog();
2604
0
                if (m_poCatalogObject &&
2605
0
                    m_poCatalogObject->GetType() == PDFObjectType_Dictionary)
2606
0
                    poCatalogDictCopy =
2607
0
                        m_poCatalogObject->GetDictionary()->Clone();
2608
0
            }
2609
0
        }
2610
32.6k
    }
2611
2612
    /* Close document (and file descriptor) to be able to open it */
2613
    /* in read-write mode afterwards */
2614
32.6k
    delete m_poPageObj;
2615
32.6k
    m_poPageObj = nullptr;
2616
32.6k
    delete m_poCatalogObject;
2617
32.6k
    m_poCatalogObject = nullptr;
2618
32.6k
#ifdef HAVE_POPPLER
2619
32.6k
    if (m_bUseLib.test(PDFLIB_POPPLER))
2620
32.6k
    {
2621
32.6k
        m_poCatalogObjectPoppler.reset();
2622
32.6k
        PDFFreeDoc(m_poDocPoppler);
2623
32.6k
    }
2624
32.6k
    m_poDocPoppler = nullptr;
2625
32.6k
#endif
2626
#ifdef HAVE_PODOFO
2627
    if (m_bUseLib.test(PDFLIB_PODOFO))
2628
    {
2629
        delete m_poDocPodofo;
2630
    }
2631
    m_poDocPodofo = nullptr;
2632
#endif
2633
#ifdef HAVE_PDFIUM
2634
    if (!m_bIsOvrDS)
2635
    {
2636
        if (m_bUseLib.test(PDFLIB_PDFIUM))
2637
        {
2638
            UnloadPdfiumDocumentPage(&m_poDocPdfium, &m_poPagePdfium);
2639
        }
2640
    }
2641
    m_poDocPdfium = nullptr;
2642
    m_poPagePdfium = nullptr;
2643
#endif  // ~ HAVE_PDFIUM
2644
2645
32.6k
    m_bHasLoadedLayers = true;
2646
32.6k
    m_apoLayers.clear();
2647
2648
    /* Now do the update */
2649
32.6k
    if (poPageDictCopy)
2650
0
    {
2651
0
        VSILFILE *fp = VSIFOpenL(m_osFilename, "rb+");
2652
0
        if (fp != nullptr)
2653
0
        {
2654
0
            GDALPDFUpdateWriter oWriter(fp);
2655
0
            if (oWriter.ParseTrailerAndXRef())
2656
0
            {
2657
0
                if ((m_bProjDirty || m_bNeatLineDirty) &&
2658
0
                    poPageDictCopy != nullptr)
2659
0
                    oWriter.UpdateProj(this, m_dfDPI, poPageDictCopy,
2660
0
                                       GDALPDFObjectNum(nNum), nGen);
2661
2662
0
                if (m_bInfoDirty)
2663
0
                    oWriter.UpdateInfo(this);
2664
2665
0
                if (m_bXMPDirty && poCatalogDictCopy != nullptr)
2666
0
                    oWriter.UpdateXMP(this, poCatalogDictCopy);
2667
0
            }
2668
0
            oWriter.Close();
2669
0
        }
2670
0
        else
2671
0
        {
2672
0
            CPLError(CE_Failure, CPLE_AppDefined,
2673
0
                     "Cannot open %s in update mode", m_osFilename.c_str());
2674
0
        }
2675
0
    }
2676
32.6k
    delete poPageDictCopy;
2677
32.6k
    poPageDictCopy = nullptr;
2678
32.6k
    delete poCatalogDictCopy;
2679
32.6k
    poCatalogDictCopy = nullptr;
2680
2681
32.6k
    if (m_nGCPCount > 0)
2682
23
    {
2683
23
        GDALDeinitGCPs(m_nGCPCount, m_pasGCPList);
2684
23
        CPLFree(m_pasGCPList);
2685
23
        m_pasGCPList = nullptr;
2686
23
        m_nGCPCount = 0;
2687
23
    }
2688
2689
32.6k
    CleanupIntermediateResources();
2690
2691
    // Do that only after having destroyed Poppler objects
2692
32.6k
    m_fp.reset();
2693
32.6k
}
2694
2695
/************************************************************************/
2696
/*                             IRasterIO()                              */
2697
/************************************************************************/
2698
2699
CPLErr PDFDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
2700
                             int nXSize, int nYSize, void *pData, int nBufXSize,
2701
                             int nBufYSize, GDALDataType eBufType,
2702
                             int nBandCount, BANDMAP_TYPE panBandMap,
2703
                             GSpacing nPixelSpace, GSpacing nLineSpace,
2704
                             GSpacing nBandSpace,
2705
                             GDALRasterIOExtraArg *psExtraArg)
2706
0
{
2707
    // Try to pass the request to the most appropriate overview dataset.
2708
0
    if (nBufXSize < nXSize && nBufYSize < nYSize)
2709
0
    {
2710
0
        int bTried = FALSE;
2711
0
        const CPLErr eErr = TryOverviewRasterIO(
2712
0
            eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
2713
0
            eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace,
2714
0
            nBandSpace, psExtraArg, &bTried);
2715
0
        if (bTried)
2716
0
            return eErr;
2717
0
    }
2718
2719
0
    int nBandBlockXSize, nBandBlockYSize;
2720
0
    int bReadPixels = FALSE;
2721
0
    GetRasterBand(1)->GetBlockSize(&nBandBlockXSize, &nBandBlockYSize);
2722
0
    if (m_aiTiles.empty() && eRWFlag == GF_Read && nXSize == nBufXSize &&
2723
0
        nYSize == nBufYSize &&
2724
0
        (nBufXSize > nBandBlockXSize || nBufYSize > nBandBlockYSize) &&
2725
0
        eBufType == GDT_UInt8 && nBandCount == nBands &&
2726
0
        IsAllBands(nBandCount, panBandMap))
2727
0
    {
2728
0
        bReadPixels = TRUE;
2729
#ifdef HAVE_PODOFO
2730
        if (m_bUseLib.test(PDFLIB_PODOFO) && nBands == 4)
2731
        {
2732
            bReadPixels = FALSE;
2733
        }
2734
#endif
2735
0
    }
2736
2737
0
    if (bReadPixels)
2738
0
        return ReadPixels(nXOff, nYOff, nXSize, nYSize, nPixelSpace, nLineSpace,
2739
0
                          nBandSpace, static_cast<GByte *>(pData));
2740
2741
0
    if (nBufXSize != nXSize || nBufYSize != nYSize || eBufType != GDT_UInt8)
2742
0
    {
2743
0
        m_bCacheBlocksForOtherBands = true;
2744
0
    }
2745
0
    CPLErr eErr = GDALPamDataset::IRasterIO(
2746
0
        eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
2747
0
        eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace, nBandSpace,
2748
0
        psExtraArg);
2749
0
    m_bCacheBlocksForOtherBands = false;
2750
0
    return eErr;
2751
0
}
2752
2753
/************************************************************************/
2754
/*                             IRasterIO()                              */
2755
/************************************************************************/
2756
2757
CPLErr PDFRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
2758
                                int nXSize, int nYSize, void *pData,
2759
                                int nBufXSize, int nBufYSize,
2760
                                GDALDataType eBufType, GSpacing nPixelSpace,
2761
                                GSpacing nLineSpace,
2762
                                GDALRasterIOExtraArg *psExtraArg)
2763
9.39M
{
2764
9.39M
    PDFDataset *poGDS = cpl::down_cast<PDFDataset *>(poDS);
2765
2766
    // Try to pass the request to the most appropriate overview dataset.
2767
9.39M
    if (nBufXSize < nXSize && nBufYSize < nYSize)
2768
0
    {
2769
0
        int bTried = FALSE;
2770
0
        const CPLErr eErr = TryOverviewRasterIO(
2771
0
            eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
2772
0
            eBufType, nPixelSpace, nLineSpace, psExtraArg, &bTried);
2773
0
        if (bTried)
2774
0
            return eErr;
2775
0
    }
2776
2777
9.39M
    if (nBufXSize != nXSize || nBufYSize != nYSize || eBufType != GDT_UInt8)
2778
9.39M
    {
2779
9.39M
        poGDS->m_bCacheBlocksForOtherBands = true;
2780
9.39M
    }
2781
9.39M
    CPLErr eErr = GDALPamRasterBand::IRasterIO(
2782
9.39M
        eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
2783
9.39M
        eBufType, nPixelSpace, nLineSpace, psExtraArg);
2784
9.39M
    poGDS->m_bCacheBlocksForOtherBands = false;
2785
9.39M
    return eErr;
2786
9.39M
}
2787
2788
/************************************************************************/
2789
/*                      PDFDatasetErrorFunction()                       */
2790
/************************************************************************/
2791
2792
#ifdef HAVE_POPPLER
2793
2794
static void PDFDatasetErrorFunctionCommon(const CPLString &osError)
2795
14.1M
{
2796
14.1M
    if (strcmp(osError.c_str(), "Incorrect password") == 0)
2797
67
        return;
2798
    /* Reported on newer USGS GeoPDF */
2799
14.1M
    if (strcmp(osError.c_str(),
2800
14.1M
               "Couldn't find group for reference to set OFF") == 0)
2801
503
    {
2802
503
        CPLDebug("PDF", "%s", osError.c_str());
2803
503
        return;
2804
503
    }
2805
2806
14.1M
    CPLError(CE_Failure, CPLE_AppDefined, "%s", osError.c_str());
2807
14.1M
}
2808
2809
static int g_nPopplerErrors = 0;
2810
constexpr int MAX_POPPLER_ERRORS = 1000;
2811
2812
static void PDFDatasetErrorFunction(ErrorCategory /* eErrCategory */,
2813
                                    Goffset nPos, const char *pszMsg)
2814
14.1M
{
2815
14.1M
    if (g_nPopplerErrors >= MAX_POPPLER_ERRORS)
2816
9.19k
    {
2817
        // If there are too many errors, then unregister ourselves and turn
2818
        // quiet error mode, as the error() function in poppler can spend
2819
        // significant time formatting an error message we won't emit...
2820
9.19k
        setErrorCallback(nullptr);
2821
9.19k
        globalParams->setErrQuiet(true);
2822
9.19k
        return;
2823
9.19k
    }
2824
2825
14.1M
    g_nPopplerErrors++;
2826
14.1M
    CPLString osError;
2827
2828
14.1M
    if (nPos >= 0)
2829
13.4M
        osError.Printf("Pos = " CPL_FRMT_GUIB ", ",
2830
13.4M
                       static_cast<GUIntBig>(nPos));
2831
14.1M
    osError += pszMsg;
2832
14.1M
    PDFDatasetErrorFunctionCommon(osError);
2833
14.1M
}
2834
#endif
2835
2836
/************************************************************************/
2837
/*               GDALPDFParseStreamContentOnlyDrawForm()                */
2838
/************************************************************************/
2839
2840
static CPLString GDALPDFParseStreamContentOnlyDrawForm(const char *pszContent)
2841
8.05k
{
2842
8.05k
    CPLString osToken;
2843
8.05k
    char ch;
2844
8.05k
    int nCurIdx = 0;
2845
8.05k
    CPLString osCurrentForm;
2846
2847
    // CPLDebug("PDF", "content = %s", pszContent);
2848
2849
7.71M
    while ((ch = *pszContent) != '\0')
2850
7.71M
    {
2851
7.71M
        if (ch == '%')
2852
1.44k
        {
2853
            /* Skip comments until end-of-line */
2854
45.8k
            while ((ch = *pszContent) != '\0')
2855
45.8k
            {
2856
45.8k
                if (ch == '\r' || ch == '\n')
2857
1.43k
                    break;
2858
44.3k
                pszContent++;
2859
44.3k
            }
2860
1.44k
            if (ch == 0)
2861
10
                break;
2862
1.44k
        }
2863
7.71M
        else if (ch == ' ' || ch == '\r' || ch == '\n')
2864
13.1k
        {
2865
13.1k
            if (!osToken.empty())
2866
10.7k
            {
2867
10.7k
                if (nCurIdx == 0 && osToken[0] == '/')
2868
3.08k
                {
2869
3.08k
                    osCurrentForm = osToken.substr(1);
2870
3.08k
                    nCurIdx++;
2871
3.08k
                }
2872
7.69k
                else if (nCurIdx == 1 && osToken == "Do")
2873
79
                {
2874
79
                    nCurIdx++;
2875
79
                }
2876
7.61k
                else
2877
7.61k
                {
2878
7.61k
                    return "";
2879
7.61k
                }
2880
10.7k
            }
2881
5.54k
            osToken = "";
2882
5.54k
        }
2883
7.70M
        else
2884
7.70M
            osToken += ch;
2885
7.70M
        pszContent++;
2886
7.70M
    }
2887
2888
441
    return osCurrentForm;
2889
8.05k
}
2890
2891
/************************************************************************/
2892
/*                     GDALPDFParseStreamContent()                      */
2893
/************************************************************************/
2894
2895
typedef enum
2896
{
2897
    STATE_INIT,
2898
    STATE_AFTER_q,
2899
    STATE_AFTER_cm,
2900
    STATE_AFTER_Do
2901
} PDFStreamState;
2902
2903
/* This parser is reduced to understanding sequences that draw rasters, such as
2904
   :
2905
   q
2906
   scaleX 0 0 scaleY translateX translateY cm
2907
   /ImXXX Do
2908
   Q
2909
2910
   All other sequences will abort the parsing.
2911
2912
   Returns TRUE if the stream only contains images.
2913
*/
2914
2915
static int GDALPDFParseStreamContent(const char *pszContent,
2916
                                     GDALPDFDictionary *poXObjectDict,
2917
                                     double *pdfDPI, int *pbDPISet,
2918
                                     int *pnBands,
2919
                                     std::vector<GDALPDFTileDesc> &asTiles,
2920
                                     int bAcceptRotationTerms)
2921
8.00k
{
2922
8.00k
    CPLString osToken;
2923
8.00k
    char ch;
2924
8.00k
    PDFStreamState nState = STATE_INIT;
2925
8.00k
    int nCurIdx = 0;
2926
8.00k
    double adfVals[6];
2927
8.00k
    CPLString osCurrentImage;
2928
2929
8.00k
    double dfDPI = DEFAULT_DPI;
2930
8.00k
    *pbDPISet = FALSE;
2931
2932
7.76M
    while ((ch = *pszContent) != '\0')
2933
7.75M
    {
2934
7.75M
        if (ch == '%')
2935
1.46k
        {
2936
            /* Skip comments until end-of-line */
2937
45.8k
            while ((ch = *pszContent) != '\0')
2938
45.8k
            {
2939
45.8k
                if (ch == '\r' || ch == '\n')
2940
1.45k
                    break;
2941
44.3k
                pszContent++;
2942
44.3k
            }
2943
1.46k
            if (ch == 0)
2944
12
                break;
2945
1.46k
        }
2946
7.75M
        else if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n')
2947
39.7k
        {
2948
39.7k
            if (!osToken.empty())
2949
31.4k
            {
2950
31.4k
                if (nState == STATE_INIT)
2951
8.25k
                {
2952
8.25k
                    if (osToken == "q")
2953
2.87k
                    {
2954
2.87k
                        nState = STATE_AFTER_q;
2955
2.87k
                        nCurIdx = 0;
2956
2.87k
                    }
2957
5.37k
                    else if (osToken != "Q")
2958
5.21k
                        return FALSE;
2959
8.25k
                }
2960
23.2k
                else if (nState == STATE_AFTER_q)
2961
20.4k
                {
2962
20.4k
                    if (osToken == "q")
2963
1.04k
                    {
2964
                        // ignore
2965
1.04k
                    }
2966
19.4k
                    else if (nCurIdx < 6)
2967
16.7k
                    {
2968
16.7k
                        adfVals[nCurIdx++] = CPLAtof(osToken);
2969
16.7k
                    }
2970
2.67k
                    else if (nCurIdx == 6 && osToken == "cm")
2971
1.26k
                    {
2972
1.26k
                        nState = STATE_AFTER_cm;
2973
1.26k
                        nCurIdx = 0;
2974
1.26k
                    }
2975
1.41k
                    else
2976
1.41k
                        return FALSE;
2977
20.4k
                }
2978
2.78k
                else if (nState == STATE_AFTER_cm)
2979
2.14k
                {
2980
2.14k
                    if (nCurIdx == 0 && osToken[0] == '/')
2981
898
                    {
2982
898
                        osCurrentImage = osToken.substr(1);
2983
898
                    }
2984
1.25k
                    else if (osToken == "Do")
2985
691
                    {
2986
691
                        nState = STATE_AFTER_Do;
2987
691
                    }
2988
559
                    else
2989
559
                        return FALSE;
2990
2.14k
                }
2991
635
                else if (nState == STATE_AFTER_Do)
2992
635
                {
2993
635
                    if (osToken == "Q")
2994
614
                    {
2995
614
                        GDALPDFObject *poImage =
2996
614
                            poXObjectDict->Get(osCurrentImage);
2997
614
                        if (poImage != nullptr &&
2998
557
                            poImage->GetType() == PDFObjectType_Dictionary)
2999
548
                        {
3000
548
                            GDALPDFTileDesc sTile;
3001
548
                            GDALPDFDictionary *poImageDict =
3002
548
                                poImage->GetDictionary();
3003
548
                            GDALPDFObject *poWidth = poImageDict->Get("Width");
3004
548
                            GDALPDFObject *poHeight =
3005
548
                                poImageDict->Get("Height");
3006
548
                            GDALPDFObject *poColorSpace =
3007
548
                                poImageDict->Get("ColorSpace");
3008
548
                            GDALPDFObject *poSMask = poImageDict->Get("SMask");
3009
548
                            if (poColorSpace &&
3010
527
                                poColorSpace->GetType() == PDFObjectType_Name)
3011
295
                            {
3012
295
                                if (poColorSpace->GetName() == "DeviceRGB")
3013
177
                                {
3014
177
                                    sTile.nBands = 3;
3015
177
                                    if (*pnBands < 3)
3016
161
                                        *pnBands = 3;
3017
177
                                }
3018
118
                                else if (poColorSpace->GetName() ==
3019
118
                                         "DeviceGray")
3020
111
                                {
3021
111
                                    sTile.nBands = 1;
3022
111
                                    if (*pnBands < 1)
3023
91
                                        *pnBands = 1;
3024
111
                                }
3025
7
                                else
3026
7
                                    sTile.nBands = 0;
3027
295
                            }
3028
548
                            if (poSMask != nullptr)
3029
6
                                *pnBands = 4;
3030
3031
548
                            if (poWidth && poHeight &&
3032
515
                                ((bAcceptRotationTerms &&
3033
0
                                  adfVals[1] == -adfVals[2]) ||
3034
515
                                 (!bAcceptRotationTerms && adfVals[1] == 0.0 &&
3035
515
                                  adfVals[2] == 0.0)))
3036
513
                            {
3037
513
                                double dfWidth = Get(poWidth);
3038
513
                                double dfHeight = Get(poHeight);
3039
513
                                double dfScaleX = adfVals[0];
3040
513
                                double dfScaleY = adfVals[3];
3041
513
                                if (dfWidth > 0 && dfHeight > 0 &&
3042
511
                                    dfScaleX > 0 && dfScaleY > 0 &&
3043
510
                                    dfWidth / dfScaleX * DEFAULT_DPI <
3044
510
                                        INT_MAX &&
3045
510
                                    dfHeight / dfScaleY * DEFAULT_DPI < INT_MAX)
3046
510
                                {
3047
510
                                    double dfDPI_X = ROUND_IF_CLOSE(
3048
510
                                        dfWidth / dfScaleX * DEFAULT_DPI, 1e-3);
3049
510
                                    double dfDPI_Y = ROUND_IF_CLOSE(
3050
510
                                        dfHeight / dfScaleY * DEFAULT_DPI,
3051
510
                                        1e-3);
3052
                                    // CPLDebug("PDF", "Image %s, width = %.16g,
3053
                                    // height = %.16g, scaleX = %.16g, scaleY =
3054
                                    // %.16g --> DPI_X = %.16g, DPI_Y = %.16g",
3055
                                    //                 osCurrentImage.c_str(),
3056
                                    //                 dfWidth, dfHeight,
3057
                                    //                 dfScaleX, dfScaleY,
3058
                                    //                 dfDPI_X, dfDPI_Y);
3059
510
                                    if (dfDPI_X > dfDPI)
3060
233
                                        dfDPI = dfDPI_X;
3061
510
                                    if (dfDPI_Y > dfDPI)
3062
35
                                        dfDPI = dfDPI_Y;
3063
3064
510
                                    memcpy(&(sTile.adfCM), adfVals,
3065
510
                                           6 * sizeof(double));
3066
510
                                    sTile.poImage = poImage;
3067
510
                                    sTile.dfWidth = dfWidth;
3068
510
                                    sTile.dfHeight = dfHeight;
3069
510
                                    asTiles.push_back(sTile);
3070
3071
510
                                    *pbDPISet = TRUE;
3072
510
                                    *pdfDPI = dfDPI;
3073
510
                                }
3074
513
                            }
3075
548
                        }
3076
614
                        nState = STATE_INIT;
3077
614
                    }
3078
21
                    else
3079
21
                        return FALSE;
3080
635
                }
3081
31.4k
            }
3082
32.5k
            osToken = "";
3083
32.5k
        }
3084
7.71M
        else
3085
7.71M
            osToken += ch;
3086
7.75M
        pszContent++;
3087
7.75M
    }
3088
3089
797
    return TRUE;
3090
8.00k
}
3091
3092
/************************************************************************/
3093
/*                          CheckTiledRaster()                          */
3094
/************************************************************************/
3095
3096
int PDFDataset::CheckTiledRaster()
3097
205
{
3098
205
    size_t i;
3099
205
    int l_nBlockXSize = 0;
3100
205
    int l_nBlockYSize = 0;
3101
205
    const double dfUserUnit = m_dfDPI * USER_UNIT_IN_INCH;
3102
3103
    /* First pass : check that all tiles have same DPI, */
3104
    /* are contained entirely in the raster size, */
3105
    /* and determine the block size */
3106
266
    for (i = 0; i < m_asTiles.size(); i++)
3107
207
    {
3108
207
        double dfDrawWidth = m_asTiles[i].adfCM[0] * dfUserUnit;
3109
207
        double dfDrawHeight = m_asTiles[i].adfCM[3] * dfUserUnit;
3110
207
        double dfX = m_asTiles[i].adfCM[4] * dfUserUnit;
3111
207
        double dfY = m_asTiles[i].adfCM[5] * dfUserUnit;
3112
207
        int nX = static_cast<int>(dfX + 0.1);
3113
207
        int nY = static_cast<int>(dfY + 0.1);
3114
207
        int nWidth = static_cast<int>(m_asTiles[i].dfWidth + 1e-8);
3115
207
        int nHeight = static_cast<int>(m_asTiles[i].dfHeight + 1e-8);
3116
3117
207
        GDALPDFDictionary *poImageDict = m_asTiles[i].poImage->GetDictionary();
3118
207
        GDALPDFObject *poBitsPerComponent =
3119
207
            poImageDict->Get("BitsPerComponent");
3120
207
        GDALPDFObject *poColorSpace = poImageDict->Get("ColorSpace");
3121
207
        GDALPDFObject *poFilter = poImageDict->Get("Filter");
3122
3123
        /* Podofo cannot uncompress JPEG2000 streams */
3124
207
        if (m_bUseLib.test(PDFLIB_PODOFO) && poFilter != nullptr &&
3125
0
            poFilter->GetType() == PDFObjectType_Name &&
3126
0
            poFilter->GetName() == "JPXDecode")
3127
0
        {
3128
0
            CPLDebug("PDF", "Tile %d : Incompatible image for tiled reading",
3129
0
                     static_cast<int>(i));
3130
0
            return FALSE;
3131
0
        }
3132
3133
207
        if (poBitsPerComponent == nullptr || Get(poBitsPerComponent) != 8 ||
3134
148
            poColorSpace == nullptr ||
3135
148
            poColorSpace->GetType() != PDFObjectType_Name ||
3136
107
            (poColorSpace->GetName() != "DeviceRGB" &&
3137
39
             poColorSpace->GetName() != "DeviceGray"))
3138
103
        {
3139
103
            CPLDebug("PDF", "Tile %d : Incompatible image for tiled reading",
3140
103
                     static_cast<int>(i));
3141
103
            return FALSE;
3142
103
        }
3143
3144
104
        if (fabs(dfDrawWidth - m_asTiles[i].dfWidth) > 1e-2 ||
3145
84
            fabs(dfDrawHeight - m_asTiles[i].dfHeight) > 1e-2 ||
3146
80
            fabs(nWidth - m_asTiles[i].dfWidth) > 1e-8 ||
3147
80
            fabs(nHeight - m_asTiles[i].dfHeight) > 1e-8 ||
3148
80
            fabs(nX - dfX) > 1e-1 || fabs(nY - dfY) > 1e-1 || nX < 0 ||
3149
62
            nY < 0 || nX + nWidth > nRasterXSize || nY >= nRasterYSize)
3150
43
        {
3151
43
            CPLDebug("PDF", "Tile %d : %f %f %f %f %f %f", static_cast<int>(i),
3152
43
                     dfX, dfY, dfDrawWidth, dfDrawHeight, m_asTiles[i].dfWidth,
3153
43
                     m_asTiles[i].dfHeight);
3154
43
            return FALSE;
3155
43
        }
3156
61
        if (l_nBlockXSize == 0 && l_nBlockYSize == 0 && nX == 0 && nY != 0)
3157
0
        {
3158
0
            l_nBlockXSize = nWidth;
3159
0
            l_nBlockYSize = nHeight;
3160
0
        }
3161
61
    }
3162
59
    if (l_nBlockXSize <= 0 || l_nBlockYSize <= 0 || l_nBlockXSize > 2048 ||
3163
0
        l_nBlockYSize > 2048)
3164
59
        return FALSE;
3165
3166
0
    int nXBlocks = DIV_ROUND_UP(nRasterXSize, l_nBlockXSize);
3167
0
    int nYBlocks = DIV_ROUND_UP(nRasterYSize, l_nBlockYSize);
3168
3169
    /* Second pass to determine that all tiles are properly aligned on block
3170
     * size */
3171
0
    for (i = 0; i < m_asTiles.size(); i++)
3172
0
    {
3173
0
        double dfX = m_asTiles[i].adfCM[4] * dfUserUnit;
3174
0
        double dfY = m_asTiles[i].adfCM[5] * dfUserUnit;
3175
0
        int nX = static_cast<int>(dfX + 0.1);
3176
0
        int nY = static_cast<int>(dfY + 0.1);
3177
0
        int nWidth = static_cast<int>(m_asTiles[i].dfWidth + 1e-8);
3178
0
        int nHeight = static_cast<int>(m_asTiles[i].dfHeight + 1e-8);
3179
0
        int bOK = TRUE;
3180
0
        int nBlockXOff = nX / l_nBlockXSize;
3181
0
        if ((nX % l_nBlockXSize) != 0)
3182
0
            bOK = FALSE;
3183
0
        if (nBlockXOff < nXBlocks - 1 && nWidth != l_nBlockXSize)
3184
0
            bOK = FALSE;
3185
0
        if (nBlockXOff == nXBlocks - 1 && nX + nWidth != nRasterXSize)
3186
0
            bOK = FALSE;
3187
3188
0
        if (nY > 0 && nHeight != l_nBlockYSize)
3189
0
            bOK = FALSE;
3190
0
        if (nY == 0 && nHeight != nRasterYSize - (nYBlocks - 1) * l_nBlockYSize)
3191
0
            bOK = FALSE;
3192
3193
0
        if (!bOK)
3194
0
        {
3195
0
            CPLDebug("PDF", "Tile %d : %d %d %d %d", static_cast<int>(i), nX,
3196
0
                     nY, nWidth, nHeight);
3197
0
            return FALSE;
3198
0
        }
3199
0
    }
3200
3201
    /* Third pass to set the aiTiles array */
3202
0
    m_aiTiles.resize(static_cast<size_t>(nXBlocks) * nYBlocks, -1);
3203
0
    for (i = 0; i < m_asTiles.size(); i++)
3204
0
    {
3205
0
        double dfX = m_asTiles[i].adfCM[4] * dfUserUnit;
3206
0
        double dfY = m_asTiles[i].adfCM[5] * dfUserUnit;
3207
0
        int nHeight = static_cast<int>(m_asTiles[i].dfHeight + 1e-8);
3208
0
        int nX = static_cast<int>(dfX + 0.1);
3209
0
        int nY = nRasterYSize - (static_cast<int>(dfY + 0.1) + nHeight);
3210
0
        int nBlockXOff = nX / l_nBlockXSize;
3211
0
        int nBlockYOff = nY / l_nBlockYSize;
3212
0
        m_aiTiles[nBlockYOff * nXBlocks + nBlockXOff] = static_cast<int>(i);
3213
0
    }
3214
3215
0
    this->m_nBlockXSize = l_nBlockXSize;
3216
0
    this->m_nBlockYSize = l_nBlockYSize;
3217
3218
0
    return TRUE;
3219
0
}
3220
3221
/************************************************************************/
3222
/*                        GuessDPIAndBandCount()                        */
3223
/************************************************************************/
3224
3225
void PDFDataset::GuessDPIAndBandCount(GDALPDFDictionary *poPageDict,
3226
                                      double &dfDPI, int &nBandsGuessed)
3227
32.6k
{
3228
    /* Try to get a better value from the images that are drawn */
3229
    /* Very simplistic logic. Will only work for raster only PDF */
3230
3231
32.6k
    GDALPDFObject *poContents = poPageDict->Get("Contents");
3232
32.6k
    if (poContents != nullptr && poContents->GetType() == PDFObjectType_Array)
3233
1.90k
    {
3234
1.90k
        GDALPDFArray *poContentsArray = poContents->GetArray();
3235
1.90k
        if (poContentsArray->GetLength() == 1)
3236
317
        {
3237
317
            poContents = poContentsArray->Get(0);
3238
317
        }
3239
1.90k
    }
3240
3241
32.6k
    GDALPDFObject *poXObject = poPageDict->LookupObject("Resources.XObject");
3242
32.6k
    if (poContents != nullptr &&
3243
25.8k
        poContents->GetType() == PDFObjectType_Dictionary &&
3244
24.1k
        poXObject != nullptr &&
3245
8.55k
        poXObject->GetType() == PDFObjectType_Dictionary)
3246
8.44k
    {
3247
8.44k
        GDALPDFDictionary *poXObjectDict = poXObject->GetDictionary();
3248
8.44k
        GDALPDFDictionary *poContentDict = poXObjectDict;
3249
8.44k
        GDALPDFStream *poPageStream = poContents->GetStream();
3250
8.44k
        if (poPageStream != nullptr)
3251
8.13k
        {
3252
8.13k
            char *pszContent = nullptr;
3253
8.13k
            constexpr int64_t MAX_LENGTH = 10 * 1000 * 1000;
3254
8.13k
            const int64_t nLength = poPageStream->GetLength(MAX_LENGTH);
3255
8.13k
            int bResetTiles = FALSE;
3256
8.13k
            double dfScaleDPI = 1.0;
3257
3258
8.13k
            if (nLength < MAX_LENGTH)
3259
8.13k
            {
3260
8.13k
                CPLString osForm;
3261
8.13k
                pszContent = poPageStream->GetBytes();
3262
8.13k
                if (pszContent != nullptr)
3263
8.05k
                {
3264
#ifdef DEBUG
3265
                    const char *pszDumpStream =
3266
                        CPLGetConfigOption("PDF_DUMP_STREAM", nullptr);
3267
                    if (pszDumpStream != nullptr)
3268
                    {
3269
                        VSILFILE *fpDump = VSIFOpenL(pszDumpStream, "wb");
3270
                        if (fpDump)
3271
                        {
3272
                            VSIFWriteL(pszContent, 1, static_cast<int>(nLength),
3273
                                       fpDump);
3274
                            VSIFCloseL(fpDump);
3275
                        }
3276
                    }
3277
#endif  // DEBUG
3278
8.05k
                    osForm = GDALPDFParseStreamContentOnlyDrawForm(pszContent);
3279
8.05k
                    if (osForm.empty())
3280
7.91k
                    {
3281
                        /* Special case for USGS Topo PDF, like
3282
                             * CA_Hollywood_20090811_OM_geo.pdf */
3283
7.91k
                        const char *pszOGCDo = strstr(pszContent, " /XO1 Do");
3284
7.91k
                        if (pszOGCDo)
3285
13
                        {
3286
13
                            const char *pszcm = strstr(pszContent, " cm ");
3287
13
                            if (pszcm != nullptr && pszcm < pszOGCDo)
3288
0
                            {
3289
0
                                const char *pszNextcm = strstr(pszcm + 2, "cm");
3290
0
                                if (pszNextcm == nullptr ||
3291
0
                                    pszNextcm > pszOGCDo)
3292
0
                                {
3293
0
                                    const char *pszIter = pszcm;
3294
0
                                    while (pszIter > pszContent)
3295
0
                                    {
3296
0
                                        if ((*pszIter >= '0' &&
3297
0
                                             *pszIter <= '9') ||
3298
0
                                            *pszIter == '-' ||
3299
0
                                            *pszIter == '.' || *pszIter == ' ')
3300
0
                                            pszIter--;
3301
0
                                        else
3302
0
                                        {
3303
0
                                            pszIter++;
3304
0
                                            break;
3305
0
                                        }
3306
0
                                    }
3307
0
                                    CPLString oscm(pszIter);
3308
0
                                    oscm.resize(pszcm - pszIter);
3309
0
                                    char **papszTokens =
3310
0
                                        CSLTokenizeString(oscm);
3311
0
                                    double dfScaleX = -1.0;
3312
0
                                    double dfScaleY = -2.0;
3313
0
                                    if (CSLCount(papszTokens) == 6)
3314
0
                                    {
3315
0
                                        dfScaleX = CPLAtof(papszTokens[0]);
3316
0
                                        dfScaleY = CPLAtof(papszTokens[3]);
3317
0
                                    }
3318
0
                                    CSLDestroy(papszTokens);
3319
0
                                    if (dfScaleX == dfScaleY && dfScaleX > 0.0)
3320
0
                                    {
3321
0
                                        osForm = "XO1";
3322
0
                                        bResetTiles = TRUE;
3323
0
                                        dfScaleDPI = 1.0 / dfScaleX;
3324
0
                                    }
3325
0
                                }
3326
0
                            }
3327
13
                            else
3328
13
                            {
3329
13
                                osForm = "XO1";
3330
13
                                bResetTiles = TRUE;
3331
13
                            }
3332
13
                        }
3333
                        /* Special case for USGS Topo PDF, like
3334
                             * CA_Sacramento_East_20120308_TM_geo.pdf */
3335
7.90k
                        else
3336
7.90k
                        {
3337
7.90k
                            CPLString osOCG =
3338
7.90k
                                FindLayerOCG(poPageDict, "Orthoimage");
3339
7.90k
                            if (!osOCG.empty())
3340
0
                            {
3341
0
                                const char *pszBDCLookup =
3342
0
                                    CPLSPrintf("/OC /%s BDC", osOCG.c_str());
3343
0
                                const char *pszBDC =
3344
0
                                    strstr(pszContent, pszBDCLookup);
3345
0
                                if (pszBDC != nullptr)
3346
0
                                {
3347
0
                                    const char *pszIter =
3348
0
                                        pszBDC + strlen(pszBDCLookup);
3349
0
                                    while (*pszIter != '\0')
3350
0
                                    {
3351
0
                                        if (*pszIter == 13 || *pszIter == 10 ||
3352
0
                                            *pszIter == ' ' || *pszIter == 'q')
3353
0
                                            pszIter++;
3354
0
                                        else
3355
0
                                            break;
3356
0
                                    }
3357
0
                                    if (STARTS_WITH(pszIter,
3358
0
                                                    "1 0 0 1 0 0 cm\n"))
3359
0
                                        pszIter += strlen("1 0 0 1 0 0 cm\n");
3360
0
                                    if (*pszIter == '/')
3361
0
                                    {
3362
0
                                        pszIter++;
3363
0
                                        const char *pszDo =
3364
0
                                            strstr(pszIter, " Do");
3365
0
                                        if (pszDo != nullptr)
3366
0
                                        {
3367
0
                                            osForm = pszIter;
3368
0
                                            osForm.resize(pszDo - pszIter);
3369
0
                                            bResetTiles = TRUE;
3370
0
                                        }
3371
0
                                    }
3372
0
                                }
3373
0
                            }
3374
7.90k
                        }
3375
7.91k
                    }
3376
8.05k
                }
3377
3378
8.13k
                if (!osForm.empty())
3379
152
                {
3380
152
                    CPLFree(pszContent);
3381
152
                    pszContent = nullptr;
3382
3383
152
                    GDALPDFObject *poObjForm = poXObjectDict->Get(osForm);
3384
152
                    if (poObjForm != nullptr &&
3385
104
                        poObjForm->GetType() == PDFObjectType_Dictionary &&
3386
104
                        (poPageStream = poObjForm->GetStream()) != nullptr)
3387
104
                    {
3388
104
                        GDALPDFDictionary *poObjFormDict =
3389
104
                            poObjForm->GetDictionary();
3390
104
                        GDALPDFObject *poSubtype =
3391
104
                            poObjFormDict->Get("Subtype");
3392
104
                        if (poSubtype != nullptr &&
3393
104
                            poSubtype->GetType() == PDFObjectType_Name &&
3394
104
                            poSubtype->GetName() == "Form")
3395
103
                        {
3396
103
                            if (poPageStream->GetLength(MAX_LENGTH) <
3397
103
                                MAX_LENGTH)
3398
103
                            {
3399
103
                                pszContent = poPageStream->GetBytes();
3400
3401
103
                                GDALPDFObject *poXObject2 =
3402
103
                                    poObjFormDict->LookupObject(
3403
103
                                        "Resources.XObject");
3404
103
                                if (poXObject2 != nullptr &&
3405
3
                                    poXObject2->GetType() ==
3406
3
                                        PDFObjectType_Dictionary)
3407
3
                                    poContentDict = poXObject2->GetDictionary();
3408
103
                            }
3409
103
                        }
3410
104
                    }
3411
152
                }
3412
8.13k
            }
3413
3414
8.13k
            if (pszContent != nullptr)
3415
8.00k
            {
3416
8.00k
                int bDPISet = FALSE;
3417
3418
8.00k
                const char *pszContentToParse = pszContent;
3419
8.00k
                if (bResetTiles)
3420
0
                {
3421
0
                    while (*pszContentToParse != '\0')
3422
0
                    {
3423
0
                        if (*pszContentToParse == 13 ||
3424
0
                            *pszContentToParse == 10 ||
3425
0
                            *pszContentToParse == ' ' ||
3426
0
                            (*pszContentToParse >= '0' &&
3427
0
                             *pszContentToParse <= '9') ||
3428
0
                            *pszContentToParse == '.' ||
3429
0
                            *pszContentToParse == '-' ||
3430
0
                            *pszContentToParse == 'l' ||
3431
0
                            *pszContentToParse == 'm' ||
3432
0
                            *pszContentToParse == 'n' ||
3433
0
                            *pszContentToParse == 'W')
3434
0
                            pszContentToParse++;
3435
0
                        else
3436
0
                            break;
3437
0
                    }
3438
0
                }
3439
3440
8.00k
                GDALPDFParseStreamContent(pszContentToParse, poContentDict,
3441
8.00k
                                          &dfDPI, &bDPISet, &nBandsGuessed,
3442
8.00k
                                          m_asTiles, bResetTiles);
3443
8.00k
                CPLFree(pszContent);
3444
8.00k
                if (bDPISet)
3445
470
                {
3446
470
                    dfDPI *= dfScaleDPI;
3447
3448
470
                    CPLDebug("PDF", "DPI guessed from contents stream = %.16g",
3449
470
                             dfDPI);
3450
470
                    SetMetadataItem("DPI", CPLSPrintf("%.16g", dfDPI));
3451
470
                    if (bResetTiles)
3452
0
                        m_asTiles.resize(0);
3453
470
                }
3454
7.53k
                else
3455
7.53k
                    m_asTiles.resize(0);
3456
8.00k
            }
3457
8.13k
        }
3458
8.44k
    }
3459
3460
32.6k
    GDALPDFObject *poUserUnit = nullptr;
3461
32.6k
    if ((poUserUnit = poPageDict->Get("UserUnit")) != nullptr &&
3462
3.93k
        (poUserUnit->GetType() == PDFObjectType_Int ||
3463
41
         poUserUnit->GetType() == PDFObjectType_Real))
3464
3.90k
    {
3465
3.90k
        dfDPI = ROUND_IF_CLOSE(Get(poUserUnit) * DEFAULT_DPI, 1e-5);
3466
3.90k
        CPLDebug("PDF", "Found UserUnit in Page --> DPI = %.16g", dfDPI);
3467
3.90k
    }
3468
32.6k
}
3469
3470
/************************************************************************/
3471
/*                              FindXMP()                               */
3472
/************************************************************************/
3473
3474
void PDFDataset::FindXMP(GDALPDFObject *poObj)
3475
0
{
3476
0
    if (poObj->GetType() != PDFObjectType_Dictionary)
3477
0
        return;
3478
3479
0
    GDALPDFDictionary *poDict = poObj->GetDictionary();
3480
0
    GDALPDFObject *poType = poDict->Get("Type");
3481
0
    GDALPDFObject *poSubtype = poDict->Get("Subtype");
3482
0
    if (poType == nullptr || poType->GetType() != PDFObjectType_Name ||
3483
0
        poType->GetName() != "Metadata" || poSubtype == nullptr ||
3484
0
        poSubtype->GetType() != PDFObjectType_Name ||
3485
0
        poSubtype->GetName() != "XML")
3486
0
    {
3487
0
        return;
3488
0
    }
3489
3490
0
    GDALPDFStream *poStream = poObj->GetStream();
3491
0
    if (poStream == nullptr)
3492
0
        return;
3493
3494
0
    char *pszContent = poStream->GetBytes();
3495
0
    const auto nLength = poStream->GetLength();
3496
0
    if (pszContent != nullptr && nLength > 15 &&
3497
0
        STARTS_WITH(pszContent, "<?xpacket begin="))
3498
0
    {
3499
0
        char *apszMDList[2];
3500
0
        apszMDList[0] = pszContent;
3501
0
        apszMDList[1] = nullptr;
3502
0
        SetMetadata(apszMDList, "xml:XMP");
3503
0
    }
3504
0
    CPLFree(pszContent);
3505
0
}
3506
3507
/************************************************************************/
3508
/*                             ParseInfo()                              */
3509
/************************************************************************/
3510
3511
void PDFDataset::ParseInfo(GDALPDFObject *poInfoObj)
3512
32.6k
{
3513
32.6k
    if (poInfoObj->GetType() != PDFObjectType_Dictionary)
3514
23.9k
        return;
3515
3516
8.69k
    GDALPDFDictionary *poInfoObjDict = poInfoObj->GetDictionary();
3517
8.69k
    GDALPDFObject *poItem = nullptr;
3518
8.69k
    int bOneMDISet = FALSE;
3519
8.69k
    if ((poItem = poInfoObjDict->Get("Author")) != nullptr &&
3520
2.36k
        poItem->GetType() == PDFObjectType_String)
3521
2.36k
    {
3522
2.36k
        SetMetadataItem("AUTHOR", poItem->GetString().c_str());
3523
2.36k
        bOneMDISet = TRUE;
3524
2.36k
    }
3525
8.69k
    if ((poItem = poInfoObjDict->Get("Creator")) != nullptr &&
3526
5.95k
        poItem->GetType() == PDFObjectType_String)
3527
5.94k
    {
3528
5.94k
        SetMetadataItem("CREATOR", poItem->GetString().c_str());
3529
5.94k
        bOneMDISet = TRUE;
3530
5.94k
    }
3531
8.69k
    if ((poItem = poInfoObjDict->Get("Keywords")) != nullptr &&
3532
1.01k
        poItem->GetType() == PDFObjectType_String)
3533
1.01k
    {
3534
1.01k
        SetMetadataItem("KEYWORDS", poItem->GetString().c_str());
3535
1.01k
        bOneMDISet = TRUE;
3536
1.01k
    }
3537
8.69k
    if ((poItem = poInfoObjDict->Get("Subject")) != nullptr &&
3538
1.50k
        poItem->GetType() == PDFObjectType_String)
3539
1.50k
    {
3540
1.50k
        SetMetadataItem("SUBJECT", poItem->GetString().c_str());
3541
1.50k
        bOneMDISet = TRUE;
3542
1.50k
    }
3543
8.69k
    if ((poItem = poInfoObjDict->Get("Title")) != nullptr &&
3544
2.97k
        poItem->GetType() == PDFObjectType_String)
3545
2.92k
    {
3546
2.92k
        SetMetadataItem("TITLE", poItem->GetString().c_str());
3547
2.92k
        bOneMDISet = TRUE;
3548
2.92k
    }
3549
8.69k
    if ((poItem = poInfoObjDict->Get("Producer")) != nullptr &&
3550
7.26k
        poItem->GetType() == PDFObjectType_String)
3551
6.65k
    {
3552
6.65k
        if (bOneMDISet ||
3553
1.10k
            poItem->GetString() != "PoDoFo - http://podofo.sf.net")
3554
6.65k
        {
3555
6.65k
            SetMetadataItem("PRODUCER", poItem->GetString().c_str());
3556
6.65k
            bOneMDISet = TRUE;
3557
6.65k
        }
3558
6.65k
    }
3559
8.69k
    if ((poItem = poInfoObjDict->Get("CreationDate")) != nullptr &&
3560
7.17k
        poItem->GetType() == PDFObjectType_String)
3561
7.14k
    {
3562
7.14k
        if (bOneMDISet)
3563
6.41k
            SetMetadataItem("CREATION_DATE", poItem->GetString().c_str());
3564
7.14k
    }
3565
8.69k
}
3566
3567
#if defined(HAVE_POPPLER) || defined(HAVE_PDFIUM)
3568
3569
/************************************************************************/
3570
/*                              AddLayer()                              */
3571
/************************************************************************/
3572
3573
void PDFDataset::AddLayer(const std::string &osName, int iPage)
3574
285k
{
3575
285k
    LayerStruct layerStruct;
3576
285k
    layerStruct.osName = osName;
3577
285k
    layerStruct.nInsertIdx = static_cast<int>(m_oLayerNameSet.size());
3578
285k
    layerStruct.iPage = iPage;
3579
285k
    m_oLayerNameSet.emplace_back(std::move(layerStruct));
3580
285k
}
3581
3582
/************************************************************************/
3583
/*                           SortLayerList()                            */
3584
/************************************************************************/
3585
3586
void PDFDataset::SortLayerList()
3587
6.98k
{
3588
6.98k
    if (!m_oLayerNameSet.empty())
3589
4.18k
    {
3590
        // Sort layers by prioritizing page number and then insertion index
3591
4.18k
        std::sort(m_oLayerNameSet.begin(), m_oLayerNameSet.end(),
3592
4.18k
                  [](const LayerStruct &a, const LayerStruct &b)
3593
552k
                  {
3594
552k
                      if (a.iPage < b.iPage)
3595
1.27k
                          return true;
3596
550k
                      if (a.iPage > b.iPage)
3597
11
                          return false;
3598
550k
                      return a.nInsertIdx < b.nInsertIdx;
3599
550k
                  });
3600
4.18k
    }
3601
6.98k
}
3602
3603
/************************************************************************/
3604
/*                          CreateLayerList()                           */
3605
/************************************************************************/
3606
3607
void PDFDataset::CreateLayerList()
3608
6.98k
{
3609
6.98k
    SortLayerList();
3610
3611
6.98k
    if (m_oLayerNameSet.size() >= 100)
3612
108
    {
3613
108
        for (const auto &oLayerStruct : m_oLayerNameSet)
3614
256k
        {
3615
256k
            m_aosLayerNames.AddNameValue(
3616
256k
                CPLSPrintf("LAYER_%03d_NAME", m_aosLayerNames.size()),
3617
256k
                oLayerStruct.osName.c_str());
3618
256k
        }
3619
108
    }
3620
6.87k
    else
3621
6.87k
    {
3622
6.87k
        for (const auto &oLayerStruct : m_oLayerNameSet)
3623
29.1k
        {
3624
29.1k
            m_aosLayerNames.AddNameValue(
3625
29.1k
                CPLSPrintf("LAYER_%02d_NAME", m_aosLayerNames.size()),
3626
29.1k
                oLayerStruct.osName.c_str());
3627
29.1k
        }
3628
6.87k
    }
3629
6.98k
}
3630
3631
/************************************************************************/
3632
/*                 BuildPostfixedLayerNameAndAddLayer()                 */
3633
/************************************************************************/
3634
3635
/** Append a suffix with the page number(s) to the provided layer name, if
3636
 * it makes sense (that is if it is a multiple page PDF and we haven't selected
3637
 * a specific name). And also call AddLayer() on it if successful.
3638
 * If may return an empty string if the layer isn't used by the page of interest
3639
 */
3640
std::string PDFDataset::BuildPostfixedLayerNameAndAddLayer(
3641
    const std::string &osName, const std::pair<int, int> &oOCGRef,
3642
    int iPageOfInterest, int nPageCount)
3643
282k
{
3644
282k
    std::string osPostfixedName = osName;
3645
282k
    int iLayerPage = 0;
3646
282k
    if (nPageCount > 1 && !m_oMapOCGNumGenToPages.empty())
3647
357
    {
3648
357
        const auto oIterToPages = m_oMapOCGNumGenToPages.find(oOCGRef);
3649
357
        if (oIterToPages != m_oMapOCGNumGenToPages.end())
3650
294
        {
3651
294
            const auto &anPages = oIterToPages->second;
3652
294
            if (iPageOfInterest > 0)
3653
0
            {
3654
0
                if (std::find(anPages.begin(), anPages.end(),
3655
0
                              iPageOfInterest) == anPages.end())
3656
0
                {
3657
0
                    return std::string();
3658
0
                }
3659
0
            }
3660
294
            else if (anPages.size() == 1)
3661
291
            {
3662
291
                iLayerPage = anPages.front();
3663
291
                osPostfixedName += CPLSPrintf(" (page %d)", anPages.front());
3664
291
            }
3665
3
            else
3666
3
            {
3667
3
                osPostfixedName += " (pages ";
3668
12
                for (size_t j = 0; j < anPages.size(); ++j)
3669
9
                {
3670
9
                    if (j > 0)
3671
6
                        osPostfixedName += ", ";
3672
9
                    osPostfixedName += CPLSPrintf("%d", anPages[j]);
3673
9
                }
3674
3
                osPostfixedName += ')';
3675
3
            }
3676
294
        }
3677
357
    }
3678
3679
282k
    AddLayer(osPostfixedName, iLayerPage);
3680
3681
282k
    return osPostfixedName;
3682
282k
}
3683
3684
#endif  //  defined(HAVE_POPPLER) || defined(HAVE_PDFIUM)
3685
3686
#ifdef HAVE_POPPLER
3687
3688
/************************************************************************/
3689
/*                        ExploreLayersPoppler()                        */
3690
/************************************************************************/
3691
3692
void PDFDataset::ExploreLayersPoppler(GDALPDFArray *poArray,
3693
                                      int iPageOfInterest, int nPageCount,
3694
                                      CPLString osTopLayer, int nRecLevel,
3695
                                      int &nVisited, bool &bStop)
3696
31.7k
{
3697
31.7k
    if (nRecLevel == 16 || nVisited == 1000)
3698
107
    {
3699
107
        CPLError(
3700
107
            CE_Failure, CPLE_AppDefined,
3701
107
            "ExploreLayersPoppler(): too deep exploration or too many items");
3702
107
        bStop = true;
3703
107
        return;
3704
107
    }
3705
31.6k
    if (bStop)
3706
0
        return;
3707
3708
31.6k
    int nLength = poArray->GetLength();
3709
31.6k
    CPLString osCurLayer;
3710
1.27M
    for (int i = 0; i < nLength; i++)
3711
1.24M
    {
3712
1.24M
        nVisited++;
3713
1.24M
        GDALPDFObject *poObj = poArray->Get(i);
3714
1.24M
        if (poObj == nullptr)
3715
48.7k
            continue;
3716
1.19M
        if (i == 0 && poObj->GetType() == PDFObjectType_String)
3717
1.37k
        {
3718
1.37k
            std::string osName =
3719
1.37k
                PDFSanitizeLayerName(poObj->GetString().c_str());
3720
1.37k
            if (!osTopLayer.empty())
3721
855
            {
3722
855
                osTopLayer += '.';
3723
855
                osTopLayer += osName;
3724
855
            }
3725
523
            else
3726
523
                osTopLayer = std::move(osName);
3727
1.37k
            AddLayer(osTopLayer, 0);
3728
1.37k
            m_oLayerOCGListPoppler.push_back(std::pair(osTopLayer, nullptr));
3729
1.37k
        }
3730
1.19M
        else if (poObj->GetType() == PDFObjectType_Array)
3731
25.7k
        {
3732
25.7k
            ExploreLayersPoppler(poObj->GetArray(), iPageOfInterest, nPageCount,
3733
25.7k
                                 osCurLayer, nRecLevel + 1, nVisited, bStop);
3734
25.7k
            if (bStop)
3735
1.60k
                return;
3736
24.1k
            osCurLayer = "";
3737
24.1k
        }
3738
1.17M
        else if (poObj->GetType() == PDFObjectType_Dictionary)
3739
458k
        {
3740
458k
            GDALPDFDictionary *poDict = poObj->GetDictionary();
3741
458k
            GDALPDFObject *poName = poDict->Get("Name");
3742
458k
            if (poName != nullptr && poName->GetType() == PDFObjectType_String)
3743
398k
            {
3744
398k
                std::string osName =
3745
398k
                    PDFSanitizeLayerName(poName->GetString().c_str());
3746
                /* coverity[copy_paste_error] */
3747
398k
                if (!osTopLayer.empty())
3748
336k
                {
3749
336k
                    osCurLayer = osTopLayer;
3750
336k
                    osCurLayer += '.';
3751
336k
                    osCurLayer += osName;
3752
336k
                }
3753
61.3k
                else
3754
61.3k
                    osCurLayer = std::move(osName);
3755
                // CPLDebug("PDF", "Layer %s", osCurLayer.c_str());
3756
3757
#if POPPLER_MAJOR_VERSION > 25 ||                                              \
3758
    (POPPLER_MAJOR_VERSION == 25 && POPPLER_MINOR_VERSION >= 2)
3759
                const
3760
#endif
3761
398k
                    OCGs *optContentConfig =
3762
398k
                        m_poDocPoppler->getOptContentConfig();
3763
398k
                struct Ref r;
3764
398k
                r.num = poObj->GetRefNum().toInt();
3765
398k
                r.gen = poObj->GetRefGen();
3766
398k
                OptionalContentGroup *ocg = optContentConfig->findOcgByRef(r);
3767
398k
                if (ocg)
3768
282k
                {
3769
282k
                    const auto oRefPair = std::pair(poObj->GetRefNum().toInt(),
3770
282k
                                                    poObj->GetRefGen());
3771
282k
                    const std::string osPostfixedName =
3772
282k
                        BuildPostfixedLayerNameAndAddLayer(
3773
282k
                            osCurLayer, oRefPair, iPageOfInterest, nPageCount);
3774
282k
                    if (osPostfixedName.empty())
3775
0
                        continue;
3776
3777
282k
                    m_oLayerOCGListPoppler.push_back(
3778
282k
                        std::make_pair(osPostfixedName, ocg));
3779
282k
                    m_aoLayerWithRef.emplace_back(osPostfixedName.c_str(),
3780
282k
                                                  poObj->GetRefNum(), r.gen);
3781
282k
                }
3782
398k
            }
3783
458k
        }
3784
1.19M
    }
3785
31.6k
}
3786
3787
/************************************************************************/
3788
/*                         FindLayersPoppler()                          */
3789
/************************************************************************/
3790
3791
void PDFDataset::FindLayersPoppler(int iPageOfInterest)
3792
32.6k
{
3793
32.6k
    int nPageCount = 0;
3794
32.6k
    const auto poPages = GetPagesKids();
3795
32.6k
    if (poPages)
3796
31.5k
        nPageCount = poPages->GetLength();
3797
3798
#if POPPLER_MAJOR_VERSION > 25 ||                                              \
3799
    (POPPLER_MAJOR_VERSION == 25 && POPPLER_MINOR_VERSION >= 2)
3800
    const
3801
#endif
3802
32.6k
        OCGs *optContentConfig = m_poDocPoppler->getOptContentConfig();
3803
32.6k
    if (optContentConfig == nullptr || !optContentConfig->isOk())
3804
25.6k
        return;
3805
3806
#if POPPLER_MAJOR_VERSION > 25 ||                                              \
3807
    (POPPLER_MAJOR_VERSION == 25 && POPPLER_MINOR_VERSION >= 2)
3808
    const
3809
#endif
3810
6.98k
        Array *array = optContentConfig->getOrderArray();
3811
6.98k
    if (array)
3812
6.04k
    {
3813
6.04k
        GDALPDFArray *poArray = GDALPDFCreateArray(array);
3814
6.04k
        int nVisited = 0;
3815
6.04k
        bool bStop = false;
3816
6.04k
        ExploreLayersPoppler(poArray, iPageOfInterest, nPageCount, CPLString(),
3817
6.04k
                             0, nVisited, bStop);
3818
6.04k
        delete poArray;
3819
6.04k
    }
3820
944
    else
3821
944
    {
3822
944
        for (const auto &refOCGPair : optContentConfig->getOCGs())
3823
1.45k
        {
3824
1.45k
            auto ocg = refOCGPair.second.get();
3825
1.45k
            if (ocg != nullptr && ocg->getName() != nullptr)
3826
1.35k
            {
3827
1.35k
                const char *pszLayerName =
3828
1.35k
                    reinterpret_cast<const char *>(ocg->getName()->c_str());
3829
1.35k
                AddLayer(pszLayerName, 0);
3830
1.35k
                m_oLayerOCGListPoppler.push_back(
3831
1.35k
                    std::make_pair(CPLString(pszLayerName), ocg));
3832
1.35k
            }
3833
1.45k
        }
3834
944
    }
3835
3836
6.98k
    CreateLayerList();
3837
6.98k
    m_oMDMD_PDF.SetMetadata(m_aosLayerNames.List(), "LAYERS");
3838
6.98k
}
3839
3840
/************************************************************************/
3841
/*                       TurnLayersOnOffPoppler()                       */
3842
/************************************************************************/
3843
3844
void PDFDataset::TurnLayersOnOffPoppler()
3845
32.6k
{
3846
#if POPPLER_MAJOR_VERSION > 25 ||                                              \
3847
    (POPPLER_MAJOR_VERSION == 25 && POPPLER_MINOR_VERSION >= 2)
3848
    const
3849
#endif
3850
32.6k
        OCGs *optContentConfig = m_poDocPoppler->getOptContentConfig();
3851
32.6k
    if (optContentConfig == nullptr || !optContentConfig->isOk())
3852
25.6k
        return;
3853
3854
    // Which layers to turn ON ?
3855
6.98k
    const char *pszLayers = GetOption(papszOpenOptions, "LAYERS", nullptr);
3856
6.98k
    if (pszLayers)
3857
0
    {
3858
0
        int i;
3859
0
        int bAll = EQUAL(pszLayers, "ALL");
3860
0
        for (const auto &refOCGPair : optContentConfig->getOCGs())
3861
0
        {
3862
0
            auto ocg = refOCGPair.second.get();
3863
0
            ocg->setState((bAll) ? OptionalContentGroup::On
3864
0
                                 : OptionalContentGroup::Off);
3865
0
        }
3866
3867
0
        char **papszLayers = CSLTokenizeString2(pszLayers, ",", 0);
3868
0
        for (i = 0; !bAll && papszLayers[i] != nullptr; i++)
3869
0
        {
3870
0
            bool isFound = false;
3871
0
            for (auto oIter2 = m_oLayerOCGListPoppler.begin();
3872
0
                 oIter2 != m_oLayerOCGListPoppler.end(); ++oIter2)
3873
0
            {
3874
0
                if (oIter2->first != papszLayers[i])
3875
0
                    continue;
3876
3877
0
                isFound = true;
3878
0
                auto oIter = oIter2;
3879
0
                if (oIter->second)
3880
0
                {
3881
                    // CPLDebug("PDF", "Turn '%s' on", papszLayers[i]);
3882
0
                    oIter->second->setState(OptionalContentGroup::On);
3883
0
                }
3884
3885
                // Turn child layers on, unless there's one of them explicitly
3886
                // listed in the list.
3887
0
                size_t nLen = strlen(papszLayers[i]);
3888
0
                int bFoundChildLayer = FALSE;
3889
0
                oIter = m_oLayerOCGListPoppler.begin();
3890
0
                for (;
3891
0
                     oIter != m_oLayerOCGListPoppler.end() && !bFoundChildLayer;
3892
0
                     ++oIter)
3893
0
                {
3894
0
                    if (oIter->first.size() > nLen &&
3895
0
                        strncmp(oIter->first.c_str(), papszLayers[i], nLen) ==
3896
0
                            0 &&
3897
0
                        oIter->first[nLen] == '.')
3898
0
                    {
3899
0
                        for (int j = 0; papszLayers[j] != nullptr; j++)
3900
0
                        {
3901
0
                            if (strcmp(papszLayers[j], oIter->first.c_str()) ==
3902
0
                                0)
3903
0
                            {
3904
0
                                bFoundChildLayer = TRUE;
3905
0
                                break;
3906
0
                            }
3907
0
                        }
3908
0
                    }
3909
0
                }
3910
3911
0
                if (!bFoundChildLayer)
3912
0
                {
3913
0
                    oIter = m_oLayerOCGListPoppler.begin();
3914
0
                    for (; oIter != m_oLayerOCGListPoppler.end() &&
3915
0
                           !bFoundChildLayer;
3916
0
                         ++oIter)
3917
0
                    {
3918
0
                        if (oIter->first.size() > nLen &&
3919
0
                            strncmp(oIter->first.c_str(), papszLayers[i],
3920
0
                                    nLen) == 0 &&
3921
0
                            oIter->first[nLen] == '.')
3922
0
                        {
3923
0
                            if (oIter->second)
3924
0
                            {
3925
                                // CPLDebug("PDF", "Turn '%s' on too",
3926
                                // oIter->first.c_str());
3927
0
                                oIter->second->setState(
3928
0
                                    OptionalContentGroup::On);
3929
0
                            }
3930
0
                        }
3931
0
                    }
3932
0
                }
3933
3934
                // Turn parent layers on too
3935
0
                std::string layer(papszLayers[i]);
3936
0
                std::string::size_type j;
3937
0
                while ((j = layer.find_last_of('.')) != std::string::npos)
3938
0
                {
3939
0
                    layer.resize(j);
3940
0
                    oIter = m_oLayerOCGListPoppler.begin();
3941
0
                    for (; oIter != m_oLayerOCGListPoppler.end(); ++oIter)
3942
0
                    {
3943
0
                        if (oIter->first == layer && oIter->second)
3944
0
                        {
3945
                            // CPLDebug("PDF", "Turn '%s' on too",
3946
                            // layer.c_str());
3947
0
                            oIter->second->setState(OptionalContentGroup::On);
3948
0
                        }
3949
0
                    }
3950
0
                }
3951
0
            }
3952
0
            if (!isFound)
3953
0
            {
3954
0
                CPLError(CE_Warning, CPLE_AppDefined, "Unknown layer '%s'",
3955
0
                         papszLayers[i]);
3956
0
            }
3957
0
        }
3958
0
        CSLDestroy(papszLayers);
3959
3960
0
        m_bUseOCG = true;
3961
0
    }
3962
3963
    // Which layers to turn OFF ?
3964
6.98k
    const char *pszLayersOFF =
3965
6.98k
        GetOption(papszOpenOptions, "LAYERS_OFF", nullptr);
3966
6.98k
    if (pszLayersOFF)
3967
0
    {
3968
0
        char **papszLayersOFF = CSLTokenizeString2(pszLayersOFF, ",", 0);
3969
0
        for (int i = 0; papszLayersOFF[i] != nullptr; i++)
3970
0
        {
3971
0
            bool isFound = false;
3972
0
            for (auto oIter2 = m_oLayerOCGListPoppler.begin();
3973
0
                 oIter2 != m_oLayerOCGListPoppler.end(); ++oIter2)
3974
0
            {
3975
0
                if (oIter2->first != papszLayersOFF[i])
3976
0
                    continue;
3977
3978
0
                isFound = true;
3979
0
                auto oIter = oIter2;
3980
0
                if (oIter->second)
3981
0
                {
3982
                    // CPLDebug("PDF", "Turn '%s' off", papszLayersOFF[i]);
3983
0
                    oIter->second->setState(OptionalContentGroup::Off);
3984
0
                }
3985
3986
                // Turn child layers off too
3987
0
                size_t nLen = strlen(papszLayersOFF[i]);
3988
0
                oIter = m_oLayerOCGListPoppler.begin();
3989
0
                for (; oIter != m_oLayerOCGListPoppler.end(); ++oIter)
3990
0
                {
3991
0
                    if (oIter->first.size() > nLen &&
3992
0
                        strncmp(oIter->first.c_str(), papszLayersOFF[i],
3993
0
                                nLen) == 0 &&
3994
0
                        oIter->first[nLen] == '.')
3995
0
                    {
3996
0
                        if (oIter->second)
3997
0
                        {
3998
                            // CPLDebug("PDF", "Turn '%s' off too",
3999
                            // oIter->first.c_str());
4000
0
                            oIter->second->setState(OptionalContentGroup::Off);
4001
0
                        }
4002
0
                    }
4003
0
                }
4004
0
            }
4005
0
            if (!isFound)
4006
0
            {
4007
0
                CPLError(CE_Warning, CPLE_AppDefined, "Unknown layer '%s'",
4008
0
                         papszLayersOFF[i]);
4009
0
            }
4010
0
        }
4011
0
        CSLDestroy(papszLayersOFF);
4012
4013
0
        m_bUseOCG = true;
4014
0
    }
4015
6.98k
}
4016
4017
#endif
4018
4019
#ifdef HAVE_PDFIUM
4020
4021
/************************************************************************/
4022
/*                        ExploreLayersPdfium()                         */
4023
/************************************************************************/
4024
4025
void PDFDataset::ExploreLayersPdfium(GDALPDFArray *poArray, int iPageOfInterest,
4026
                                     int nPageCount, int nRecLevel,
4027
                                     CPLString osTopLayer)
4028
{
4029
    if (nRecLevel == 16)
4030
        return;
4031
4032
    const int nLength = poArray->GetLength();
4033
    std::string osCurLayer;
4034
    for (int i = 0; i < nLength; i++)
4035
    {
4036
        GDALPDFObject *poObj = poArray->Get(i);
4037
        if (poObj == nullptr)
4038
            continue;
4039
        if (i == 0 && poObj->GetType() == PDFObjectType_String)
4040
        {
4041
            const std::string osName =
4042
                PDFSanitizeLayerName(poObj->GetString().c_str());
4043
            if (!osTopLayer.empty())
4044
                osTopLayer = std::string(osTopLayer).append(".").append(osName);
4045
            else
4046
                osTopLayer = osName;
4047
            AddLayer(osTopLayer, 0);
4048
            m_oMapLayerNameToOCGNumGenPdfium[osTopLayer] = std::pair(-1, -1);
4049
        }
4050
        else if (poObj->GetType() == PDFObjectType_Array)
4051
        {
4052
            ExploreLayersPdfium(poObj->GetArray(), iPageOfInterest, nPageCount,
4053
                                nRecLevel + 1, osCurLayer);
4054
            osCurLayer.clear();
4055
        }
4056
        else if (poObj->GetType() == PDFObjectType_Dictionary)
4057
        {
4058
            GDALPDFDictionary *poDict = poObj->GetDictionary();
4059
            GDALPDFObject *poName = poDict->Get("Name");
4060
            if (poName != nullptr && poName->GetType() == PDFObjectType_String)
4061
            {
4062
                std::string osName =
4063
                    PDFSanitizeLayerName(poName->GetString().c_str());
4064
                // coverity[copy_paste_error]
4065
                if (!osTopLayer.empty())
4066
                {
4067
                    osCurLayer =
4068
                        std::string(osTopLayer).append(".").append(osName);
4069
                }
4070
                else
4071
                    osCurLayer = std::move(osName);
4072
                // CPLDebug("PDF", "Layer %s", osCurLayer.c_str());
4073
4074
                const auto oRefPair =
4075
                    std::pair(poObj->GetRefNum().toInt(), poObj->GetRefGen());
4076
                const std::string osPostfixedName =
4077
                    BuildPostfixedLayerNameAndAddLayer(
4078
                        osCurLayer, oRefPair, iPageOfInterest, nPageCount);
4079
                if (osPostfixedName.empty())
4080
                    continue;
4081
4082
                m_aoLayerWithRef.emplace_back(
4083
                    osPostfixedName, poObj->GetRefNum(), poObj->GetRefGen());
4084
                m_oMapLayerNameToOCGNumGenPdfium[osPostfixedName] = oRefPair;
4085
            }
4086
        }
4087
    }
4088
}
4089
4090
/************************************************************************/
4091
/*                          FindLayersPdfium()                          */
4092
/************************************************************************/
4093
4094
void PDFDataset::FindLayersPdfium(int iPageOfInterest)
4095
{
4096
    int nPageCount = 0;
4097
    const auto poPages = GetPagesKids();
4098
    if (poPages)
4099
        nPageCount = poPages->GetLength();
4100
4101
    GDALPDFObject *poCatalog = GetCatalog();
4102
    if (poCatalog == nullptr ||
4103
        poCatalog->GetType() != PDFObjectType_Dictionary)
4104
        return;
4105
    GDALPDFObject *poOrder = poCatalog->LookupObject("OCProperties.D.Order");
4106
    if (poOrder != nullptr && poOrder->GetType() == PDFObjectType_Array)
4107
    {
4108
        ExploreLayersPdfium(poOrder->GetArray(), iPageOfInterest, nPageCount,
4109
                            0);
4110
    }
4111
#if 0
4112
    else
4113
    {
4114
        GDALPDFObject* poOCGs = poD->GetDictionary()->Get("OCGs");
4115
        if( poOCGs != nullptr && poOCGs->GetType() == PDFObjectType_Array )
4116
        {
4117
            GDALPDFArray* poArray = poOCGs->GetArray();
4118
            int nLength = poArray->GetLength();
4119
            for(int i=0;i<nLength;i++)
4120
            {
4121
                GDALPDFObject* poObj = poArray->Get(i);
4122
                if( poObj != nullptr )
4123
                {
4124
                    // TODO ?
4125
                }
4126
            }
4127
        }
4128
    }
4129
#endif
4130
4131
    CreateLayerList();
4132
    m_oMDMD_PDF.SetMetadata(m_aosLayerNames.List(), "LAYERS");
4133
}
4134
4135
/************************************************************************/
4136
/*                       TurnLayersOnOffPdfium()                        */
4137
/************************************************************************/
4138
4139
void PDFDataset::TurnLayersOnOffPdfium()
4140
{
4141
    GDALPDFObject *poCatalog = GetCatalog();
4142
    if (poCatalog == nullptr ||
4143
        poCatalog->GetType() != PDFObjectType_Dictionary)
4144
        return;
4145
    GDALPDFObject *poOCGs = poCatalog->LookupObject("OCProperties.OCGs");
4146
    if (poOCGs == nullptr || poOCGs->GetType() != PDFObjectType_Array)
4147
        return;
4148
4149
    // Which layers to turn ON ?
4150
    const char *pszLayers = GetOption(papszOpenOptions, "LAYERS", nullptr);
4151
    if (pszLayers)
4152
    {
4153
        int i;
4154
        int bAll = EQUAL(pszLayers, "ALL");
4155
4156
        GDALPDFArray *poOCGsArray = poOCGs->GetArray();
4157
        int nLength = poOCGsArray->GetLength();
4158
        for (i = 0; i < nLength; i++)
4159
        {
4160
            GDALPDFObject *poOCG = poOCGsArray->Get(i);
4161
            m_oMapOCGNumGenToVisibilityStatePdfium[std::pair(
4162
                poOCG->GetRefNum().toInt(), poOCG->GetRefGen())] =
4163
                (bAll) ? VISIBILITY_ON : VISIBILITY_OFF;
4164
        }
4165
4166
        char **papszLayers = CSLTokenizeString2(pszLayers, ",", 0);
4167
        for (i = 0; !bAll && papszLayers[i] != nullptr; i++)
4168
        {
4169
            auto oIter = m_oMapLayerNameToOCGNumGenPdfium.find(papszLayers[i]);
4170
            if (oIter != m_oMapLayerNameToOCGNumGenPdfium.end())
4171
            {
4172
                if (oIter->second.first >= 0)
4173
                {
4174
                    // CPLDebug("PDF", "Turn '%s' on", papszLayers[i]);
4175
                    m_oMapOCGNumGenToVisibilityStatePdfium[oIter->second] =
4176
                        VISIBILITY_ON;
4177
                }
4178
4179
                // Turn child layers on, unless there's one of them explicitly
4180
                // listed in the list.
4181
                size_t nLen = strlen(papszLayers[i]);
4182
                int bFoundChildLayer = FALSE;
4183
                oIter = m_oMapLayerNameToOCGNumGenPdfium.begin();
4184
                for (; oIter != m_oMapLayerNameToOCGNumGenPdfium.end() &&
4185
                       !bFoundChildLayer;
4186
                     oIter++)
4187
                {
4188
                    if (oIter->first.size() > nLen &&
4189
                        strncmp(oIter->first.c_str(), papszLayers[i], nLen) ==
4190
                            0 &&
4191
                        oIter->first[nLen] == '.')
4192
                    {
4193
                        for (int j = 0; papszLayers[j] != nullptr; j++)
4194
                        {
4195
                            if (strcmp(papszLayers[j], oIter->first.c_str()) ==
4196
                                0)
4197
                                bFoundChildLayer = TRUE;
4198
                        }
4199
                    }
4200
                }
4201
4202
                if (!bFoundChildLayer)
4203
                {
4204
                    oIter = m_oMapLayerNameToOCGNumGenPdfium.begin();
4205
                    for (; oIter != m_oMapLayerNameToOCGNumGenPdfium.end() &&
4206
                           !bFoundChildLayer;
4207
                         oIter++)
4208
                    {
4209
                        if (oIter->first.size() > nLen &&
4210
                            strncmp(oIter->first.c_str(), papszLayers[i],
4211
                                    nLen) == 0 &&
4212
                            oIter->first[nLen] == '.')
4213
                        {
4214
                            if (oIter->second.first >= 0)
4215
                            {
4216
                                // CPLDebug("PDF", "Turn '%s' on too",
4217
                                // oIter->first.c_str());
4218
                                m_oMapOCGNumGenToVisibilityStatePdfium
4219
                                    [oIter->second] = VISIBILITY_ON;
4220
                            }
4221
                        }
4222
                    }
4223
                }
4224
4225
                // Turn parent layers on too
4226
                char *pszLastDot = nullptr;
4227
                while ((pszLastDot = strrchr(papszLayers[i], '.')) != nullptr)
4228
                {
4229
                    *pszLastDot = '\0';
4230
                    oIter =
4231
                        m_oMapLayerNameToOCGNumGenPdfium.find(papszLayers[i]);
4232
                    if (oIter != m_oMapLayerNameToOCGNumGenPdfium.end())
4233
                    {
4234
                        if (oIter->second.first >= 0)
4235
                        {
4236
                            // CPLDebug("PDF", "Turn '%s' on too",
4237
                            // papszLayers[i]);
4238
                            m_oMapOCGNumGenToVisibilityStatePdfium
4239
                                [oIter->second] = VISIBILITY_ON;
4240
                        }
4241
                    }
4242
                }
4243
            }
4244
            else
4245
            {
4246
                CPLError(CE_Warning, CPLE_AppDefined, "Unknown layer '%s'",
4247
                         papszLayers[i]);
4248
            }
4249
        }
4250
        CSLDestroy(papszLayers);
4251
4252
        m_bUseOCG = true;
4253
    }
4254
4255
    // Which layers to turn OFF ?
4256
    const char *pszLayersOFF =
4257
        GetOption(papszOpenOptions, "LAYERS_OFF", nullptr);
4258
    if (pszLayersOFF)
4259
    {
4260
        char **papszLayersOFF = CSLTokenizeString2(pszLayersOFF, ",", 0);
4261
        for (int i = 0; papszLayersOFF[i] != nullptr; i++)
4262
        {
4263
            auto oIter =
4264
                m_oMapLayerNameToOCGNumGenPdfium.find(papszLayersOFF[i]);
4265
            if (oIter != m_oMapLayerNameToOCGNumGenPdfium.end())
4266
            {
4267
                if (oIter->second.first >= 0)
4268
                {
4269
                    // CPLDebug("PDF", "Turn '%s' (%d,%d) off",
4270
                    // papszLayersOFF[i], oIter->second.first,
4271
                    // oIter->second.second);
4272
                    m_oMapOCGNumGenToVisibilityStatePdfium[oIter->second] =
4273
                        VISIBILITY_OFF;
4274
                }
4275
4276
                // Turn child layers off too
4277
                size_t nLen = strlen(papszLayersOFF[i]);
4278
                oIter = m_oMapLayerNameToOCGNumGenPdfium.begin();
4279
                for (; oIter != m_oMapLayerNameToOCGNumGenPdfium.end(); oIter++)
4280
                {
4281
                    if (oIter->first.size() > nLen &&
4282
                        strncmp(oIter->first.c_str(), papszLayersOFF[i],
4283
                                nLen) == 0 &&
4284
                        oIter->first[nLen] == '.')
4285
                    {
4286
                        if (oIter->second.first >= 0)
4287
                        {
4288
                            // CPLDebug("PDF", "Turn '%s' off too",
4289
                            // oIter->first.c_str());
4290
                            m_oMapOCGNumGenToVisibilityStatePdfium
4291
                                [oIter->second] = VISIBILITY_OFF;
4292
                        }
4293
                    }
4294
                }
4295
            }
4296
            else
4297
            {
4298
                CPLError(CE_Warning, CPLE_AppDefined, "Unknown layer '%s'",
4299
                         papszLayersOFF[i]);
4300
            }
4301
        }
4302
        CSLDestroy(papszLayersOFF);
4303
4304
        m_bUseOCG = true;
4305
    }
4306
}
4307
4308
/************************************************************************/
4309
/*                   GetVisibilityStateForOGCPdfium()                   */
4310
/************************************************************************/
4311
4312
PDFDataset::VisibilityState PDFDataset::GetVisibilityStateForOGCPdfium(int nNum,
4313
                                                                       int nGen)
4314
{
4315
    auto oIter =
4316
        m_oMapOCGNumGenToVisibilityStatePdfium.find(std::pair(nNum, nGen));
4317
    if (oIter == m_oMapOCGNumGenToVisibilityStatePdfium.end())
4318
        return VISIBILITY_DEFAULT;
4319
    return oIter->second;
4320
}
4321
4322
#endif /* HAVE_PDFIUM */
4323
4324
/************************************************************************/
4325
/*                            GetPagesKids()                            */
4326
/************************************************************************/
4327
4328
GDALPDFArray *PDFDataset::GetPagesKids()
4329
65.3k
{
4330
65.3k
    const auto poCatalog = GetCatalog();
4331
65.3k
    if (!poCatalog || poCatalog->GetType() != PDFObjectType_Dictionary)
4332
0
    {
4333
0
        return nullptr;
4334
0
    }
4335
65.3k
    const auto poKids = poCatalog->LookupObject("Pages.Kids");
4336
65.3k
    if (!poKids || poKids->GetType() != PDFObjectType_Array)
4337
2.16k
    {
4338
2.16k
        return nullptr;
4339
2.16k
    }
4340
63.1k
    return poKids->GetArray();
4341
65.3k
}
4342
4343
/************************************************************************/
4344
/*                           MapOCGsToPages()                           */
4345
/************************************************************************/
4346
4347
void PDFDataset::MapOCGsToPages()
4348
32.6k
{
4349
32.6k
    const auto poKidsArray = GetPagesKids();
4350
32.6k
    if (!poKidsArray)
4351
1.08k
    {
4352
1.08k
        return;
4353
1.08k
    }
4354
31.5k
    const int nKidsArrayLength = poKidsArray->GetLength();
4355
89.6k
    for (int iPage = 0; iPage < nKidsArrayLength; ++iPage)
4356
58.0k
    {
4357
58.0k
        const auto poPage = poKidsArray->Get(iPage);
4358
58.0k
        if (poPage && poPage->GetType() == PDFObjectType_Dictionary)
4359
39.3k
        {
4360
39.3k
            const auto poXObject = poPage->LookupObject("Resources.XObject");
4361
39.3k
            if (poXObject && poXObject->GetType() == PDFObjectType_Dictionary)
4362
11.8k
            {
4363
11.8k
                for (const auto &oNameObjectPair :
4364
11.8k
                     poXObject->GetDictionary()->GetValues())
4365
79.7k
                {
4366
79.7k
                    const auto poProperties =
4367
79.7k
                        oNameObjectPair.second->LookupObject(
4368
79.7k
                            "Resources.Properties");
4369
79.7k
                    if (poProperties &&
4370
228
                        poProperties->GetType() == PDFObjectType_Dictionary)
4371
226
                    {
4372
226
                        const auto &oMap =
4373
226
                            poProperties->GetDictionary()->GetValues();
4374
226
                        for (const auto &[osKey, poObj] : oMap)
4375
444
                        {
4376
444
                            if (poObj->GetRefNum().toBool() &&
4377
402
                                poObj->GetType() == PDFObjectType_Dictionary)
4378
394
                            {
4379
394
                                GDALPDFObject *poType =
4380
394
                                    poObj->GetDictionary()->Get("Type");
4381
394
                                GDALPDFObject *poName =
4382
394
                                    poObj->GetDictionary()->Get("Name");
4383
394
                                if (poType &&
4384
362
                                    poType->GetType() == PDFObjectType_Name &&
4385
362
                                    poType->GetName() == "OCG" && poName &&
4386
351
                                    poName->GetType() == PDFObjectType_String)
4387
351
                                {
4388
351
                                    m_oMapOCGNumGenToPages
4389
351
                                        [std::pair(poObj->GetRefNum().toInt(),
4390
351
                                                   poObj->GetRefGen())]
4391
351
                                            .push_back(iPage + 1);
4392
351
                                }
4393
394
                            }
4394
444
                        }
4395
226
                    }
4396
79.7k
                }
4397
11.8k
            }
4398
39.3k
        }
4399
58.0k
    }
4400
31.5k
}
4401
4402
/************************************************************************/
4403
/*                            FindLayerOCG()                            */
4404
/************************************************************************/
4405
4406
CPLString PDFDataset::FindLayerOCG(GDALPDFDictionary *poPageDict,
4407
                                   const char *pszLayerName)
4408
7.90k
{
4409
7.90k
    GDALPDFObject *poProperties =
4410
7.90k
        poPageDict->LookupObject("Resources.Properties");
4411
7.90k
    if (poProperties != nullptr &&
4412
2.68k
        poProperties->GetType() == PDFObjectType_Dictionary)
4413
2.67k
    {
4414
2.67k
        const auto &oMap = poProperties->GetDictionary()->GetValues();
4415
2.67k
        for (const auto &[osKey, poObj] : oMap)
4416
14.7k
        {
4417
14.7k
            if (poObj->GetRefNum().toBool() &&
4418
14.5k
                poObj->GetType() == PDFObjectType_Dictionary)
4419
14.4k
            {
4420
14.4k
                GDALPDFObject *poType = poObj->GetDictionary()->Get("Type");
4421
14.4k
                GDALPDFObject *poName = poObj->GetDictionary()->Get("Name");
4422
14.4k
                if (poType != nullptr &&
4423
14.1k
                    poType->GetType() == PDFObjectType_Name &&
4424
14.1k
                    poType->GetName() == "OCG" && poName != nullptr &&
4425
13.9k
                    poName->GetType() == PDFObjectType_String)
4426
13.9k
                {
4427
13.9k
                    if (poName->GetString() == pszLayerName)
4428
0
                        return osKey;
4429
13.9k
                }
4430
14.4k
            }
4431
14.7k
        }
4432
2.67k
    }
4433
7.90k
    return "";
4434
7.90k
}
4435
4436
/************************************************************************/
4437
/*                         FindLayersGeneric()                          */
4438
/************************************************************************/
4439
4440
void PDFDataset::FindLayersGeneric(GDALPDFDictionary *poPageDict)
4441
0
{
4442
0
    GDALPDFObject *poProperties =
4443
0
        poPageDict->LookupObject("Resources.Properties");
4444
0
    if (poProperties != nullptr &&
4445
0
        poProperties->GetType() == PDFObjectType_Dictionary)
4446
0
    {
4447
0
        const auto &oMap = poProperties->GetDictionary()->GetValues();
4448
0
        for (const auto &[osKey, poObj] : oMap)
4449
0
        {
4450
0
            if (poObj->GetRefNum().toBool() &&
4451
0
                poObj->GetType() == PDFObjectType_Dictionary)
4452
0
            {
4453
0
                GDALPDFObject *poType = poObj->GetDictionary()->Get("Type");
4454
0
                GDALPDFObject *poName = poObj->GetDictionary()->Get("Name");
4455
0
                if (poType != nullptr &&
4456
0
                    poType->GetType() == PDFObjectType_Name &&
4457
0
                    poType->GetName() == "OCG" && poName != nullptr &&
4458
0
                    poName->GetType() == PDFObjectType_String)
4459
0
                {
4460
0
                    m_aoLayerWithRef.emplace_back(
4461
0
                        PDFSanitizeLayerName(poName->GetString().c_str())
4462
0
                            .c_str(),
4463
0
                        poObj->GetRefNum(), poObj->GetRefGen());
4464
0
                }
4465
0
            }
4466
0
        }
4467
0
    }
4468
0
}
4469
4470
/************************************************************************/
4471
/*                                Open()                                */
4472
/************************************************************************/
4473
4474
PDFDataset *PDFDataset::Open(GDALOpenInfo *poOpenInfo)
4475
4476
43.4k
{
4477
43.4k
    if (!PDFDatasetIdentify(poOpenInfo))
4478
0
        return nullptr;
4479
4480
43.4k
    const char *pszUserPwd =
4481
43.4k
        GetOption(poOpenInfo->papszOpenOptions, "USER_PWD", nullptr);
4482
4483
43.4k
    const bool bOpenSubdataset = STARTS_WITH(poOpenInfo->pszFilename, "PDF:");
4484
43.4k
    const bool bOpenSubdatasetImage =
4485
43.4k
        STARTS_WITH(poOpenInfo->pszFilename, "PDF_IMAGE:");
4486
43.4k
    int iPage = -1;
4487
43.4k
    int nImageNum = -1;
4488
43.4k
    std::string osSubdatasetName;
4489
43.4k
    const char *pszFilename = poOpenInfo->pszFilename;
4490
4491
43.4k
    if (bOpenSubdataset)
4492
0
    {
4493
0
        iPage = atoi(pszFilename + 4);
4494
0
        if (iPage <= 0)
4495
0
            return nullptr;
4496
0
        pszFilename = strchr(pszFilename + 4, ':');
4497
0
        if (pszFilename == nullptr)
4498
0
            return nullptr;
4499
0
        pszFilename++;
4500
0
        osSubdatasetName = CPLSPrintf("Page %d", iPage);
4501
0
    }
4502
43.4k
    else if (bOpenSubdatasetImage)
4503
0
    {
4504
0
        iPage = atoi(pszFilename + 10);
4505
0
        if (iPage <= 0)
4506
0
            return nullptr;
4507
0
        const char *pszNext = strchr(pszFilename + 10, ':');
4508
0
        if (pszNext == nullptr)
4509
0
            return nullptr;
4510
0
        nImageNum = atoi(pszNext + 1);
4511
0
        if (nImageNum <= 0)
4512
0
            return nullptr;
4513
0
        pszFilename = strchr(pszNext + 1, ':');
4514
0
        if (pszFilename == nullptr)
4515
0
            return nullptr;
4516
0
        pszFilename++;
4517
0
        osSubdatasetName = CPLSPrintf("Image %d", nImageNum);
4518
0
    }
4519
43.4k
    else
4520
43.4k
        iPage = 1;
4521
4522
43.4k
    std::bitset<PDFLIB_COUNT> bHasLib;
4523
43.4k
    bHasLib.reset();
4524
    // Each library set their flag
4525
43.4k
#if defined(HAVE_POPPLER)
4526
43.4k
    bHasLib.set(PDFLIB_POPPLER);
4527
43.4k
#endif  // HAVE_POPPLER
4528
#if defined(HAVE_PODOFO)
4529
    bHasLib.set(PDFLIB_PODOFO);
4530
#endif  // HAVE_PODOFO
4531
#if defined(HAVE_PDFIUM)
4532
    bHasLib.set(PDFLIB_PDFIUM);
4533
#endif  // HAVE_PDFIUM
4534
4535
43.4k
    std::bitset<PDFLIB_COUNT> bUseLib;
4536
4537
    // More than one library available
4538
    // Detect which one
4539
43.4k
    if (bHasLib.count() != 1)
4540
0
    {
4541
0
        const char *pszDefaultLib = bHasLib.test(PDFLIB_PDFIUM)    ? "PDFIUM"
4542
0
                                    : bHasLib.test(PDFLIB_POPPLER) ? "POPPLER"
4543
0
                                                                   : "PODOFO";
4544
0
        const char *pszPDFLib =
4545
0
            GetOption(poOpenInfo->papszOpenOptions, "PDF_LIB", pszDefaultLib);
4546
0
        while (true)
4547
0
        {
4548
0
            if (EQUAL(pszPDFLib, "POPPLER"))
4549
0
                bUseLib.set(PDFLIB_POPPLER);
4550
0
            else if (EQUAL(pszPDFLib, "PODOFO"))
4551
0
                bUseLib.set(PDFLIB_PODOFO);
4552
0
            else if (EQUAL(pszPDFLib, "PDFIUM"))
4553
0
                bUseLib.set(PDFLIB_PDFIUM);
4554
4555
0
            if (bUseLib.count() != 1 || (bHasLib & bUseLib) == 0)
4556
0
            {
4557
0
                CPLDebug("PDF",
4558
0
                         "Invalid value for GDAL_PDF_LIB config option: %s. "
4559
0
                         "Fallback to %s",
4560
0
                         pszPDFLib, pszDefaultLib);
4561
0
                pszPDFLib = pszDefaultLib;
4562
0
                bUseLib.reset();
4563
0
            }
4564
0
            else
4565
0
                break;
4566
0
        }
4567
0
    }
4568
43.4k
    else
4569
43.4k
        bUseLib = bHasLib;
4570
4571
43.4k
    GDALPDFObject *poPageObj = nullptr;
4572
43.4k
#ifdef HAVE_POPPLER
4573
43.4k
    PDFDoc *poDocPoppler = nullptr;
4574
43.4k
    Page *poPagePoppler = nullptr;
4575
43.4k
    Catalog *poCatalogPoppler = nullptr;
4576
43.4k
#endif
4577
#ifdef HAVE_PODOFO
4578
    std::unique_ptr<PoDoFo::PdfMemDocument> poDocPodofo;
4579
    PoDoFo::PdfPage *poPagePodofo = nullptr;
4580
#endif
4581
#ifdef HAVE_PDFIUM
4582
    TPdfiumDocumentStruct *poDocPdfium = nullptr;
4583
    TPdfiumPageStruct *poPagePdfium = nullptr;
4584
#endif
4585
43.4k
    int nPages = 0;
4586
43.4k
    VSIVirtualHandleUniquePtr fp;
4587
4588
43.4k
#ifdef HAVE_POPPLER
4589
43.4k
    if (bUseLib.test(PDFLIB_POPPLER))
4590
43.4k
    {
4591
43.4k
        static bool globalParamsCreatedByGDAL = false;
4592
43.4k
        {
4593
43.4k
            CPLMutexHolderD(&hGlobalParamsMutex);
4594
            /* poppler global variable */
4595
43.4k
            if (globalParams == nullptr)
4596
11
            {
4597
11
                globalParamsCreatedByGDAL = true;
4598
11
                globalParams.reset(new GlobalParams());
4599
11
            }
4600
4601
43.4k
            globalParams->setPrintCommands(CPLTestBool(
4602
43.4k
                CPLGetConfigOption("GDAL_PDF_PRINT_COMMANDS", "FALSE")));
4603
43.4k
        }
4604
4605
43.4k
        const auto registerErrorCallback = []()
4606
86.8k
        {
4607
            /* Set custom error handler for poppler errors */
4608
86.8k
            setErrorCallback(PDFDatasetErrorFunction);
4609
86.8k
            assert(globalParams);  // avoid CSA false positive
4610
86.8k
            globalParams->setErrQuiet(false);
4611
86.8k
        };
4612
4613
43.4k
        fp.reset(VSIFOpenL(pszFilename, "rb"));
4614
43.4k
        if (!fp)
4615
0
            return nullptr;
4616
4617
43.4k
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
4618
43.4k
        {
4619
            // Workaround for ossfuzz only due to
4620
            // https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=37584
4621
            // https://gitlab.freedesktop.org/poppler/poppler/-/issues/1137
4622
43.4k
            GByte *pabyRet = nullptr;
4623
43.4k
            vsi_l_offset nSize = 0;
4624
43.4k
            if (VSIIngestFile(fp.get(), pszFilename, &pabyRet, &nSize,
4625
43.4k
                              10 * 1024 * 1024))
4626
43.4k
            {
4627
                // Replace nul byte by something else so that strstr() works
4628
1.53G
                for (size_t i = 0; i < nSize; i++)
4629
1.53G
                {
4630
1.53G
                    if (pabyRet[i] == 0)
4631
65.9M
                        pabyRet[i] = ' ';
4632
1.53G
                }
4633
43.4k
                if (strstr(reinterpret_cast<const char *>(pabyRet),
4634
43.4k
                           "/JBIG2Decode"))
4635
16
                {
4636
16
                    CPLError(CE_Failure, CPLE_AppDefined,
4637
16
                             "/JBIG2Decode found. Giving up due to potential "
4638
16
                             "very long processing time.");
4639
16
                    CPLFree(pabyRet);
4640
16
                    return nullptr;
4641
16
                }
4642
43.4k
            }
4643
43.4k
            CPLFree(pabyRet);
4644
43.4k
        }
4645
0
#endif
4646
4647
0
        fp.reset(VSICreateBufferedReaderHandle(fp.release()));
4648
43.4k
        while (true)
4649
43.4k
        {
4650
43.4k
            fp->Seek(0, SEEK_SET);
4651
43.4k
            g_nPopplerErrors = 0;
4652
43.4k
            if (globalParamsCreatedByGDAL)
4653
43.4k
                registerErrorCallback();
4654
43.4k
            Object oObj;
4655
43.4k
            auto poStream = std::make_unique<VSIPDFFileStream>(
4656
43.4k
                fp.get(), pszFilename, std::move(oObj));
4657
43.4k
            const bool bFoundLinearizedHint = poStream->FoundLinearizedHint();
4658
43.4k
#if POPPLER_MAJOR_VERSION > 22 ||                                              \
4659
43.4k
    (POPPLER_MAJOR_VERSION == 22 && POPPLER_MINOR_VERSION > 2)
4660
43.4k
            std::optional<GooString> osUserPwd;
4661
43.4k
            if (pszUserPwd)
4662
0
                osUserPwd = std::optional<GooString>(pszUserPwd);
4663
43.4k
            try
4664
43.4k
            {
4665
#if POPPLER_MAJOR_VERSION > 26 ||                                              \
4666
    (POPPLER_MAJOR_VERSION == 26 && POPPLER_MINOR_VERSION >= 2)
4667
                poDocPoppler = new PDFDoc(
4668
                    std::move(poStream), std::optional<GooString>(), osUserPwd);
4669
#else
4670
43.4k
                poDocPoppler = new PDFDoc(
4671
43.4k
                    poStream.release(), std::optional<GooString>(), osUserPwd);
4672
43.4k
#endif
4673
43.4k
            }
4674
43.4k
            catch (const std::exception &e)
4675
43.4k
            {
4676
0
                CPLError(CE_Failure, CPLE_AppDefined,
4677
0
                         "PDFDoc::PDFDoc() failed with %s", e.what());
4678
0
                return nullptr;
4679
0
            }
4680
#else
4681
            GooString *poUserPwd = nullptr;
4682
            if (pszUserPwd)
4683
                poUserPwd = new GooString(pszUserPwd);
4684
            poDocPoppler = new PDFDoc(poStream.release(), nullptr, poUserPwd);
4685
            delete poUserPwd;
4686
#endif
4687
43.4k
            if (globalParamsCreatedByGDAL)
4688
43.4k
                registerErrorCallback();
4689
43.4k
            if (g_nPopplerErrors >= MAX_POPPLER_ERRORS)
4690
4.60k
            {
4691
4.60k
                PDFFreeDoc(poDocPoppler);
4692
4.60k
                return nullptr;
4693
4.60k
            }
4694
4695
38.8k
            if (!poDocPoppler->isOk() || poDocPoppler->getNumPages() == 0)
4696
5.95k
            {
4697
5.95k
                if (poDocPoppler->getErrorCode() == errEncrypted)
4698
74
                {
4699
74
                    if (pszUserPwd && EQUAL(pszUserPwd, "ASK_INTERACTIVE"))
4700
0
                    {
4701
0
                        pszUserPwd =
4702
0
                            PDFEnterPasswordFromConsoleIfNeeded(pszUserPwd);
4703
0
                        PDFFreeDoc(poDocPoppler);
4704
4705
                        /* Reset errors that could have been issued during
4706
                         * opening and that */
4707
                        /* did not result in an invalid document */
4708
0
                        CPLErrorReset();
4709
4710
0
                        continue;
4711
0
                    }
4712
74
                    else if (pszUserPwd == nullptr)
4713
74
                    {
4714
74
                        CPLError(CE_Failure, CPLE_AppDefined,
4715
74
                                 "A password is needed. You can specify it "
4716
74
                                 "through the PDF_USER_PWD "
4717
74
                                 "configuration option / USER_PWD open option "
4718
74
                                 "(that can be set to ASK_INTERACTIVE)");
4719
74
                    }
4720
0
                    else
4721
0
                    {
4722
0
                        CPLError(CE_Failure, CPLE_AppDefined,
4723
0
                                 "Invalid password");
4724
0
                    }
4725
74
                }
4726
5.87k
                else
4727
5.87k
                {
4728
5.87k
                    CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF");
4729
5.87k
                }
4730
4731
5.95k
                PDFFreeDoc(poDocPoppler);
4732
5.95k
                return nullptr;
4733
5.95k
            }
4734
32.8k
            else if (poDocPoppler->isLinearized() && !bFoundLinearizedHint)
4735
1
            {
4736
                // This is a likely defect of poppler Linearization.cc file that
4737
                // recognizes a file as linearized if the /Linearized hint is
4738
                // missing, but the content of this dictionary are present. But
4739
                // given the hacks of PDFFreeDoc() and
4740
                // VSIPDFFileStream::FillBuffer() opening such a file will
4741
                // result in a null-ptr deref at closing if we try to access a
4742
                // page and build the page cache, so just exit now
4743
1
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF");
4744
4745
1
                PDFFreeDoc(poDocPoppler);
4746
1
                return nullptr;
4747
1
            }
4748
32.8k
            else
4749
32.8k
            {
4750
32.8k
                break;
4751
32.8k
            }
4752
38.8k
        }
4753
4754
32.8k
        poCatalogPoppler = poDocPoppler->getCatalog();
4755
32.8k
        if (poCatalogPoppler == nullptr || !poCatalogPoppler->isOk())
4756
0
        {
4757
0
            CPLError(CE_Failure, CPLE_AppDefined,
4758
0
                     "Invalid PDF : invalid catalog");
4759
0
            PDFFreeDoc(poDocPoppler);
4760
0
            return nullptr;
4761
0
        }
4762
4763
32.8k
        nPages = poDocPoppler->getNumPages();
4764
4765
32.8k
        if (iPage == 1 && nPages > 10000 &&
4766
0
            CPLTestBool(CPLGetConfigOption("GDAL_PDF_LIMIT_PAGE_COUNT", "YES")))
4767
0
        {
4768
0
            CPLError(CE_Warning, CPLE_AppDefined,
4769
0
                     "This PDF document reports %d pages. "
4770
0
                     "Limiting count to 10000 for performance reasons. "
4771
0
                     "You may remove this limit by setting the "
4772
0
                     "GDAL_PDF_LIMIT_PAGE_COUNT configuration option to NO",
4773
0
                     nPages);
4774
0
            nPages = 10000;
4775
0
        }
4776
4777
32.8k
        if (iPage < 1 || iPage > nPages)
4778
0
        {
4779
0
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid page number (%d/%d)",
4780
0
                     iPage, nPages);
4781
0
            PDFFreeDoc(poDocPoppler);
4782
0
            return nullptr;
4783
0
        }
4784
4785
        /* Sanity check to validate page count */
4786
32.8k
        if (iPage > 1 && nPages <= 10000 && iPage != nPages)
4787
0
        {
4788
0
            poPagePoppler = poCatalogPoppler->getPage(nPages);
4789
0
            if (poPagePoppler == nullptr || !poPagePoppler->isOk())
4790
0
            {
4791
0
                CPLError(CE_Failure, CPLE_AppDefined,
4792
0
                         "Invalid PDF : invalid page count");
4793
0
                PDFFreeDoc(poDocPoppler);
4794
0
                return nullptr;
4795
0
            }
4796
0
        }
4797
4798
32.8k
        poPagePoppler = poCatalogPoppler->getPage(iPage);
4799
32.8k
        if (poPagePoppler == nullptr || !poPagePoppler->isOk())
4800
161
        {
4801
161
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF : invalid page");
4802
161
            PDFFreeDoc(poDocPoppler);
4803
161
            return nullptr;
4804
161
        }
4805
4806
#if POPPLER_MAJOR_VERSION > 25 ||                                              \
4807
    (POPPLER_MAJOR_VERSION == 25 && POPPLER_MINOR_VERSION >= 3)
4808
        const Object &oPageObj = poPagePoppler->getPageObj();
4809
#else
4810
        /* Here's the dirty part: this is a private member */
4811
        /* so we had to #define private public to get it ! */
4812
32.6k
        const Object &oPageObj = poPagePoppler->pageObj;
4813
32.6k
#endif
4814
32.6k
        if (!oPageObj.isDict())
4815
0
        {
4816
0
            CPLError(CE_Failure, CPLE_AppDefined,
4817
0
                     "Invalid PDF : !oPageObj.isDict()");
4818
0
            PDFFreeDoc(poDocPoppler);
4819
0
            return nullptr;
4820
0
        }
4821
4822
32.6k
        poPageObj = new GDALPDFObjectPoppler(&oPageObj);
4823
32.6k
        Ref *poPageRef = poCatalogPoppler->getPageRef(iPage);
4824
32.6k
        if (poPageRef != nullptr)
4825
32.6k
        {
4826
32.6k
            cpl::down_cast<GDALPDFObjectPoppler *>(poPageObj)->SetRefNumAndGen(
4827
32.6k
                GDALPDFObjectNum(poPageRef->num), poPageRef->gen);
4828
32.6k
        }
4829
32.6k
    }
4830
32.6k
#endif  // ~ HAVE_POPPLER
4831
4832
#ifdef HAVE_PODOFO
4833
    if (bUseLib.test(PDFLIB_PODOFO) && poPageObj == nullptr)
4834
    {
4835
#if !(PODOFO_VERSION_MAJOR > 0 ||                                              \
4836
      (PODOFO_VERSION_MAJOR == 0 && PODOFO_VERSION_MINOR >= 10))
4837
        PoDoFo::PdfError::EnableDebug(false);
4838
        PoDoFo::PdfError::EnableLogging(false);
4839
#endif
4840
4841
        poDocPodofo = std::make_unique<PoDoFo::PdfMemDocument>();
4842
        try
4843
        {
4844
            poDocPodofo->Load(pszFilename);
4845
        }
4846
        catch (PoDoFo::PdfError &oError)
4847
        {
4848
#if PODOFO_VERSION_MAJOR > 0 ||                                                \
4849
    (PODOFO_VERSION_MAJOR == 0 && PODOFO_VERSION_MINOR >= 10)
4850
            if (oError.GetCode() == PoDoFo::PdfErrorCode::InvalidPassword)
4851
#else
4852
            if (oError.GetError() == PoDoFo::ePdfError_InvalidPassword)
4853
#endif
4854
            {
4855
                if (pszUserPwd)
4856
                {
4857
                    pszUserPwd =
4858
                        PDFEnterPasswordFromConsoleIfNeeded(pszUserPwd);
4859
4860
                    try
4861
                    {
4862
#if PODOFO_VERSION_MAJOR > 0 ||                                                \
4863
    (PODOFO_VERSION_MAJOR == 0 && PODOFO_VERSION_MINOR >= 10)
4864
                        poDocPodofo =
4865
                            std::make_unique<PoDoFo::PdfMemDocument>();
4866
                        poDocPodofo->Load(pszFilename, pszUserPwd);
4867
#else
4868
                        poDocPodofo->SetPassword(pszUserPwd);
4869
#endif
4870
                    }
4871
                    catch (PoDoFo::PdfError &oError2)
4872
                    {
4873
#if PODOFO_VERSION_MAJOR > 0 ||                                                \
4874
    (PODOFO_VERSION_MAJOR == 0 && PODOFO_VERSION_MINOR >= 10)
4875
                        if (oError2.GetCode() ==
4876
                            PoDoFo::PdfErrorCode::InvalidPassword)
4877
#else
4878
                        if (oError2.GetError() ==
4879
                            PoDoFo::ePdfError_InvalidPassword)
4880
#endif
4881
                        {
4882
                            CPLError(CE_Failure, CPLE_AppDefined,
4883
                                     "Invalid password");
4884
                        }
4885
                        else
4886
                        {
4887
                            CPLError(CE_Failure, CPLE_AppDefined,
4888
                                     "Invalid PDF : %s", oError2.what());
4889
                        }
4890
                        return nullptr;
4891
                    }
4892
                    catch (...)
4893
                    {
4894
                        CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF");
4895
                        return nullptr;
4896
                    }
4897
                }
4898
                else
4899
                {
4900
                    CPLError(CE_Failure, CPLE_AppDefined,
4901
                             "A password is needed. You can specify it through "
4902
                             "the PDF_USER_PWD "
4903
                             "configuration option / USER_PWD open option "
4904
                             "(that can be set to ASK_INTERACTIVE)");
4905
                    return nullptr;
4906
                }
4907
            }
4908
            else
4909
            {
4910
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF : %s",
4911
                         oError.what());
4912
                return nullptr;
4913
            }
4914
        }
4915
        catch (...)
4916
        {
4917
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF");
4918
            return nullptr;
4919
        }
4920
4921
#if PODOFO_VERSION_MAJOR > 0 ||                                                \
4922
    (PODOFO_VERSION_MAJOR == 0 && PODOFO_VERSION_MINOR >= 10)
4923
        auto &oPageCollections = poDocPodofo->GetPages();
4924
        nPages = static_cast<int>(oPageCollections.GetCount());
4925
#else
4926
        nPages = poDocPodofo->GetPageCount();
4927
#endif
4928
        if (iPage < 1 || iPage > nPages)
4929
        {
4930
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid page number (%d/%d)",
4931
                     iPage, nPages);
4932
            return nullptr;
4933
        }
4934
4935
        try
4936
        {
4937
#if PODOFO_VERSION_MAJOR > 0 ||                                                \
4938
    (PODOFO_VERSION_MAJOR == 0 && PODOFO_VERSION_MINOR >= 10)
4939
            /* Sanity check to validate page count */
4940
            if (iPage != nPages)
4941
                CPL_IGNORE_RET_VAL(oPageCollections.GetPageAt(nPages - 1));
4942
4943
            poPagePodofo = &oPageCollections.GetPageAt(iPage - 1);
4944
#else
4945
            /* Sanity check to validate page count */
4946
            if (iPage != nPages)
4947
                CPL_IGNORE_RET_VAL(poDocPodofo->GetPage(nPages - 1));
4948
4949
            poPagePodofo = poDocPodofo->GetPage(iPage - 1);
4950
#endif
4951
        }
4952
        catch (PoDoFo::PdfError &oError)
4953
        {
4954
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF : %s",
4955
                     oError.what());
4956
            return nullptr;
4957
        }
4958
        catch (...)
4959
        {
4960
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF");
4961
            return nullptr;
4962
        }
4963
4964
        if (poPagePodofo == nullptr)
4965
        {
4966
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid PDF : invalid page");
4967
            return nullptr;
4968
        }
4969
4970
#if PODOFO_VERSION_MAJOR > 0 ||                                                \
4971
    (PODOFO_VERSION_MAJOR == 0 && PODOFO_VERSION_MINOR >= 10)
4972
        const PoDoFo::PdfObject *pObj = &poPagePodofo->GetObject();
4973
#else
4974
        const PoDoFo::PdfObject *pObj = poPagePodofo->GetObject();
4975
#endif
4976
        poPageObj = new GDALPDFObjectPodofo(pObj, poDocPodofo->GetObjects());
4977
    }
4978
#endif  // ~ HAVE_PODOFO
4979
4980
#ifdef HAVE_PDFIUM
4981
    if (bUseLib.test(PDFLIB_PDFIUM) && poPageObj == nullptr)
4982
    {
4983
        if (!LoadPdfiumDocumentPage(pszFilename, pszUserPwd, iPage,
4984
                                    &poDocPdfium, &poPagePdfium, &nPages))
4985
        {
4986
            // CPLError is called inside function
4987
            return nullptr;
4988
        }
4989
4990
        const auto pageObj = poPagePdfium->page->GetDict();
4991
        if (pageObj == nullptr)
4992
        {
4993
            CPLError(CE_Failure, CPLE_AppDefined,
4994
                     "Invalid PDF : invalid page object");
4995
            UnloadPdfiumDocumentPage(&poDocPdfium, &poPagePdfium);
4996
            return nullptr;
4997
        }
4998
        poPageObj = GDALPDFObjectPdfium::Build(pageObj);
4999
    }
5000
#endif  // ~ HAVE_PDFIUM
5001
5002
32.6k
    if (poPageObj == nullptr)
5003
0
        return nullptr;
5004
32.6k
    GDALPDFDictionary *poPageDict = poPageObj->GetDictionary();
5005
32.6k
    if (poPageDict == nullptr)
5006
0
    {
5007
0
        delete poPageObj;
5008
5009
0
        CPLError(CE_Failure, CPLE_AppDefined,
5010
0
                 "Invalid PDF : poPageDict == nullptr");
5011
0
#ifdef HAVE_POPPLER
5012
0
        if (bUseLib.test(PDFLIB_POPPLER))
5013
0
            PDFFreeDoc(poDocPoppler);
5014
0
#endif
5015
#ifdef HAVE_PDFIUM
5016
        if (bUseLib.test(PDFLIB_PDFIUM))
5017
        {
5018
            UnloadPdfiumDocumentPage(&poDocPdfium, &poPagePdfium);
5019
        }
5020
#endif
5021
0
        return nullptr;
5022
0
    }
5023
5024
32.6k
    const char *pszDumpObject = CPLGetConfigOption("PDF_DUMP_OBJECT", nullptr);
5025
32.6k
    if (pszDumpObject != nullptr)
5026
0
    {
5027
0
        GDALPDFDumper oDumper(pszFilename, pszDumpObject);
5028
0
        oDumper.Dump(poPageObj);
5029
0
    }
5030
5031
32.6k
    PDFDataset *poDS = new PDFDataset();
5032
32.6k
    poDS->m_fp = std::move(fp);
5033
32.6k
    poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
5034
32.6k
    poDS->m_bUseLib = bUseLib;
5035
32.6k
    poDS->m_osFilename = pszFilename;
5036
32.6k
    poDS->eAccess = poOpenInfo->eAccess;
5037
5038
32.6k
    if (nPages > 1 && !bOpenSubdataset)
5039
3.56k
    {
5040
3.56k
        int i;
5041
3.56k
        CPLStringList aosList;
5042
86.3k
        for (i = 0; i < nPages; i++)
5043
82.7k
        {
5044
82.7k
            char szKey[32];
5045
82.7k
            snprintf(szKey, sizeof(szKey), "SUBDATASET_%d_NAME", i + 1);
5046
82.7k
            aosList.AddNameValue(
5047
82.7k
                szKey, CPLSPrintf("PDF:%d:%s", i + 1, poOpenInfo->pszFilename));
5048
82.7k
            snprintf(szKey, sizeof(szKey), "SUBDATASET_%d_DESC", i + 1);
5049
82.7k
            aosList.AddNameValue(szKey, CPLSPrintf("Page %d of %s", i + 1,
5050
82.7k
                                                   poOpenInfo->pszFilename));
5051
82.7k
        }
5052
3.56k
        poDS->SetMetadata(aosList.List(), "SUBDATASETS");
5053
3.56k
    }
5054
5055
32.6k
#ifdef HAVE_POPPLER
5056
32.6k
    poDS->m_poDocPoppler = poDocPoppler;
5057
32.6k
#endif
5058
#ifdef HAVE_PODOFO
5059
    poDS->m_poDocPodofo = poDocPodofo.release();
5060
#endif
5061
#ifdef HAVE_PDFIUM
5062
    poDS->m_poDocPdfium = poDocPdfium;
5063
    poDS->m_poPagePdfium = poPagePdfium;
5064
#endif
5065
32.6k
    poDS->m_poPageObj = poPageObj;
5066
32.6k
    poDS->m_osUserPwd = pszUserPwd ? pszUserPwd : "";
5067
32.6k
    poDS->m_iPage = iPage;
5068
5069
32.6k
    const char *pszDumpCatalog =
5070
32.6k
        CPLGetConfigOption("PDF_DUMP_CATALOG", nullptr);
5071
32.6k
    if (pszDumpCatalog != nullptr)
5072
0
    {
5073
0
        GDALPDFDumper oDumper(pszFilename, pszDumpCatalog);
5074
0
        auto poCatalog = poDS->GetCatalog();
5075
0
        if (poCatalog)
5076
0
            oDumper.Dump(poCatalog);
5077
0
    }
5078
5079
32.6k
    int nBandsGuessed = 0;
5080
32.6k
    if (nImageNum < 0)
5081
32.6k
    {
5082
32.6k
        double dfDPI = std::numeric_limits<double>::quiet_NaN();
5083
32.6k
        poDS->GuessDPIAndBandCount(poPageDict, dfDPI, nBandsGuessed);
5084
32.6k
        if (!std::isnan(dfDPI))
5085
4.28k
            poDS->m_dfDPI = dfDPI;
5086
32.6k
        if (nBandsGuessed < 4)
5087
32.6k
            nBandsGuessed = 0;
5088
32.6k
    }
5089
5090
32.6k
    int nTargetBands = 3;
5091
#ifdef HAVE_PDFIUM
5092
    // Use Alpha channel for PDFIUM as default format RGBA
5093
    if (bUseLib.test(PDFLIB_PDFIUM))
5094
        nTargetBands = 4;
5095
#endif
5096
32.6k
    if (nBandsGuessed)
5097
6
        nTargetBands = nBandsGuessed;
5098
32.6k
    const char *pszPDFBands =
5099
32.6k
        GetOption(poOpenInfo->papszOpenOptions, "BANDS", nullptr);
5100
32.6k
    if (pszPDFBands)
5101
18.8k
    {
5102
18.8k
        nTargetBands = atoi(pszPDFBands);
5103
18.8k
        if (nTargetBands != 3 && nTargetBands != 4)
5104
0
        {
5105
0
            CPLError(CE_Warning, CPLE_NotSupported,
5106
0
                     "Invalid value for GDAL_PDF_BANDS. Using 3 as a fallback");
5107
0
            nTargetBands = 3;
5108
0
        }
5109
18.8k
    }
5110
#ifdef HAVE_PODOFO
5111
    if (bUseLib.test(PDFLIB_PODOFO) && nTargetBands == 4 &&
5112
        poDS->m_aiTiles.empty())
5113
    {
5114
        CPLError(CE_Warning, CPLE_NotSupported,
5115
                 "GDAL_PDF_BANDS=4 not supported when PDF driver is compiled "
5116
                 "against Podofo. "
5117
                 "Using 3 as a fallback");
5118
        nTargetBands = 3;
5119
    }
5120
#endif
5121
5122
    // Create bands. We must do that before initializing PAM. But at that point
5123
    // we don't know yet the dataset dimension, since we need to know the DPI,
5124
    // that we can fully know only after loading PAM... So we will have to patch
5125
    // later the band dimension.
5126
130k
    for (int iBand = 1; iBand <= nTargetBands; iBand++)
5127
98.0k
    {
5128
98.0k
        if (poDS->m_poImageObj != nullptr)
5129
0
            poDS->SetBand(iBand, new PDFImageRasterBand(poDS, iBand));
5130
98.0k
        else
5131
98.0k
            poDS->SetBand(iBand, new PDFRasterBand(poDS, iBand, 0));
5132
98.0k
    }
5133
5134
    /* -------------------------------------------------------------------- */
5135
    /*      Initialize any PAM information.                                 */
5136
    /* -------------------------------------------------------------------- */
5137
32.6k
    if (bOpenSubdataset || bOpenSubdatasetImage)
5138
0
    {
5139
0
        poDS->SetPhysicalFilename(pszFilename);
5140
0
        poDS->SetSubdatasetName(osSubdatasetName.c_str());
5141
0
    }
5142
32.6k
    else
5143
32.6k
    {
5144
32.6k
        poDS->SetDescription(poOpenInfo->pszFilename);
5145
32.6k
    }
5146
5147
32.6k
    poDS->TryLoadXML();
5148
5149
    // Establish DPI
5150
32.6k
    const char *pszDPI =
5151
32.6k
        GetOption(poOpenInfo->papszOpenOptions, "DPI", nullptr);
5152
32.6k
    if (pszDPI == nullptr)
5153
13.8k
        pszDPI = poDS->GDALPamDataset::GetMetadataItem("DPI");
5154
32.6k
    if (pszDPI != nullptr)
5155
18.8k
    {
5156
18.8k
        poDS->m_dfDPI = CPLAtof(pszDPI);
5157
5158
18.8k
        if (CPLTestBool(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
5159
18.8k
                                             "SAVE_DPI_TO_PAM", "FALSE")))
5160
0
        {
5161
0
            const std::string osDPI(pszDPI);
5162
0
            poDS->GDALPamDataset::SetMetadataItem("DPI", osDPI.c_str());
5163
0
        }
5164
18.8k
    }
5165
5166
32.6k
    if (poDS->m_dfDPI < 1e-2 || poDS->m_dfDPI > 7200)
5167
22
    {
5168
22
        CPLError(CE_Warning, CPLE_AppDefined,
5169
22
                 "Invalid value for GDAL_PDF_DPI. Using default value instead");
5170
22
        poDS->m_dfDPI = GDAL_DEFAULT_DPI;
5171
22
    }
5172
32.6k
    poDS->SetMetadataItem("DPI", CPLSPrintf("%.16g", poDS->m_dfDPI));
5173
5174
32.6k
    double dfX1 = 0.0;
5175
32.6k
    double dfY1 = 0.0;
5176
32.6k
    double dfX2 = 0.0;
5177
32.6k
    double dfY2 = 0.0;
5178
5179
32.6k
#ifdef HAVE_POPPLER
5180
32.6k
    if (bUseLib.test(PDFLIB_POPPLER))
5181
32.6k
    {
5182
32.6k
        const auto *psMediaBox = poPagePoppler->getMediaBox();
5183
32.6k
        dfX1 = psMediaBox->x1;
5184
32.6k
        dfY1 = psMediaBox->y1;
5185
32.6k
        dfX2 = psMediaBox->x2;
5186
32.6k
        dfY2 = psMediaBox->y2;
5187
32.6k
    }
5188
32.6k
#endif
5189
5190
#ifdef HAVE_PODOFO
5191
    if (bUseLib.test(PDFLIB_PODOFO))
5192
    {
5193
        CPLAssert(poPagePodofo);
5194
        auto oMediaBox = poPagePodofo->GetMediaBox();
5195
        dfX1 = oMediaBox.GetLeft();
5196
        dfY1 = oMediaBox.GetBottom();
5197
#if PODOFO_VERSION_MAJOR > 0 ||                                                \
5198
    (PODOFO_VERSION_MAJOR == 0 && PODOFO_VERSION_MINOR >= 10)
5199
        dfX2 = dfX1 + oMediaBox.Width;
5200
        dfY2 = dfY1 + oMediaBox.Height;
5201
#else
5202
        dfX2 = dfX1 + oMediaBox.GetWidth();
5203
        dfY2 = dfY1 + oMediaBox.GetHeight();
5204
#endif
5205
    }
5206
#endif
5207
5208
#ifdef HAVE_PDFIUM
5209
    if (bUseLib.test(PDFLIB_PDFIUM))
5210
    {
5211
        CPLAssert(poPagePdfium);
5212
        CFX_FloatRect rect = poPagePdfium->page->GetBBox();
5213
        dfX1 = rect.left;
5214
        dfX2 = rect.right;
5215
        dfY1 = rect.bottom;
5216
        dfY2 = rect.top;
5217
    }
5218
#endif  // ~ HAVE_PDFIUM
5219
5220
32.6k
    double dfUserUnit = poDS->m_dfDPI * USER_UNIT_IN_INCH;
5221
32.6k
    poDS->m_dfPageWidth = dfX2 - dfX1;
5222
32.6k
    poDS->m_dfPageHeight = dfY2 - dfY1;
5223
    // CPLDebug("PDF", "left=%f right=%f bottom=%f top=%f", dfX1, dfX2, dfY1,
5224
    // dfY2);
5225
32.6k
    const double dfXSize = floor((dfX2 - dfX1) * dfUserUnit + 0.5);
5226
32.6k
    const double dfYSize = floor((dfY2 - dfY1) * dfUserUnit + 0.5);
5227
32.6k
    if (!(dfXSize >= 0 && dfXSize <= INT_MAX && dfYSize >= 0 &&
5228
32.6k
          dfYSize <= INT_MAX))
5229
7
    {
5230
7
        delete poDS;
5231
7
        return nullptr;
5232
7
    }
5233
32.6k
    poDS->nRasterXSize = static_cast<int>(dfXSize);
5234
32.6k
    poDS->nRasterYSize = static_cast<int>(dfYSize);
5235
5236
32.6k
    if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize))
5237
20
    {
5238
20
        delete poDS;
5239
20
        return nullptr;
5240
20
    }
5241
5242
32.6k
    double dfRotation = 0;
5243
32.6k
#ifdef HAVE_POPPLER
5244
32.6k
    if (bUseLib.test(PDFLIB_POPPLER))
5245
32.6k
        dfRotation = poDocPoppler->getPageRotate(iPage);
5246
32.6k
#endif
5247
5248
#ifdef HAVE_PODOFO
5249
    if (bUseLib.test(PDFLIB_PODOFO))
5250
    {
5251
        CPLAssert(poPagePodofo);
5252
#if PODOFO_VERSION_MAJOR >= 1
5253
        poPagePodofo->TryGetRotationRaw(dfRotation);
5254
#elif (PODOFO_VERSION_MAJOR == 0 && PODOFO_VERSION_MINOR >= 10)
5255
        dfRotation = poPagePodofo->GetRotationRaw();
5256
#else
5257
        dfRotation = poPagePodofo->GetRotation();
5258
#endif
5259
    }
5260
#endif
5261
5262
#ifdef HAVE_PDFIUM
5263
    if (bUseLib.test(PDFLIB_PDFIUM))
5264
    {
5265
        CPLAssert(poPagePdfium);
5266
        dfRotation = poPagePdfium->page->GetPageRotation() * 90;
5267
    }
5268
#endif
5269
5270
32.6k
    if (dfRotation == 90 || dfRotation == -90 || dfRotation == 270)
5271
173
    {
5272
/* FIXME: the podofo case should be implemented. This needs to rotate */
5273
/* the output of pdftoppm */
5274
173
#if defined(HAVE_POPPLER) || defined(HAVE_PDFIUM)
5275
173
        if (bUseLib.test(PDFLIB_POPPLER) || bUseLib.test(PDFLIB_PDFIUM))
5276
173
        {
5277
173
            int nTmp = poDS->nRasterXSize;
5278
173
            poDS->nRasterXSize = poDS->nRasterYSize;
5279
173
            poDS->nRasterYSize = nTmp;
5280
173
        }
5281
173
#endif
5282
173
    }
5283
5284
32.6k
    if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "@OPEN_FOR_OVERVIEW"))
5285
18.7k
    {
5286
18.7k
        poDS->m_nBlockXSize = 512;
5287
18.7k
        poDS->m_nBlockYSize = 512;
5288
18.7k
    }
5289
    /* Check if the PDF is only made of regularly tiled images */
5290
    /* (like some USGS GeoPDF production) */
5291
13.8k
    else if (dfRotation == 0.0 && !poDS->m_asTiles.empty() &&
5292
205
             EQUAL(GetOption(poOpenInfo->papszOpenOptions, "LAYERS", "ALL"),
5293
13.8k
                   "ALL"))
5294
205
    {
5295
205
        poDS->CheckTiledRaster();
5296
205
        if (!poDS->m_aiTiles.empty())
5297
0
            poDS->SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
5298
205
    }
5299
5300
32.6k
    GDALPDFObject *poLGIDict = nullptr;
5301
32.6k
    GDALPDFObject *poVP = nullptr;
5302
32.6k
    int bIsOGCBP = FALSE;
5303
32.6k
    if ((poLGIDict = poPageDict->Get("LGIDict")) != nullptr && nImageNum < 0)
5304
537
    {
5305
        /* Cf 08-139r3_GeoPDF_Encoding_Best_Practice_Version_2.2.pdf */
5306
537
        CPLDebug("PDF", "OGC Encoding Best Practice style detected");
5307
537
        if (poDS->ParseLGIDictObject(poLGIDict))
5308
169
        {
5309
169
            if (poDS->m_bHasCTM)
5310
148
            {
5311
148
                if (dfRotation == 90)
5312
0
                {
5313
0
                    poDS->m_gt.xorig = poDS->m_adfCTM[4];
5314
0
                    poDS->m_gt.xscale = poDS->m_adfCTM[2] / dfUserUnit;
5315
0
                    poDS->m_gt.xrot = poDS->m_adfCTM[0] / dfUserUnit;
5316
0
                    poDS->m_gt.yorig = poDS->m_adfCTM[5];
5317
0
                    poDS->m_gt.yrot = poDS->m_adfCTM[3] / dfUserUnit;
5318
0
                    poDS->m_gt.yscale = poDS->m_adfCTM[1] / dfUserUnit;
5319
0
                }
5320
148
                else if (dfRotation == -90 || dfRotation == 270)
5321
0
                {
5322
0
                    poDS->m_gt.xorig =
5323
0
                        poDS->m_adfCTM[4] +
5324
0
                        poDS->m_adfCTM[2] * poDS->m_dfPageHeight +
5325
0
                        poDS->m_adfCTM[0] * poDS->m_dfPageWidth;
5326
0
                    poDS->m_gt.xscale = -poDS->m_adfCTM[2] / dfUserUnit;
5327
0
                    poDS->m_gt.xrot = -poDS->m_adfCTM[0] / dfUserUnit;
5328
0
                    poDS->m_gt.yorig =
5329
0
                        poDS->m_adfCTM[5] +
5330
0
                        poDS->m_adfCTM[3] * poDS->m_dfPageHeight +
5331
0
                        poDS->m_adfCTM[1] * poDS->m_dfPageWidth;
5332
0
                    poDS->m_gt.yrot = -poDS->m_adfCTM[3] / dfUserUnit;
5333
0
                    poDS->m_gt.yscale = -poDS->m_adfCTM[1] / dfUserUnit;
5334
0
                }
5335
148
                else
5336
148
                {
5337
148
                    poDS->m_gt.xorig = poDS->m_adfCTM[4] +
5338
148
                                       poDS->m_adfCTM[2] * dfY2 +
5339
148
                                       poDS->m_adfCTM[0] * dfX1;
5340
148
                    poDS->m_gt.xscale = poDS->m_adfCTM[0] / dfUserUnit;
5341
148
                    poDS->m_gt.xrot = -poDS->m_adfCTM[2] / dfUserUnit;
5342
148
                    poDS->m_gt.yorig = poDS->m_adfCTM[5] +
5343
148
                                       poDS->m_adfCTM[3] * dfY2 +
5344
148
                                       poDS->m_adfCTM[1] * dfX1;
5345
148
                    poDS->m_gt.yrot = poDS->m_adfCTM[1] / dfUserUnit;
5346
148
                    poDS->m_gt.yscale = -poDS->m_adfCTM[3] / dfUserUnit;
5347
148
                }
5348
5349
148
                poDS->m_bGeoTransformValid = true;
5350
148
            }
5351
5352
169
            bIsOGCBP = TRUE;
5353
5354
169
            int i;
5355
251
            for (i = 0; i < poDS->m_nGCPCount; i++)
5356
82
            {
5357
82
                if (dfRotation == 90)
5358
0
                {
5359
0
                    double dfPixel =
5360
0
                        poDS->m_pasGCPList[i].dfGCPPixel * dfUserUnit;
5361
0
                    double dfLine =
5362
0
                        poDS->m_pasGCPList[i].dfGCPLine * dfUserUnit;
5363
0
                    poDS->m_pasGCPList[i].dfGCPPixel = dfLine;
5364
0
                    poDS->m_pasGCPList[i].dfGCPLine = dfPixel;
5365
0
                }
5366
82
                else if (dfRotation == -90 || dfRotation == 270)
5367
0
                {
5368
0
                    double dfPixel =
5369
0
                        poDS->m_pasGCPList[i].dfGCPPixel * dfUserUnit;
5370
0
                    double dfLine =
5371
0
                        poDS->m_pasGCPList[i].dfGCPLine * dfUserUnit;
5372
0
                    poDS->m_pasGCPList[i].dfGCPPixel =
5373
0
                        poDS->nRasterXSize - dfLine;
5374
0
                    poDS->m_pasGCPList[i].dfGCPLine =
5375
0
                        poDS->nRasterYSize - dfPixel;
5376
0
                }
5377
82
                else
5378
82
                {
5379
82
                    poDS->m_pasGCPList[i].dfGCPPixel =
5380
82
                        (-dfX1 + poDS->m_pasGCPList[i].dfGCPPixel) * dfUserUnit;
5381
82
                    poDS->m_pasGCPList[i].dfGCPLine =
5382
82
                        (dfY2 - poDS->m_pasGCPList[i].dfGCPLine) * dfUserUnit;
5383
82
                }
5384
82
            }
5385
169
        }
5386
537
    }
5387
32.1k
    else if ((poVP = poPageDict->Get("VP")) != nullptr && nImageNum < 0)
5388
5.80k
    {
5389
        /* Cf adobe_supplement_iso32000.pdf */
5390
5.80k
        CPLDebug("PDF", "Adobe ISO32000 style Geospatial PDF perhaps ?");
5391
5.80k
        if (dfX1 != 0 || dfY1 != 0)
5392
112
        {
5393
112
            CPLDebug("PDF", "non null dfX1 or dfY1 values. untested case...");
5394
112
        }
5395
5.80k
        poDS->ParseVP(poVP, dfX2 - dfX1, dfY2 - dfY1);
5396
5.80k
    }
5397
26.3k
    else
5398
26.3k
    {
5399
26.3k
        GDALPDFObject *poXObject =
5400
26.3k
            poPageDict->LookupObject("Resources.XObject");
5401
5402
26.3k
        if (poXObject != nullptr &&
5403
7.82k
            poXObject->GetType() == PDFObjectType_Dictionary)
5404
7.78k
        {
5405
7.78k
            GDALPDFDictionary *poXObjectDict = poXObject->GetDictionary();
5406
7.78k
            const auto &oMap = poXObjectDict->GetValues();
5407
7.78k
            int nSubDataset = 0;
5408
7.78k
            for (const auto &[osKey, poObj] : oMap)
5409
52.2k
            {
5410
52.2k
                if (poObj->GetType() == PDFObjectType_Dictionary)
5411
48.5k
                {
5412
48.5k
                    GDALPDFDictionary *poDict = poObj->GetDictionary();
5413
48.5k
                    GDALPDFObject *poSubtype = nullptr;
5414
48.5k
                    GDALPDFObject *poMeasure = nullptr;
5415
48.5k
                    GDALPDFObject *poWidth = nullptr;
5416
48.5k
                    GDALPDFObject *poHeight = nullptr;
5417
48.5k
                    int nW = 0;
5418
48.5k
                    int nH = 0;
5419
48.5k
                    if ((poSubtype = poDict->Get("Subtype")) != nullptr &&
5420
47.3k
                        poSubtype->GetType() == PDFObjectType_Name &&
5421
47.3k
                        poSubtype->GetName() == "Image" &&
5422
11.5k
                        (poMeasure = poDict->Get("Measure")) != nullptr &&
5423
1
                        poMeasure->GetType() == PDFObjectType_Dictionary &&
5424
0
                        (poWidth = poDict->Get("Width")) != nullptr &&
5425
0
                        poWidth->GetType() == PDFObjectType_Int &&
5426
0
                        (nW = poWidth->GetInt()) > 0 &&
5427
0
                        (poHeight = poDict->Get("Height")) != nullptr &&
5428
0
                        poHeight->GetType() == PDFObjectType_Int &&
5429
0
                        (nH = poHeight->GetInt()) > 0)
5430
0
                    {
5431
0
                        if (nImageNum < 0)
5432
0
                            CPLDebug("PDF",
5433
0
                                     "Measure found on Image object (%d)",
5434
0
                                     poObj->GetRefNum().toInt());
5435
5436
0
                        GDALPDFObject *poColorSpace = poDict->Get("ColorSpace");
5437
0
                        GDALPDFObject *poBitsPerComponent =
5438
0
                            poDict->Get("BitsPerComponent");
5439
0
                        if (poObj->GetRefNum().toBool() &&
5440
0
                            poObj->GetRefGen() == 0 &&
5441
0
                            poColorSpace != nullptr &&
5442
0
                            poColorSpace->GetType() == PDFObjectType_Name &&
5443
0
                            (poColorSpace->GetName() == "DeviceGray" ||
5444
0
                             poColorSpace->GetName() == "DeviceRGB") &&
5445
0
                            (poBitsPerComponent == nullptr ||
5446
0
                             (poBitsPerComponent->GetType() ==
5447
0
                                  PDFObjectType_Int &&
5448
0
                              poBitsPerComponent->GetInt() == 8)))
5449
0
                        {
5450
0
                            if (nImageNum < 0)
5451
0
                            {
5452
0
                                nSubDataset++;
5453
0
                                poDS->SetMetadataItem(
5454
0
                                    CPLSPrintf("SUBDATASET_%d_NAME",
5455
0
                                               nSubDataset),
5456
0
                                    CPLSPrintf("PDF_IMAGE:%d:%d:%s", iPage,
5457
0
                                               poObj->GetRefNum().toInt(),
5458
0
                                               pszFilename),
5459
0
                                    "SUBDATASETS");
5460
0
                                poDS->SetMetadataItem(
5461
0
                                    CPLSPrintf("SUBDATASET_%d_DESC",
5462
0
                                               nSubDataset),
5463
0
                                    CPLSPrintf("Georeferenced image of size "
5464
0
                                               "%dx%d of page %d of %s",
5465
0
                                               nW, nH, iPage, pszFilename),
5466
0
                                    "SUBDATASETS");
5467
0
                            }
5468
0
                            else if (poObj->GetRefNum().toInt() == nImageNum)
5469
0
                            {
5470
0
                                poDS->nRasterXSize = nW;
5471
0
                                poDS->nRasterYSize = nH;
5472
0
                                poDS->ParseMeasure(poMeasure, nW, nH, 0, nH, nW,
5473
0
                                                   0);
5474
0
                                poDS->m_poImageObj = poObj;
5475
0
                                if (poColorSpace->GetName() == "DeviceGray")
5476
0
                                {
5477
0
                                    for (int i = 1; i < poDS->nBands; ++i)
5478
0
                                        delete poDS->papoBands[i];
5479
0
                                    poDS->nBands = 1;
5480
0
                                }
5481
0
                                break;
5482
0
                            }
5483
0
                        }
5484
0
                    }
5485
48.5k
                }
5486
52.2k
            }
5487
7.78k
        }
5488
5489
26.3k
        if (nImageNum >= 0 && poDS->m_poImageObj == nullptr)
5490
0
        {
5491
0
            CPLError(CE_Failure, CPLE_AppDefined, "Cannot find image %d",
5492
0
                     nImageNum);
5493
0
            delete poDS;
5494
0
            return nullptr;
5495
0
        }
5496
5497
        /* Not a geospatial PDF doc */
5498
26.3k
    }
5499
5500
    /* If pixel size or top left coordinates are very close to an int, round
5501
     * them to the int */
5502
32.6k
    double dfEps =
5503
32.6k
        (fabs(poDS->m_gt.xorig) > 1e5 && fabs(poDS->m_gt.yorig) > 1e5) ? 1e-5
5504
32.6k
                                                                       : 1e-8;
5505
32.6k
    poDS->m_gt.xorig = ROUND_IF_CLOSE(poDS->m_gt.xorig, dfEps);
5506
32.6k
    poDS->m_gt.xscale = ROUND_IF_CLOSE(poDS->m_gt.xscale);
5507
32.6k
    poDS->m_gt.yorig = ROUND_IF_CLOSE(poDS->m_gt.yorig, dfEps);
5508
32.6k
    poDS->m_gt.yscale = ROUND_IF_CLOSE(poDS->m_gt.yscale);
5509
5510
32.6k
    if (bUseLib.test(PDFLIB_PDFIUM))
5511
0
    {
5512
        // Attempt to "fix" the loss of precision due to the use of float32 for
5513
        // numbers by pdfium
5514
0
        if ((fabs(poDS->m_gt.xorig) > 1e5 || fabs(poDS->m_gt.yorig) > 1e5) &&
5515
0
            fabs(poDS->m_gt.xorig - std::round(poDS->m_gt.xorig)) <
5516
0
                1e-6 * fabs(poDS->m_gt.xorig) &&
5517
0
            fabs(poDS->m_gt.xscale - std::round(poDS->m_gt.xscale)) <
5518
0
                1e-3 * fabs(poDS->m_gt.xscale) &&
5519
0
            fabs(poDS->m_gt.yorig - std::round(poDS->m_gt.yorig)) <
5520
0
                1e-6 * fabs(poDS->m_gt.yorig) &&
5521
0
            fabs(poDS->m_gt.yscale - std::round(poDS->m_gt.yscale)) <
5522
0
                1e-3 * fabs(poDS->m_gt.yscale))
5523
0
        {
5524
0
            for (int i = 0; i < 6; i++)
5525
0
            {
5526
0
                poDS->m_gt[i] = std::round(poDS->m_gt[i]);
5527
0
            }
5528
0
        }
5529
0
    }
5530
5531
32.6k
    if (poDS->m_poNeatLine)
5532
2.53k
    {
5533
2.53k
        char *pszNeatLineWkt = nullptr;
5534
2.53k
        OGRLinearRing *poRing = poDS->m_poNeatLine->getExteriorRing();
5535
        /* Adobe style is already in target SRS units */
5536
2.53k
        if (bIsOGCBP)
5537
168
        {
5538
168
            int nPoints = poRing->getNumPoints();
5539
168
            int i;
5540
5541
76.8k
            for (i = 0; i < nPoints; i++)
5542
76.7k
            {
5543
76.7k
                double x, y;
5544
76.7k
                if (dfRotation == 90.0)
5545
0
                {
5546
0
                    x = poRing->getY(i) * dfUserUnit;
5547
0
                    y = poRing->getX(i) * dfUserUnit;
5548
0
                }
5549
76.7k
                else if (dfRotation == -90.0 || dfRotation == 270.0)
5550
0
                {
5551
0
                    x = poDS->nRasterXSize - poRing->getY(i) * dfUserUnit;
5552
0
                    y = poDS->nRasterYSize - poRing->getX(i) * dfUserUnit;
5553
0
                }
5554
76.7k
                else
5555
76.7k
                {
5556
76.7k
                    x = (-dfX1 + poRing->getX(i)) * dfUserUnit;
5557
76.7k
                    y = (dfY2 - poRing->getY(i)) * dfUserUnit;
5558
76.7k
                }
5559
76.7k
                double X = poDS->m_gt.xorig + x * poDS->m_gt.xscale +
5560
76.7k
                           y * poDS->m_gt.xrot;
5561
76.7k
                double Y = poDS->m_gt.yorig + x * poDS->m_gt.yrot +
5562
76.7k
                           y * poDS->m_gt.yscale;
5563
76.7k
                poRing->setPoint(i, X, Y);
5564
76.7k
            }
5565
168
        }
5566
2.53k
        poRing->closeRings();
5567
5568
2.53k
        poDS->m_poNeatLine->exportToWkt(&pszNeatLineWkt);
5569
2.53k
        if (nImageNum < 0)
5570
2.53k
            poDS->SetMetadataItem("NEATLINE", pszNeatLineWkt);
5571
2.53k
        CPLFree(pszNeatLineWkt);
5572
2.53k
    }
5573
5574
32.6k
    poDS->MapOCGsToPages();
5575
5576
32.6k
#ifdef HAVE_POPPLER
5577
32.6k
    if (bUseLib.test(PDFLIB_POPPLER))
5578
32.6k
    {
5579
32.6k
        auto poMetadata = poCatalogPoppler->readMetadata();
5580
32.6k
        if (poMetadata)
5581
5.42k
        {
5582
5.42k
            const char *pszContent = poMetadata->c_str();
5583
5.42k
            if (pszContent != nullptr &&
5584
5.42k
                STARTS_WITH(pszContent, "<?xpacket begin="))
5585
5.03k
            {
5586
5.03k
                const char *const apszMDList[2] = {pszContent, nullptr};
5587
5.03k
                poDS->SetMetadata(const_cast<char **>(apszMDList), "xml:XMP");
5588
5.03k
            }
5589
#if (POPPLER_MAJOR_VERSION < 21 ||                                             \
5590
     (POPPLER_MAJOR_VERSION == 21 && POPPLER_MINOR_VERSION < 10))
5591
            delete poMetadata;
5592
#endif
5593
5.42k
        }
5594
5595
        /* Read Info object */
5596
        /* The test is necessary since with some corrupted PDFs
5597
         * poDocPoppler->getDocInfo() */
5598
        /* might abort() */
5599
32.6k
        if (poDocPoppler->getXRef()->isOk())
5600
32.6k
        {
5601
32.6k
            Object oInfo = poDocPoppler->getDocInfo();
5602
32.6k
            GDALPDFObjectPoppler oInfoObjPoppler(&oInfo, FALSE);
5603
32.6k
            poDS->ParseInfo(&oInfoObjPoppler);
5604
32.6k
        }
5605
5606
        /* Find layers */
5607
32.6k
        poDS->FindLayersPoppler(
5608
32.6k
            (bOpenSubdataset || bOpenSubdatasetImage) ? iPage : 0);
5609
5610
        /* Turn user specified layers on or off */
5611
32.6k
        poDS->TurnLayersOnOffPoppler();
5612
32.6k
    }
5613
32.6k
#endif
5614
5615
#ifdef HAVE_PODOFO
5616
    if (bUseLib.test(PDFLIB_PODOFO))
5617
    {
5618
        for (const auto &obj : poDS->m_poDocPodofo->GetObjects())
5619
        {
5620
            GDALPDFObjectPodofo oObjPodofo(obj,
5621
                                           poDS->m_poDocPodofo->GetObjects());
5622
            poDS->FindXMP(&oObjPodofo);
5623
        }
5624
5625
        /* Find layers */
5626
        poDS->FindLayersGeneric(poPageDict);
5627
5628
        /* Read Info object */
5629
        const PoDoFo::PdfInfo *poInfo = poDS->m_poDocPodofo->GetInfo();
5630
        if (poInfo != nullptr)
5631
        {
5632
            GDALPDFObjectPodofo oInfoObjPodofo(
5633
#if PODOFO_VERSION_MAJOR > 0 ||                                                \
5634
    (PODOFO_VERSION_MAJOR == 0 && PODOFO_VERSION_MINOR >= 10)
5635
                &(poInfo->GetObject()),
5636
#else
5637
                poInfo->GetObject(),
5638
#endif
5639
                poDS->m_poDocPodofo->GetObjects());
5640
            poDS->ParseInfo(&oInfoObjPodofo);
5641
        }
5642
    }
5643
#endif
5644
#ifdef HAVE_PDFIUM
5645
    if (bUseLib.test(PDFLIB_PDFIUM))
5646
    {
5647
        // coverity is confused by WrapRetain(), believing that multiple
5648
        // smart pointers manage the same raw pointer. Which is actually
5649
        // true, but a RetainPtr holds a reference counted object. It is
5650
        // thus safe to have several RetainPtr holding it.
5651
        // coverity[multiple_init_smart_ptr]
5652
        GDALPDFObjectPdfium *poRoot = GDALPDFObjectPdfium::Build(
5653
            pdfium::WrapRetain(poDocPdfium->doc->GetRoot()));
5654
        if (poRoot->GetType() == PDFObjectType_Dictionary)
5655
        {
5656
            GDALPDFDictionary *poDict = poRoot->GetDictionary();
5657
            GDALPDFObject *poMetadata(poDict->Get("Metadata"));
5658
            if (poMetadata != nullptr)
5659
            {
5660
                GDALPDFStream *poStream = poMetadata->GetStream();
5661
                if (poStream != nullptr)
5662
                {
5663
                    char *pszContent = poStream->GetBytes();
5664
                    const auto nLength = poStream->GetLength();
5665
                    if (pszContent != nullptr && nLength > 15 &&
5666
                        STARTS_WITH(pszContent, "<?xpacket begin="))
5667
                    {
5668
                        char *apszMDList[2];
5669
                        apszMDList[0] = pszContent;
5670
                        apszMDList[1] = nullptr;
5671
                        poDS->SetMetadata(apszMDList, "xml:XMP");
5672
                    }
5673
                    CPLFree(pszContent);
5674
                }
5675
            }
5676
        }
5677
        delete poRoot;
5678
5679
        /* Find layers */
5680
        poDS->FindLayersPdfium((bOpenSubdataset || bOpenSubdatasetImage) ? iPage
5681
                                                                         : 0);
5682
5683
        /* Turn user specified layers on or off */
5684
        poDS->TurnLayersOnOffPdfium();
5685
5686
        GDALPDFObjectPdfium *poInfo =
5687
            GDALPDFObjectPdfium::Build(poDocPdfium->doc->GetInfo());
5688
        if (poInfo)
5689
        {
5690
            /* Read Info object */
5691
            poDS->ParseInfo(poInfo);
5692
            delete poInfo;
5693
        }
5694
    }
5695
#endif  // ~ HAVE_PDFIUM
5696
5697
    // Patch band size with actual dataset size
5698
130k
    for (int iBand = 1; iBand <= poDS->nBands; iBand++)
5699
98.0k
    {
5700
98.0k
        cpl::down_cast<PDFRasterBand *>(poDS->GetRasterBand(iBand))
5701
98.0k
            ->SetSize(poDS->nRasterXSize, poDS->nRasterYSize);
5702
98.0k
    }
5703
5704
    /* Check if this is a raster-only PDF file and that we are */
5705
    /* opened in vector-only mode */
5706
32.6k
    if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) == 0 &&
5707
24.4k
        (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
5708
5.63k
        !poDS->OpenVectorLayers(poPageDict))
5709
4.48k
    {
5710
4.48k
        CPLDebug("PDF", "This is a raster-only PDF dataset, "
5711
4.48k
                        "but it has been opened in vector-only mode");
5712
        /* Clear dirty flag */
5713
4.48k
        poDS->m_bProjDirty = false;
5714
4.48k
        poDS->m_bNeatLineDirty = false;
5715
4.48k
        poDS->m_bInfoDirty = false;
5716
4.48k
        poDS->m_bXMPDirty = false;
5717
4.48k
        delete poDS;
5718
4.48k
        return nullptr;
5719
4.48k
    }
5720
5721
    /* -------------------------------------------------------------------- */
5722
    /*      Support overviews.                                              */
5723
    /* -------------------------------------------------------------------- */
5724
28.1k
    if (!CSLFetchNameValue(poOpenInfo->papszOpenOptions, "@OPEN_FOR_OVERVIEW"))
5725
9.40k
    {
5726
9.40k
        poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);
5727
9.40k
    }
5728
5729
    /* Clear dirty flag */
5730
28.1k
    poDS->m_bProjDirty = false;
5731
28.1k
    poDS->m_bNeatLineDirty = false;
5732
28.1k
    poDS->m_bInfoDirty = false;
5733
28.1k
    poDS->m_bXMPDirty = false;
5734
5735
28.1k
    return (poDS);
5736
32.6k
}
5737
5738
/************************************************************************/
5739
/*                         ParseLGIDictObject()                         */
5740
/************************************************************************/
5741
5742
int PDFDataset::ParseLGIDictObject(GDALPDFObject *poLGIDict)
5743
537
{
5744
537
    bool bOK = false;
5745
537
    if (poLGIDict->GetType() == PDFObjectType_Array)
5746
33
    {
5747
33
        GDALPDFArray *poArray = poLGIDict->GetArray();
5748
33
        int nArrayLength = poArray->GetLength();
5749
33
        int iMax = -1;
5750
33
        GDALPDFObject *poArrayElt = nullptr;
5751
33
        for (int i = 0; i < nArrayLength; i++)
5752
33
        {
5753
33
            if ((poArrayElt = poArray->Get(i)) == nullptr ||
5754
33
                poArrayElt->GetType() != PDFObjectType_Dictionary)
5755
33
            {
5756
33
                CPLError(CE_Failure, CPLE_AppDefined,
5757
33
                         "LGIDict[%d] is not a dictionary", i);
5758
33
                return FALSE;
5759
33
            }
5760
5761
0
            int bIsBestCandidate = FALSE;
5762
0
            if (ParseLGIDictDictFirstPass(poArrayElt->GetDictionary(),
5763
0
                                          &bIsBestCandidate))
5764
0
            {
5765
0
                if (bIsBestCandidate || iMax < 0)
5766
0
                    iMax = i;
5767
0
            }
5768
0
        }
5769
5770
0
        if (iMax < 0)
5771
0
            return FALSE;
5772
5773
0
        poArrayElt = poArray->Get(iMax);
5774
0
        bOK = CPL_TO_BOOL(
5775
0
            ParseLGIDictDictSecondPass(poArrayElt->GetDictionary()));
5776
0
    }
5777
504
    else if (poLGIDict->GetType() == PDFObjectType_Dictionary)
5778
468
    {
5779
468
        bOK = ParseLGIDictDictFirstPass(poLGIDict->GetDictionary()) &&
5780
307
              ParseLGIDictDictSecondPass(poLGIDict->GetDictionary());
5781
468
    }
5782
36
    else
5783
36
    {
5784
36
        CPLError(CE_Failure, CPLE_AppDefined, "LGIDict is of type %s",
5785
36
                 poLGIDict->GetTypeName());
5786
36
    }
5787
5788
504
    return bOK;
5789
537
}
5790
5791
/************************************************************************/
5792
/*                                Get()                                 */
5793
/************************************************************************/
5794
5795
static double Get(GDALPDFObject *poObj, int nIndice)
5796
1.25M
{
5797
1.25M
    if (poObj->GetType() == PDFObjectType_Array && nIndice >= 0)
5798
626k
    {
5799
626k
        poObj = poObj->GetArray()->Get(nIndice);
5800
626k
        if (poObj == nullptr)
5801
262
            return 0;
5802
626k
        return Get(poObj);
5803
626k
    }
5804
631k
    else if (poObj->GetType() == PDFObjectType_Int)
5805
191k
        return poObj->GetInt();
5806
440k
    else if (poObj->GetType() == PDFObjectType_Real)
5807
123k
        return poObj->GetReal();
5808
316k
    else if (poObj->GetType() == PDFObjectType_String)
5809
8.94k
    {
5810
8.94k
        const char *pszStr = poObj->GetString().c_str();
5811
8.94k
        size_t nLen = strlen(pszStr);
5812
8.94k
        if (nLen == 0)
5813
5.26k
            return 0;
5814
        /* cf Military_Installations_2008.pdf that has values like "96 0 0.0W"
5815
         */
5816
3.67k
        char chLast = pszStr[nLen - 1];
5817
3.67k
        if (chLast == 'W' || chLast == 'E' || chLast == 'N' || chLast == 'S')
5818
392
        {
5819
392
            double dfDeg = CPLAtof(pszStr);
5820
392
            double dfMin = 0.0;
5821
392
            double dfSec = 0.0;
5822
392
            const char *pszNext = strchr(pszStr, ' ');
5823
392
            if (pszNext)
5824
118
                pszNext++;
5825
392
            if (pszNext)
5826
118
                dfMin = CPLAtof(pszNext);
5827
392
            if (pszNext)
5828
118
                pszNext = strchr(pszNext, ' ');
5829
392
            if (pszNext)
5830
118
                pszNext++;
5831
392
            if (pszNext)
5832
118
                dfSec = CPLAtof(pszNext);
5833
392
            double dfVal = dfDeg + dfMin / 60 + dfSec / 3600;
5834
392
            if (chLast == 'W' || chLast == 'S')
5835
0
                return -dfVal;
5836
392
            else
5837
392
                return dfVal;
5838
392
        }
5839
3.28k
        return CPLAtof(pszStr);
5840
3.67k
    }
5841
307k
    else
5842
307k
    {
5843
307k
        CPLError(CE_Warning, CPLE_AppDefined, "Unexpected type : %s",
5844
307k
                 poObj->GetTypeName());
5845
307k
        return 0;
5846
307k
    }
5847
1.25M
}
5848
5849
/************************************************************************/
5850
/*                                Get()                                 */
5851
/************************************************************************/
5852
5853
static double Get(GDALPDFDictionary *poDict, const char *pszName)
5854
0
{
5855
0
    GDALPDFObject *poObj = poDict->Get(pszName);
5856
0
    if (poObj != nullptr)
5857
0
        return Get(poObj);
5858
0
    CPLError(CE_Failure, CPLE_AppDefined, "Cannot find parameter %s", pszName);
5859
0
    return 0;
5860
0
}
5861
5862
/************************************************************************/
5863
/*                     ParseLGIDictDictFirstPass()                      */
5864
/************************************************************************/
5865
5866
int PDFDataset::ParseLGIDictDictFirstPass(GDALPDFDictionary *poLGIDict,
5867
                                          int *pbIsBestCandidate)
5868
468
{
5869
468
    if (pbIsBestCandidate)
5870
0
        *pbIsBestCandidate = FALSE;
5871
5872
468
    if (poLGIDict == nullptr)
5873
0
        return FALSE;
5874
5875
    /* -------------------------------------------------------------------- */
5876
    /*      Extract Type attribute                                          */
5877
    /* -------------------------------------------------------------------- */
5878
468
    GDALPDFObject *poType = poLGIDict->Get("Type");
5879
468
    if (poType == nullptr)
5880
43
    {
5881
43
        CPLError(CE_Failure, CPLE_AppDefined,
5882
43
                 "Cannot find Type of LGIDict object");
5883
43
        return FALSE;
5884
43
    }
5885
5886
425
    if (poType->GetType() != PDFObjectType_Name)
5887
0
    {
5888
0
        CPLError(CE_Failure, CPLE_AppDefined,
5889
0
                 "Invalid type for Type of LGIDict object");
5890
0
        return FALSE;
5891
0
    }
5892
5893
425
    if (strcmp(poType->GetName().c_str(), "LGIDict") != 0)
5894
80
    {
5895
80
        CPLError(CE_Failure, CPLE_AppDefined,
5896
80
                 "Invalid value for Type of LGIDict object : %s",
5897
80
                 poType->GetName().c_str());
5898
80
        return FALSE;
5899
80
    }
5900
5901
    /* -------------------------------------------------------------------- */
5902
    /*      Extract Version attribute                                       */
5903
    /* -------------------------------------------------------------------- */
5904
345
    GDALPDFObject *poVersion = poLGIDict->Get("Version");
5905
345
    if (poVersion == nullptr)
5906
0
    {
5907
0
        CPLError(CE_Failure, CPLE_AppDefined,
5908
0
                 "Cannot find Version of LGIDict object");
5909
0
        return FALSE;
5910
0
    }
5911
5912
345
    if (poVersion->GetType() == PDFObjectType_String)
5913
290
    {
5914
        /* OGC best practice is 2.1 */
5915
290
        CPLDebug("PDF", "LGIDict Version : %s", poVersion->GetString().c_str());
5916
290
    }
5917
55
    else if (poVersion->GetType() == PDFObjectType_Int)
5918
1
    {
5919
        /* Old TerraGo is 2 */
5920
1
        CPLDebug("PDF", "LGIDict Version : %d", poVersion->GetInt());
5921
1
    }
5922
5923
    /* USGS PDF maps have several LGIDict. Keep the one whose description */
5924
    /* is "Map Layers" by default */
5925
345
    const char *pszNeatlineToSelect =
5926
345
        GetOption(papszOpenOptions, "NEATLINE", "Map Layers");
5927
5928
    /* -------------------------------------------------------------------- */
5929
    /*      Extract Neatline attribute                                      */
5930
    /* -------------------------------------------------------------------- */
5931
345
    GDALPDFObject *poNeatline = poLGIDict->Get("Neatline");
5932
345
    if (poNeatline != nullptr && poNeatline->GetType() == PDFObjectType_Array)
5933
338
    {
5934
338
        int nLength = poNeatline->GetArray()->GetLength();
5935
338
        if ((nLength % 2) != 0 || nLength < 4)
5936
38
        {
5937
38
            CPLError(CE_Failure, CPLE_AppDefined,
5938
38
                     "Invalid length for Neatline");
5939
38
            return FALSE;
5940
38
        }
5941
5942
300
        GDALPDFObject *poDescription = poLGIDict->Get("Description");
5943
300
        bool bIsAskedNeatline = false;
5944
300
        if (poDescription != nullptr &&
5945
32
            poDescription->GetType() == PDFObjectType_String)
5946
32
        {
5947
32
            CPLDebug("PDF", "Description = %s",
5948
32
                     poDescription->GetString().c_str());
5949
5950
32
            if (EQUAL(poDescription->GetString().c_str(), pszNeatlineToSelect))
5951
0
            {
5952
0
                m_dfMaxArea = 1e300;
5953
0
                bIsAskedNeatline = true;
5954
0
            }
5955
32
        }
5956
5957
300
        if (!bIsAskedNeatline)
5958
300
        {
5959
300
            double dfMinX = 0.0;
5960
300
            double dfMinY = 0.0;
5961
300
            double dfMaxX = 0.0;
5962
300
            double dfMaxY = 0.0;
5963
130k
            for (int i = 0; i < nLength; i += 2)
5964
130k
            {
5965
130k
                double dfX = Get(poNeatline, i);
5966
130k
                double dfY = Get(poNeatline, i + 1);
5967
130k
                if (i == 0 || dfX < dfMinX)
5968
765
                    dfMinX = dfX;
5969
130k
                if (i == 0 || dfY < dfMinY)
5970
1.22k
                    dfMinY = dfY;
5971
130k
                if (i == 0 || dfX > dfMaxX)
5972
1.31k
                    dfMaxX = dfX;
5973
130k
                if (i == 0 || dfY > dfMaxY)
5974
910
                    dfMaxY = dfY;
5975
130k
            }
5976
300
            double dfArea = (dfMaxX - dfMinX) * (dfMaxY - dfMinY);
5977
300
            if (dfArea < m_dfMaxArea)
5978
0
            {
5979
0
                CPLDebug("PDF", "Not the largest neatline. Skipping it");
5980
0
                return TRUE;
5981
0
            }
5982
5983
300
            CPLDebug("PDF", "This is the largest neatline for now");
5984
300
            m_dfMaxArea = dfArea;
5985
300
        }
5986
0
        else
5987
0
            CPLDebug("PDF", "The \"%s\" registration will be selected",
5988
0
                     pszNeatlineToSelect);
5989
5990
300
        if (pbIsBestCandidate)
5991
0
            *pbIsBestCandidate = TRUE;
5992
5993
300
        delete m_poNeatLine;
5994
300
        m_poNeatLine = new OGRPolygon();
5995
300
        OGRLinearRing *poRing = new OGRLinearRing();
5996
300
        if (nLength == 4)
5997
3
        {
5998
            /* 2 points only ? They are the bounding box */
5999
3
            double dfX1 = Get(poNeatline, 0);
6000
3
            double dfY1 = Get(poNeatline, 1);
6001
3
            double dfX2 = Get(poNeatline, 2);
6002
3
            double dfY2 = Get(poNeatline, 3);
6003
3
            poRing->addPoint(dfX1, dfY1);
6004
3
            poRing->addPoint(dfX2, dfY1);
6005
3
            poRing->addPoint(dfX2, dfY2);
6006
3
            poRing->addPoint(dfX1, dfY2);
6007
3
        }
6008
297
        else
6009
297
        {
6010
130k
            for (int i = 0; i < nLength; i += 2)
6011
130k
            {
6012
130k
                double dfX = Get(poNeatline, i);
6013
130k
                double dfY = Get(poNeatline, i + 1);
6014
130k
                poRing->addPoint(dfX, dfY);
6015
130k
            }
6016
297
        }
6017
300
        poRing->closeRings();
6018
300
        m_poNeatLine->addRingDirectly(poRing);
6019
300
    }
6020
6021
307
    return TRUE;
6022
345
}
6023
6024
/************************************************************************/
6025
/*                     ParseLGIDictDictSecondPass()                     */
6026
/************************************************************************/
6027
6028
int PDFDataset::ParseLGIDictDictSecondPass(GDALPDFDictionary *poLGIDict)
6029
307
{
6030
307
    int i;
6031
6032
    /* -------------------------------------------------------------------- */
6033
    /*      Extract Description attribute                                   */
6034
    /* -------------------------------------------------------------------- */
6035
307
    GDALPDFObject *poDescription = poLGIDict->Get("Description");
6036
307
    if (poDescription != nullptr &&
6037
36
        poDescription->GetType() == PDFObjectType_String)
6038
36
    {
6039
36
        CPLDebug("PDF", "Description = %s", poDescription->GetString().c_str());
6040
36
    }
6041
6042
    /* -------------------------------------------------------------------- */
6043
    /*      Extract CTM attribute                                           */
6044
    /* -------------------------------------------------------------------- */
6045
307
    GDALPDFObject *poCTM = poLGIDict->Get("CTM");
6046
307
    m_bHasCTM = false;
6047
307
    if (poCTM != nullptr && poCTM->GetType() == PDFObjectType_Array &&
6048
271
        CPLTestBool(CPLGetConfigOption("PDF_USE_CTM", "YES")))
6049
271
    {
6050
271
        int nLength = poCTM->GetArray()->GetLength();
6051
271
        if (nLength != 6)
6052
56
        {
6053
56
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid length for CTM");
6054
56
            return FALSE;
6055
56
        }
6056
6057
215
        m_bHasCTM = true;
6058
1.50k
        for (i = 0; i < nLength; i++)
6059
1.29k
        {
6060
1.29k
            m_adfCTM[i] = Get(poCTM, i);
6061
            /* Nullify rotation terms that are significantly smaller than */
6062
            /* scaling terms. */
6063
1.29k
            if ((i == 1 || i == 2) &&
6064
430
                fabs(m_adfCTM[i]) < fabs(m_adfCTM[0]) * 1e-10)
6065
125
                m_adfCTM[i] = 0;
6066
1.29k
            CPLDebug("PDF", "CTM[%d] = %.16g", i, m_adfCTM[i]);
6067
1.29k
        }
6068
215
    }
6069
6070
    /* -------------------------------------------------------------------- */
6071
    /*      Extract Registration attribute                                  */
6072
    /* -------------------------------------------------------------------- */
6073
251
    GDALPDFObject *poRegistration = poLGIDict->Get("Registration");
6074
251
    if (poRegistration != nullptr &&
6075
23
        poRegistration->GetType() == PDFObjectType_Array)
6076
23
    {
6077
23
        GDALPDFArray *poRegistrationArray = poRegistration->GetArray();
6078
23
        int nLength = poRegistrationArray->GetLength();
6079
23
        if (nLength > 4 || (!m_bHasCTM && nLength >= 2) ||
6080
0
            CPLTestBool(CPLGetConfigOption("PDF_REPORT_GCPS", "NO")))
6081
23
        {
6082
23
            m_nGCPCount = 0;
6083
23
            m_pasGCPList =
6084
23
                static_cast<GDAL_GCP *>(CPLCalloc(sizeof(GDAL_GCP), nLength));
6085
6086
116
            for (i = 0; i < nLength; i++)
6087
93
            {
6088
93
                GDALPDFObject *poGCP = poRegistrationArray->Get(i);
6089
93
                if (poGCP != nullptr &&
6090
93
                    poGCP->GetType() == PDFObjectType_Array &&
6091
90
                    poGCP->GetArray()->GetLength() == 4)
6092
90
                {
6093
90
                    double dfUserX = Get(poGCP, 0);
6094
90
                    double dfUserY = Get(poGCP, 1);
6095
90
                    double dfX = Get(poGCP, 2);
6096
90
                    double dfY = Get(poGCP, 3);
6097
90
                    CPLDebug("PDF", "GCP[%d].userX = %.16g", i, dfUserX);
6098
90
                    CPLDebug("PDF", "GCP[%d].userY = %.16g", i, dfUserY);
6099
90
                    CPLDebug("PDF", "GCP[%d].x = %.16g", i, dfX);
6100
90
                    CPLDebug("PDF", "GCP[%d].y = %.16g", i, dfY);
6101
6102
90
                    char szID[32];
6103
90
                    snprintf(szID, sizeof(szID), "%d", m_nGCPCount + 1);
6104
90
                    m_pasGCPList[m_nGCPCount].pszId = CPLStrdup(szID);
6105
90
                    m_pasGCPList[m_nGCPCount].pszInfo = CPLStrdup("");
6106
90
                    m_pasGCPList[m_nGCPCount].dfGCPPixel = dfUserX;
6107
90
                    m_pasGCPList[m_nGCPCount].dfGCPLine = dfUserY;
6108
90
                    m_pasGCPList[m_nGCPCount].dfGCPX = dfX;
6109
90
                    m_pasGCPList[m_nGCPCount].dfGCPY = dfY;
6110
90
                    m_nGCPCount++;
6111
90
                }
6112
93
            }
6113
6114
23
            if (m_nGCPCount == 0)
6115
0
            {
6116
0
                CPLFree(m_pasGCPList);
6117
0
                m_pasGCPList = nullptr;
6118
0
            }
6119
23
        }
6120
23
    }
6121
6122
251
    if (!m_bHasCTM && m_nGCPCount == 0)
6123
13
    {
6124
13
        CPLDebug("PDF", "Neither CTM nor Registration found");
6125
13
        return FALSE;
6126
13
    }
6127
6128
    /* -------------------------------------------------------------------- */
6129
    /*      Extract Projection attribute                                    */
6130
    /* -------------------------------------------------------------------- */
6131
238
    GDALPDFObject *poProjection = poLGIDict->Get("Projection");
6132
238
    if (poProjection == nullptr ||
6133
219
        poProjection->GetType() != PDFObjectType_Dictionary)
6134
27
    {
6135
27
        CPLError(CE_Failure, CPLE_AppDefined, "Could not find Projection");
6136
27
        return FALSE;
6137
27
    }
6138
6139
211
    return ParseProjDict(poProjection->GetDictionary());
6140
238
}
6141
6142
/************************************************************************/
6143
/*                           ParseProjDict()                            */
6144
/************************************************************************/
6145
6146
int PDFDataset::ParseProjDict(GDALPDFDictionary *poProjDict)
6147
211
{
6148
211
    if (poProjDict == nullptr)
6149
0
        return FALSE;
6150
211
    OGRSpatialReference oSRS;
6151
211
    oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
6152
6153
    /* -------------------------------------------------------------------- */
6154
    /*      Extract WKT attribute (GDAL extension)                          */
6155
    /* -------------------------------------------------------------------- */
6156
211
    GDALPDFObject *poWKT = poProjDict->Get("WKT");
6157
211
    if (poWKT != nullptr && poWKT->GetType() == PDFObjectType_String &&
6158
21
        CPLTestBool(CPLGetConfigOption("GDAL_PDF_OGC_BP_READ_WKT", "TRUE")))
6159
21
    {
6160
21
        CPLDebug("PDF", "Found WKT attribute (GDAL extension). Using it");
6161
21
        const char *pszWKTRead = poWKT->GetString().c_str();
6162
21
        if (pszWKTRead[0] != 0)
6163
21
            m_oSRS.importFromWkt(pszWKTRead);
6164
21
        return TRUE;
6165
21
    }
6166
6167
    /* -------------------------------------------------------------------- */
6168
    /*      Extract Type attribute                                          */
6169
    /* -------------------------------------------------------------------- */
6170
190
    GDALPDFObject *poType = poProjDict->Get("Type");
6171
190
    if (poType == nullptr)
6172
10
    {
6173
10
        CPLError(CE_Failure, CPLE_AppDefined,
6174
10
                 "Cannot find Type of Projection object");
6175
10
        return FALSE;
6176
10
    }
6177
6178
180
    if (poType->GetType() != PDFObjectType_Name)
6179
3
    {
6180
3
        CPLError(CE_Failure, CPLE_AppDefined,
6181
3
                 "Invalid type for Type of Projection object");
6182
3
        return FALSE;
6183
3
    }
6184
6185
177
    if (strcmp(poType->GetName().c_str(), "Projection") != 0)
6186
13
    {
6187
13
        CPLError(CE_Failure, CPLE_AppDefined,
6188
13
                 "Invalid value for Type of Projection object : %s",
6189
13
                 poType->GetName().c_str());
6190
13
        return FALSE;
6191
13
    }
6192
6193
    /* -------------------------------------------------------------------- */
6194
    /*      Extract Datum attribute                                         */
6195
    /* -------------------------------------------------------------------- */
6196
164
    int bIsWGS84 = FALSE;
6197
164
    int bIsNAD83 = FALSE;
6198
    /* int bIsNAD27 = FALSE; */
6199
6200
164
    GDALPDFObject *poDatum = poProjDict->Get("Datum");
6201
164
    if (poDatum != nullptr)
6202
43
    {
6203
43
        if (poDatum->GetType() == PDFObjectType_String)
6204
43
        {
6205
            /* Using Annex A of
6206
             * http://portal.opengeospatial.org/files/?artifact_id=40537 */
6207
43
            const char *pszDatum = poDatum->GetString().c_str();
6208
43
            CPLDebug("PDF", "Datum = %s", pszDatum);
6209
43
            if (EQUAL(pszDatum, "WE") || EQUAL(pszDatum, "WGE"))
6210
19
            {
6211
19
                bIsWGS84 = TRUE;
6212
19
                oSRS.SetWellKnownGeogCS("WGS84");
6213
19
            }
6214
24
            else if (EQUAL(pszDatum, "NAR") || STARTS_WITH_CI(pszDatum, "NAR-"))
6215
0
            {
6216
0
                bIsNAD83 = TRUE;
6217
0
                oSRS.SetWellKnownGeogCS("NAD83");
6218
0
            }
6219
24
            else if (EQUAL(pszDatum, "NAS") || STARTS_WITH_CI(pszDatum, "NAS-"))
6220
0
            {
6221
                /* bIsNAD27 = TRUE; */
6222
0
                oSRS.SetWellKnownGeogCS("NAD27");
6223
0
            }
6224
24
            else if (EQUAL(pszDatum, "HEN")) /* HERAT North, Afghanistan */
6225
0
            {
6226
0
                oSRS.SetGeogCS("unknown" /*const char * pszGeogName*/,
6227
0
                               "unknown" /*const char * pszDatumName */,
6228
0
                               "International 1924", 6378388, 297);
6229
0
                oSRS.SetTOWGS84(-333, -222, 114);
6230
0
            }
6231
24
            else if (EQUAL(pszDatum, "ING-A")) /* INDIAN 1960, Vietnam 16N */
6232
0
            {
6233
0
                oSRS.importFromEPSG(4131);
6234
0
            }
6235
24
            else if (EQUAL(pszDatum, "GDS")) /* Geocentric Datum of Australia */
6236
0
            {
6237
0
                oSRS.importFromEPSG(4283);
6238
0
            }
6239
24
            else if (STARTS_WITH_CI(pszDatum, "OHA-")) /* Old Hawaiian */
6240
0
            {
6241
0
                oSRS.importFromEPSG(4135); /* matches OHA-M (Mean) */
6242
0
                if (!EQUAL(pszDatum, "OHA-M"))
6243
0
                {
6244
0
                    CPLError(CE_Warning, CPLE_AppDefined,
6245
0
                             "Using OHA-M (Old Hawaiian Mean) definition for "
6246
0
                             "%s. Potential issue with datum shift parameters",
6247
0
                             pszDatum);
6248
0
                    OGR_SRSNode *poNode = oSRS.GetRoot();
6249
0
                    int iChild = poNode->FindChild("AUTHORITY");
6250
0
                    if (iChild != -1)
6251
0
                        poNode->DestroyChild(iChild);
6252
0
                    iChild = poNode->FindChild("DATUM");
6253
0
                    if (iChild != -1)
6254
0
                    {
6255
0
                        poNode = poNode->GetChild(iChild);
6256
0
                        iChild = poNode->FindChild("AUTHORITY");
6257
0
                        if (iChild != -1)
6258
0
                            poNode->DestroyChild(iChild);
6259
0
                    }
6260
0
                }
6261
0
            }
6262
24
            else
6263
24
            {
6264
24
                CPLError(CE_Warning, CPLE_AppDefined,
6265
24
                         "Unhandled (yet) value for Datum : %s. Defaulting to "
6266
24
                         "WGS84...",
6267
24
                         pszDatum);
6268
24
                oSRS.SetGeogCS("unknown" /*const char * pszGeogName*/,
6269
24
                               "unknown" /*const char * pszDatumName */,
6270
24
                               "unknown", 6378137, 298.257223563);
6271
24
            }
6272
43
        }
6273
0
        else if (poDatum->GetType() == PDFObjectType_Dictionary)
6274
0
        {
6275
0
            GDALPDFDictionary *poDatumDict = poDatum->GetDictionary();
6276
6277
0
            GDALPDFObject *poDatumDescription = poDatumDict->Get("Description");
6278
0
            const char *pszDatumDescription = "unknown";
6279
0
            if (poDatumDescription != nullptr &&
6280
0
                poDatumDescription->GetType() == PDFObjectType_String)
6281
0
                pszDatumDescription = poDatumDescription->GetString().c_str();
6282
0
            CPLDebug("PDF", "Datum.Description = %s", pszDatumDescription);
6283
6284
0
            GDALPDFObject *poEllipsoid = poDatumDict->Get("Ellipsoid");
6285
0
            if (poEllipsoid == nullptr ||
6286
0
                !(poEllipsoid->GetType() == PDFObjectType_String ||
6287
0
                  poEllipsoid->GetType() == PDFObjectType_Dictionary))
6288
0
            {
6289
0
                CPLError(
6290
0
                    CE_Warning, CPLE_AppDefined,
6291
0
                    "Cannot find Ellipsoid in Datum. Defaulting to WGS84...");
6292
0
                oSRS.SetGeogCS("unknown", pszDatumDescription, "unknown",
6293
0
                               6378137, 298.257223563);
6294
0
            }
6295
0
            else if (poEllipsoid->GetType() == PDFObjectType_String)
6296
0
            {
6297
0
                const char *pszEllipsoid = poEllipsoid->GetString().c_str();
6298
0
                CPLDebug("PDF", "Datum.Ellipsoid = %s", pszEllipsoid);
6299
0
                if (EQUAL(pszEllipsoid, "WE"))
6300
0
                {
6301
0
                    oSRS.SetGeogCS("unknown", pszDatumDescription, "WGS 84",
6302
0
                                   6378137, 298.257223563);
6303
0
                }
6304
0
                else
6305
0
                {
6306
0
                    CPLError(CE_Warning, CPLE_AppDefined,
6307
0
                             "Unhandled (yet) value for Ellipsoid : %s. "
6308
0
                             "Defaulting to WGS84...",
6309
0
                             pszEllipsoid);
6310
0
                    oSRS.SetGeogCS("unknown", pszDatumDescription, pszEllipsoid,
6311
0
                                   6378137, 298.257223563);
6312
0
                }
6313
0
            }
6314
0
            else  // if (poEllipsoid->GetType() == PDFObjectType_Dictionary)
6315
0
            {
6316
0
                GDALPDFDictionary *poEllipsoidDict =
6317
0
                    poEllipsoid->GetDictionary();
6318
6319
0
                GDALPDFObject *poEllipsoidDescription =
6320
0
                    poEllipsoidDict->Get("Description");
6321
0
                const char *pszEllipsoidDescription = "unknown";
6322
0
                if (poEllipsoidDescription != nullptr &&
6323
0
                    poEllipsoidDescription->GetType() == PDFObjectType_String)
6324
0
                    pszEllipsoidDescription =
6325
0
                        poEllipsoidDescription->GetString().c_str();
6326
0
                CPLDebug("PDF", "Datum.Ellipsoid.Description = %s",
6327
0
                         pszEllipsoidDescription);
6328
6329
0
                double dfSemiMajor = Get(poEllipsoidDict, "SemiMajorAxis");
6330
0
                CPLDebug("PDF", "Datum.Ellipsoid.SemiMajorAxis = %.16g",
6331
0
                         dfSemiMajor);
6332
0
                double dfInvFlattening = -1.0;
6333
6334
0
                if (poEllipsoidDict->Get("InvFlattening"))
6335
0
                {
6336
0
                    dfInvFlattening = Get(poEllipsoidDict, "InvFlattening");
6337
0
                    CPLDebug("PDF", "Datum.Ellipsoid.InvFlattening = %.16g",
6338
0
                             dfInvFlattening);
6339
0
                }
6340
0
                else if (poEllipsoidDict->Get("SemiMinorAxis"))
6341
0
                {
6342
0
                    double dfSemiMinor = Get(poEllipsoidDict, "SemiMinorAxis");
6343
0
                    CPLDebug("PDF", "Datum.Ellipsoid.SemiMinorAxis = %.16g",
6344
0
                             dfSemiMinor);
6345
0
                    dfInvFlattening =
6346
0
                        OSRCalcInvFlattening(dfSemiMajor, dfSemiMinor);
6347
0
                }
6348
6349
0
                if (dfSemiMajor != 0.0 && dfInvFlattening != -1.0)
6350
0
                {
6351
0
                    oSRS.SetGeogCS("unknown", pszDatumDescription,
6352
0
                                   pszEllipsoidDescription, dfSemiMajor,
6353
0
                                   dfInvFlattening);
6354
0
                }
6355
0
                else
6356
0
                {
6357
0
                    CPLError(
6358
0
                        CE_Warning, CPLE_AppDefined,
6359
0
                        "Invalid Ellipsoid object. Defaulting to WGS84...");
6360
0
                    oSRS.SetGeogCS("unknown", pszDatumDescription,
6361
0
                                   pszEllipsoidDescription, 6378137,
6362
0
                                   298.257223563);
6363
0
                }
6364
0
            }
6365
6366
0
            GDALPDFObject *poTOWGS84 = poDatumDict->Get("ToWGS84");
6367
0
            if (poTOWGS84 != nullptr &&
6368
0
                poTOWGS84->GetType() == PDFObjectType_Dictionary)
6369
0
            {
6370
0
                GDALPDFDictionary *poTOWGS84Dict = poTOWGS84->GetDictionary();
6371
0
                double dx = Get(poTOWGS84Dict, "dx");
6372
0
                double dy = Get(poTOWGS84Dict, "dy");
6373
0
                double dz = Get(poTOWGS84Dict, "dz");
6374
0
                if (poTOWGS84Dict->Get("rx") && poTOWGS84Dict->Get("ry") &&
6375
0
                    poTOWGS84Dict->Get("rz") && poTOWGS84Dict->Get("sf"))
6376
0
                {
6377
0
                    double rx = Get(poTOWGS84Dict, "rx");
6378
0
                    double ry = Get(poTOWGS84Dict, "ry");
6379
0
                    double rz = Get(poTOWGS84Dict, "rz");
6380
0
                    double sf = Get(poTOWGS84Dict, "sf");
6381
0
                    oSRS.SetTOWGS84(dx, dy, dz, rx, ry, rz, sf);
6382
0
                }
6383
0
                else
6384
0
                {
6385
0
                    oSRS.SetTOWGS84(dx, dy, dz);
6386
0
                }
6387
0
            }
6388
0
        }
6389
43
    }
6390
6391
    /* -------------------------------------------------------------------- */
6392
    /*      Extract Hemisphere attribute                                    */
6393
    /* -------------------------------------------------------------------- */
6394
164
    CPLString osHemisphere;
6395
164
    GDALPDFObject *poHemisphere = poProjDict->Get("Hemisphere");
6396
164
    if (poHemisphere != nullptr &&
6397
0
        poHemisphere->GetType() == PDFObjectType_String)
6398
0
    {
6399
0
        osHemisphere = poHemisphere->GetString();
6400
0
    }
6401
6402
    /* -------------------------------------------------------------------- */
6403
    /*      Extract ProjectionType attribute                                */
6404
    /* -------------------------------------------------------------------- */
6405
164
    GDALPDFObject *poProjectionType = poProjDict->Get("ProjectionType");
6406
164
    if (poProjectionType == nullptr ||
6407
160
        poProjectionType->GetType() != PDFObjectType_String)
6408
4
    {
6409
4
        CPLError(CE_Failure, CPLE_AppDefined,
6410
4
                 "Cannot find ProjectionType of Projection object");
6411
4
        return FALSE;
6412
4
    }
6413
160
    CPLString osProjectionType(poProjectionType->GetString());
6414
160
    CPLDebug("PDF", "Projection.ProjectionType = %s", osProjectionType.c_str());
6415
6416
    /* Unhandled: NONE, GEODETIC */
6417
6418
160
    if (EQUAL(osProjectionType, "GEOGRAPHIC"))
6419
148
    {
6420
        /* Nothing to do */
6421
148
    }
6422
6423
    /* Unhandled: LOCAL CARTESIAN, MG (MGRS) */
6424
6425
12
    else if (EQUAL(osProjectionType, "UT")) /* UTM */
6426
0
    {
6427
0
        const double dfZone = Get(poProjDict, "Zone");
6428
0
        if (dfZone >= 1 && dfZone <= 60)
6429
0
        {
6430
0
            int nZone = static_cast<int>(dfZone);
6431
0
            int bNorth = EQUAL(osHemisphere, "N");
6432
0
            if (bIsWGS84)
6433
0
                oSRS.importFromEPSG(((bNorth) ? 32600 : 32700) + nZone);
6434
0
            else
6435
0
                oSRS.SetUTM(nZone, bNorth);
6436
0
        }
6437
0
    }
6438
6439
12
    else if (EQUAL(osProjectionType,
6440
12
                   "UP")) /* Universal Polar Stereographic (UPS) */
6441
0
    {
6442
0
        int bNorth = EQUAL(osHemisphere, "N");
6443
0
        if (bIsWGS84)
6444
0
            oSRS.importFromEPSG((bNorth) ? 32661 : 32761);
6445
0
        else
6446
0
            oSRS.SetPS((bNorth) ? 90 : -90, 0, 0.994, 200000, 200000);
6447
0
    }
6448
6449
12
    else if (EQUAL(osProjectionType, "SPCS")) /* State Plane */
6450
0
    {
6451
0
        const double dfZone = Get(poProjDict, "Zone");
6452
0
        if (dfZone >= 0 && dfZone <= INT_MAX)
6453
0
        {
6454
0
            int nZone = static_cast<int>(dfZone);
6455
0
            oSRS.SetStatePlane(nZone, bIsNAD83);
6456
0
        }
6457
0
    }
6458
6459
12
    else if (EQUAL(osProjectionType, "AC")) /* Albers Equal Area Conic */
6460
0
    {
6461
0
        double dfStdP1 = Get(poProjDict, "StandardParallelOne");
6462
0
        double dfStdP2 = Get(poProjDict, "StandardParallelTwo");
6463
0
        double dfCenterLat = Get(poProjDict, "OriginLatitude");
6464
0
        double dfCenterLong = Get(poProjDict, "CentralMeridian");
6465
0
        double dfFalseEasting = Get(poProjDict, "FalseEasting");
6466
0
        double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
6467
0
        oSRS.SetACEA(dfStdP1, dfStdP2, dfCenterLat, dfCenterLong,
6468
0
                     dfFalseEasting, dfFalseNorthing);
6469
0
    }
6470
6471
12
    else if (EQUAL(osProjectionType, "AL")) /* Azimuthal Equidistant */
6472
0
    {
6473
0
        double dfCenterLat = Get(poProjDict, "OriginLatitude");
6474
0
        double dfCenterLong = Get(poProjDict, "CentralMeridian");
6475
0
        double dfFalseEasting = Get(poProjDict, "FalseEasting");
6476
0
        double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
6477
0
        oSRS.SetAE(dfCenterLat, dfCenterLong, dfFalseEasting, dfFalseNorthing);
6478
0
    }
6479
6480
12
    else if (EQUAL(osProjectionType, "BF")) /* Bonne */
6481
0
    {
6482
0
        double dfStdP1 = Get(poProjDict, "OriginLatitude");
6483
0
        double dfCentralMeridian = Get(poProjDict, "CentralMeridian");
6484
0
        double dfFalseEasting = Get(poProjDict, "FalseEasting");
6485
0
        double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
6486
0
        oSRS.SetBonne(dfStdP1, dfCentralMeridian, dfFalseEasting,
6487
0
                      dfFalseNorthing);
6488
0
    }
6489
6490
12
    else if (EQUAL(osProjectionType, "CS")) /* Cassini */
6491
0
    {
6492
0
        double dfCenterLat = Get(poProjDict, "OriginLatitude");
6493
0
        double dfCenterLong = Get(poProjDict, "CentralMeridian");
6494
0
        double dfFalseEasting = Get(poProjDict, "FalseEasting");
6495
0
        double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
6496
0
        oSRS.SetCS(dfCenterLat, dfCenterLong, dfFalseEasting, dfFalseNorthing);
6497
0
    }
6498
6499
12
    else if (EQUAL(osProjectionType, "LI")) /* Cylindrical Equal Area */
6500
0
    {
6501
0
        double dfStdP1 = Get(poProjDict, "OriginLatitude");
6502
0
        double dfCentralMeridian = Get(poProjDict, "CentralMeridian");
6503
0
        double dfFalseEasting = Get(poProjDict, "FalseEasting");
6504
0
        double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
6505
0
        oSRS.SetCEA(dfStdP1, dfCentralMeridian, dfFalseEasting,
6506
0
                    dfFalseNorthing);
6507
0
    }
6508
6509
12
    else if (EQUAL(osProjectionType, "EF")) /* Eckert IV */
6510
0
    {
6511
0
        double dfCentralMeridian = Get(poProjDict, "CentralMeridian");
6512
0
        double dfFalseEasting = Get(poProjDict, "FalseEasting");
6513
0
        double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
6514
0
        oSRS.SetEckertIV(dfCentralMeridian, dfFalseEasting, dfFalseNorthing);
6515
0
    }
6516
6517
12
    else if (EQUAL(osProjectionType, "ED")) /* Eckert VI */
6518
0
    {
6519
0
        double dfCentralMeridian = Get(poProjDict, "CentralMeridian");
6520
0
        double dfFalseEasting = Get(poProjDict, "FalseEasting");
6521
0
        double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
6522
0
        oSRS.SetEckertVI(dfCentralMeridian, dfFalseEasting, dfFalseNorthing);
6523
0
    }
6524
6525
12
    else if (EQUAL(osProjectionType, "CP")) /* Equidistant Cylindrical */
6526
0
    {
6527
0
        double dfCenterLat = Get(poProjDict, "StandardParallel");
6528
0
        double dfCenterLong = Get(poProjDict, "CentralMeridian");
6529
0
        double dfFalseEasting = Get(poProjDict, "FalseEasting");
6530
0
        double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
6531
0
        oSRS.SetEquirectangular(dfCenterLat, dfCenterLong, dfFalseEasting,
6532
0
                                dfFalseNorthing);
6533
0
    }
6534
6535
12
    else if (EQUAL(osProjectionType, "GN")) /* Gnomonic */
6536
0
    {
6537
0
        double dfCenterLat = Get(poProjDict, "OriginLatitude");
6538
0
        double dfCenterLong = Get(poProjDict, "CentralMeridian");
6539
0
        double dfFalseEasting = Get(poProjDict, "FalseEasting");
6540
0
        double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
6541
0
        oSRS.SetGnomonic(dfCenterLat, dfCenterLong, dfFalseEasting,
6542
0
                         dfFalseNorthing);
6543
0
    }
6544
6545
12
    else if (EQUAL(osProjectionType, "LE")) /* Lambert Conformal Conic */
6546
0
    {
6547
0
        double dfStdP1 = Get(poProjDict, "StandardParallelOne");
6548
0
        double dfStdP2 = Get(poProjDict, "StandardParallelTwo");
6549
0
        double dfCenterLat = Get(poProjDict, "OriginLatitude");
6550
0
        double dfCenterLong = Get(poProjDict, "CentralMeridian");
6551
0
        double dfFalseEasting = Get(poProjDict, "FalseEasting");
6552
0
        double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
6553
0
        oSRS.SetLCC(dfStdP1, dfStdP2, dfCenterLat, dfCenterLong, dfFalseEasting,
6554
0
                    dfFalseNorthing);
6555
0
    }
6556
6557
12
    else if (EQUAL(osProjectionType, "MC")) /* Mercator */
6558
0
    {
6559
#ifdef not_supported
6560
        if (poProjDict->Get("StandardParallelOne") == nullptr)
6561
#endif
6562
0
        {
6563
0
            double dfCenterLat = Get(poProjDict, "OriginLatitude");
6564
0
            double dfCenterLong = Get(poProjDict, "CentralMeridian");
6565
0
            double dfScale = Get(poProjDict, "ScaleFactor");
6566
0
            double dfFalseEasting = Get(poProjDict, "FalseEasting");
6567
0
            double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
6568
0
            oSRS.SetMercator(dfCenterLat, dfCenterLong, dfScale, dfFalseEasting,
6569
0
                             dfFalseNorthing);
6570
0
        }
6571
#ifdef not_supported
6572
        else
6573
        {
6574
            double dfStdP1 = Get(poProjDict, "StandardParallelOne");
6575
            double dfCenterLat = poProjDict->Get("OriginLatitude")
6576
                                     ? Get(poProjDict, "OriginLatitude")
6577
                                     : 0;
6578
            double dfCenterLong = Get(poProjDict, "CentralMeridian");
6579
            double dfFalseEasting = Get(poProjDict, "FalseEasting");
6580
            double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
6581
            oSRS.SetMercator2SP(dfStdP1, dfCenterLat, dfCenterLong,
6582
                                dfFalseEasting, dfFalseNorthing);
6583
        }
6584
#endif
6585
0
    }
6586
6587
12
    else if (EQUAL(osProjectionType, "MH")) /* Miller Cylindrical */
6588
0
    {
6589
0
        double dfCenterLat = 0 /* ? */;
6590
0
        double dfCenterLong = Get(poProjDict, "CentralMeridian");
6591
0
        double dfFalseEasting = Get(poProjDict, "FalseEasting");
6592
0
        double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
6593
0
        oSRS.SetMC(dfCenterLat, dfCenterLong, dfFalseEasting, dfFalseNorthing);
6594
0
    }
6595
6596
12
    else if (EQUAL(osProjectionType, "MP")) /* Mollweide */
6597
0
    {
6598
0
        double dfCentralMeridian = Get(poProjDict, "CentralMeridian");
6599
0
        double dfFalseEasting = Get(poProjDict, "FalseEasting");
6600
0
        double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
6601
0
        oSRS.SetMollweide(dfCentralMeridian, dfFalseEasting, dfFalseNorthing);
6602
0
    }
6603
6604
    /* Unhandled:  "NY" : Ney's (Modified Lambert Conformal Conic) */
6605
6606
12
    else if (EQUAL(osProjectionType, "NT")) /* New Zealand Map Grid */
6607
0
    {
6608
        /* No parameter specified in the PDF, so let's take the ones of
6609
         * EPSG:27200 */
6610
0
        double dfCenterLat = -41;
6611
0
        double dfCenterLong = 173;
6612
0
        double dfFalseEasting = 2510000;
6613
0
        double dfFalseNorthing = 6023150;
6614
0
        oSRS.SetNZMG(dfCenterLat, dfCenterLong, dfFalseEasting,
6615
0
                     dfFalseNorthing);
6616
0
    }
6617
6618
12
    else if (EQUAL(osProjectionType, "OC")) /* Oblique Mercator */
6619
0
    {
6620
0
        double dfCenterLat = Get(poProjDict, "OriginLatitude");
6621
0
        double dfLat1 = Get(poProjDict, "LatitudeOne");
6622
0
        double dfLong1 = Get(poProjDict, "LongitudeOne");
6623
0
        double dfLat2 = Get(poProjDict, "LatitudeTwo");
6624
0
        double dfLong2 = Get(poProjDict, "LongitudeTwo");
6625
0
        double dfScale = Get(poProjDict, "ScaleFactor");
6626
0
        double dfFalseEasting = Get(poProjDict, "FalseEasting");
6627
0
        double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
6628
0
        oSRS.SetHOM2PNO(dfCenterLat, dfLat1, dfLong1, dfLat2, dfLong2, dfScale,
6629
0
                        dfFalseEasting, dfFalseNorthing);
6630
0
    }
6631
6632
12
    else if (EQUAL(osProjectionType, "OD")) /* Orthographic */
6633
0
    {
6634
0
        double dfCenterLat = Get(poProjDict, "OriginLatitude");
6635
0
        double dfCenterLong = Get(poProjDict, "CentralMeridian");
6636
0
        double dfFalseEasting = Get(poProjDict, "FalseEasting");
6637
0
        double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
6638
0
        oSRS.SetOrthographic(dfCenterLat, dfCenterLong, dfFalseEasting,
6639
0
                             dfFalseNorthing);
6640
0
    }
6641
6642
12
    else if (EQUAL(osProjectionType, "PG")) /* Polar Stereographic */
6643
0
    {
6644
0
        double dfCenterLat = Get(poProjDict, "LatitudeTrueScale");
6645
0
        double dfCenterLong = Get(poProjDict, "LongitudeDownFromPole");
6646
0
        double dfScale = 1.0;
6647
0
        double dfFalseEasting = Get(poProjDict, "FalseEasting");
6648
0
        double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
6649
0
        oSRS.SetPS(dfCenterLat, dfCenterLong, dfScale, dfFalseEasting,
6650
0
                   dfFalseNorthing);
6651
0
    }
6652
6653
12
    else if (EQUAL(osProjectionType, "PH")) /* Polyconic */
6654
0
    {
6655
0
        double dfCenterLat = Get(poProjDict, "OriginLatitude");
6656
0
        double dfCenterLong = Get(poProjDict, "CentralMeridian");
6657
0
        double dfFalseEasting = Get(poProjDict, "FalseEasting");
6658
0
        double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
6659
0
        oSRS.SetPolyconic(dfCenterLat, dfCenterLong, dfFalseEasting,
6660
0
                          dfFalseNorthing);
6661
0
    }
6662
6663
12
    else if (EQUAL(osProjectionType, "SA")) /* Sinusoidal */
6664
0
    {
6665
0
        double dfCenterLong = Get(poProjDict, "CentralMeridian");
6666
0
        double dfFalseEasting = Get(poProjDict, "FalseEasting");
6667
0
        double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
6668
0
        oSRS.SetSinusoidal(dfCenterLong, dfFalseEasting, dfFalseNorthing);
6669
0
    }
6670
6671
12
    else if (EQUAL(osProjectionType, "SD")) /* Stereographic */
6672
0
    {
6673
0
        double dfCenterLat = Get(poProjDict, "OriginLatitude");
6674
0
        double dfCenterLong = Get(poProjDict, "CentralMeridian");
6675
0
        double dfScale = 1.0;
6676
0
        double dfFalseEasting = Get(poProjDict, "FalseEasting");
6677
0
        double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
6678
0
        oSRS.SetStereographic(dfCenterLat, dfCenterLong, dfScale,
6679
0
                              dfFalseEasting, dfFalseNorthing);
6680
0
    }
6681
6682
12
    else if (EQUAL(osProjectionType, "TC")) /* Transverse Mercator */
6683
0
    {
6684
0
        double dfCenterLat = Get(poProjDict, "OriginLatitude");
6685
0
        double dfCenterLong = Get(poProjDict, "CentralMeridian");
6686
0
        double dfScale = Get(poProjDict, "ScaleFactor");
6687
0
        double dfFalseEasting = Get(poProjDict, "FalseEasting");
6688
0
        double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
6689
0
        if (dfCenterLat == 0.0 && dfScale == 0.9996 && dfCenterLong >= -180 &&
6690
0
            dfCenterLong <= 180 && dfFalseEasting == 500000 &&
6691
0
            (dfFalseNorthing == 0.0 || dfFalseNorthing == 10000000.0))
6692
0
        {
6693
0
            const int nZone =
6694
0
                static_cast<int>(floor((dfCenterLong + 180.0) / 6.0) + 1);
6695
0
            int bNorth = dfFalseNorthing == 0;
6696
0
            if (bIsWGS84)
6697
0
                oSRS.importFromEPSG(((bNorth) ? 32600 : 32700) + nZone);
6698
0
            else if (bIsNAD83 && bNorth)
6699
0
                oSRS.importFromEPSG(26900 + nZone);
6700
0
            else
6701
0
                oSRS.SetUTM(nZone, bNorth);
6702
0
        }
6703
0
        else
6704
0
        {
6705
0
            oSRS.SetTM(dfCenterLat, dfCenterLong, dfScale, dfFalseEasting,
6706
0
                       dfFalseNorthing);
6707
0
        }
6708
0
    }
6709
6710
    /* Unhandled TX : Transverse Cylindrical Equal Area */
6711
6712
12
    else if (EQUAL(osProjectionType, "VA")) /* Van der Grinten */
6713
0
    {
6714
0
        double dfCenterLong = Get(poProjDict, "CentralMeridian");
6715
0
        double dfFalseEasting = Get(poProjDict, "FalseEasting");
6716
0
        double dfFalseNorthing = Get(poProjDict, "FalseNorthing");
6717
0
        oSRS.SetVDG(dfCenterLong, dfFalseEasting, dfFalseNorthing);
6718
0
    }
6719
6720
12
    else
6721
12
    {
6722
12
        CPLError(CE_Failure, CPLE_AppDefined,
6723
12
                 "Unhandled (yet) value for ProjectionType : %s",
6724
12
                 osProjectionType.c_str());
6725
12
        return FALSE;
6726
12
    }
6727
6728
    /* -------------------------------------------------------------------- */
6729
    /*      Extract Units attribute                                         */
6730
    /* -------------------------------------------------------------------- */
6731
148
    CPLString osUnits;
6732
148
    GDALPDFObject *poUnits = poProjDict->Get("Units");
6733
148
    if (poUnits != nullptr && poUnits->GetType() == PDFObjectType_String &&
6734
0
        !EQUAL(osProjectionType, "GEOGRAPHIC"))
6735
0
    {
6736
0
        osUnits = poUnits->GetString();
6737
0
        CPLDebug("PDF", "Projection.Units = %s", osUnits.c_str());
6738
6739
        // This is super weird. The false easting/northing of the SRS
6740
        // are expressed in the unit, but the geotransform is expressed in
6741
        // meters. Hence this hack to have an equivalent SRS definition, but
6742
        // with linear units converted in meters.
6743
0
        if (EQUAL(osUnits, "M"))
6744
0
            oSRS.SetLinearUnits("Meter", 1.0);
6745
0
        else if (EQUAL(osUnits, "FT"))
6746
0
        {
6747
0
            oSRS.SetLinearUnits("foot", 0.3048);
6748
0
            oSRS.SetLinearUnitsAndUpdateParameters("Meter", 1.0);
6749
0
        }
6750
0
        else if (EQUAL(osUnits, "USSF"))
6751
0
        {
6752
0
            oSRS.SetLinearUnits(SRS_UL_US_FOOT, CPLAtof(SRS_UL_US_FOOT_CONV));
6753
0
            oSRS.SetLinearUnitsAndUpdateParameters("Meter", 1.0);
6754
0
        }
6755
0
        else
6756
0
            CPLError(CE_Warning, CPLE_AppDefined, "Unhandled unit: %s",
6757
0
                     osUnits.c_str());
6758
0
    }
6759
6760
    /* -------------------------------------------------------------------- */
6761
    /*      Export SpatialRef                                               */
6762
    /* -------------------------------------------------------------------- */
6763
148
    m_oSRS = std::move(oSRS);
6764
6765
148
    return TRUE;
6766
160
}
6767
6768
/************************************************************************/
6769
/*                              ParseVP()                               */
6770
/************************************************************************/
6771
6772
int PDFDataset::ParseVP(GDALPDFObject *poVP, double dfMediaBoxWidth,
6773
                        double dfMediaBoxHeight)
6774
5.80k
{
6775
5.80k
    int i;
6776
6777
5.80k
    if (poVP->GetType() != PDFObjectType_Array)
6778
55
        return FALSE;
6779
6780
5.75k
    GDALPDFArray *poVPArray = poVP->GetArray();
6781
6782
5.75k
    int nLength = poVPArray->GetLength();
6783
5.75k
    CPLDebug("PDF", "VP length = %d", nLength);
6784
5.75k
    if (nLength < 1)
6785
0
        return FALSE;
6786
6787
    /* -------------------------------------------------------------------- */
6788
    /*      Find the largest BBox                                           */
6789
    /* -------------------------------------------------------------------- */
6790
5.75k
    const char *pszNeatlineToSelect =
6791
5.75k
        GetOption(papszOpenOptions, "NEATLINE", "Map Layers");
6792
6793
5.75k
    int iLargest = 0;
6794
5.75k
    int iRequestedVP = -1;
6795
5.75k
    double dfLargestArea = 0;
6796
6797
12.0k
    for (i = 0; i < nLength; i++)
6798
7.42k
    {
6799
7.42k
        GDALPDFObject *poVPElt = poVPArray->Get(i);
6800
7.42k
        if (poVPElt == nullptr ||
6801
6.99k
            poVPElt->GetType() != PDFObjectType_Dictionary)
6802
966
        {
6803
966
            return FALSE;
6804
966
        }
6805
6806
6.45k
        GDALPDFDictionary *poVPEltDict = poVPElt->GetDictionary();
6807
6808
6.45k
        GDALPDFObject *poMeasure = poVPEltDict->Get("Measure");
6809
6.45k
        if (poMeasure == nullptr ||
6810
4.91k
            poMeasure->GetType() != PDFObjectType_Dictionary)
6811
1.62k
        {
6812
1.62k
            continue;
6813
1.62k
        }
6814
        /* --------------------------------------------------------------------
6815
         */
6816
        /*      Extract Subtype attribute */
6817
        /* --------------------------------------------------------------------
6818
         */
6819
4.83k
        GDALPDFDictionary *poMeasureDict = poMeasure->GetDictionary();
6820
4.83k
        GDALPDFObject *poSubtype = poMeasureDict->Get("Subtype");
6821
4.83k
        if (poSubtype == nullptr || poSubtype->GetType() != PDFObjectType_Name)
6822
304
        {
6823
304
            continue;
6824
304
        }
6825
6826
4.52k
        CPLDebug("PDF", "Subtype = %s", poSubtype->GetName().c_str());
6827
4.52k
        if (!EQUAL(poSubtype->GetName().c_str(), "GEO"))
6828
191
        {
6829
191
            continue;
6830
191
        }
6831
6832
4.33k
        GDALPDFObject *poName = poVPEltDict->Get("Name");
6833
4.33k
        if (poName != nullptr && poName->GetType() == PDFObjectType_String)
6834
3.64k
        {
6835
3.64k
            CPLDebug("PDF", "Name = %s", poName->GetString().c_str());
6836
3.64k
            if (EQUAL(poName->GetString().c_str(), pszNeatlineToSelect))
6837
0
            {
6838
0
                iRequestedVP = i;
6839
0
            }
6840
3.64k
        }
6841
6842
4.33k
        GDALPDFObject *poBBox = poVPEltDict->Get("BBox");
6843
4.33k
        if (poBBox == nullptr || poBBox->GetType() != PDFObjectType_Array)
6844
113
        {
6845
113
            CPLError(CE_Failure, CPLE_AppDefined, "Cannot find Bbox object");
6846
113
            return FALSE;
6847
113
        }
6848
6849
4.22k
        int nBboxLength = poBBox->GetArray()->GetLength();
6850
4.22k
        if (nBboxLength != 4)
6851
81
        {
6852
81
            CPLError(CE_Failure, CPLE_AppDefined,
6853
81
                     "Invalid length for Bbox object");
6854
81
            return FALSE;
6855
81
        }
6856
6857
4.14k
        double adfBBox[4];
6858
4.14k
        adfBBox[0] = Get(poBBox, 0);
6859
4.14k
        adfBBox[1] = Get(poBBox, 1);
6860
4.14k
        adfBBox[2] = Get(poBBox, 2);
6861
4.14k
        adfBBox[3] = Get(poBBox, 3);
6862
4.14k
        double dfArea =
6863
4.14k
            fabs(adfBBox[2] - adfBBox[0]) * fabs(adfBBox[3] - adfBBox[1]);
6864
4.14k
        if (dfArea > dfLargestArea)
6865
3.59k
        {
6866
3.59k
            iLargest = i;
6867
3.59k
            dfLargestArea = dfArea;
6868
3.59k
        }
6869
4.14k
    }
6870
6871
4.59k
    if (nLength > 1)
6872
1.00k
    {
6873
1.00k
        CPLDebug("PDF", "Largest BBox in VP array is element %d", iLargest);
6874
1.00k
    }
6875
6876
4.59k
    GDALPDFObject *poVPElt = nullptr;
6877
6878
4.59k
    if (iRequestedVP > -1)
6879
0
    {
6880
0
        CPLDebug("PDF", "Requested NEATLINE BBox in VP array is element %d",
6881
0
                 iRequestedVP);
6882
0
        poVPElt = poVPArray->Get(iRequestedVP);
6883
0
    }
6884
4.59k
    else
6885
4.59k
    {
6886
4.59k
        poVPElt = poVPArray->Get(iLargest);
6887
4.59k
    }
6888
6889
4.59k
    if (poVPElt == nullptr || poVPElt->GetType() != PDFObjectType_Dictionary)
6890
0
    {
6891
0
        return FALSE;
6892
0
    }
6893
6894
4.59k
    GDALPDFDictionary *poVPEltDict = poVPElt->GetDictionary();
6895
6896
4.59k
    GDALPDFObject *poBBox = poVPEltDict->Get("BBox");
6897
4.59k
    if (poBBox == nullptr || poBBox->GetType() != PDFObjectType_Array)
6898
110
    {
6899
110
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find Bbox object");
6900
110
        return FALSE;
6901
110
    }
6902
6903
4.48k
    int nBboxLength = poBBox->GetArray()->GetLength();
6904
4.48k
    if (nBboxLength != 4)
6905
192
    {
6906
192
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid length for Bbox object");
6907
192
        return FALSE;
6908
192
    }
6909
6910
4.29k
    double dfULX = Get(poBBox, 0);
6911
4.29k
    double dfULY = dfMediaBoxHeight - Get(poBBox, 1);
6912
4.29k
    double dfLRX = Get(poBBox, 2);
6913
4.29k
    double dfLRY = dfMediaBoxHeight - Get(poBBox, 3);
6914
6915
    /* -------------------------------------------------------------------- */
6916
    /*      Extract Measure attribute                                       */
6917
    /* -------------------------------------------------------------------- */
6918
4.29k
    GDALPDFObject *poMeasure = poVPEltDict->Get("Measure");
6919
4.29k
    if (poMeasure == nullptr ||
6920
3.84k
        poMeasure->GetType() != PDFObjectType_Dictionary)
6921
465
    {
6922
465
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find Measure object");
6923
465
        return FALSE;
6924
465
    }
6925
6926
3.82k
    int bRet = ParseMeasure(poMeasure, dfMediaBoxWidth, dfMediaBoxHeight, dfULX,
6927
3.82k
                            dfULY, dfLRX, dfLRY);
6928
6929
    /* -------------------------------------------------------------------- */
6930
    /*      Extract PointData attribute                                     */
6931
    /* -------------------------------------------------------------------- */
6932
3.82k
    GDALPDFObject *poPointData = poVPEltDict->Get("PtData");
6933
3.82k
    if (poPointData != nullptr &&
6934
0
        poPointData->GetType() == PDFObjectType_Dictionary)
6935
0
    {
6936
0
        CPLDebug("PDF", "Found PointData");
6937
0
    }
6938
6939
3.82k
    return bRet;
6940
4.29k
}
6941
6942
/************************************************************************/
6943
/*                            ParseMeasure()                            */
6944
/************************************************************************/
6945
6946
int PDFDataset::ParseMeasure(GDALPDFObject *poMeasure, double dfMediaBoxWidth,
6947
                             double dfMediaBoxHeight, double dfULX,
6948
                             double dfULY, double dfLRX, double dfLRY)
6949
3.82k
{
6950
3.82k
    GDALPDFDictionary *poMeasureDict = poMeasure->GetDictionary();
6951
6952
    /* -------------------------------------------------------------------- */
6953
    /*      Extract Subtype attribute                                       */
6954
    /* -------------------------------------------------------------------- */
6955
3.82k
    GDALPDFObject *poSubtype = poMeasureDict->Get("Subtype");
6956
3.82k
    if (poSubtype == nullptr || poSubtype->GetType() != PDFObjectType_Name)
6957
243
    {
6958
243
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find Subtype object");
6959
243
        return FALSE;
6960
243
    }
6961
6962
3.58k
    CPLDebug("PDF", "Subtype = %s", poSubtype->GetName().c_str());
6963
3.58k
    if (!EQUAL(poSubtype->GetName().c_str(), "GEO"))
6964
12
        return FALSE;
6965
6966
    /* -------------------------------------------------------------------- */
6967
    /*      Extract Bounds attribute (optional)                             */
6968
    /* -------------------------------------------------------------------- */
6969
6970
    /* http://acrobatusers.com/sites/default/files/gallery_pictures/SEVERODVINSK.pdf
6971
     */
6972
    /* has lgit:LPTS, lgit:GPTS and lgit:Bounds that have more precision than */
6973
    /* LPTS, GPTS and Bounds. Use those ones */
6974
6975
3.57k
    GDALPDFObject *poBounds = poMeasureDict->Get("lgit:Bounds");
6976
3.57k
    if (poBounds != nullptr && poBounds->GetType() == PDFObjectType_Array)
6977
0
    {
6978
0
        CPLDebug("PDF", "Using lgit:Bounds");
6979
0
    }
6980
3.57k
    else if ((poBounds = poMeasureDict->Get("Bounds")) == nullptr ||
6981
2.14k
             poBounds->GetType() != PDFObjectType_Array)
6982
1.43k
    {
6983
1.43k
        poBounds = nullptr;
6984
1.43k
    }
6985
6986
3.57k
    if (poBounds != nullptr)
6987
2.13k
    {
6988
2.13k
        int nBoundsLength = poBounds->GetArray()->GetLength();
6989
2.13k
        if (nBoundsLength == 8)
6990
1.79k
        {
6991
1.79k
            double adfBounds[8];
6992
16.1k
            for (int i = 0; i < 8; i++)
6993
14.3k
            {
6994
14.3k
                adfBounds[i] = Get(poBounds, i);
6995
14.3k
                CPLDebug("PDF", "Bounds[%d] = %f", i, adfBounds[i]);
6996
14.3k
            }
6997
6998
            // TODO we should use it to restrict the neatline but
6999
            // I have yet to set a sample where bounds are not the four
7000
            // corners of the unit square.
7001
1.79k
        }
7002
2.13k
    }
7003
7004
    /* -------------------------------------------------------------------- */
7005
    /*      Extract GPTS attribute                                          */
7006
    /* -------------------------------------------------------------------- */
7007
3.57k
    GDALPDFObject *poGPTS = poMeasureDict->Get("lgit:GPTS");
7008
3.57k
    if (poGPTS != nullptr && poGPTS->GetType() == PDFObjectType_Array)
7009
0
    {
7010
0
        CPLDebug("PDF", "Using lgit:GPTS");
7011
0
    }
7012
3.57k
    else if ((poGPTS = poMeasureDict->Get("GPTS")) == nullptr ||
7013
3.53k
             poGPTS->GetType() != PDFObjectType_Array)
7014
51
    {
7015
51
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find GPTS object");
7016
51
        return FALSE;
7017
51
    }
7018
7019
3.51k
    int nGPTSLength = poGPTS->GetArray()->GetLength();
7020
3.51k
    if ((nGPTSLength % 2) != 0 || nGPTSLength < 6)
7021
147
    {
7022
147
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid length for GPTS object");
7023
147
        return FALSE;
7024
147
    }
7025
7026
3.37k
    std::vector<double> adfGPTS(nGPTSLength);
7027
33.4k
    for (int i = 0; i < nGPTSLength; i++)
7028
30.0k
    {
7029
30.0k
        adfGPTS[i] = Get(poGPTS, i);
7030
30.0k
        CPLDebug("PDF", "GPTS[%d] = %.18f", i, adfGPTS[i]);
7031
30.0k
    }
7032
7033
    /* -------------------------------------------------------------------- */
7034
    /*      Extract LPTS attribute                                          */
7035
    /* -------------------------------------------------------------------- */
7036
3.37k
    GDALPDFObject *poLPTS = poMeasureDict->Get("lgit:LPTS");
7037
3.37k
    if (poLPTS != nullptr && poLPTS->GetType() == PDFObjectType_Array)
7038
0
    {
7039
0
        CPLDebug("PDF", "Using lgit:LPTS");
7040
0
    }
7041
3.37k
    else if ((poLPTS = poMeasureDict->Get("LPTS")) == nullptr ||
7042
3.28k
             poLPTS->GetType() != PDFObjectType_Array)
7043
93
    {
7044
93
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find LPTS object");
7045
93
        return FALSE;
7046
93
    }
7047
7048
3.27k
    int nLPTSLength = poLPTS->GetArray()->GetLength();
7049
3.27k
    if (nLPTSLength != nGPTSLength)
7050
237
    {
7051
237
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid length for LPTS object");
7052
237
        return FALSE;
7053
237
    }
7054
7055
3.04k
    std::vector<double> adfLPTS(nLPTSLength);
7056
27.3k
    for (int i = 0; i < nLPTSLength; i++)
7057
24.3k
    {
7058
24.3k
        adfLPTS[i] = Get(poLPTS, i);
7059
24.3k
        CPLDebug("PDF", "LPTS[%d] = %f", i, adfLPTS[i]);
7060
24.3k
    }
7061
7062
    /* -------------------------------------------------------------------- */
7063
    /*      Extract GCS attribute                                           */
7064
    /* -------------------------------------------------------------------- */
7065
3.04k
    GDALPDFObject *poGCS = poMeasureDict->Get("GCS");
7066
3.04k
    if (poGCS == nullptr || poGCS->GetType() != PDFObjectType_Dictionary)
7067
119
    {
7068
119
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find GCS object");
7069
119
        return FALSE;
7070
119
    }
7071
7072
2.92k
    GDALPDFDictionary *poGCSDict = poGCS->GetDictionary();
7073
7074
    /* -------------------------------------------------------------------- */
7075
    /*      Extract GCS.Type attribute                                      */
7076
    /* -------------------------------------------------------------------- */
7077
2.92k
    GDALPDFObject *poGCSType = poGCSDict->Get("Type");
7078
2.92k
    if (poGCSType == nullptr || poGCSType->GetType() != PDFObjectType_Name)
7079
59
    {
7080
59
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find GCS.Type object");
7081
59
        return FALSE;
7082
59
    }
7083
7084
2.86k
    CPLDebug("PDF", "GCS.Type = %s", poGCSType->GetName().c_str());
7085
7086
    /* -------------------------------------------------------------------- */
7087
    /*      Extract EPSG attribute                                          */
7088
    /* -------------------------------------------------------------------- */
7089
2.86k
    GDALPDFObject *poEPSG = poGCSDict->Get("EPSG");
7090
2.86k
    int nEPSGCode = 0;
7091
2.86k
    if (poEPSG != nullptr && poEPSG->GetType() == PDFObjectType_Int)
7092
1.62k
    {
7093
1.62k
        nEPSGCode = poEPSG->GetInt();
7094
1.62k
        CPLDebug("PDF", "GCS.EPSG = %d", nEPSGCode);
7095
1.62k
    }
7096
7097
    /* -------------------------------------------------------------------- */
7098
    /*      Extract GCS.WKT attribute                                       */
7099
    /* -------------------------------------------------------------------- */
7100
2.86k
    GDALPDFObject *poGCSWKT = poGCSDict->Get("WKT");
7101
2.86k
    if (poGCSWKT != nullptr && poGCSWKT->GetType() != PDFObjectType_String)
7102
2
    {
7103
2
        poGCSWKT = nullptr;
7104
2
    }
7105
7106
2.86k
    if (poGCSWKT != nullptr)
7107
2.82k
        CPLDebug("PDF", "GCS.WKT = %s", poGCSWKT->GetString().c_str());
7108
7109
2.86k
    if (nEPSGCode <= 0 && poGCSWKT == nullptr)
7110
9
    {
7111
9
        CPLError(CE_Failure, CPLE_AppDefined,
7112
9
                 "Cannot find GCS.WKT or GCS.EPSG objects");
7113
9
        return FALSE;
7114
9
    }
7115
7116
2.85k
    if (poGCSWKT != nullptr)
7117
2.82k
    {
7118
2.82k
        m_oSRS.importFromWkt(poGCSWKT->GetString().c_str());
7119
2.82k
    }
7120
7121
2.85k
    bool bSRSOK = false;
7122
2.85k
    if (nEPSGCode != 0)
7123
1.62k
    {
7124
        // At time of writing EPSG CRS codes are <= 32767.
7125
        // The usual practice is that codes >= 100000 are in the ESRI namespace
7126
        // instead
7127
1.62k
        if (nEPSGCode >= 100000)
7128
68
        {
7129
68
            CPLErrorHandlerPusher oHandler(CPLQuietErrorHandler);
7130
68
            OGRSpatialReference oSRS_ESRI;
7131
68
            oSRS_ESRI.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
7132
68
            if (oSRS_ESRI.SetFromUserInput(CPLSPrintf("ESRI:%d", nEPSGCode)) ==
7133
68
                OGRERR_NONE)
7134
68
            {
7135
68
                bSRSOK = true;
7136
7137
                // Check consistency of ESRI:xxxx and WKT definitions
7138
68
                if (poGCSWKT != nullptr)
7139
64
                {
7140
64
                    if (!m_oSRS.GetName() ||
7141
45
                        (!EQUAL(oSRS_ESRI.GetName(), m_oSRS.GetName()) &&
7142
16
                         !oSRS_ESRI.IsSame(&m_oSRS)))
7143
35
                    {
7144
35
                        CPLDebug("PDF",
7145
35
                                 "Definition from ESRI:%d and WKT=%s do not "
7146
35
                                 "match. Using WKT string",
7147
35
                                 nEPSGCode, poGCSWKT->GetString().c_str());
7148
35
                        bSRSOK = false;
7149
35
                    }
7150
64
                }
7151
68
                if (bSRSOK)
7152
33
                {
7153
33
                    m_oSRS = std::move(oSRS_ESRI);
7154
33
                }
7155
68
            }
7156
68
        }
7157
1.55k
        else if (m_oSRS.importFromEPSG(nEPSGCode) == OGRERR_NONE)
7158
935
        {
7159
935
            bSRSOK = true;
7160
935
        }
7161
1.62k
    }
7162
7163
2.85k
    if (!bSRSOK)
7164
1.88k
    {
7165
1.88k
        if (poGCSWKT == nullptr)
7166
6
        {
7167
6
            CPLError(CE_Failure, CPLE_AppDefined,
7168
6
                     "Cannot resolve EPSG object, and GCS.WKT not found");
7169
6
            return FALSE;
7170
6
        }
7171
7172
1.88k
        if (m_oSRS.importFromWkt(poGCSWKT->GetString().c_str()) != OGRERR_NONE)
7173
608
        {
7174
608
            m_oSRS.Clear();
7175
608
            return FALSE;
7176
608
        }
7177
1.88k
    }
7178
7179
    /* -------------------------------------------------------------------- */
7180
    /*      Compute geotransform                                            */
7181
    /* -------------------------------------------------------------------- */
7182
2.24k
    OGRSpatialReference *poSRSGeog = m_oSRS.CloneGeogCS();
7183
7184
    /* Files found at
7185
     * http://carto.iict.ch/blog/publications-cartographiques-au-format-geospatial-pdf/
7186
     */
7187
    /* are in a PROJCS. However the coordinates in GPTS array are not in (lat,
7188
     * long) as required by the */
7189
    /* ISO 32000 supplement spec, but in (northing, easting). Adobe reader is
7190
     * able to understand that, */
7191
    /* so let's also try to do it with a heuristics. */
7192
7193
2.24k
    bool bReproject = true;
7194
2.24k
    if (m_oSRS.IsProjected())
7195
1.33k
    {
7196
6.55k
        for (int i = 0; i < nGPTSLength / 2; i++)
7197
5.27k
        {
7198
5.27k
            if (fabs(adfGPTS[2 * i]) > 91 || fabs(adfGPTS[2 * i + 1]) > 361)
7199
61
            {
7200
61
                CPLDebug("PDF", "GPTS coordinates seems to be in (northing, "
7201
61
                                "easting), which is non-standard");
7202
61
                bReproject = false;
7203
61
                break;
7204
61
            }
7205
5.27k
        }
7206
1.33k
    }
7207
7208
2.24k
    OGRCoordinateTransformation *poCT = nullptr;
7209
2.24k
    if (bReproject)
7210
2.18k
    {
7211
2.18k
        poCT = OGRCreateCoordinateTransformation(poSRSGeog, &m_oSRS);
7212
2.18k
        if (poCT == nullptr)
7213
3
        {
7214
3
            delete poSRSGeog;
7215
3
            m_oSRS.Clear();
7216
3
            return FALSE;
7217
3
        }
7218
2.18k
    }
7219
7220
2.23k
    std::vector<GDAL_GCP> asGCPS(nGPTSLength / 2);
7221
7222
    /* Create NEATLINE */
7223
2.23k
    OGRLinearRing *poRing = nullptr;
7224
2.23k
    if (nGPTSLength == 8)
7225
2.23k
    {
7226
2.23k
        m_poNeatLine = new OGRPolygon();
7227
2.23k
        poRing = new OGRLinearRing();
7228
2.23k
        m_poNeatLine->addRingDirectly(poRing);
7229
2.23k
    }
7230
7231
10.3k
    for (int i = 0; i < nGPTSLength / 2; i++)
7232
8.32k
    {
7233
        /* We probably assume LPTS is 0 or 1 */
7234
8.32k
        asGCPS[i].dfGCPPixel =
7235
8.32k
            (dfULX * (1 - adfLPTS[2 * i + 0]) + dfLRX * adfLPTS[2 * i + 0]) /
7236
8.32k
            dfMediaBoxWidth * nRasterXSize;
7237
8.32k
        asGCPS[i].dfGCPLine =
7238
8.32k
            (dfULY * (1 - adfLPTS[2 * i + 1]) + dfLRY * adfLPTS[2 * i + 1]) /
7239
8.32k
            dfMediaBoxHeight * nRasterYSize;
7240
7241
8.32k
        double lat = adfGPTS[2 * i];
7242
8.32k
        double lon = adfGPTS[2 * i + 1];
7243
8.32k
        double x = lon;
7244
8.32k
        double y = lat;
7245
8.32k
        if (bReproject)
7246
8.08k
        {
7247
8.08k
            if (!poCT->Transform(1, &x, &y, nullptr))
7248
210
            {
7249
210
                CPLError(CE_Failure, CPLE_AppDefined,
7250
210
                         "Cannot reproject (%f, %f)", lon, lat);
7251
210
                delete poSRSGeog;
7252
210
                delete poCT;
7253
210
                m_oSRS.Clear();
7254
210
                return FALSE;
7255
210
            }
7256
8.08k
        }
7257
7258
8.11k
        x = ROUND_IF_CLOSE(x);
7259
8.11k
        y = ROUND_IF_CLOSE(y);
7260
7261
8.11k
        asGCPS[i].dfGCPX = x;
7262
8.11k
        asGCPS[i].dfGCPY = y;
7263
7264
8.11k
        if (poRing)
7265
8.11k
            poRing->addPoint(x, y);
7266
8.11k
    }
7267
7268
2.02k
    delete poSRSGeog;
7269
2.02k
    delete poCT;
7270
7271
2.02k
    if (!GDALGCPsToGeoTransform(nGPTSLength / 2, asGCPS.data(), m_gt.data(),
7272
2.02k
                                FALSE))
7273
926
    {
7274
926
        CPLDebug("PDF",
7275
926
                 "Could not compute GT with exact match. Try with approximate");
7276
926
        if (!GDALGCPsToGeoTransform(nGPTSLength / 2, asGCPS.data(), m_gt.data(),
7277
926
                                    TRUE))
7278
420
        {
7279
420
            CPLError(CE_Failure, CPLE_AppDefined,
7280
420
                     "Could not compute GT with approximate match.");
7281
420
            return FALSE;
7282
420
        }
7283
926
    }
7284
1.60k
    m_bGeoTransformValid = true;
7285
7286
    // If the non scaling terms of the geotransform are significantly smaller
7287
    // than the pixel size, then nullify them as being just artifacts of
7288
    //  reprojection and GDALGCPsToGeoTransform() numerical imprecisions.
7289
1.60k
    const double dfPixelSize = std::min(fabs(m_gt.xscale), fabs(m_gt.yscale));
7290
1.60k
    const double dfRotationShearTerm =
7291
1.60k
        std::max(fabs(m_gt.xrot), fabs(m_gt.yrot));
7292
1.60k
    if (dfRotationShearTerm < 1e-5 * dfPixelSize ||
7293
630
        (m_bUseLib.test(PDFLIB_PDFIUM) &&
7294
0
         std::min(fabs(m_gt.xrot), fabs(m_gt.yrot)) < 1e-5 * dfPixelSize))
7295
978
    {
7296
978
        dfLRX =
7297
978
            m_gt.xorig + nRasterXSize * m_gt.xscale + nRasterYSize * m_gt.xrot;
7298
978
        dfLRY =
7299
978
            m_gt.yorig + nRasterXSize * m_gt.yrot + nRasterYSize * m_gt.yscale;
7300
978
        m_gt.xscale = (dfLRX - m_gt.xorig) / nRasterXSize;
7301
978
        m_gt.yscale = (dfLRY - m_gt.yorig) / nRasterYSize;
7302
978
        m_gt.xrot = m_gt.yrot = 0;
7303
978
    }
7304
7305
1.60k
    return TRUE;
7306
2.02k
}
7307
7308
/************************************************************************/
7309
/*                           GetSpatialRef()                            */
7310
/************************************************************************/
7311
7312
const OGRSpatialReference *PDFDataset::GetSpatialRef() const
7313
33.0k
{
7314
33.0k
    const auto poSRS = GDALPamDataset::GetSpatialRef();
7315
33.0k
    if (poSRS)
7316
736
        return poSRS;
7317
7318
32.2k
    if (!m_oSRS.IsEmpty() && m_bGeoTransformValid)
7319
570
        return &m_oSRS;
7320
31.7k
    return nullptr;
7321
32.2k
}
7322
7323
/************************************************************************/
7324
/*                          GetGeoTransform()                           */
7325
/************************************************************************/
7326
7327
CPLErr PDFDataset::GetGeoTransform(GDALGeoTransform &gt) const
7328
7329
7.33k
{
7330
7.33k
    if (GDALPamDataset::GetGeoTransform(gt) == CE_None)
7331
0
    {
7332
0
        return CE_None;
7333
0
    }
7334
7335
7.33k
    gt = m_gt;
7336
7.33k
    return ((m_bGeoTransformValid) ? CE_None : CE_Failure);
7337
7.33k
}
7338
7339
/************************************************************************/
7340
/*                           SetSpatialRef()                            */
7341
/************************************************************************/
7342
7343
CPLErr PDFDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
7344
0
{
7345
0
    if (eAccess == GA_ReadOnly)
7346
0
        GDALPamDataset::SetSpatialRef(poSRS);
7347
7348
0
    m_oSRS.Clear();
7349
0
    if (poSRS)
7350
0
        m_oSRS = *poSRS;
7351
0
    m_bProjDirty = true;
7352
0
    return CE_None;
7353
0
}
7354
7355
/************************************************************************/
7356
/*                          SetGeoTransform()                           */
7357
/************************************************************************/
7358
7359
CPLErr PDFDataset::SetGeoTransform(const GDALGeoTransform &gt)
7360
0
{
7361
0
    if (eAccess == GA_ReadOnly)
7362
0
        GDALPamDataset::SetGeoTransform(gt);
7363
7364
0
    m_gt = gt;
7365
0
    m_bGeoTransformValid = true;
7366
0
    m_bProjDirty = true;
7367
7368
    /* Reset NEATLINE if not explicitly set by the user */
7369
0
    if (!m_bNeatLineDirty)
7370
0
        SetMetadataItem("NEATLINE", nullptr);
7371
0
    return CE_None;
7372
0
}
7373
7374
/************************************************************************/
7375
/*                       GetMetadataDomainList()                        */
7376
/************************************************************************/
7377
7378
char **PDFDataset::GetMetadataDomainList()
7379
0
{
7380
0
    return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
7381
0
                                   TRUE, "xml:XMP", "LAYERS",
7382
0
                                   "EMBEDDED_METADATA", nullptr);
7383
0
}
7384
7385
/************************************************************************/
7386
/*                            GetMetadata()                             */
7387
/************************************************************************/
7388
7389
CSLConstList PDFDataset::GetMetadata(const char *pszDomain)
7390
59.2k
{
7391
59.2k
    if (pszDomain != nullptr && EQUAL(pszDomain, "EMBEDDED_METADATA"))
7392
0
    {
7393
0
        char **papszRet = m_oMDMD_PDF.GetMetadata(pszDomain);
7394
0
        if (papszRet)
7395
0
            return papszRet;
7396
7397
0
        GDALPDFObject *poCatalog = GetCatalog();
7398
0
        if (poCatalog == nullptr)
7399
0
            return nullptr;
7400
0
        GDALPDFObject *poFirstElt =
7401
0
            poCatalog->LookupObject("Names.EmbeddedFiles.Names[0]");
7402
0
        GDALPDFObject *poF =
7403
0
            poCatalog->LookupObject("Names.EmbeddedFiles.Names[1].EF.F");
7404
7405
0
        if (poFirstElt == nullptr ||
7406
0
            poFirstElt->GetType() != PDFObjectType_String ||
7407
0
            poFirstElt->GetString() != "Metadata")
7408
0
            return nullptr;
7409
0
        if (poF == nullptr || poF->GetType() != PDFObjectType_Dictionary)
7410
0
            return nullptr;
7411
0
        GDALPDFStream *poStream = poF->GetStream();
7412
0
        if (poStream == nullptr)
7413
0
            return nullptr;
7414
7415
0
        char *apszMetadata[2] = {nullptr, nullptr};
7416
0
        apszMetadata[0] = poStream->GetBytes();
7417
0
        m_oMDMD_PDF.SetMetadata(apszMetadata, pszDomain);
7418
0
        VSIFree(apszMetadata[0]);
7419
0
        return m_oMDMD_PDF.GetMetadata(pszDomain);
7420
0
    }
7421
59.2k
    if (pszDomain == nullptr || EQUAL(pszDomain, ""))
7422
22.0k
    {
7423
22.0k
        CSLConstList papszPAMMD = GDALPamDataset::GetMetadata(pszDomain);
7424
108k
        for (CSLConstList papszIter = papszPAMMD; papszIter && *papszIter;
7425
86.7k
             ++papszIter)
7426
86.7k
        {
7427
86.7k
            char *pszKey = nullptr;
7428
86.7k
            const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
7429
86.7k
            if (pszKey && pszValue)
7430
86.7k
            {
7431
86.7k
                if (m_oMDMD_PDF.GetMetadataItem(pszKey, pszDomain) == nullptr)
7432
28.9k
                    m_oMDMD_PDF.SetMetadataItem(pszKey, pszValue, pszDomain);
7433
86.7k
            }
7434
86.7k
            CPLFree(pszKey);
7435
86.7k
        }
7436
22.0k
        return m_oMDMD_PDF.GetMetadata(pszDomain);
7437
22.0k
    }
7438
37.2k
    if (EQUAL(pszDomain, "LAYERS") || EQUAL(pszDomain, "xml:XMP") ||
7439
37.2k
        EQUAL(pszDomain, "SUBDATASETS"))
7440
0
    {
7441
0
        return m_oMDMD_PDF.GetMetadata(pszDomain);
7442
0
    }
7443
37.2k
    return GDALPamDataset::GetMetadata(pszDomain);
7444
37.2k
}
7445
7446
/************************************************************************/
7447
/*                            SetMetadata()                             */
7448
/************************************************************************/
7449
7450
CPLErr PDFDataset::SetMetadata(CSLConstList papszMetadata,
7451
                               const char *pszDomain)
7452
8.64k
{
7453
8.64k
    if (pszDomain == nullptr || EQUAL(pszDomain, ""))
7454
53
    {
7455
53
        char **papszMetadataDup = CSLDuplicate(papszMetadata);
7456
53
        m_oMDMD_PDF.SetMetadata(nullptr, pszDomain);
7457
7458
284
        for (char **papszIter = papszMetadataDup; papszIter && *papszIter;
7459
231
             ++papszIter)
7460
231
        {
7461
231
            char *pszKey = nullptr;
7462
231
            const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
7463
231
            if (pszKey && pszValue)
7464
231
            {
7465
231
                SetMetadataItem(pszKey, pszValue, pszDomain);
7466
231
            }
7467
231
            CPLFree(pszKey);
7468
231
        }
7469
53
        CSLDestroy(papszMetadataDup);
7470
53
        return CE_None;
7471
53
    }
7472
8.59k
    else if (EQUAL(pszDomain, "xml:XMP"))
7473
5.03k
    {
7474
5.03k
        m_bXMPDirty = true;
7475
5.03k
        return m_oMDMD_PDF.SetMetadata(papszMetadata, pszDomain);
7476
5.03k
    }
7477
3.56k
    else if (EQUAL(pszDomain, "SUBDATASETS"))
7478
3.56k
    {
7479
3.56k
        return m_oMDMD_PDF.SetMetadata(papszMetadata, pszDomain);
7480
3.56k
    }
7481
0
    else
7482
0
    {
7483
0
        return GDALPamDataset::SetMetadata(papszMetadata, pszDomain);
7484
0
    }
7485
8.64k
}
7486
7487
/************************************************************************/
7488
/*                          GetMetadataItem()                           */
7489
/************************************************************************/
7490
7491
const char *PDFDataset::GetMetadataItem(const char *pszName,
7492
                                        const char *pszDomain)
7493
51.8k
{
7494
51.8k
    if (pszDomain != nullptr && EQUAL(pszDomain, "_INTERNAL_") &&
7495
0
        pszName != nullptr && EQUAL(pszName, "PDF_LIB"))
7496
0
    {
7497
0
        if (m_bUseLib.test(PDFLIB_POPPLER))
7498
0
            return "POPPLER";
7499
0
        if (m_bUseLib.test(PDFLIB_PODOFO))
7500
0
            return "PODOFO";
7501
0
        if (m_bUseLib.test(PDFLIB_PDFIUM))
7502
0
            return "PDFIUM";
7503
0
    }
7504
51.8k
    return CSLFetchNameValue(GetMetadata(pszDomain), pszName);
7505
51.8k
}
7506
7507
/************************************************************************/
7508
/*                          SetMetadataItem()                           */
7509
/************************************************************************/
7510
7511
CPLErr PDFDataset::SetMetadataItem(const char *pszName, const char *pszValue,
7512
                                   const char *pszDomain)
7513
119k
{
7514
119k
    if (pszDomain == nullptr || EQUAL(pszDomain, ""))
7515
62.7k
    {
7516
62.7k
        if (EQUAL(pszName, "NEATLINE"))
7517
2.55k
        {
7518
2.55k
            const char *pszOldValue =
7519
2.55k
                m_oMDMD_PDF.GetMetadataItem(pszName, pszDomain);
7520
2.55k
            if ((pszValue == nullptr && pszOldValue != nullptr) ||
7521
2.55k
                (pszValue != nullptr && pszOldValue == nullptr) ||
7522
0
                (pszValue != nullptr && pszOldValue != nullptr &&
7523
0
                 strcmp(pszValue, pszOldValue) != 0))
7524
2.55k
            {
7525
2.55k
                m_bProjDirty = true;
7526
2.55k
                m_bNeatLineDirty = true;
7527
2.55k
            }
7528
2.55k
            return m_oMDMD_PDF.SetMetadataItem(pszName, pszValue, pszDomain);
7529
2.55k
        }
7530
60.1k
        else
7531
60.1k
        {
7532
60.1k
            if (EQUAL(pszName, "AUTHOR") || EQUAL(pszName, "PRODUCER") ||
7533
51.1k
                EQUAL(pszName, "CREATOR") || EQUAL(pszName, "CREATION_DATE") ||
7534
38.8k
                EQUAL(pszName, "SUBJECT") || EQUAL(pszName, "TITLE") ||
7535
34.3k
                EQUAL(pszName, "KEYWORDS"))
7536
26.8k
            {
7537
26.8k
                if (pszValue == nullptr)
7538
0
                    pszValue = "";
7539
26.8k
                const char *pszOldValue =
7540
26.8k
                    m_oMDMD_PDF.GetMetadataItem(pszName, pszDomain);
7541
26.8k
                if (pszOldValue == nullptr ||
7542
0
                    strcmp(pszValue, pszOldValue) != 0)
7543
26.8k
                {
7544
26.8k
                    m_bInfoDirty = true;
7545
26.8k
                }
7546
26.8k
                return m_oMDMD_PDF.SetMetadataItem(pszName, pszValue,
7547
26.8k
                                                   pszDomain);
7548
26.8k
            }
7549
33.3k
            else if (EQUAL(pszName, "DPI"))
7550
33.2k
            {
7551
33.2k
                return m_oMDMD_PDF.SetMetadataItem(pszName, pszValue,
7552
33.2k
                                                   pszDomain);
7553
33.2k
            }
7554
157
            else
7555
157
            {
7556
157
                m_oMDMD_PDF.SetMetadataItem(pszName, pszValue, pszDomain);
7557
157
                return GDALPamDataset::SetMetadataItem(pszName, pszValue,
7558
157
                                                       pszDomain);
7559
157
            }
7560
60.1k
        }
7561
62.7k
    }
7562
56.4k
    else if (EQUAL(pszDomain, "xml:XMP"))
7563
0
    {
7564
0
        m_bXMPDirty = true;
7565
0
        return m_oMDMD_PDF.SetMetadataItem(pszName, pszValue, pszDomain);
7566
0
    }
7567
56.4k
    else if (EQUAL(pszDomain, "SUBDATASETS"))
7568
0
    {
7569
0
        return m_oMDMD_PDF.SetMetadataItem(pszName, pszValue, pszDomain);
7570
0
    }
7571
56.4k
    else
7572
56.4k
    {
7573
56.4k
        return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
7574
56.4k
    }
7575
119k
}
7576
7577
/************************************************************************/
7578
/*                            GetGCPCount()                             */
7579
/************************************************************************/
7580
7581
int PDFDataset::GetGCPCount()
7582
7.32k
{
7583
7.32k
    return m_nGCPCount;
7584
7.32k
}
7585
7586
/************************************************************************/
7587
/*                          GetGCPSpatialRef()                          */
7588
/************************************************************************/
7589
7590
const OGRSpatialReference *PDFDataset::GetGCPSpatialRef() const
7591
7.32k
{
7592
7.32k
    if (!m_oSRS.IsEmpty() && m_nGCPCount != 0)
7593
2
        return &m_oSRS;
7594
7.32k
    return nullptr;
7595
7.32k
}
7596
7597
/************************************************************************/
7598
/*                              GetGCPs()                               */
7599
/************************************************************************/
7600
7601
const GDAL_GCP *PDFDataset::GetGCPs()
7602
7.32k
{
7603
7.32k
    return m_pasGCPList;
7604
7.32k
}
7605
7606
/************************************************************************/
7607
/*                              SetGCPs()                               */
7608
/************************************************************************/
7609
7610
CPLErr PDFDataset::SetGCPs(int nGCPCountIn, const GDAL_GCP *pasGCPListIn,
7611
                           const OGRSpatialReference *poSRS)
7612
0
{
7613
0
    const char *pszGEO_ENCODING =
7614
0
        CPLGetConfigOption("GDAL_PDF_GEO_ENCODING", "ISO32000");
7615
0
    if (nGCPCountIn != 4 && EQUAL(pszGEO_ENCODING, "ISO32000"))
7616
0
    {
7617
0
        CPLError(CE_Failure, CPLE_NotSupported,
7618
0
                 "PDF driver only supports writing 4 GCPs when "
7619
0
                 "GDAL_PDF_GEO_ENCODING=ISO32000.");
7620
0
        return CE_Failure;
7621
0
    }
7622
7623
    /* Free previous GCPs */
7624
0
    GDALDeinitGCPs(m_nGCPCount, m_pasGCPList);
7625
0
    CPLFree(m_pasGCPList);
7626
7627
    /* Duplicate in GCPs */
7628
0
    m_nGCPCount = nGCPCountIn;
7629
0
    m_pasGCPList = GDALDuplicateGCPs(m_nGCPCount, pasGCPListIn);
7630
7631
0
    m_oSRS.Clear();
7632
0
    if (poSRS)
7633
0
        m_oSRS = *poSRS;
7634
7635
0
    m_bProjDirty = true;
7636
7637
    /* Reset NEATLINE if not explicitly set by the user */
7638
0
    if (!m_bNeatLineDirty)
7639
0
        SetMetadataItem("NEATLINE", nullptr);
7640
7641
0
    return CE_None;
7642
0
}
7643
7644
#endif  // #ifdef HAVE_PDF_READ_SUPPORT
7645
7646
/************************************************************************/
7647
/*                            GDALPDFOpen()                             */
7648
/************************************************************************/
7649
7650
GDALDataset *GDALPDFOpen(
7651
#ifdef HAVE_PDF_READ_SUPPORT
7652
    const char *pszFilename, GDALAccess eAccess
7653
#else
7654
    CPL_UNUSED const char *pszFilename, CPL_UNUSED GDALAccess eAccess
7655
#endif
7656
)
7657
53
{
7658
53
#ifdef HAVE_PDF_READ_SUPPORT
7659
53
    GDALOpenInfo oOpenInfo(pszFilename, eAccess);
7660
53
    return PDFDataset::Open(&oOpenInfo);
7661
#else
7662
    return nullptr;
7663
#endif
7664
53
}
7665
7666
/************************************************************************/
7667
/*                        GDALPDFUnloadDriver()                         */
7668
/************************************************************************/
7669
7670
static void GDALPDFUnloadDriver(CPL_UNUSED GDALDriver *poDriver)
7671
0
{
7672
0
#ifdef HAVE_POPPLER
7673
0
    if (hGlobalParamsMutex != nullptr)
7674
0
        CPLDestroyMutex(hGlobalParamsMutex);
7675
0
#endif
7676
#ifdef HAVE_PDFIUM
7677
    if (PDFDataset::g_bPdfiumInit)
7678
    {
7679
        CPLCreateOrAcquireMutex(&g_oPdfiumLoadDocMutex, PDFIUM_MUTEX_TIMEOUT);
7680
        // Destroy every loaded document or page
7681
        TMapPdfiumDatasets::iterator itDoc;
7682
        TMapPdfiumPages::iterator itPage;
7683
        for (itDoc = g_mPdfiumDatasets.begin();
7684
             itDoc != g_mPdfiumDatasets.end(); ++itDoc)
7685
        {
7686
            TPdfiumDocumentStruct *pDoc = itDoc->second;
7687
            for (itPage = pDoc->pages.begin(); itPage != pDoc->pages.end();
7688
                 ++itPage)
7689
            {
7690
                TPdfiumPageStruct *pPage = itPage->second;
7691
7692
                CPLCreateOrAcquireMutex(&g_oPdfiumReadMutex,
7693
                                        PDFIUM_MUTEX_TIMEOUT);
7694
                CPLCreateOrAcquireMutex(&(pPage->readMutex),
7695
                                        PDFIUM_MUTEX_TIMEOUT);
7696
                CPLReleaseMutex(pPage->readMutex);
7697
                CPLDestroyMutex(pPage->readMutex);
7698
                FPDF_ClosePage(FPDFPageFromIPDFPage(pPage->page));
7699
                delete pPage;
7700
                CPLReleaseMutex(g_oPdfiumReadMutex);
7701
            }  // ~ foreach page
7702
7703
            FPDF_CloseDocument(FPDFDocumentFromCPDFDocument(pDoc->doc));
7704
            CPLFree(pDoc->filename);
7705
            VSIFCloseL(static_cast<VSILFILE *>(pDoc->psFileAccess->m_Param));
7706
            delete pDoc->psFileAccess;
7707
            pDoc->pages.clear();
7708
7709
            delete pDoc;
7710
        }  // ~ foreach document
7711
        g_mPdfiumDatasets.clear();
7712
        FPDF_DestroyLibrary();
7713
        PDFDataset::g_bPdfiumInit = FALSE;
7714
7715
        CPLReleaseMutex(g_oPdfiumLoadDocMutex);
7716
7717
        if (g_oPdfiumReadMutex)
7718
            CPLDestroyMutex(g_oPdfiumReadMutex);
7719
        CPLDestroyMutex(g_oPdfiumLoadDocMutex);
7720
    }
7721
#endif
7722
0
}
7723
7724
/************************************************************************/
7725
/*                        PDFSanitizeLayerName()                        */
7726
/************************************************************************/
7727
7728
CPLString PDFSanitizeLayerName(const char *pszName)
7729
627k
{
7730
627k
    if (!CPLTestBool(CPLGetConfigOption("GDAL_PDF_LAUNDER_LAYER_NAMES", "YES")))
7731
0
        return pszName;
7732
7733
627k
    CPLString osName;
7734
48.3M
    for (int i = 0; pszName[i] != '\0'; i++)
7735
47.6M
    {
7736
47.6M
        if (pszName[i] == ' ' || pszName[i] == '.' || pszName[i] == ',')
7737
4.66M
            osName += "_";
7738
43.0M
        else if (pszName[i] != '"')
7739
42.9M
            osName += pszName[i];
7740
47.6M
    }
7741
627k
    if (osName.empty())
7742
492
        osName = "unnamed";
7743
627k
    return osName;
7744
627k
}
7745
7746
/************************************************************************/
7747
/*                      GDALPDFListLayersAlgorithm                      */
7748
/************************************************************************/
7749
7750
#ifdef HAVE_PDF_READ_SUPPORT
7751
7752
class GDALPDFListLayersAlgorithm final : public GDALAlgorithm
7753
{
7754
  public:
7755
    GDALPDFListLayersAlgorithm()
7756
0
        : GDALAlgorithm("list-layers",
7757
0
                        std::string("List layers of a PDF dataset"),
7758
0
                        "/drivers/raster/pdf.html")
7759
0
    {
7760
0
        AddInputDatasetArg(&m_dataset, GDAL_OF_RASTER | GDAL_OF_VECTOR);
7761
0
        AddOutputFormatArg(&m_format).SetDefault(m_format).SetChoices("json",
7762
0
                                                                      "text");
7763
0
        AddOutputStringArg(&m_output);
7764
0
    }
7765
7766
  protected:
7767
    bool RunImpl(GDALProgressFunc, void *) override;
7768
7769
  private:
7770
    GDALArgDatasetValue m_dataset{};
7771
    std::string m_format = "json";
7772
    std::string m_output{};
7773
};
7774
7775
bool GDALPDFListLayersAlgorithm::RunImpl(GDALProgressFunc, void *)
7776
0
{
7777
0
    auto poDS = dynamic_cast<PDFDataset *>(m_dataset.GetDatasetRef());
7778
0
    if (!poDS)
7779
0
    {
7780
0
        ReportError(CE_Failure, CPLE_AppDefined, "%s is not a PDF",
7781
0
                    m_dataset.GetName().c_str());
7782
0
        return false;
7783
0
    }
7784
0
    if (m_format == "json")
7785
0
    {
7786
0
        CPLJSonStreamingWriter oWriter(nullptr, nullptr);
7787
0
        oWriter.StartArray();
7788
0
        for (const auto &[key, value] : cpl::IterateNameValue(
7789
0
                 const_cast<CSLConstList>(poDS->GetMetadata("LAYERS"))))
7790
0
        {
7791
0
            CPL_IGNORE_RET_VAL(key);
7792
0
            oWriter.Add(value);
7793
0
        }
7794
0
        oWriter.EndArray();
7795
0
        m_output = oWriter.GetString();
7796
0
        m_output += '\n';
7797
0
    }
7798
0
    else
7799
0
    {
7800
0
        for (const auto &[key, value] : cpl::IterateNameValue(
7801
0
                 const_cast<CSLConstList>(poDS->GetMetadata("LAYERS"))))
7802
0
        {
7803
0
            CPL_IGNORE_RET_VAL(key);
7804
0
            m_output += value;
7805
0
            m_output += '\n';
7806
0
        }
7807
0
    }
7808
0
    return true;
7809
0
}
7810
7811
/************************************************************************/
7812
/*                    GDALPDFInstantiateAlgorithm()                     */
7813
/************************************************************************/
7814
7815
static GDALAlgorithm *
7816
GDALPDFInstantiateAlgorithm(const std::vector<std::string> &aosPath)
7817
0
{
7818
0
    if (aosPath.size() == 1 && aosPath[0] == "list-layers")
7819
0
    {
7820
0
        return std::make_unique<GDALPDFListLayersAlgorithm>().release();
7821
0
    }
7822
0
    else
7823
0
    {
7824
0
        return nullptr;
7825
0
    }
7826
0
}
7827
7828
#endif  // HAVE_PDF_READ_SUPPORT
7829
7830
/************************************************************************/
7831
/*                          GDALRegister_PDF()                          */
7832
/************************************************************************/
7833
7834
void GDALRegister_PDF()
7835
7836
22
{
7837
22
    if (!GDAL_CHECK_VERSION("PDF driver"))
7838
0
        return;
7839
7840
22
    if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
7841
0
        return;
7842
7843
22
    GDALDriver *poDriver = new GDALDriver();
7844
22
    PDFDriverSetCommonMetadata(poDriver);
7845
7846
22
#ifdef HAVE_PDF_READ_SUPPORT
7847
22
    poDriver->pfnOpen = PDFDataset::OpenWrapper;
7848
22
    poDriver->pfnInstantiateAlgorithm = GDALPDFInstantiateAlgorithm;
7849
22
#endif  // HAVE_PDF_READ_SUPPORT
7850
7851
22
    poDriver->pfnCreateCopy = GDALPDFCreateCopy;
7852
22
    poDriver->pfnCreate = PDFWritableVectorDataset::Create;
7853
22
    poDriver->pfnUnloadDriver = GDALPDFUnloadDriver;
7854
7855
22
    GetGDALDriverManager()->RegisterDriver(poDriver);
7856
22
}