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