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