Coverage Report

Created: 2026-08-11 08:26

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