Coverage Report

Created: 2025-12-31 08:30

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