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