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