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